wonderbox_codegen/
lib.rs

1#![feature(proc_macro_diagnostic)]
2
3extern crate proc_macro;
4
5mod spanned;
6
7use crate::spanned::SpannedUnstable;
8use proc_macro::{Diagnostic, Level, TokenStream};
9use quote::quote;
10use syn::{
11    parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, AttributeArgs, FnArg,
12    FnDecl, ImplItem, Item, ItemImpl, MethodSig, ReturnType, Type,
13};
14
15type Result<T> = std::result::Result<T, Diagnostic>;
16
17#[proc_macro_attribute]
18pub fn autoresolvable(attr: TokenStream, item: TokenStream) -> TokenStream {
19    let item = parse_macro_input!(item as Item);
20    let _attr = parse_macro_input!(attr as AttributeArgs);
21
22    let result = generate_autoresolvable_impl(&item);
23
24    let emitted_tokens = match result {
25        Ok(token_stream) => token_stream,
26        Err(diagnostic) => {
27            diagnostic.emit();
28            quote! {
29                #item
30            }
31        }
32    };
33
34    emitted_tokens.into()
35}
36
37fn generate_autoresolvable_impl(item: &Item) -> Result<proc_macro2::TokenStream> {
38    let item = parse_item_impl(item)?;
39
40    validate_item_impl(item)?;
41
42    let self_ty = &item.self_ty;
43
44    let constructors = parse_constructors(&item);
45
46    validate_constructors(item, &constructors)?;
47
48    let constructor = determine_constructor_to_autoresolve(&constructors);
49
50    let constructor_argument_types = parse_constructor_argument_types(constructor)?;
51
52    let resolutions = generate_type_resolutions(&constructor_argument_types);
53
54    let (impl_generics, _type_generics, where_clause) = item.generics.split_for_impl();
55    let ident = &constructor.ident;
56
57    Ok(quote! {
58        #item
59
60        impl #impl_generics wonderbox::internal::AutoResolvable for #self_ty #where_clause {
61             fn try_resolve(container: &wonderbox::Container) -> Option<Self> {
62                Some(Self::#ident(#resolutions))
63             }
64        }
65    })
66}
67
68fn parse_item_impl(item: &Item) -> Result<&ItemImpl> {
69    match item {
70        Item::Impl(item_impl) => Ok(item_impl),
71        _ => {
72            let error_message = format!("{} needs to be placed over an impl block", ATTRIBUTE_NAME);
73            Err(Diagnostic::spanned(
74                item.span_unstable(),
75                Level::Error,
76                error_message,
77            ))
78        }
79    }
80}
81
82fn validate_item_impl(item_impl: &ItemImpl) -> Result<()> {
83    if item_impl.trait_.is_none() {
84        Ok(())
85    } else {
86        let error_message = format!(
87            "{} must be placed over a direct impl, not a trait impl",
88            ATTRIBUTE_NAME
89        );
90        Err(Diagnostic::spanned(
91            item_impl.span_unstable(),
92            Level::Error,
93            error_message,
94        ))
95    }
96}
97
98fn validate_constructors(item_impl: &ItemImpl, constructors: &[&MethodSig]) -> Result<()> {
99    if constructors.len() == 1 {
100        Ok(())
101    } else {
102        let error_message = format!("Expected one constructor, found {}", constructors.len());
103        Err(Diagnostic::spanned(
104            item_impl.span_unstable(),
105            Level::Error,
106            error_message,
107        ))
108    }
109}
110
111fn determine_constructor_to_autoresolve<'a, 'b>(
112    constructors: &'a [&'b MethodSig],
113) -> &'b MethodSig {
114    constructors.first().unwrap()
115}
116
117fn parse_constructors(item_impl: &ItemImpl) -> Vec<&MethodSig> {
118    item_impl
119        .items
120        .iter()
121        .filter_map(parse_method_signature)
122        .filter(|declaration| returns_self(&declaration.decl, &item_impl.self_ty))
123        .filter(|inputs| has_no_self_parameter(&inputs.decl))
124        .collect()
125}
126
127fn parse_method_signature(impl_item: &ImplItem) -> Option<&MethodSig> {
128    match impl_item {
129        ImplItem::Method(method) => Some(&method.sig),
130        _ => None,
131    }
132}
133
134fn returns_self(function: &FnDecl, explicit_self_type: &Type) -> bool {
135    match &function.output {
136        ReturnType::Default => false,
137        ReturnType::Type(_, return_type) => {
138            **return_type == generate_self_type() || **return_type == *explicit_self_type
139        }
140    }
141}
142
143fn has_no_self_parameter(function: &FnDecl) -> bool {
144    let first_input = function.inputs.first();
145    match first_input {
146        Some(first_arg) => match first_arg.value() {
147            FnArg::SelfRef(_) | FnArg::SelfValue(_) => false,
148            _ => true,
149        },
150        None => true,
151    }
152}
153
154fn parse_constructor_argument_types(constructor: &MethodSig) -> Result<Vec<&Type>> {
155    constructor
156        .decl
157        .inputs
158        .iter()
159        .map(|arg| match arg {
160            FnArg::SelfRef(_) | FnArg::SelfValue(_) => unreachable!(),
161            FnArg::Captured(arg) => Ok(&arg.ty),
162            _ => Err(Diagnostic::spanned(
163                arg.span_unstable(),
164                Level::Error,
165                "Only normal, non self type parameters are supported",
166            )),
167        })
168        .collect()
169}
170
171fn generate_type_resolutions(types: &[&Type]) -> Punctuated<proc_macro2::TokenStream, Comma> {
172    types
173        .iter()
174        .map(|type_| {
175            quote! {
176                container.try_resolve::<#type_>()?
177            }
178        })
179        .collect()
180}
181
182fn generate_self_type() -> Type {
183    parse_quote! {
184        Self
185    }
186}
187
188const ATTRIBUTE_NAME: &str = "#[autoresolvable]";