name_it_macros/
lib.rs

1// lint me harder
2#![forbid(non_ascii_idents)]
3#![deny(
4    future_incompatible,
5    keyword_idents,
6    elided_lifetimes_in_paths,
7    meta_variable_misuse,
8    noop_method_call,
9    pointer_structural_match,
10    unused_lifetimes,
11    unused_qualifications,
12    clippy::wildcard_dependencies,
13    clippy::debug_assert_with_mut_call,
14    clippy::empty_line_after_outer_attr,
15    clippy::panic,
16    clippy::unwrap_used,
17    clippy::redundant_field_names,
18    clippy::rest_pat_in_fully_bound_structs,
19    clippy::unneeded_field_pattern,
20    clippy::useless_let_if_seq
21)]
22#![warn(clippy::pedantic)]
23
24use std::iter;
25
26use proc_macro::{Span, TokenStream};
27
28use proc_macro_error::{abort, proc_macro_error, OptionExt, ResultExt as _};
29use quote::{format_ident, quote};
30
31struct SignatureVisitor;
32
33impl syn::visit_mut::VisitMut for SignatureVisitor {
34    fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) {
35        let replace = match &*i.ident.to_string() {
36            "static" | "fut" => false,
37            "_" => true,
38            _ => {
39                abort!(
40                    i,
41                    "custom lifetimes in #[name_it] args are not supported yet"
42                );
43            }
44        };
45
46        if replace {
47            i.ident = syn::Ident::new("fut", Span::call_site().into());
48        }
49
50        syn::visit_mut::visit_lifetime_mut(self, i);
51    }
52
53    fn visit_type_reference_mut(&mut self, i: &mut syn::TypeReference) {
54        if i.lifetime.is_none() {
55            i.lifetime = Some(syn::Lifetime::new("'_", Span::call_site().into()));
56        }
57
58        syn::visit_mut::visit_type_reference_mut(self, i);
59    }
60
61    fn visit_type_trait_object_mut(&mut self, i: &mut syn::TypeTraitObject) {
62        let mut found = false;
63        for bound in &i.bounds {
64            if matches!(bound, syn::TypeParamBound::Lifetime(_)) {
65                found = true;
66                break;
67            }
68        }
69
70        if !found {
71            i.bounds
72                .push(syn::TypeParamBound::Lifetime(syn::Lifetime::new(
73                    "'_",
74                    Span::call_site().into(),
75                )));
76        }
77
78        syn::visit_mut::visit_type_trait_object_mut(self, i);
79    }
80}
81
82fn bump_visibility(vis: syn::Visibility) -> syn::Visibility {
83    let super_ = syn::Ident::new("super", Span::call_site().into());
84    match vis {
85        syn::Visibility::Public(_) | syn::Visibility::Crate(_) => vis,
86        syn::Visibility::Restricted(mut vis) => {
87            let first = vis
88                .path
89                .segments
90                .first_mut()
91                .expect_or_abort("empty path in visibility declration");
92            if first.ident == "self" {
93                first.ident = super_;
94                return syn::Visibility::Restricted(vis);
95            }
96
97            vis.path.segments.insert(
98                0,
99                syn::PathSegment {
100                    ident: super_,
101                    arguments: syn::PathArguments::None,
102                },
103            );
104            syn::Visibility::Restricted(vis)
105        }
106        syn::Visibility::Inherited => syn::Visibility::Restricted(syn::VisRestricted {
107            pub_token: syn::token::Pub::default(),
108            paren_token: syn::token::Paren::default(),
109            in_token: None,
110            path: Box::new(syn::Path {
111                leading_colon: None,
112                segments: std::iter::once(syn::PathSegment {
113                    ident: super_,
114                    arguments: syn::PathArguments::None,
115                })
116                .collect(),
117            }),
118        }),
119    }
120}
121
122#[proc_macro_error]
123#[proc_macro_attribute]
124pub fn name_it(attr: TokenStream, func: TokenStream) -> TokenStream {
125    let mut func: syn::ItemFn =
126        syn::parse(func).expect_or_abort("#[name_it] accepts only a function");
127    let type_name: syn::Ident =
128        syn::parse(attr).expect_or_abort("#[name_it] sole argument must be an ident");
129
130    if func.sig.asyncness.is_none() {
131        abort!(func, "#[name_it] only works on async functions");
132    }
133
134    if !func.sig.generics.params.is_empty() {
135        abort!(
136            func.sig.generics,
137            "generics are not supported by #[name_it] yet"
138        );
139    }
140
141    let func_name = func.sig.ident.clone();
142    let mut func_return_type = func.sig.output.clone();
143    if func_return_type == syn::ReturnType::Default {
144        func_return_type = syn::ReturnType::Type(
145            <syn::Token![->]>::default(),
146            Box::new(syn::Type::Tuple(syn::TypeTuple {
147                paren_token: syn::token::Paren::default(),
148                elems: syn::punctuated::Punctuated::new(),
149            })),
150        );
151    }
152
153    let mut wrapped_func = func.clone();
154    wrapped_func.sig.asyncness = None;
155    let fut_lifetime = syn::Lifetime::new("'fut", Span::call_site().into());
156    let mut generics = syn::Generics::default();
157    generics
158        .params
159        .push(syn::GenericParam::Lifetime(syn::LifetimeDef::new(
160            fut_lifetime.clone(),
161        )));
162    wrapped_func.sig.generics = generics;
163    wrapped_func.sig.output = syn::ReturnType::Type(
164        <syn::Token![->]>::default(),
165        Box::new(syn::Type::Path(syn::TypePath {
166            qself: None,
167            path: syn::Path {
168                leading_colon: None,
169                segments: iter::once(syn::PathSegment {
170                    ident: type_name.clone(),
171                    arguments: syn::PathArguments::AngleBracketed(
172                        syn::AngleBracketedGenericArguments {
173                            colon2_token: None,
174                            lt_token: <syn::Token![<]>::default(),
175                            args: [syn::GenericArgument::Lifetime(fut_lifetime)]
176                                .into_iter()
177                                .collect(),
178                            gt_token: <syn::Token![>]>::default(),
179                        },
180                    ),
181                })
182                .collect(),
183            },
184        })),
185    );
186    let arg_idents = func.sig.inputs.iter().map(|arg| match arg {
187        syn::FnArg::Receiver(_) => {
188            abort!(arg, "methods are not supported by #[name_it]");
189        }
190        syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
191            syn::Pat::Ident(ident)
192                if ident.by_ref.is_none() && ident.subpat.is_none() && ident.attrs.is_empty() =>
193            {
194                if matches!(&*pat_type.ty, syn::Type::ImplTrait(_)) {
195                    abort!(pat_type.ty, "generics are not supported by #[name_it] yet");
196                }
197
198                ident.ident.clone()
199            }
200            _ => abort!(
201                arg,
202                "only simple `ident` patterns in function args are supported by #[name_it] for now"
203            ),
204        },
205    });
206    syn::visit_mut::visit_signature_mut(&mut SignatureVisitor, &mut wrapped_func.sig);
207
208    let vis = func.vis.clone();
209    func.vis = bump_visibility(func.vis);
210    let new_vis = func.vis.clone();
211
212    let module_name = format_ident!("_{}_impl", func_name);
213    func.sig.ident = func_name.clone();
214    wrapped_func.block = Box::new(
215        syn::parse(
216            quote! {{
217                let fut = #module_name::#func_name(#(#arg_idents),*);
218                // SAFETY:
219                // 1. type and alignment are the same, so transmuting to array of `MaybeUninit<u8>`
220                //    is always ok
221                // 2. we pass these bytes into `::new()` of the corresponding type
222                unsafe {
223                    let bytes = ::name_it::transmute_generic(fut);
224                    ::name_it::Named::new(#module_name::#type_name::new(bytes))
225                }
226            }}
227            .into(),
228        )
229        .expect("failed to parse function block from procmacro, this is a bug"),
230    );
231
232    let underscores = func.sig.inputs.iter().map(|_| syn::TypeInfer {
233        underscore_token: <syn::Token![_]>::default(),
234    });
235
236    quote! {
237        mod #module_name {
238            use super::*;
239
240            #[forbid(elided_lifetimes_in_paths)]
241            #func
242
243            ::name_it::_name_it_inner!(#new_vis type #type_name = #func_name(#(#underscores),*) #func_return_type);
244        }
245
246        #vis type #type_name<'fut> = ::name_it::Named<#module_name::#type_name<'fut>>;
247
248        #[allow(unused_mut)]
249        #wrapped_func
250    }
251    .into()
252}