Skip to main content

auto_di_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    FnArg, GenericArgument, ImplItem, ImplItemFn, Item, ItemFn, ItemImpl, ItemStruct, LitStr, Pat,
5    PathArguments, ReturnType, Signature, Type, parse_macro_input,
6};
7
8#[derive(Default, Clone)]
9struct ProviderOptions {
10    name: Option<String>,
11    primary: bool,
12    scope: Option<String>,
13    eager: bool,
14    profile: Option<String>,
15    condition_key: Option<String>,
16    condition_value: Option<String>,
17    post_construct: Option<String>,
18    pre_destroy: Option<String>,
19}
20
21/// Registers a lazily-created singleton provider.
22///
23/// It can be placed on either a free provider function or an inherent `impl`
24/// containing a `new` constructor. Constructors may be synchronous or async.
25/// Every injected parameter must have the form `name: Arc<Dependency>`.
26#[proc_macro_attribute]
27pub fn singleton(attribute: TokenStream, item: TokenStream) -> TokenStream {
28    let options = match parse_provider_options(attribute) {
29        Ok(options) => options,
30        Err(error) => return error.to_compile_error().into(),
31    };
32    let item = parse_macro_input!(item as Item);
33
34    let expanded = match item {
35        Item::Fn(function) => expand_function(function, &options),
36        Item::Impl(item_impl) => expand_impl(item_impl, &options),
37        other => Err(syn::Error::new_spanned(
38            other,
39            "#[singleton] can only be used on a function or an inherent impl block",
40        )),
41    };
42
43    match expanded {
44        Ok(tokens) => tokens.into(),
45        Err(error) => error.to_compile_error().into(),
46    }
47}
48
49fn parse_provider_options(attribute: TokenStream) -> syn::Result<ProviderOptions> {
50    parse_provider_options2(attribute.into())
51}
52
53fn parse_provider_options2(attribute: proc_macro2::TokenStream) -> syn::Result<ProviderOptions> {
54    use syn::parse::Parser;
55    let mut options = ProviderOptions::default();
56    let parser = syn::meta::parser(|meta| {
57        if meta.path.is_ident("primary") {
58            options.primary = true;
59            return Ok(());
60        }
61        if meta.path.is_ident("eager") {
62            options.eager = true;
63            return Ok(());
64        }
65        let value = meta.value()?.parse::<LitStr>()?.value();
66        if meta.path.is_ident("name") {
67            options.name = Some(value);
68        } else if meta.path.is_ident("scope") {
69            options.scope = Some(value);
70        } else if meta.path.is_ident("profile") {
71            options.profile = Some(value);
72        } else if meta.path.is_ident("condition") {
73            let (key, expected) = value
74                .split_once('=')
75                .map_or((value.as_str(), None), |(k, v)| (k, Some(v)));
76            options.condition_key = Some(key.to_owned());
77            options.condition_value = expected.map(str::to_owned);
78        } else if meta.path.is_ident("post_construct") {
79            options.post_construct = Some(value);
80        } else if meta.path.is_ident("pre_destroy") {
81            options.pre_destroy = Some(value);
82        } else {
83            return Err(meta.error("unknown provider option"));
84        }
85        Ok(())
86    });
87    parser.parse2(attribute)?;
88    if let Some(scope) = &options.scope {
89        if !matches!(scope.as_str(), "singleton" | "prototype" | "request") {
90            return Err(syn::Error::new(
91                proc_macro2::Span::call_site(),
92                "scope must be singleton, prototype, or request",
93            ));
94        }
95    }
96    Ok(options)
97}
98
99#[proc_macro_attribute]
100pub fn component(attribute: TokenStream, item: TokenStream) -> TokenStream {
101    singleton(attribute, item)
102}
103
104#[proc_macro_attribute]
105pub fn service(attribute: TokenStream, item: TokenStream) -> TokenStream {
106    singleton(attribute, item)
107}
108
109#[proc_macro_attribute]
110pub fn repository(attribute: TokenStream, item: TokenStream) -> TokenStream {
111    singleton(attribute, item)
112}
113
114/// Marker consumed from constructor parameters by the surrounding provider macro.
115#[proc_macro_attribute]
116pub fn qualifier(_attribute: TokenStream, item: TokenStream) -> TokenStream {
117    item
118}
119
120/// Boots the global context, creates eager beans, and runs shutdown hooks.
121#[proc_macro_attribute]
122pub fn application(_attribute: TokenStream, item: TokenStream) -> TokenStream {
123    let mut function = parse_macro_input!(item as ItemFn);
124    if function.sig.asyncness.is_none() {
125        return syn::Error::new_spanned(&function.sig, "#[application] requires async fn")
126            .to_compile_error()
127            .into();
128    }
129    let body = function.block;
130    function
131        .attrs
132        .push(syn::parse_quote!(#[::auto_di::__private::tokio::main]));
133    function.block = Box::new(syn::parse_quote!({
134        let __container = ::auto_di::global_container()?;
135        __container.initialize_eager().await?;
136        let __result = (async move #body).await;
137        __container.shutdown().await?;
138        __result
139    }));
140    quote!(#function).into()
141}
142
143/// Generates environment binding and registers the resulting struct as a bean.
144#[proc_macro_attribute]
145pub fn configuration_properties(attribute: TokenStream, item: TokenStream) -> TokenStream {
146    let prefix = parse_macro_input!(attribute as LitStr).value();
147    let structure = parse_macro_input!(item as ItemStruct);
148    match expand_configuration_properties(prefix, structure) {
149        Ok(tokens) => tokens.into(),
150        Err(error) => error.to_compile_error().into(),
151    }
152}
153
154fn expand_configuration_properties(
155    prefix: String,
156    structure: ItemStruct,
157) -> syn::Result<proc_macro2::TokenStream> {
158    let ident = &structure.ident;
159    let fields = match &structure.fields {
160        syn::Fields::Named(fields) => &fields.named,
161        _ => {
162            return Err(syn::Error::new_spanned(
163                &structure,
164                "configuration properties require named fields",
165            ));
166        }
167    };
168    let bindings = fields.iter().map(|field| {
169        let name = field.ident.as_ref().expect("named field");
170        let ty = &field.ty;
171        let key = format!("{}_{}", prefix, name).replace(['.', '-'], "_").to_uppercase();
172        quote! {
173            #name: ::std::env::var(#key)
174                .map_err(|error| ::auto_di::DiError::Configuration { key: #key.into(), message: error.to_string() })?
175                .parse::<#ty>()
176                .map_err(|_| ::auto_di::DiError::Configuration { key: #key.into(), message: "value could not be parsed".into() })?
177        }
178    });
179    let provider_name = format_ident!("configuration_properties_{ident}");
180    let invocation = quote! { <#ident as ::auto_di::ConfigurationProperties>::from_environment()? };
181    let registration = registration(
182        &provider_name,
183        &syn::parse_quote!(#ident),
184        &[],
185        &[],
186        &[],
187        invocation,
188    );
189    Ok(quote! {
190        #structure
191        impl ::auto_di::ConfigurationProperties for #ident {
192            fn from_environment() -> ::std::result::Result<Self, ::auto_di::DiError> {
193                Ok(Self { #(#bindings),* })
194            }
195        }
196        #registration
197    })
198}
199
200/// Registers all `#[bean]` methods in an inherent impl as singleton providers.
201/// The configuration object is itself a singleton created through `Default`.
202#[proc_macro_attribute]
203pub fn configuration(_attribute: TokenStream, item: TokenStream) -> TokenStream {
204    let item_impl = parse_macro_input!(item as ItemImpl);
205    match expand_configuration(item_impl) {
206        Ok(tokens) => tokens.into(),
207        Err(error) => error.to_compile_error().into(),
208    }
209}
210
211/// Registers `#[bean]` methods from any ordinary impl block. The owner is a
212/// singleton created through `Default`, allowing `&self` bean methods.
213#[proc_macro_attribute]
214pub fn beans(attribute: TokenStream, item: TokenStream) -> TokenStream {
215    configuration(attribute, item)
216}
217
218/// Registers a standalone bean, or is consumed as a bean marker when nested
219/// inside a `#[configuration]` impl.
220#[proc_macro_attribute]
221pub fn bean(attribute: TokenStream, item: TokenStream) -> TokenStream {
222    singleton(attribute, item)
223}
224
225fn expand_function(
226    mut function: ItemFn,
227    options: &ProviderOptions,
228) -> syn::Result<proc_macro2::TokenStream> {
229    let function_name = function.sig.ident.clone();
230    let output_type = match &function.sig.output {
231        ReturnType::Type(_, ty) => ty.as_ref().clone(),
232        ReturnType::Default => {
233            return Err(syn::Error::new_spanned(
234                &function.sig,
235                "a #[singleton] provider must return a value",
236            ));
237        }
238    };
239    let (argument_names, dependency_types, qualifiers) = dependencies(&mut function.sig)?;
240    let invocation = if function.sig.asyncness.is_some() {
241        quote! { #function_name(#(#argument_names),*).await }
242    } else {
243        quote! { #function_name(#(#argument_names),*) }
244    };
245
246    let registration = registration_with_options(
247        &function_name,
248        &output_type,
249        &argument_names,
250        &dependency_types,
251        &qualifiers,
252        invocation,
253        options,
254    );
255
256    Ok(quote! {
257        #function
258        #registration
259    })
260}
261
262fn expand_impl(
263    mut item_impl: ItemImpl,
264    options: &ProviderOptions,
265) -> syn::Result<proc_macro2::TokenStream> {
266    if item_impl.trait_.is_some() {
267        return Err(syn::Error::new_spanned(
268            &item_impl,
269            "#[singleton] requires an inherent impl, not a trait impl",
270        ));
271    }
272    if !item_impl.generics.params.is_empty() {
273        return Err(syn::Error::new_spanned(
274            &item_impl.generics,
275            "generic singleton impl blocks are not supported yet",
276        ));
277    }
278
279    let constructor_index = item_impl
280        .items
281        .iter()
282        .position(|item| match item {
283            ImplItem::Fn(function) => function.sig.ident == "new",
284            _ => false,
285        })
286        .ok_or_else(|| {
287            syn::Error::new_spanned(
288                &item_impl.self_ty,
289                "a #[singleton] impl must contain a new(...) -> Self constructor",
290            )
291        })?;
292    let output_type = item_impl.self_ty.as_ref().clone();
293    let constructor = match &mut item_impl.items[constructor_index] {
294        ImplItem::Fn(function) => function,
295        _ => unreachable!(),
296    };
297    validate_impl_constructor(constructor)?;
298
299    let (argument_names, dependency_types, qualifiers) = dependencies(&mut constructor.sig)?;
300    let type_ident = singleton_type_ident(&output_type)?;
301    let constructor_name = constructor.sig.ident.clone();
302    let invocation = if constructor.sig.asyncness.is_some() {
303        quote! { <#output_type>::#constructor_name(#(#argument_names),*).await }
304    } else {
305        quote! { <#output_type>::#constructor_name(#(#argument_names),*) }
306    };
307    let registration = registration_with_options(
308        type_ident,
309        &output_type,
310        &argument_names,
311        &dependency_types,
312        &qualifiers,
313        invocation,
314        options,
315    );
316
317    let mut bean_registrations = Vec::new();
318    for item in &mut item_impl.items {
319        let ImplItem::Fn(method) = item else { continue };
320        let Some(bean_attribute) = method
321            .attrs
322            .iter()
323            .find(|attribute| attribute.path().is_ident("bean"))
324        else {
325            continue;
326        };
327        let bean_options = match &bean_attribute.meta {
328            syn::Meta::Path(_) => ProviderOptions::default(),
329            syn::Meta::List(list) => parse_provider_options2(list.tokens.clone())?,
330            syn::Meta::NameValue(_) => {
331                return Err(syn::Error::new_spanned(bean_attribute, "use #[bean(...)]"));
332            }
333        };
334        method
335            .attrs
336            .retain(|attribute| !attribute.path().is_ident("bean"));
337
338        let bean_type = match &method.sig.output {
339            ReturnType::Type(_, ty) => ty.as_ref().clone(),
340            ReturnType::Default => {
341                return Err(syn::Error::new_spanned(
342                    &method.sig,
343                    "a #[bean] method must return a value",
344                ));
345            }
346        };
347        let (has_receiver, bean_arguments, bean_dependencies, bean_qualifiers) =
348            bean_dependencies(&mut method.sig)?;
349        let method_name = method.sig.ident.clone();
350        let bean_registration_name = format_ident!("bean_{type_ident}_{method_name}");
351        let bean_invocation = if has_receiver {
352            quote! { owner.#method_name(#(#bean_arguments),*) }
353        } else {
354            quote! { <#output_type>::#method_name(#(#bean_arguments),*) }
355        };
356        let bean_invocation = if method.sig.asyncness.is_some() {
357            quote! { #bean_invocation.await }
358        } else {
359            bean_invocation
360        };
361        let prelude = has_receiver.then(|| {
362            quote! {
363                let owner: ::std::sync::Arc<#output_type> =
364                    container.resolve_dependency::<#output_type>(&context).await?;
365            }
366        });
367
368        bean_registrations.push(registration_with_prelude(
369            &bean_registration_name,
370            &bean_type,
371            &bean_arguments,
372            &bean_dependencies,
373            &bean_qualifiers,
374            prelude.unwrap_or_default(),
375            bean_invocation,
376            &bean_options,
377        ));
378    }
379
380    Ok(quote! {
381        #item_impl
382        #registration
383        #(#bean_registrations)*
384    })
385}
386
387fn expand_configuration(mut item_impl: ItemImpl) -> syn::Result<proc_macro2::TokenStream> {
388    if item_impl.trait_.is_some() || !item_impl.generics.params.is_empty() {
389        return Err(syn::Error::new_spanned(
390            &item_impl,
391            "#[configuration] requires a non-generic inherent impl",
392        ));
393    }
394
395    let configuration_type = item_impl.self_ty.as_ref().clone();
396    let configuration_ident = singleton_type_ident(&configuration_type)?.clone();
397    let configuration_registration = registration(
398        &format_ident!("configuration_{configuration_ident}"),
399        &configuration_type,
400        &[],
401        &[],
402        &[],
403        quote! { <#configuration_type as ::std::default::Default>::default() },
404    );
405    let mut registrations = Vec::new();
406
407    for item in &mut item_impl.items {
408        let ImplItem::Fn(method) = item else { continue };
409        let Some(bean_attribute) = method
410            .attrs
411            .iter()
412            .find(|attr| attr.path().is_ident("bean"))
413        else {
414            continue;
415        };
416        let bean_options = match &bean_attribute.meta {
417            syn::Meta::Path(_) => ProviderOptions::default(),
418            syn::Meta::List(list) => parse_provider_options2(list.tokens.clone())?,
419            syn::Meta::NameValue(_) => {
420                return Err(syn::Error::new_spanned(bean_attribute, "use #[bean(...)]"));
421            }
422        };
423        method.attrs.retain(|attr| !attr.path().is_ident("bean"));
424
425        let output_type = match &method.sig.output {
426            ReturnType::Type(_, ty) => ty.as_ref().clone(),
427            ReturnType::Default => {
428                return Err(syn::Error::new_spanned(
429                    &method.sig,
430                    "a #[bean] method must return a value",
431                ));
432            }
433        };
434        let (has_receiver, argument_names, dependency_types, qualifiers) =
435            bean_dependencies(&mut method.sig)?;
436        let method_name = method.sig.ident.clone();
437        let registration_name = format_ident!("bean_{configuration_ident}_{method_name}");
438        let invocation = if has_receiver {
439            quote! { configuration.#method_name(#(#argument_names),*) }
440        } else {
441            quote! { <#configuration_type>::#method_name(#(#argument_names),*) }
442        };
443        let invocation = if method.sig.asyncness.is_some() {
444            quote! { #invocation.await }
445        } else {
446            invocation
447        };
448        let prelude = has_receiver.then(|| {
449            quote! {
450                let configuration: ::std::sync::Arc<#configuration_type> =
451                    container.resolve_dependency::<#configuration_type>(&context).await?;
452            }
453        });
454
455        registrations.push(registration_with_prelude(
456            &registration_name,
457            &output_type,
458            &argument_names,
459            &dependency_types,
460            &qualifiers,
461            prelude.unwrap_or_default(),
462            invocation,
463            &bean_options,
464        ));
465    }
466
467    if registrations.is_empty() {
468        return Err(syn::Error::new_spanned(
469            &item_impl,
470            "#[configuration] must contain at least one #[bean] method",
471        ));
472    }
473
474    Ok(quote! {
475        #item_impl
476        #configuration_registration
477        #(#registrations)*
478    })
479}
480
481fn validate_impl_constructor(constructor: &ImplItemFn) -> syn::Result<()> {
482    if constructor.sig.receiver().is_some() {
483        return Err(syn::Error::new_spanned(
484            &constructor.sig,
485            "the singleton new constructor must be an associated function",
486        ));
487    }
488    match &constructor.sig.output {
489        ReturnType::Type(_, ty) if matches!(ty.as_ref(), Type::Path(path) if path.path.is_ident("Self")) => {
490            Ok(())
491        }
492        _ => Err(syn::Error::new_spanned(
493            &constructor.sig.output,
494            "the singleton new constructor must return Self",
495        )),
496    }
497}
498
499fn dependencies(
500    signature: &mut Signature,
501) -> syn::Result<(Vec<syn::Ident>, Vec<Type>, Vec<Option<String>>)> {
502    let mut argument_names = Vec::new();
503    let mut dependency_types = Vec::new();
504    let mut qualifiers = Vec::new();
505
506    for input in &mut signature.inputs {
507        let FnArg::Typed(argument) = input else {
508            return Err(syn::Error::new_spanned(
509                input,
510                "singleton constructors cannot have a self receiver",
511            ));
512        };
513        let Pat::Ident(pattern) = argument.pat.as_ref() else {
514            return Err(syn::Error::new_spanned(
515                &argument.pat,
516                "use a simple parameter name for an injected dependency",
517            ));
518        };
519        argument_names.push(pattern.ident.clone());
520        validate_dependency_type(argument.ty.as_ref())?;
521        dependency_types.push(argument.ty.as_ref().clone());
522        qualifiers.push(take_qualifier(&mut argument.attrs)?);
523    }
524    Ok((argument_names, dependency_types, qualifiers))
525}
526
527fn bean_dependencies(
528    signature: &mut Signature,
529) -> syn::Result<(bool, Vec<syn::Ident>, Vec<Type>, Vec<Option<String>>)> {
530    let mut has_receiver = false;
531    let mut names = Vec::new();
532    let mut types = Vec::new();
533    let mut qualifiers = Vec::new();
534    for input in &mut signature.inputs {
535        match input {
536            FnArg::Receiver(receiver) => {
537                if has_receiver || receiver.reference.is_none() || receiver.mutability.is_some() {
538                    return Err(syn::Error::new_spanned(
539                        receiver,
540                        "a #[bean] method only supports an immutable &self receiver",
541                    ));
542                }
543                has_receiver = true;
544            }
545            FnArg::Typed(argument) => {
546                let Pat::Ident(pattern) = argument.pat.as_ref() else {
547                    return Err(syn::Error::new_spanned(
548                        &argument.pat,
549                        "use a simple parameter name for an injected dependency",
550                    ));
551                };
552                names.push(pattern.ident.clone());
553                validate_dependency_type(argument.ty.as_ref())?;
554                types.push(argument.ty.as_ref().clone());
555                qualifiers.push(take_qualifier(&mut argument.attrs)?);
556            }
557        }
558    }
559    Ok((has_receiver, names, types, qualifiers))
560}
561
562fn take_qualifier(attributes: &mut Vec<syn::Attribute>) -> syn::Result<Option<String>> {
563    let mut value = None;
564    for attribute in attributes
565        .iter()
566        .filter(|attr| attr.path().is_ident("qualifier"))
567    {
568        if value.is_some() {
569            return Err(syn::Error::new_spanned(
570                attribute,
571                "only one qualifier is allowed",
572            ));
573        }
574        value = Some(attribute.parse_args::<LitStr>()?.value());
575    }
576    attributes.retain(|attr| !attr.path().is_ident("qualifier"));
577    Ok(value)
578}
579
580fn registration(
581    name: &syn::Ident,
582    output_type: &Type,
583    argument_names: &[syn::Ident],
584    dependency_types: &[Type],
585    qualifiers: &[Option<String>],
586    invocation: proc_macro2::TokenStream,
587) -> proc_macro2::TokenStream {
588    registration_with_options(
589        name,
590        output_type,
591        argument_names,
592        dependency_types,
593        qualifiers,
594        invocation,
595        &ProviderOptions::default(),
596    )
597}
598
599fn registration_with_options(
600    name: &syn::Ident,
601    output_type: &Type,
602    argument_names: &[syn::Ident],
603    dependency_types: &[Type],
604    qualifiers: &[Option<String>],
605    invocation: proc_macro2::TokenStream,
606    options: &ProviderOptions,
607) -> proc_macro2::TokenStream {
608    registration_with_prelude(
609        name,
610        output_type,
611        argument_names,
612        dependency_types,
613        qualifiers,
614        quote! {},
615        invocation,
616        options,
617    )
618}
619
620fn registration_with_prelude(
621    name: &syn::Ident,
622    output_type: &Type,
623    argument_names: &[syn::Ident],
624    dependency_types: &[Type],
625    qualifiers: &[Option<String>],
626    prelude: proc_macro2::TokenStream,
627    invocation: proc_macro2::TokenStream,
628    options: &ProviderOptions,
629) -> proc_macro2::TokenStream {
630    let factory_name = format_ident!("__di_factory_{name}");
631    let type_id_name = format_ident!("__di_type_id_{name}");
632    let type_name_name = format_ident!("__di_type_name_{name}");
633    let destroy_name = format_ident!("__di_destroy_{name}");
634    let option_tokens = |value: Option<&str>| match value {
635        Some(value) => quote!(Some(#value)),
636        None => quote!(None),
637    };
638    let bean_name = option_tokens(options.name.as_deref());
639    let primary = options.primary;
640    let eager = options.eager;
641    let profile = option_tokens(options.profile.as_deref());
642    let condition_key = option_tokens(options.condition_key.as_deref());
643    let condition_value = option_tokens(options.condition_value.as_deref());
644    let scope = match options.scope.as_deref().unwrap_or("singleton") {
645        "prototype" => quote!(::auto_di::Scope::Prototype),
646        "request" => quote!(::auto_di::Scope::Request),
647        _ => quote!(::auto_di::Scope::Singleton),
648    };
649    let post_construct = options.post_construct.as_ref().map(|method| {
650        let method = format_ident!("{method}");
651        quote! { value.#method().await; }
652    });
653    let (destroy_function, destroy_value) = if let Some(method) = &options.pre_destroy {
654        let method = format_ident!("{method}");
655        (
656            quote! {
657                #[doc(hidden)]
658                fn #destroy_name(value: ::auto_di::DynArc) -> ::auto_di::BoxFuture<'static, ::std::result::Result<(), ::auto_di::DiError>> {
659                    ::std::boxed::Box::pin(async move {
660                        let value = value.downcast::<#output_type>()
661                            .map_err(|_| ::auto_di::DiError::TypeMismatch(::std::any::type_name::<#output_type>()))?;
662                        value.#method().await;
663                        Ok(())
664                    })
665                }
666            },
667            quote!(Some(#destroy_name as _)),
668        )
669    } else {
670        (quote! {}, quote!(None))
671    };
672    let resolve_dependencies = argument_names
673        .iter()
674        .zip(dependency_types.iter())
675        .zip(qualifiers.iter())
676        .map(|((name, ty), qualifier)| dependency_resolution(name, ty, qualifier.as_deref()));
677
678    quote! {
679        #[doc(hidden)]
680        fn #type_id_name() -> ::std::any::TypeId {
681            ::std::any::TypeId::of::<#output_type>()
682        }
683
684        #[doc(hidden)]
685        fn #type_name_name() -> &'static str {
686            ::std::any::type_name::<#output_type>()
687        }
688
689        #[doc(hidden)]
690        fn #factory_name<'a>(
691            container: &'a ::auto_di::Container,
692            context: ::auto_di::ResolutionContext,
693        ) -> ::auto_di::BoxFuture<'a, ::std::result::Result<::auto_di::DynArc, ::auto_di::DiError>> {
694            ::std::boxed::Box::pin(async move {
695                #prelude
696                #(#resolve_dependencies)*
697                let value: #output_type = #invocation;
698                #post_construct
699                Ok(::std::sync::Arc::new(value) as ::auto_di::DynArc)
700            })
701        }
702
703        #destroy_function
704
705        ::auto_di::__private::inventory::submit! {
706            ::auto_di::ProviderDescriptor::configured(
707                #type_id_name,
708                #type_name_name,
709                #factory_name,
710                #bean_name,
711                #primary,
712                #scope,
713                #eager,
714                #profile,
715                #condition_key,
716                #condition_value,
717                #destroy_value,
718            )
719        }
720    }
721}
722
723fn singleton_type_ident(ty: &Type) -> syn::Result<&syn::Ident> {
724    let Type::Path(path) = ty else {
725        return Err(syn::Error::new_spanned(
726            ty,
727            "singleton impl type must be a named type",
728        ));
729    };
730    path.path
731        .segments
732        .last()
733        .map(|segment| &segment.ident)
734        .ok_or_else(|| syn::Error::new_spanned(ty, "singleton impl type must be a named type"))
735}
736
737fn validate_dependency_type(ty: &Type) -> syn::Result<()> {
738    if generic_inner(ty, "Arc").is_some()
739        || generic_inner(ty, "Provider").is_some()
740        || generic_inner(ty, "Lazy").is_some()
741    {
742        return Ok(());
743    }
744    if let Some(inner) = generic_inner(ty, "Option").or_else(|| generic_inner(ty, "Vec")) {
745        if generic_inner(inner, "Arc").is_some() {
746            return Ok(());
747        }
748    }
749    Err(syn::Error::new_spanned(
750        ty,
751        "dependency must be Arc<T>, Option<Arc<T>>, Vec<Arc<T>>, Provider<T>, or Lazy<T>",
752    ))
753}
754
755fn generic_inner<'a>(ty: &'a Type, expected: &str) -> Option<&'a Type> {
756    let Type::Path(path) = ty else { return None };
757    let segment = path.path.segments.last()?;
758    if segment.ident != expected {
759        return None;
760    }
761    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
762        return None;
763    };
764    match arguments.args.first() {
765        Some(GenericArgument::Type(inner)) if arguments.args.len() == 1 => Some(inner),
766        _ => None,
767    }
768}
769
770fn dependency_resolution(
771    name: &syn::Ident,
772    ty: &Type,
773    qualifier: Option<&str>,
774) -> proc_macro2::TokenStream {
775    if let Some(inner) = generic_inner(ty, "Arc") {
776        if matches!(inner, Type::TraitObject(_)) {
777            if let Some(qualifier) = qualifier {
778                return quote! {
779                    let #name: #ty = (*container.resolve_named_dependency::<#ty>(#qualifier, &context).await?).clone();
780                };
781            }
782            return quote! {
783                let #name: #ty = (*container.resolve_dependency::<#ty>(&context).await?).clone();
784            };
785        }
786        if let Some(qualifier) = qualifier {
787            return quote! {
788                let #name: #ty = container.resolve_named_dependency::<#inner>(#qualifier, &context).await?;
789            };
790        }
791        return quote! {
792            let #name: #ty = container.resolve_dependency::<#inner>(&context).await?;
793        };
794    }
795    if let Some(wrapped) = generic_inner(ty, "Option") {
796        let inner = generic_inner(wrapped, "Arc").expect("validated Option<Arc<T>>");
797        return quote! {
798            let #name: #ty = container.resolve_optional_dependency::<#inner>(&context).await?;
799        };
800    }
801    if let Some(wrapped) = generic_inner(ty, "Vec") {
802        let inner = generic_inner(wrapped, "Arc").expect("validated Vec<Arc<T>>");
803        return quote! {
804            let #name: #ty = container.resolve_all_dependency::<#inner>(&context).await?;
805        };
806    }
807    quote! { let #name: #ty = ::std::default::Default::default(); }
808}