Skip to content

Commit

Permalink
error-derive: Add macro for defining errors
Browse files Browse the repository at this point in the history
  • Loading branch information
simonwuelker committed Aug 10, 2024
1 parent 6108bde commit d403fc2
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ members = [
"crates/util/serialization/serialize-json",
"crates/resourceloader",
"crates/settings",
"crates/error-derive",
]
resolver = "2"

Expand Down Expand Up @@ -66,6 +67,7 @@ ipc = { path = "crates/ipc" }
serialize-json = { path = "crates/util/serialization/serialize-json" }
resourceloader = { path = "crates/resourceloader" }
settings = { path = "crates/settings" }
error-derive = { path = "crates/error-derive" }

log = "0.4"
criterion = { version = "0.4", features = ["html_reports"] }
Expand Down
18 changes: 18 additions & 0 deletions crates/error-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "error-derive"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
repository.workspace = true
license.workspace = true

[lints]
workspace = true

[dependencies]
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }

[lib]
proc-macro = true
91 changes: 91 additions & 0 deletions crates/error-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_derive(Error, attributes(msg))]
pub fn deserialize(input: TokenStream) -> TokenStream {
let item: syn::ItemEnum = syn::parse(input).expect("Could not parse input as enum");

let name = item.ident;
let mut variant_displays = vec![];
let mut from_impls = vec![];
let mut variant_sources = vec![];

for variant in &item.variants {
let ident = &variant.ident;

let display_attribute = variant
.attrs
.iter()
.flat_map(|attr| match &attr.meta {
syn::Meta::NameValue(name_value) => Some(name_value),
_ => None,
})
.flat_map(|attr| {
let ident = attr.path.get_ident()?.to_string();

Some((ident, &attr.value))
})
.find(|(name, _)| name == "msg")
.map(|(_, value)| value);

let Some(display_value) = display_attribute else {
panic!("need display attribute");
};

match &variant.fields {
syn::Fields::Unit => {
variant_displays.push(quote!(Self::#ident => (#display_value).fmt(f)));
},
syn::Fields::Unnamed(unnamed_fields) => {
if unnamed_fields.unnamed.len() != 1 {
panic!("Need exactly one field");
}

let field = &unnamed_fields.unnamed[0];
let ty = &field.ty;

from_impls.push(quote!(
#[automatically_derived]
impl From<#ty> for #name {
fn from(value: #ty) -> Self {
Self::#ident(value)
}
}
));
variant_displays.push(quote!(Self::#ident(_) => (#display_value).fmt(f)));
variant_sources.push(quote!(Self::#ident(ref value) => Some(value)));
},
syn::Fields::Named(_) => panic!("named fields are not allowed"),
}
}

quote!(
#[automatically_derived]
impl ::std::fmt::Display for #name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
match self {
#(
#variant_displays,
)*
}
}
}

#(
#from_impls
)*

#[automatically_derived]
impl ::std::error::Error for #name {
fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
match self {
#(
#variant_sources,
)*
_ => None,
}
}
}
)
.into()
}

0 comments on commit d403fc2

Please sign in to comment.