1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse_macro_input;
4
5const SINGLETON_DEFAULT: bool = false;
6
7#[proc_macro_attribute]
8pub fn provider(args: TokenStream, input: TokenStream) -> TokenStream {
9 let args = parse_macro_input!(args as syn::AttributeArgs);
10 let input = parse_macro_input!(input as syn::ItemFn);
11 provider_impl(&args, &input)
12}
13
14fn provider_impl(args: &syn::AttributeArgs, input: &syn::ItemFn) -> TokenStream {
15 let input_fn_name = input.sig.ident.clone();
16 let config_type_name = get_config_type_name(input);
17 let (output_type_name, output_type_params) = get_output_type_name(input);
18 let is_singleton = get_singleton_tag(args);
19
20 let log_creating = format!(
21 "Type '{}' is not global, creating new instance.",
22 output_type_name.to_string()
23 );
24 let output_type_name_as_str = output_type_name.to_string();
25
26 let fn_call = match is_singleton {
27 false => quote! {
28 debug!(#log_creating);
29 #input_fn_name(self)
30 },
31 true => quote! {
32 self.resolve_singleton(|context| {
33 #input_fn_name(context)
34 }, #output_type_name_as_str)
35
36 },
37 };
38
39 let gen = quote! {
40 impl crate::dose_private::Injector<#output_type_name #output_type_params> for dose::Context<#config_type_name> {
41 fn get(&mut self) -> #output_type_name #output_type_params {
42 #fn_call
43 }
44 }
45 #input
46 };
47 gen.into()
48}
49
50fn get_singleton_tag(args: &syn::AttributeArgs) -> bool {
51 let singleton_tag = match args.first() {
52 Some(val) => val,
53 None => return SINGLETON_DEFAULT,
54 };
55 let singleton_tag = match singleton_tag {
56 syn::NestedMeta::Meta(meta) => meta,
57 _ => panic!("Resolver trait not provided"),
58 };
59 let tag_name = singleton_tag.path().segments.first().unwrap().ident.clone();
60 if tag_name.to_string() != "singleton" {
61 return false;
62 }
63 let tag_value = match singleton_tag {
64 syn::Meta::NameValue(val) => val,
65 _ => return false,
66 };
67 let tag_value = match &tag_value.lit {
68 syn::Lit::Bool(val) => val,
69 _ => panic!("Should be a bool"),
70 };
71
72 tag_value.value
73}
74
75fn get_output_type_name(input: &syn::ItemFn) -> (syn::Ident, syn::PathArguments) {
76 let output_type = match &input.sig.output {
77 syn::ReturnType::Type(_, b) => b.as_ref().clone(),
78 _ => panic!("No output to the function"),
79 };
80 let output_type = match output_type {
81 syn::Type::Path(path) => path,
82 _ => panic!("Unsupported type"),
83 };
84 let output_type = output_type.path.segments.first().unwrap();
85
86 (output_type.ident.clone(), output_type.arguments.clone())
87}
88
89fn get_config_type_name(input: &syn::ItemFn) -> syn::Ident {
90 let config_type = match input.sig.inputs.first() {
91 Some(ty) => ty,
92 None => panic!("Function need a first argument"),
93 };
94 let config_type = match config_type {
95 syn::FnArg::Typed(ty) => ty,
96 _ => panic!("Not typed"),
97 };
98 let config_type = match &*config_type.ty {
99 syn::Type::Reference(r) => r,
100 _ => panic!("Argument should be a reference to the resolver"),
101 };
102 let config_type = match &*config_type.elem {
103 syn::Type::Path(path) => path,
104 _ => panic!("Argument should be a reference to the resolver"),
105 };
106 let config_type = config_type.path.segments.first().unwrap();
107 let config_type = match &config_type.arguments {
108 syn::PathArguments::AngleBracketed(br) => br,
109 _ => panic!("Argument should be a reference to the resolver"),
110 };
111 let config_type = config_type.args.first().unwrap();
112 let config_type = match config_type {
113 syn::GenericArgument::Type(ty) => ty,
114 _ => panic!("Argument should be a reference to the resolver"),
115 };
116 let config_type = match &*config_type {
117 syn::Type::Path(path) => path,
118 _ => panic!("Argument should be a reference to the resolver"),
119 };
120 let config_type = config_type.path.segments.first().unwrap();
121 let config_type = config_type.ident.clone();
122
123 config_type
124}