1use attributes::{DefineErrorArgs, ThrowsArgs, VariantArg, VariantArgs};
7use codegen::{error_definition, patch_function};
8use names::{fn_name_to_error, type_to_variant};
9use proc_macro::TokenStream;
10use quote::ToTokens;
11use syn::{parse_macro_input, Error, Item, Type};
12use types::{CompositeError, Variant};
13
14mod attributes;
15mod codegen;
16mod names;
17mod types;
18
19#[proc_macro]
20pub fn define_error(attributes: TokenStream) -> TokenStream {
21 let attrs = parse_macro_input!(attributes as DefineErrorArgs);
22 let DefineErrorArgs { type_def, variants } = attrs;
23
24 let (variants, composed) = match split_variants(variants) {
25 Ok(v) => v,
26 Err(e) => return e.to_compile_error().into(),
27 };
28
29 let error = CompositeError {
30 name: type_def.name,
31 visibility: type_def.visibility,
32 variants,
33 composed,
34 };
35
36 error_definition(error).into()
37}
38
39#[proc_macro_attribute]
40pub fn throws(attributes: TokenStream, body: TokenStream) -> TokenStream {
41 let body = parse_macro_input!(body as Item);
42 let Item::Fn(function) = body else {
43 return Error::new_spanned(body, "the throws macro can only be used on functions")
44 .into_compile_error()
45 .into();
46 };
47
48 let attrs = parse_macro_input!(attributes as ThrowsArgs);
49 let ThrowsArgs { name, variants } = attrs;
50
51 let name = name.unwrap_or_else(|| fn_name_to_error(&function.sig.ident));
52
53 let (variants, composed) = match split_variants(variants) {
54 Ok(v) => v,
55 Err(e) => return e.to_compile_error().into(),
56 };
57
58 let error = CompositeError {
59 name,
60 visibility: function.vis.clone(),
61 variants,
62 composed,
63 };
64
65 let mut stream = patch_function(function, error.name.clone()).to_token_stream();
66 stream.extend(error_definition(error));
67
68 stream.into()
69}
70
71fn split_variants(args: VariantArgs) -> Result<(Vec<Variant>, Vec<Type>), Error> {
72 let mut variants = Vec::new();
73 let mut composed = Vec::new();
74
75 for arg in args {
76 match arg {
77 VariantArg::Variant { typ, name } => {
78 let name = name.or_else(|| type_to_variant(&typ)).ok_or_else(|| {
79 Error::new_spanned(
80 &typ,
81 "variant name can only be infered if the type is a path",
82 )
83 })?;
84
85 variants.push(Variant { typ, name })
86 }
87 VariantArg::Composed { typ } => composed.push(typ),
88 }
89 }
90
91 Ok((variants, composed))
92}