throwing_macros/
lib.rs

1//! This crate contains the implementation of macros for the `throwing` crate.
2//!
3//! The recommended way to use these macros is through the main crate,
4//! since it's required for the macros to function anyway.
5
6use 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}