hooks_macro_core/
hook_macro.rs

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    /// Defaults to `::hooks::core`
31    pub hooks_core_path: Option<PathOrLit<syn::Path>>,
32
33    /// When a hook fn borrows from a lifetime,
34    /// this bound might need to be explicitly specified.
35    ///
36    /// Note that all lifetimes declared in fn generics are auto captured by `#[hook]`.
37    /// Thus, `#[hook(bounds = "...")]` is only required for
38    /// elided lifetimes and outer lifetimes.
39    ///
40    /// ```compile_fail
41    /// # extern crate hooks_dev as hooks;
42    /// # use hooks::prelude::*;
43    /// #[hook]
44    /// fn use_borrow(v: &str) -> usize {
45    ///     v.len()
46    /// }
47    /// ```
48    ///
49    /// ```
50    /// # extern crate hooks_dev as hooks;
51    /// # use hooks::prelude::*;
52    /// #[hook(bounds = "'_")]
53    /// fn use_borrow(v: &str) -> usize {
54    ///     v.len()
55    /// }
56    /// ```
57    ///
58    /// This is equivalent to `type Bounds = impl ...` in
59    /// [`hook_fn!(...);`](https://docs.rs/hooks-core/1.0.0-alpha.10/hooks_core/macro.hook_fn.html)
60    ///
61    /// ```
62    /// # extern crate hooks_dev as hooks;
63    /// # use hooks::prelude::*;
64    /// hook_fn!(
65    ///     type Bounds = impl '_;
66    ///     fn use_borrow(v: &str) -> usize {
67    ///         v.len()
68    ///     }
69    /// );
70    /// ```
71    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 mut errors = darling::error::Accumulator::default();
86
87        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| &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        // Trait bounds only
113        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                    //
134                    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![->](span),
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        // T,
194        let fn_type_generics_eot = AutoEmptyOrTrailing(TypeGenericsWithoutBraces(&generics.params));
195
196        // HooksImplTrait0: Debug, HooksImplTrait1: Any,
197        //      introduced by impl trait in return position
198        let it_impl_generics_eot = extract_impl_trait_as_type_params(&mut output_ty);
199
200        // HooksImplTrait0, HooksImplTrait1,
201        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        // PhantomData<T>, PhantomData<HooksImplTrait0>, PhantomData<HooksImplTrait1>,
206        let hook_types_phantoms_eot;
207        // <T: Clone, HooksImplTrait0: Debug, HooksImplTrait1: Any,>
208        let hook_types_impl_generics;
209        // <T, HooksImplTrait0, HooksImplTrait1,>
210        let hook_types_type_generics;
211        // _, _,
212        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        // T: Clone,
243        // The generics comes from `fn`, so there won't be default types like `<T = i32>`
244        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                //
258                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        // errors.finish().err()
299        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            // TODO: resolve `impl Trait` in it.bounds
320            // f(it.bounds)
321
322            *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            // also remove the paren for (HookImplTrait0)
339            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                        // TODO: resolve `impl Trait` in path like `Fn(impl Trait) -> impl Trait`
370                    }
371                }
372            }
373            // TODO: resolve `impl Trait` in path like `Struct<impl Trait>`
374        }
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            // TODO: resolve `impl Trait` in to.bounds
380            // f(to.bounds)
381        }
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
392/// The returned Punctuated is guaranteed to be `empty_or_trailing`
393fn 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![:](span)),
408                bounds: std::mem::take(&mut ty.bounds),
409                eq_token: None,
410                default: None,
411            },
412            syn::Token![,](span),
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.span()), 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}