Skip to main content

cgp_macro_lib/entrypoints/
cgp_impl.rs

1use proc_macro2::{Span, TokenStream};
2use quote::{ToTokens, quote};
3use syn::parse::discouraged::Speculative;
4use syn::parse::{Parse, ParseStream};
5use syn::token::{Colon, For};
6use syn::{FnArg, Ident, ImplItem, ItemImpl, Type, parse_quote, parse2};
7
8use crate::derive_provider::{
9    derive_component_name_from_provider_impl, derive_is_provider_for, derive_provider_struct,
10};
11use crate::parse::SimpleType;
12use crate::replace_self::{
13    replace_self_receiver, replace_self_type, replace_self_var, to_snake_case_ident,
14};
15
16pub fn cgp_impl(attr: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
17    let spec: ImplProviderSpec = parse2(attr)?;
18    let mut item_impl: ItemImpl = parse2(body)?;
19
20    let provider_impl = match &item_impl.trait_ {
21        Some((_, path, _)) => {
22            let consumer_trait_path = parse2(path.to_token_stream())?;
23            let context_type = item_impl.self_ty.as_ref();
24            transform_impl_trait(
25                &item_impl,
26                &consumer_trait_path,
27                &spec.provider_type,
28                context_type,
29            )?
30        }
31        None => {
32            let consumer_trait_path = parse2(item_impl.self_ty.to_token_stream())?;
33            let context_type = parse_quote! { __Context__ };
34
35            item_impl
36                .generics
37                .params
38                .insert(0, parse_quote! { __Context__ });
39
40            transform_impl_trait(
41                &item_impl,
42                &consumer_trait_path,
43                &spec.provider_type,
44                &context_type,
45            )?
46        }
47    };
48
49    let component_type = match &spec.component_type {
50        Some(component_type) => component_type.clone(),
51        None => derive_component_name_from_provider_impl(&provider_impl)?,
52    };
53
54    let is_provider_for_impl: ItemImpl = derive_is_provider_for(&component_type, &provider_impl)?;
55
56    let provider_struct = if spec.new_struct {
57        Some(derive_provider_struct(&provider_impl)?)
58    } else {
59        None
60    };
61
62    Ok(quote! {
63        #provider_struct
64
65        #provider_impl
66
67        #is_provider_for_impl
68    })
69}
70
71pub struct ImplProviderSpec {
72    pub new_struct: bool,
73    pub provider_type: Type,
74    pub component_type: Option<Type>,
75}
76
77impl Parse for ImplProviderSpec {
78    fn parse(input: ParseStream) -> syn::Result<Self> {
79        let new_struct = {
80            let fork = input.fork();
81            let new_ident: Option<Ident> = fork.parse().ok();
82            match new_ident {
83                Some(new_ident) if new_ident == "new" => {
84                    input.advance_to(&fork);
85                    true
86                }
87                _ => false,
88            }
89        };
90
91        let provider_type = input.parse()?;
92
93        let component_type = if let Some(_colon) = input.parse::<Option<Colon>>()? {
94            let component_type: Type = input.parse()?;
95            Some(component_type)
96        } else {
97            None
98        };
99
100        Ok(ImplProviderSpec {
101            new_struct,
102            provider_type,
103            component_type,
104        })
105    }
106}
107
108pub fn transform_impl_trait(
109    item_impl: &ItemImpl,
110    consumer_trait_path: &SimpleType,
111    provider_type: &Type,
112    context_type: &Type,
113) -> syn::Result<ItemImpl> {
114    let context_var = if let Ok(ident) = parse2::<Ident>(context_type.to_token_stream()) {
115        to_snake_case_ident(&ident)
116    } else {
117        Ident::new("__context__", Span::call_site())
118    };
119
120    let local_assoc_types: Vec<Ident> = item_impl
121        .items
122        .iter()
123        .filter_map(|item| {
124            if let ImplItem::Type(assoc_type) = item {
125                Some(assoc_type.ident.clone())
126            } else {
127                None
128            }
129        })
130        .collect();
131
132    let raw_out_impl = replace_self_type(
133        item_impl.to_token_stream(),
134        context_type.to_token_stream(),
135        &local_assoc_types,
136    );
137
138    let mut out_impl: ItemImpl = parse2(raw_out_impl)?;
139    out_impl.self_ty = Box::new(provider_type.clone());
140
141    let mut provider_trait_path: SimpleType = consumer_trait_path.clone();
142
143    match &mut provider_trait_path.generics {
144        Some(generics) => {
145            generics
146                .args
147                .insert(0, parse2(context_type.to_token_stream())?);
148        }
149        None => {
150            provider_trait_path.generics = Some(parse2(quote! { < #context_type > })?);
151        }
152    }
153
154    out_impl.trait_ = Some((
155        None,
156        parse2(provider_trait_path.to_token_stream())?,
157        For(Span::call_site()),
158    ));
159
160    for item in out_impl.items.iter_mut() {
161        if let ImplItem::Fn(item_fn) = item
162            && let Some(arg) = item_fn.sig.inputs.first_mut()
163            && let FnArg::Receiver(receiver) = arg
164        {
165            *arg = replace_self_receiver(receiver, &context_var, context_type.to_token_stream());
166
167            let replaced_block = replace_self_var(item_fn.block.to_token_stream(), &context_var);
168            item_fn.block = parse2(replaced_block)?;
169        }
170    }
171
172    Ok(out_impl)
173}