Skip to main content

decycle_impl/
process_trait.rs

1use proc_macro2::Span;
2use proc_macro2::TokenStream as TokenStream2;
3use proc_macro_error::*;
4use syn::spanned::Spanned;
5use syn::visit_mut::VisitMut;
6use syn::*;
7use template_quote::quote;
8use type_leak::Leaker;
9
10pub fn process_trait(
11    trait_item: &ItemTrait,
12    decycle_path: &Path,
13    marker_path: Option<&Path>,
14    alter_macro_name: Option<&Ident>,
15    leaker_config: type_leak::LeakerConfig,
16) -> TokenStream2 {
17    let random_suffix = crate::get_random();
18    let temporal_mac_name = alter_macro_name.cloned().unwrap_or_else(|| {
19        syn::Ident::new(
20            &format!("__{}_temporal_{}", &trait_item.ident, random_suffix),
21            trait_item.ident.span(),
22        )
23    });
24    let crate_version = env!("CARGO_PKG_VERSION");
25    let crate_identity = LitStr::new(&crate::get_crate_identity(), Span::call_site());
26
27    let mut modified_trait_item = trait_item.clone();
28    // Randomize Ident of GenericParam in modified_trait_item.generics
29    let generic_renames: Vec<(Ident, Ident)> = modified_trait_item
30        .generics
31        .params
32        .iter_mut()
33        .filter_map(|param| {
34            let ident = match param {
35                GenericParam::Type(tp) => &mut tp.ident,
36                GenericParam::Const(cp) => &mut cp.ident,
37                GenericParam::Lifetime(_) => return None,
38            };
39            let old_ident = ident.clone();
40            let new_ident = Ident::new(&format!("{}_{}", ident, random_suffix), ident.span());
41            *ident = new_ident.clone();
42            Some((old_ident, new_ident))
43        })
44        .collect();
45    struct GenericRenamer<'a>(&'a [(Ident, Ident)]);
46    impl VisitMut for GenericRenamer<'_> {
47        fn visit_ident_mut(&mut self, ident: &mut Ident) {
48            for (old, new) in self.0 {
49                if ident == old {
50                    *ident = new.clone();
51                    return;
52                }
53            }
54        }
55    }
56    GenericRenamer(&generic_renames).visit_item_trait_mut(&mut modified_trait_item);
57    let output0 = quote! {
58        #trait_item
59
60        #[allow(unused_macros, unused_imports, dead_code, non_local_definitions)]
61        #[doc(hidden)]
62        #[macro_export]
63        macro_rules! #temporal_mac_name {
64            (#crate_identity #crate_version [$_:path, $wl1:path $(,$wl:path)* $(,)?] {$($trait_defs:tt)*} $($t:tt)*) => {
65                $wl1! {
66                    #crate_identity
67                    #crate_version
68                    [$wl1 $(,$wl)*]
69                    {
70                        #(for attr in &modified_trait_item.attrs) { #attr }
71                        #{&modified_trait_item.vis}
72                        #{&modified_trait_item.unsafety}
73                        #{&modified_trait_item.auto_token}
74                        #{&modified_trait_item.trait_token}
75                        #{&modified_trait_item.ident}
76                        #{&modified_trait_item.generics}
77                        #{&modified_trait_item.colon_token}
78                        #{&modified_trait_item.supertraits}
79                        {
80                            #(for item in &modified_trait_item.items) { #item }
81                        },
82                        $($trait_defs)*
83                    }
84                    $($t)*
85                }
86            };
87        }
88
89        #(if alter_macro_name.is_none()) {
90            #[doc(hidden)]
91            #[allow(unused_imports, unused_macros, dead_code)]
92            #{&trait_item.vis} use #temporal_mac_name as #{&trait_item.ident};
93        } #(else) {
94            #[doc(hidden)]
95            #[allow(unused_imports, unused_macros, dead_code)]
96            pub use #temporal_mac_name;
97        }
98    };
99    proc_macro_error::set_dummy(output0.clone());
100
101    let mut leaker = Leaker::from_config(leaker_config);
102    leaker
103        .intern_with(&trait_item.generics, |v| {
104            v.visit_item_trait(trait_item);
105        })
106        .unwrap_or_else(|type_leak::NotInternableError(span)| abort!(span, "use absolute path"));
107    let referrer = leaker.finish();
108
109    let typeref_impls = if !referrer.is_empty() {
110        let marker_path = marker_path.unwrap_or_else(|| {
111            abort!(
112                Span::call_site(), "specify 'marker' arg";
113                hint = referrer.iter().next().unwrap().span() => "first type to be interned"
114            )
115        });
116        let encoded_ty =
117            type_leak::encode_generics_params_to_ty(&modified_trait_item.generics.params);
118        referrer
119            .clone()
120            .into_visitor(
121                |_, num| parse_quote!(<#marker_path as #decycle_path::Repeater<#random_suffix, #num, #encoded_ty>>::Type),
122            )
123            .visit_item_trait_mut(&mut modified_trait_item);
124        let impl_generics = modified_trait_item.generics.split_for_impl().0;
125        referrer
126            .iter()
127            .enumerate()
128            .map(|(ix, ty)| {
129                quote! {
130                    impl #impl_generics
131                    #decycle_path::Repeater<#random_suffix, #ix, #encoded_ty> for #marker_path {
132                        type Type = #ty;
133                    }
134                }
135            })
136            .collect()
137    } else {
138        quote!()
139    };
140
141    // Check that all trait paths are absolute in modified_trait_item
142    {
143        use syn::visit::Visit;
144        struct AbsolutePathChecker<'a> {
145            generic_params: Vec<&'a Ident>,
146        }
147        impl<'ast> Visit<'ast> for AbsolutePathChecker<'_> {
148            fn visit_path(&mut self, path: &'ast Path) {
149                // Skip if path has leading :: (absolute)
150                if path.leading_colon.is_some() {
151                    return;
152                }
153                // Skip if path is a single segment that matches a generic param
154                if path.segments.len() == 1 {
155                    let ident = &path.segments[0].ident;
156                    // Skip Self
157                    if ident == "Self" {
158                        return;
159                    }
160                    // Skip generic parameters
161                    if self.generic_params.contains(&ident) {
162                        return;
163                    }
164                }
165                // For multi-segment paths without leading ::, check if first segment looks like a crate
166                if path.segments.len() > 1 {
167                    let first = &path.segments[0].ident;
168                    // Common crate names that are allowed without ::
169                    if first == "std"
170                        || first == "core"
171                        || first == "alloc"
172                        || first == "crate"
173                        || first == "super"
174                        || first == "self"
175                    {
176                        syn::visit::visit_path(self, path);
177                        return;
178                    }
179                    emit_warning!(
180                        path,
181                        "path '{}' may not be absolute; consider using '::' prefix",
182                        quote!(#path)
183                    );
184                }
185                syn::visit::visit_path(self, path);
186            }
187        }
188        let generic_params: Vec<_> = modified_trait_item
189            .generics
190            .params
191            .iter()
192            .filter_map(|p| match p {
193                GenericParam::Type(tp) => Some(&tp.ident),
194                GenericParam::Const(cp) => Some(&cp.ident),
195                GenericParam::Lifetime(_) => None,
196            })
197            .collect();
198        AbsolutePathChecker { generic_params }.visit_item_trait(&modified_trait_item);
199    }
200
201    quote! {
202        #output0
203
204        #typeref_impls
205    }
206}