qualify_derive/
lib.rs

1//! Simple utility for wrapping derive macros that do not qualify paths properly.
2//!
3//! See the [README](https://github.com/SOF3/qualify-derive) for detailed explanation.
4
5extern crate proc_macro;
6
7use proc_macro2::{TokenStream, TokenTree};
8#[doc(hidden)]
9pub use quote::quote;
10
11/// Entry function to call from the `proc_macro_attribute` function.
12///
13/// Pay attention to the mixed use of `proc_macro::TokenStream` and `proc_macro2::TokenStream` in
14/// the signature.
15///
16/// See the [README](https://github.com/SOF3/qualify-derive) for detailed explanation.
17pub fn fix(
18    attr: proc_macro::TokenStream,
19    item: proc_macro::TokenStream,
20    target: TokenStream,
21    imports: &[TokenStream],
22    passthru: Option<TokenStream>,
23) -> proc_macro::TokenStream {
24    fix_(attr.into(), item.into(), target, imports, passthru)
25        .unwrap_or_else(|err| err.to_compile_error())
26        .into()
27}
28
29fn fix_(
30    attr: TokenStream,
31    item: TokenStream,
32    target: TokenStream,
33    imports: &[TokenStream],
34    passthru: Option<TokenStream>,
35) -> syn::Result<TokenStream> {
36    let mut item = syn::parse2::<syn::DeriveInput>(item)?;
37    let vis = item.vis.clone();
38    item.vis = syn::parse2(quote!(pub)).unwrap();
39    let ident = &item.ident;
40
41    let unused = item.attrs.iter().any(|attr| {
42        attr.path.is_ident("allow") && {
43            for token in attr.tokens.clone() {
44                if let TokenTree::Group(group) = token {
45                    for token in group.stream() {
46                        if let TokenTree::Ident(ident) = token {
47                            if ident == "unused"
48                                || ident == "unused_imports"
49                                || ident == "dead_code"
50                            {
51                                return true;
52                            }
53                        }
54                    }
55                }
56            }
57            false
58        }
59    });
60    let unused = if unused {
61        quote!(#[allow(unused_imports)])
62    } else {
63        quote!()
64    };
65
66    let mod_name = quote::format_ident!("__qualify_derive_{}", ident);
67
68    let passthru = passthru.and_then(|attr_name| {
69        if attr.is_empty() {
70            None
71        } else {
72            Some(quote!(#[#attr_name(#attr)]))
73        }
74    });
75
76    let output = quote! {
77        #[allow(non_snake_case)]
78        mod #mod_name {
79            use super::*;
80            #(
81                #[allow(unused_imports)]
82                use #imports;
83            )*
84            #[derive(#target)]
85            #passthru
86            #item
87        }
88        #unused
89        #vis use #mod_name::#ident;
90    };
91    Ok(output)
92}
93
94/// Convenient macro to declare an attribute that calls `fix`.
95///
96/// See the [README](https://github.com/SOF3/qualify-derive) for detailed explanation.
97#[macro_export]
98macro_rules! declare {
99    ($(#[$meta:meta])* $name:ident derives $target:ty; $(use $imports:ty;)*) => {
100        $crate::declare!(@INTERNAL $(#[$meta])* $name; $target; $($imports),*; None);
101    };
102    ($(#[$meta:meta])* $name:ident derives $target:ty; $(use $imports:ty;)* attr $attr:ident $(;)?) => {
103        $crate::declare!(@INTERNAL $(#[$meta])* $name; $target; $($imports),*; Some($crate::quote!($attr)));
104    };
105    (@INTERNAL $(#[$meta:meta])* $name:ident; $target:ty; $($imports:ty),*; $passthru:expr) => {
106        #[proc_macro_attribute]
107        $(#[$meta])*
108        pub fn $name(attr: ::proc_macro::TokenStream, item: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
109            use $crate::quote;
110            $crate::fix(attr, item, quote!($target), &[$(quote!($imports)),*], $passthru)
111        }
112    };
113}