warnings_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::spanned::Spanned;
4use syn::{parse_macro_input, FnArg, GenericParam, ItemFn};
5
6/// Turns a function into a warning that is only called if the lint is enabled.
7#[proc_macro_attribute]
8pub fn warning(_: TokenStream, input: TokenStream) -> TokenStream {
9    let input = parse_macro_input!(input as ItemFn);
10    let fn_name = &input.sig.ident;
11
12    let argument_types = input
13        .sig
14        .inputs
15        .iter()
16        .filter_map(|arg| match arg {
17            FnArg::Receiver(_) => None,
18            FnArg::Typed(arg) => Some(&arg.ty),
19        })
20        .collect::<Vec<_>>();
21    let argument_idents = input
22        .sig
23        .inputs
24        .iter()
25        .enumerate()
26        .filter_map(|(index, arg)| match arg {
27            FnArg::Receiver(_) => None,
28            FnArg::Typed(arg) => Some(syn::Ident::new(&format!("arg{}", index), arg.pat.span())),
29        })
30        .collect::<Vec<_>>();
31
32    let private_mod = format_ident!("__{}", fn_name);
33
34    let vis = &input.vis;
35
36    let (impl_generics, ty_generics, where_clause) = input.sig.generics.split_for_impl();
37    let generics = &input.sig.generics.params;
38    let phantom_data = (!input.sig.generics.params.is_empty()).then(|| {
39        let ty_generics_tuple = input.sig.generics.params.iter().map(|param| match param {
40            GenericParam::Type(ty) => {
41                let ty = &ty.ident;
42                quote!(#ty)
43            }
44            GenericParam::Lifetime(lifetime) => quote!(&#lifetime ()),
45            GenericParam::Const(_) => quote!(()),
46        });
47        quote!(PhantomData(std::marker::PhantomData<(#(#ty_generics_tuple),*)>))
48    });
49
50    let attrs = &input.attrs;
51
52    // Hand the resulting function body back to the compiler.
53    TokenStream::from(quote! {
54        #[allow(non_camel_case_types)]
55        #(#attrs)*
56        #vis struct #fn_name {}
57
58        mod #private_mod {
59            use super::*;
60
61            pub(crate) enum __Callable<#generics> #where_clause {
62                #[allow(non_camel_case_types)]
63                #fn_name,
64                #phantom_data
65            }
66
67            impl #impl_generics  __Callable #ty_generics #where_clause {
68                fn __run_if_enabled(#(#argument_idents: #argument_types),*) {
69                    <#fn_name as ::warnings::Warning>::ID.if_enabled(|| {
70                        #input
71                        #fn_name(#(#argument_idents),*);
72                    });
73                }
74            }
75
76            impl #impl_generics std::ops::Deref for __Callable #ty_generics #where_clause {
77                type Target = fn(#(#argument_types),*);
78                fn deref(&self) -> &Self::Target {
79                    &(Self::__run_if_enabled as fn(#(#argument_types),*))
80                }
81            }
82        }
83        #vis use #private_mod::__Callable::*;
84
85        impl ::warnings::Warning for #fn_name {
86            const ID: ::warnings::WarningId = ::warnings::WarningId::of::<#fn_name>();
87        }
88    })
89}