1use std::borrow::Cow;
2
3use darling::{ast::NestedMeta, FromMeta};
4use proc_macro2::Span;
5use quote::{quote_spanned, ToTokens};
6use syn::spanned::Spanned;
7
8use crate::{
9 captures::capture_lifetimes,
10 detect_hooks, detected_hooks_to_tokens,
11 utils::{
12 chain::Chain,
13 either::Either,
14 empty_or_trailing::AutoEmptyOrTrailing,
15 group::angled,
16 map::map_to_tokens,
17 path_or_lit::PathOrLit,
18 phantom::{make_phantom_or_ref, PhantomOfTy},
19 repeat::Repeat,
20 type_generics::TypeGenericsWithoutBraces,
21 },
22 DetectedHooksTokens,
23};
24
25#[cfg_attr(feature = "extra-traits", derive(PartialEq, Eq))]
26#[derive(Debug, Default, FromMeta)]
27#[non_exhaustive]
28#[darling(default)]
29pub struct HookArgs {
30 pub hooks_core_path: Option<PathOrLit<syn::Path>>,
32
33 pub bounds: Option<syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>>,
72}
73
74impl HookArgs {
75 #[inline]
76 pub fn transform_item_fn(
77 self,
78 mut item_fn: syn::ItemFn,
79 ) -> (syn::ItemFn, Option<darling::Error>) {
80 let error = self.transform_item_fn_in_place(&mut item_fn);
81 (item_fn, error)
82 }
83
84 pub fn transform_item_fn_in_place(self, item_fn: &mut syn::ItemFn) -> Option<darling::Error> {
85 let hooks_core_path = self.hooks_core_path.map_or_else(
88 || syn::Path {
89 leading_colon: Some(Default::default()),
90 segments: syn::punctuated::Punctuated::from_iter([
91 syn::PathSegment::from(syn::Ident::new("hooks", Span::call_site())),
92 syn::PathSegment::from(syn::Ident::new("core", Span::call_site())),
93 ]),
94 },
95 PathOrLit::unwrap,
96 );
97
98 let bounds = self.bounds;
99
100 let lifetimes_from_fn_generics = item_fn.sig.generics.lifetimes().map(|lt| <.lifetime);
101 let lifetimes_from_bounds = bounds.iter().flatten().filter_map(|bound| match bound {
102 syn::TypeParamBound::Lifetime(lt) => Some(lt),
103 _ => None,
104 });
105 let lifetimes = lifetimes_from_fn_generics.chain(lifetimes_from_bounds);
106
107 let captures = capture_lifetimes(
108 lifetimes,
109 quote_spanned!(hooks_core_path.span() => #hooks_core_path::Captures),
110 );
111
112 let bounds = bounds.map(|bounds| {
114 bounds
115 .into_pairs()
116 .filter_map(|pair| {
117 let (bound, punc) = pair.into_tuple();
118 match bound {
119 syn::TypeParamBound::Trait(tb) => {
120 Some(syn::punctuated::Pair::new(tb, punc))
121 }
122 _ => None,
123 }
124 })
125 .collect::<syn::punctuated::Punctuated<syn::TraitBound, _>>()
126 });
127
128 let bounds = match (captures, bounds) {
129 (Some(captures), Some(bounds)) => Some({
130 let mut ts = captures;
131
132 ts.extend([
133 quote_spanned!(item_fn.sig.fn_token.span() => +),
135 bounds.into_token_stream(),
136 ]);
137
138 ts
139 }),
140 (a, b) => a.or(b.map(ToTokens::into_token_stream)),
141 };
142
143 let sig = &mut item_fn.sig;
144
145 let span_fn_name = sig.ident.span();
146
147 let generics = &sig.generics;
148
149 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
150
151 let hooks_value_struct_field_ty = map_to_tokens(&generics.params, |params| {
152 params.pairs().filter_map(|p| {
153 make_phantom_or_ref(p.value()).map(|v| {
154 Chain(
155 v,
156 p.punct()
157 .map_or_else(|| Cow::Owned(Default::default()), |v| Cow::Borrowed(*v)),
158 )
159 })
160 })
161 });
162
163 let mut output_ty: syn::Type = {
164 let fn_rt = &mut sig.output;
165 let span;
166 match fn_rt {
167 syn::ReturnType::Default => {
168 span = span_fn_name;
169 let output_ty = syn::Type::Tuple(syn::TypeTuple {
170 paren_token: syn::token::Paren(span),
171 elems: Default::default(),
172 });
173 *fn_rt = syn::ReturnType::Type(
174 syn::Token,
175 Box::new(syn::Type::Verbatim(utils::UpdateHookUninitialized(
176 &hooks_core_path,
177 span,
178 quote_spanned!(span=> ()),
179 bounds,
180 ))),
181 );
182
183 output_ty
184 }
185 syn::ReturnType::Type(ra, ty) => {
186 span = ra.span();
187 let it = utils::UpdateHookUninitialized(&hooks_core_path, span, &**ty, bounds);
188 std::mem::replace(&mut **ty, syn::Type::Verbatim(it))
189 }
190 }
191 };
192
193 let fn_type_generics_eot = AutoEmptyOrTrailing(TypeGenericsWithoutBraces(&generics.params));
195
196 let it_impl_generics_eot = extract_impl_trait_as_type_params(&mut output_ty);
199
200 let it_type_generics_eot = map_to_tokens(&it_impl_generics_eot, |v| {
202 v.iter().map(|pair| Chain(&pair.0.ident, &pair.1))
203 });
204
205 let hook_types_phantoms_eot;
207 let hook_types_impl_generics;
209 let hook_types_type_generics;
211 let it_generics_elided_without_braces_eot;
213
214 if it_impl_generics_eot.is_empty() {
215 hook_types_phantoms_eot = Either::A(&hooks_value_struct_field_ty);
216 hook_types_impl_generics = Either::A(impl_generics);
217 hook_types_type_generics = Either::A(&type_generics);
218 it_generics_elided_without_braces_eot = None;
219 } else {
220 hook_types_phantoms_eot = Either::B(Chain(
221 &hooks_value_struct_field_ty,
222 map_to_tokens(&it_impl_generics_eot, |v| {
223 v.iter()
224 .map(|pair| Chain(PhantomOfTy(&pair.0.ident), pair.1))
225 }),
226 ));
227
228 hook_types_impl_generics = Either::B(angled(Chain(
229 AutoEmptyOrTrailing(&sig.generics.params),
230 map_to_tokens(&it_impl_generics_eot, |v| v.iter()),
231 )));
232
233 hook_types_type_generics =
234 Either::B(angled(Chain(&fn_type_generics_eot, &it_type_generics_eot)));
235
236 it_generics_elided_without_braces_eot = Some(Repeat(
237 Chain(<syn::Token![_]>::default(), <syn::Token![,]>::default()),
238 it_impl_generics_eot.len(),
239 ));
240 };
241
242 let fn_impl_generics_without_braces_eot = AutoEmptyOrTrailing(&sig.generics.params);
245
246 let mut impl_use_hook = std::mem::take(&mut item_fn.block.stmts);
247
248 let used_hooks = detect_hooks(impl_use_hook.iter_mut(), &hooks_core_path);
249
250 let DetectedHooksTokens {
251 fn_arg_data_pat: arg_hooks_data,
252 fn_stmts_extract_data: impl_extract_hooks_data,
253 } = detected_hooks_to_tokens(used_hooks.hooks, &hooks_core_path, sig.fn_token.span);
254
255 item_fn.block.stmts.push(syn::Stmt::Expr(
256 syn::Expr::Verbatim(
257 quote_spanned! { span_fn_name =>
259 enum __HooksImplNever {}
260
261 struct __HooksValueOfThisHook #hook_types_impl_generics
262 #where_clause
263 {
264 __: (
265 __HooksImplNever,
266 #hook_types_phantoms_eot
267 )
268 }
269
270 impl<
271 'hook,
272 #fn_impl_generics_without_braces_eot
273 #(#it_impl_generics_eot)*
274 > #hooks_core_path::HookValue<'hook> for
275 __HooksValueOfThisHook #hook_types_type_generics
276 #where_clause {
277 type Value = #output_ty;
278 }
279
280 #hooks_core_path::fn_hook::use_fn_hook::<
281 __HooksValueOfThisHook<
282 #fn_type_generics_eot
283 #it_generics_elided_without_braces_eot
284 >, _, _
285 >
286 (
287 move |#arg_hooks_data| {
288 #impl_extract_hooks_data
289
290 #(#impl_use_hook)*
291 }
292 )
293 },
294 ),
295 None,
296 ));
297
298 None
300 }
301
302 pub fn from_punctuated_meta_list(
303 meta_list: syn::punctuated::Punctuated<NestedMeta, syn::Token![,]>,
304 ) -> darling::Result<Self> {
305 let args: Vec<NestedMeta> = meta_list.into_iter().collect();
306 Self::from_list(&args)
307 }
308}
309
310fn replace_impl_trait_in_type(
311 ty: &mut syn::Type,
312 f: &mut impl FnMut(&mut syn::TypeImplTrait) -> syn::Type,
313) {
314 match ty {
315 syn::Type::Array(ta) => replace_impl_trait_in_type(&mut ta.elem, f),
316 syn::Type::BareFn(_) => {}
317 syn::Type::Group(g) => replace_impl_trait_in_type(&mut g.elem, f),
318 syn::Type::ImplTrait(it) => {
319 *ty = f(it)
323 }
324 syn::Type::Infer(_) => {}
325 syn::Type::Macro(_) => {}
326 syn::Type::Never(_) => {}
327 syn::Type::Paren(p) => {
328 let is_impl_trait = matches!(&*p.elem, syn::Type::ImplTrait(_));
329 replace_impl_trait_in_type(&mut p.elem, f);
330
331 const DUMMY_TYPE: syn::Type = syn::Type::Path(syn::TypePath {
332 qself: None,
333 path: syn::Path {
334 leading_colon: None,
335 segments: syn::punctuated::Punctuated::new(),
336 },
337 });
338 if is_impl_trait {
340 let new_ty = std::mem::replace(&mut *p.elem, DUMMY_TYPE);
341 *ty = new_ty;
342 }
343 }
344 syn::Type::Path(tp) => {
345 if let Some(qself) = &mut tp.qself {
346 replace_impl_trait_in_type(&mut qself.ty, f);
347 }
348 for seg in tp.path.segments.iter_mut() {
349 match &mut seg.arguments {
350 syn::PathArguments::None => {}
351 syn::PathArguments::AngleBracketed(a) => {
352 for arg in a.args.iter_mut() {
353 match arg {
354 syn::GenericArgument::Lifetime(_) => {}
355 syn::GenericArgument::Type(ty) => {
356 replace_impl_trait_in_type(ty, f);
357 }
358 syn::GenericArgument::Const(_) => {}
359 syn::GenericArgument::Constraint(_) => {}
360 syn::GenericArgument::AssocType(assoc) => {
361 replace_impl_trait_in_type(&mut assoc.ty, f);
362 }
363 syn::GenericArgument::AssocConst(_) => {}
364 _ => {}
365 }
366 }
367 }
368 syn::PathArguments::Parenthesized(_) => {
369 }
371 }
372 }
373 }
375 syn::Type::Ptr(ptr) => replace_impl_trait_in_type(&mut ptr.elem, f),
376 syn::Type::Reference(r) => replace_impl_trait_in_type(&mut r.elem, f),
377 syn::Type::Slice(s) => replace_impl_trait_in_type(&mut s.elem, f),
378 syn::Type::TraitObject(_) => {
379 }
382 syn::Type::Tuple(t) => {
383 for elem in t.elems.iter_mut() {
384 replace_impl_trait_in_type(elem, f);
385 }
386 }
387 syn::Type::Verbatim(_) => {}
388 _ => {}
389 }
390}
391
392fn extract_impl_trait_as_type_params(
394 output_ty: &mut syn::Type,
395) -> Vec<Chain<syn::TypeParam, syn::Token![,]>> {
396 let mut ret = vec![];
397 replace_impl_trait_in_type(output_ty, &mut |ty| {
398 let id = ret.len();
399 let span = ty.impl_token.span;
400
401 let ident = syn::Ident::new(&format!("HooksImplTrait{id}"), span);
402
403 ret.push(Chain(
404 syn::TypeParam {
405 attrs: vec![],
406 ident: ident.clone(),
407 colon_token: Some(syn::Token),
408 bounds: std::mem::take(&mut ty.bounds),
409 eq_token: None,
410 default: None,
411 },
412 syn::Token,
413 ));
414
415 syn::Type::Path(syn::TypePath {
416 qself: None,
417 path: ident.into(),
418 })
419 });
420 ret
421}
422
423mod utils {
424 use darling::ToTokens;
425 use proc_macro2::{Span, TokenStream};
426 use quote::quote_spanned;
427 use syn::spanned::Spanned;
428
429 use crate::utils::chain::Chain;
430
431 #[allow(non_snake_case)]
432 pub fn UpdateHookUninitialized(
433 hooks_core_path: &impl ToTokens,
434 span: Span,
435 value_ty: impl ToTokens,
436 bounds: Option<impl ToTokens>,
437 ) -> TokenStream {
438 let bounds = bounds.map(|bounds| {
439 let bounds = bounds.into_token_stream();
440
441 Chain(syn::Token), bounds)
442 });
443
444 quote_spanned! {span=>
445 impl #hooks_core_path::UpdateHookUninitialized<
446 Uninitialized =
447 impl #hooks_core_path::HookPollNextUpdate
448 + #hooks_core_path::HookUnmount
449 + ::core::default::Default
450 #bounds
451 ,
452 Hook = impl #hooks_core_path::Hook + for<'hook> #hooks_core_path::HookValue<'hook, Value = #value_ty>
453 #bounds
454 >
455 }
456 }
457}