1use proc_macro::TokenStream;
2use proc_macro2::{Ident, Span};
3use quote::{ToTokens, quote};
4use syn::punctuated::Punctuated;
5use syn::token::Comma;
6use syn::{Expr, ExprArray, Lit, PatLit};
7use syn::{ItemFn, parse_macro_input};
8
9#[proc_macro_attribute]
10pub fn register_constructor(attr: TokenStream, item: TokenStream) -> TokenStream {
11 let args =
13 parse_macro_input!(attr with Punctuated::<syn::MetaNameValue, Comma>::parse_terminated);
14
15 let args = args.into_iter().collect::<Vec<syn::MetaNameValue>>();
16
17 let inject_scope = args
18 .iter()
19 .find(|x| x.path.get_ident().is_some_and(|x| x.to_string() == "scope"))
20 .cloned();
21
22 let maps = args
23 .iter()
24 .find(|x| x.path.get_ident().is_some_and(|x| x.to_string() == "map"))
25 .cloned();
26
27 let maps = maps
28 .as_ref()
29 .and_then(|x| {
30 if let Expr::Array(ExprArray { elems, .. }) = &x.value {
31 Some(elems.iter().collect::<Vec<_>>())
32 } else {
33 None
34 }
35 })
36 .unwrap_or_default();
37
38 let maps_quote = maps
39 .iter()
40 .map(|map| quote! { builder.map_as_trait::<dyn #map>(); })
41 .collect::<Vec<_>>();
42
43 let input_fn = parse_macro_input!(item as ItemFn);
44 let fn_name = &input_fn.sig.ident;
45
46 let crate_name = proc_macro_crate::crate_name("xdi").expect("Failed to get crate name for xdi");
47
48 let crate_name = match crate_name {
49 proc_macro_crate::FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
50 proc_macro_crate::FoundCrate::Itself => Ident::new("crate", Span::call_site()),
51 };
52
53 let scope = inject_scope.as_ref().and_then(|x| {
54 if let Expr::Lit(PatLit {
55 lit: Lit::Str(val), ..
56 }) = &x.value
57 {
58 Some(val.value())
59 } else {
60 None
61 }
62 });
63
64 if let Some(inject_scope) = inject_scope
65 && scope.is_none()
66 {
67 panic!(
68 r#"Invalid scope value in register_constructor: {:?}, expected: "singleton", "transient", "task_local", "thread_local""#,
69 inject_scope.value.to_token_stream()
70 );
71 }
72
73 if scope.as_ref().is_some_and(|x| x == "singleton") {
74 let expanded = quote! {
75 #input_fn
76
77 inventory::submit! {
78 #crate_name::Registration {
79 constructor: &|builder| {
80 let builder = builder.singletone(#fn_name);
81
82 #(#maps_quote)*
83 }
84 }
85 }
86 };
87
88 return expanded.into();
89 }
90
91 if scope.as_ref().is_some_and(|x| x == "thread_local") {
92 let expanded = quote! {
93 #input_fn
94
95 inventory::submit! {
96 #crate_name::Registration {
97 constructor: &|builder| {
98 let builder = builder.thread_local(#fn_name);
99
100 #(#maps_quote)*
101 }
102 }
103 }
104 };
105
106 return expanded.into();
107 }
108
109 if scope.as_ref().is_some_and(|x| x == "task_local") {
110 let expanded = quote! {
111 #input_fn
112
113 inventory::submit! {
114 #crate_name::Registration {
115 constructor: &|builder| {
116 let builder = builder.task_local(#fn_name);
117
118 #(#maps_quote)*
119 }
120 }
121 }
122 };
123
124 return expanded.into();
125 }
126
127 if scope.as_ref().is_none_or(|x| x == "transient") {
128 let expanded = quote! {
129 #input_fn
130
131 inventory::submit! {
132 #crate_name::Registration {
133 constructor: &|builder| {
134 let builder = builder.transient(#fn_name);
135
136 #(#maps_quote)*
137 }
138 }
139 }
140 };
141
142 return expanded.into();
143 }
144
145 panic!("Unsupported inject scope: {:?}", scope);
146}