1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
|
use quote::quote;
use syn::spanned::Spanned;
use syn::{FnArg, Ident, Pat, PatType, Signature, Type, TypePath};
/// Extracted information about a resource injection parameter
pub(crate) struct ResourceInjection {
pub(crate) var_name: Ident,
pub(crate) full_type: Type,
pub(crate) inner_type: TypePath,
pub(crate) is_ref: bool,
pub(crate) is_mut: bool,
}
/// Extracts the previous type and parameter name from function arguments,
/// and collects resource injection parameters from the 2nd argument onward.
#[allow(clippy::too_many_lines)]
pub(crate) fn extract_args_info(
sig: &Signature,
) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> {
if sig.inputs.is_empty() {
return Err(syn::Error::new(
sig.span(),
"Function must have at least one parameter",
));
}
// First parameter: required, the previous type (must be owned, not a reference)
let first_arg = &sig.inputs[0];
let (prev_param, previous_type) = match first_arg {
FnArg::Typed(PatType { pat, ty, .. }) => {
let param_pat = (**pat).clone();
match &**ty {
Type::Path(type_path) => {
// Check that the type is a single-segment type (no `::`)
if type_path.path.segments.len() > 1 {
return Err(syn::Error::new(
type_path.span(),
format!(
"The type `{}` must be a simple single-segment type, \
e.g. `Empty` instead of `other::Empty`. \
Qualified paths with `::` are not allowed here.",
quote! { #type_path }
),
));
}
(param_pat, type_path.clone())
}
Type::Reference(_) => {
return Err(syn::Error::new(
ty.span(),
"The first parameter (previous type) must be taken by move, \
not by reference. \
Use `prev: SomeEntry` instead of `prev: &SomeEntry`.",
));
}
_ => {
return Err(syn::Error::new(
ty.span(),
"First parameter type must be a type path",
));
}
}
}
FnArg::Receiver(_) => {
return Err(syn::Error::new(
first_arg.span(),
"Function cannot have self parameter",
));
}
};
// 2nd to Nth parameters: optional, for resource injection
let mut resources = Vec::new();
for arg in sig.inputs.iter().skip(1) {
match arg {
FnArg::Typed(PatType { pat, ty, .. }) => {
// Extract the variable name – must be a simple identifier
let var_name = match &**pat {
Pat::Ident(pat_ident) => pat_ident.ident.clone(),
_ => {
return Err(syn::Error::new(
pat.span(),
"Resource injection parameter must be a simple identifier (e.g., `age: &Age`)",
));
}
};
let full_type = *(*ty).clone();
// Try to extract inner type for reference patterns like `&Age` -> `Age`
// and `&mut Age` -> `Age`
let (inner_type, is_ref, is_mut) = match &full_type {
Type::Reference(ref_type) => match &*ref_type.elem {
Type::Path(type_path) => {
let is_mut = ref_type.mutability.is_some();
(type_path.clone(), true, is_mut)
}
_ => {
return Err(syn::Error::new(
ty.span(),
"Reference resource type must be a type path (e.g., `age: &Age`)",
));
}
},
Type::Path(_) => {
return Err(syn::Error::new(
ty.span(),
"Resource injection parameter must be a reference (`&T` or `&mut T`), \
not an owned value. Use `age: &Age` instead of `age: Age`.",
));
}
_ => {
return Err(syn::Error::new(
ty.span(),
"Resource injection type must be a type path or reference to one \
(e.g., `age: Age` or `age: &Age`)",
));
}
};
resources.push(ResourceInjection {
var_name,
full_type,
inner_type,
is_ref,
is_mut,
});
}
FnArg::Receiver(_) => {
return Err(syn::Error::new(
arg.span(),
"Resource injection parameter cannot be self",
));
}
}
}
Ok((prev_param, previous_type, resources))
}
/// Generates `let` binding statements for immutable resource injection parameters.
///
/// Each immutable reference parameter gets a `_binding` variable that holds the
/// `res_or_default` result, then a shadowing `let` that borrows from it via `.as_ref()`.
pub(crate) fn generate_immut_resource_bindings<'a>(
resources: impl Iterator<Item = &'a ResourceInjection>,
program_type: &proc_macro2::TokenStream,
) -> Vec<proc_macro2::TokenStream> {
resources
.filter(|r| !r.is_mut)
.map(|res| {
let var_binding_name = syn::Ident::new(
&format!("__{}_binding", &res.var_name.to_string()),
res.var_name.span(),
);
let var_name = &res.var_name;
let full_type = &res.full_type;
let inner_type = &res.inner_type;
if res.is_ref {
quote! {
let #var_binding_name = ::mingling::this::<#program_type>()
.res_or_default::<#inner_type>();
let #var_name: #full_type = #var_binding_name.as_ref();
}
} else {
quote! {
let #var_name: #full_type = ::mingling::this::<#program_type>()
.res_or_default::<#full_type>();
}
}
})
.collect()
}
/// Wraps the function body in nested `__modify_res_and_return_route` closures for
/// each mutable resource parameter. The innermost closure gets the original body,
/// and each mutable parameter wraps outward from last to first.
pub(crate) fn wrap_body_with_mut_resources(
fn_body_stmts: &[syn::Stmt],
mut_resources: &[&ResourceInjection],
program_type: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let mut wrapped = quote! {
#(#fn_body_stmts)*
};
for res in mut_resources {
let var_name = &res.var_name;
let inner_type = &res.inner_type;
wrapped = quote! {
::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| {
#wrapped
}).into()
};
}
wrapped
}
|