aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/attr/metadata.rs
blob: b96e31937e915d994db7840d22410a99a606ae6e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::{Attribute, ItemFn, ReturnType, TypePath, parse_macro_input};

/// Implements the `#[metadata(EntryVariant)]` attribute macro.
///
/// It takes the enum variant ident to attach metadata to, and rewrites the
/// annotated function into:
/// - an `impl ::mingling::Metadata<ReturnType> for EntryVariant` that calls the
///   original function,
/// - a `::mingling::macros::register_metadata!(EntryVariant, ReturnType)` call,
/// - the preserved original function.
pub(crate) fn metadata_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
    let entry_variant = parse_macro_input!(attr as syn::Ident);

    let input_fn = parse_macro_input!(item as ItemFn);

    // The metadata type is the function's return type.
    let metadata_type = match &input_fn.sig.output {
        ReturnType::Type(_, ty) => match syn::parse2::<TypePath>(quote! { #ty }) {
            Ok(ty) => ty,
            Err(e) => return e.to_compile_error().into(),
        },
        ReturnType::Default => {
            return syn::Error::new(
                input_fn.sig.span(),
                "#[metadata] requires the function to have an explicit return type",
            )
            .to_compile_error()
            .into();
        }
    };

    // Preserve the original return type exactly as written, so the original
    // function signature is reproduced unchanged.
    let original_return_type = match &input_fn.sig.output {
        ReturnType::Type(_, ty) => quote! { #ty },
        ReturnType::Default => quote! { () },
    };

    // Reject async metadata functions: `Metadata::init_metadata` is synchronous.
    if input_fn.sig.asyncness.is_some() {
        return syn::Error::new(input_fn.sig.span(), "Metadata function cannot be async")
            .to_compile_error()
            .into();
    }

    let fn_name = &input_fn.sig.ident;
    let vis = &input_fn.vis;
    let original_inputs = input_fn.sig.inputs.clone();
    let fn_body_stmts = &input_fn.block.stmts;

    // Function attributes, excluding the metadata attribute itself.
    let fn_attrs: Vec<&Attribute> = input_fn
        .attrs
        .iter()
        .filter(|attr| !attr.path().is_ident("metadata"))
        .collect();

    // A metadata provider is a zero-argument function.
    if !original_inputs.is_empty() {
        return syn::Error::new(
            input_fn.sig.span(),
            "#[metadata] function cannot take any parameters",
        )
        .to_compile_error()
        .into();
    }

    let expanded = quote! {
        impl ::mingling::Metadata<#metadata_type> for #entry_variant {
            fn init_metadata() -> #metadata_type {
                #fn_name()
            }
        }

        ::mingling::macros::register_metadata!(#entry_variant, #metadata_type);

        #(#fn_attrs)*
        #vis fn #fn_name(#original_inputs) -> #original_return_type {
            #(#fn_body_stmts)*
        }
    };

    expanded.into()
}