• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use syn::punctuated::Punctuated;
2 use syn::{Ident, Type};
3 
4 use crate::usage::{IdentRefSet, IdentSet, Options};
5 
6 /// Searcher for finding type params in a syntax tree.
7 /// This can be used to determine if a given type parameter needs to be bounded in a generated impl.
8 pub trait UsesTypeParams {
9     /// Returns the subset of the queried type parameters that are used by the implementing syntax element.
10     ///
11     /// This method only accounts for direct usage by the element; indirect usage via bounds or `where`
12     /// predicates are not detected.
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>13     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>;
14 
15     /// Find all type params using `uses_type_params`, then clone the found values and return the set.
uses_type_params_cloned(&self, options: &Options, type_set: &IdentSet) -> IdentSet16     fn uses_type_params_cloned(&self, options: &Options, type_set: &IdentSet) -> IdentSet {
17         self.uses_type_params(options, type_set)
18             .into_iter()
19             .cloned()
20             .collect()
21     }
22 }
23 
24 /// Searcher for finding type params in an iterator.
25 ///
26 /// This trait extends iterators, providing a way to turn a filtered list of fields or variants into a set
27 /// of type parameter idents.
28 pub trait CollectTypeParams {
29     /// Consume an iterator, accumulating all type parameters in the elements which occur in `type_set`.
collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>30     fn collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>;
31 
32     /// Consume an iterator using `collect_type_params`, then clone all found type params and return that set.
collect_type_params_cloned(self, options: &Options, type_set: &IdentSet) -> IdentSet33     fn collect_type_params_cloned(self, options: &Options, type_set: &IdentSet) -> IdentSet;
34 }
35 
36 impl<'i, T, I> CollectTypeParams for T
37 where
38     T: IntoIterator<Item = &'i I>,
39     I: 'i + UsesTypeParams,
40 {
collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>41     fn collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
42         self.into_iter().fold(
43             IdentRefSet::with_capacity_and_hasher(type_set.len(), Default::default()),
44             |state, value| union_in_place(state, value.uses_type_params(options, type_set)),
45         )
46     }
47 
collect_type_params_cloned(self, options: &Options, type_set: &IdentSet) -> IdentSet48     fn collect_type_params_cloned(self, options: &Options, type_set: &IdentSet) -> IdentSet {
49         self.collect_type_params(options, type_set)
50             .into_iter()
51             .cloned()
52             .collect()
53     }
54 }
55 
56 /// Insert the contents of `right` into `left`.
union_in_place<'a>(mut left: IdentRefSet<'a>, right: IdentRefSet<'a>) -> IdentRefSet<'a>57 fn union_in_place<'a>(mut left: IdentRefSet<'a>, right: IdentRefSet<'a>) -> IdentRefSet<'a> {
58     left.extend(right);
59 
60     left
61 }
62 
63 impl UsesTypeParams for () {
uses_type_params<'a>(&self, _options: &Options, _type_set: &'a IdentSet) -> IdentRefSet<'a>64     fn uses_type_params<'a>(&self, _options: &Options, _type_set: &'a IdentSet) -> IdentRefSet<'a> {
65         Default::default()
66     }
67 }
68 
69 impl<T: UsesTypeParams> UsesTypeParams for Option<T> {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>70     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
71         self.as_ref()
72             .map(|v| v.uses_type_params(options, type_set))
73             .unwrap_or_default()
74     }
75 }
76 
77 impl<T: UsesTypeParams> UsesTypeParams for Vec<T> {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>78     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
79         self.collect_type_params(options, type_set)
80     }
81 }
82 
83 impl<T: UsesTypeParams, U> UsesTypeParams for Punctuated<T, U> {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>84     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
85         self.collect_type_params(options, type_set)
86     }
87 }
88 
89 uses_type_params!(syn::AngleBracketedGenericArguments, args);
90 uses_type_params!(syn::AssocType, ty);
91 uses_type_params!(syn::BareFnArg, ty);
92 uses_type_params!(syn::Constraint, bounds);
93 uses_type_params!(syn::DataEnum, variants);
94 uses_type_params!(syn::DataStruct, fields);
95 uses_type_params!(syn::DataUnion, fields);
96 uses_type_params!(syn::Field, ty);
97 uses_type_params!(syn::FieldsNamed, named);
98 uses_type_params!(syn::ParenthesizedGenericArguments, inputs, output);
99 uses_type_params!(syn::PredicateType, bounded_ty, bounds);
100 uses_type_params!(syn::QSelf, ty);
101 uses_type_params!(syn::TraitBound, path);
102 uses_type_params!(syn::TypeArray, elem);
103 uses_type_params!(syn::TypeBareFn, inputs, output);
104 uses_type_params!(syn::TypeGroup, elem);
105 uses_type_params!(syn::TypeImplTrait, bounds);
106 uses_type_params!(syn::TypeParen, elem);
107 uses_type_params!(syn::TypePtr, elem);
108 uses_type_params!(syn::TypeReference, elem);
109 uses_type_params!(syn::TypeSlice, elem);
110 uses_type_params!(syn::TypeTuple, elems);
111 uses_type_params!(syn::TypeTraitObject, bounds);
112 uses_type_params!(syn::Variant, fields);
113 
114 impl UsesTypeParams for syn::Data {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>115     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
116         match *self {
117             syn::Data::Struct(ref v) => v.uses_type_params(options, type_set),
118             syn::Data::Enum(ref v) => v.uses_type_params(options, type_set),
119             syn::Data::Union(ref v) => v.uses_type_params(options, type_set),
120         }
121     }
122 }
123 
124 impl UsesTypeParams for syn::Fields {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>125     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
126         self.collect_type_params(options, type_set)
127     }
128 }
129 
130 /// Check if an Ident exactly matches one of the sought-after type parameters.
131 impl UsesTypeParams for Ident {
uses_type_params<'a>(&self, _options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>132     fn uses_type_params<'a>(&self, _options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
133         type_set.iter().filter(|v| *v == self).collect()
134     }
135 }
136 
137 impl UsesTypeParams for syn::ReturnType {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>138     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
139         if let syn::ReturnType::Type(_, ref ty) = *self {
140             ty.uses_type_params(options, type_set)
141         } else {
142             Default::default()
143         }
144     }
145 }
146 
147 impl UsesTypeParams for Type {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>148     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
149         match *self {
150             Type::Slice(ref v) => v.uses_type_params(options, type_set),
151             Type::Array(ref v) => v.uses_type_params(options, type_set),
152             Type::Ptr(ref v) => v.uses_type_params(options, type_set),
153             Type::Reference(ref v) => v.uses_type_params(options, type_set),
154             Type::BareFn(ref v) => v.uses_type_params(options, type_set),
155             Type::Tuple(ref v) => v.uses_type_params(options, type_set),
156             Type::Path(ref v) => v.uses_type_params(options, type_set),
157             Type::Paren(ref v) => v.uses_type_params(options, type_set),
158             Type::Group(ref v) => v.uses_type_params(options, type_set),
159             Type::TraitObject(ref v) => v.uses_type_params(options, type_set),
160             Type::ImplTrait(ref v) => v.uses_type_params(options, type_set),
161             Type::Macro(_) | Type::Verbatim(_) | Type::Infer(_) | Type::Never(_) => {
162                 Default::default()
163             }
164             _ => panic!("Unknown syn::Type: {:?}", self),
165         }
166     }
167 }
168 
169 impl UsesTypeParams for syn::TypePath {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>170     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
171         let hits = self.path.uses_type_params(options, type_set);
172 
173         if options.include_type_path_qself() {
174             union_in_place(hits, self.qself.uses_type_params(options, type_set))
175         } else {
176             hits
177         }
178     }
179 }
180 
181 impl UsesTypeParams for syn::Path {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>182     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
183         // Not sure if this is even possible, but a path with no segments definitely
184         // can't use type parameters.
185         if self.segments.is_empty() {
186             return Default::default();
187         }
188 
189         // A path segment ident can only match if it is not global and it is the first segment
190         // in the path.
191         let ident_hits = if self.leading_colon.is_none() {
192             self.segments[0].ident.uses_type_params(options, type_set)
193         } else {
194             Default::default()
195         };
196 
197         // Merge ident hit, if any, with all hits from path arguments
198         self.segments.iter().fold(ident_hits, |state, segment| {
199             union_in_place(state, segment.arguments.uses_type_params(options, type_set))
200         })
201     }
202 }
203 
204 impl UsesTypeParams for syn::PathArguments {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>205     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
206         match *self {
207             syn::PathArguments::None => Default::default(),
208             syn::PathArguments::AngleBracketed(ref v) => v.uses_type_params(options, type_set),
209             syn::PathArguments::Parenthesized(ref v) => v.uses_type_params(options, type_set),
210         }
211     }
212 }
213 
214 impl UsesTypeParams for syn::WherePredicate {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>215     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
216         match *self {
217             syn::WherePredicate::Lifetime(_) => Default::default(),
218             syn::WherePredicate::Type(ref v) => v.uses_type_params(options, type_set),
219             // non-exhaustive enum
220             // TODO: replace panic with failible function
221             _ => panic!("Unknown syn::WherePredicate: {:?}", self),
222         }
223     }
224 }
225 
226 impl UsesTypeParams for syn::GenericArgument {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>227     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
228         match *self {
229             syn::GenericArgument::Type(ref v) => v.uses_type_params(options, type_set),
230             syn::GenericArgument::AssocType(ref v) => v.uses_type_params(options, type_set),
231             syn::GenericArgument::Constraint(ref v) => v.uses_type_params(options, type_set),
232             syn::GenericArgument::AssocConst(_)
233             | syn::GenericArgument::Const(_)
234             | syn::GenericArgument::Lifetime(_) => Default::default(),
235             // non-exhaustive enum
236             // TODO: replace panic with failible function
237             _ => panic!("Unknown syn::GenericArgument: {:?}", self),
238         }
239     }
240 }
241 
242 impl UsesTypeParams for syn::TypeParamBound {
uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>243     fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
244         match *self {
245             syn::TypeParamBound::Trait(ref v) => v.uses_type_params(options, type_set),
246             syn::TypeParamBound::Lifetime(_) => Default::default(),
247             // non-exhaustive enum
248             // TODO: replace panic with failible function
249             _ => panic!("Unknown syn::TypeParamBound: {:?}", self),
250         }
251     }
252 }
253 
254 #[cfg(test)]
255 mod tests {
256     use proc_macro2::Span;
257     use syn::{parse_quote, DeriveInput, Ident};
258 
259     use super::UsesTypeParams;
260     use crate::usage::IdentSet;
261     use crate::usage::Purpose::*;
262 
ident_set(idents: Vec<&str>) -> IdentSet263     fn ident_set(idents: Vec<&str>) -> IdentSet {
264         idents
265             .into_iter()
266             .map(|s| Ident::new(s, Span::call_site()))
267             .collect()
268     }
269 
270     #[test]
finds_simple()271     fn finds_simple() {
272         let input: DeriveInput = parse_quote! { struct Foo<T, U>(T, i32, A, U); };
273         let generics = ident_set(vec!["T", "U", "X"]);
274         let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
275         assert_eq!(matches.len(), 2);
276         assert!(matches.contains::<Ident>(&parse_quote!(T)));
277         assert!(matches.contains::<Ident>(&parse_quote!(U)));
278         assert!(!matches.contains::<Ident>(&parse_quote!(X)));
279         assert!(!matches.contains::<Ident>(&parse_quote!(A)));
280     }
281 
282     #[test]
finds_named()283     fn finds_named() {
284         let input: DeriveInput = parse_quote! {
285             struct Foo<T, U = usize> {
286                 bar: T,
287                 world: U,
288             }
289         };
290 
291         let generics = ident_set(vec!["T", "U", "X"]);
292 
293         let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
294 
295         assert_eq!(matches.len(), 2);
296         assert!(matches.contains::<Ident>(&parse_quote!(T)));
297         assert!(matches.contains::<Ident>(&parse_quote!(U)));
298         assert!(!matches.contains::<Ident>(&parse_quote!(X)));
299         assert!(!matches.contains::<Ident>(&parse_quote!(A)));
300     }
301 
302     #[test]
finds_as_type_arg()303     fn finds_as_type_arg() {
304         let input: DeriveInput = parse_quote! {
305             struct Foo<T, U> {
306                 bar: T,
307                 world: Vec<U>,
308             }
309         };
310 
311         let generics = ident_set(vec!["T", "U", "X"]);
312 
313         let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
314 
315         assert_eq!(matches.len(), 2);
316         assert!(matches.contains::<Ident>(&parse_quote!(T)));
317         assert!(matches.contains::<Ident>(&parse_quote!(U)));
318         assert!(!matches.contains::<Ident>(&parse_quote!(X)));
319         assert!(!matches.contains::<Ident>(&parse_quote!(A)));
320     }
321 
322     #[test]
associated_type()323     fn associated_type() {
324         let input: DeriveInput =
325             parse_quote! { struct Foo<'a, T> where T: Iterator { peek: T::Item } };
326         let generics = ident_set(vec!["T", "INTO"]);
327         let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
328         assert_eq!(matches.len(), 1);
329     }
330 
331     #[test]
box_fn_output()332     fn box_fn_output() {
333         let input: DeriveInput = parse_quote! { struct Foo<T>(Box<dyn Fn() -> T>); };
334         let generics = ident_set(vec!["T"]);
335         let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
336         assert_eq!(matches.len(), 1);
337         assert!(matches.contains::<Ident>(&parse_quote!(T)));
338     }
339 
340     #[test]
box_fn_input()341     fn box_fn_input() {
342         let input: DeriveInput = parse_quote! { struct Foo<T>(Box<dyn Fn(&T) -> ()>); };
343         let generics = ident_set(vec!["T"]);
344         let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
345         assert_eq!(matches.len(), 1);
346         assert!(matches.contains::<Ident>(&parse_quote!(T)));
347     }
348 
349     /// Test that `syn::TypePath` is correctly honoring the different modes a
350     /// search can execute in.
351     #[test]
qself_vec()352     fn qself_vec() {
353         let input: DeriveInput =
354             parse_quote! { struct Foo<T>(<Vec<T> as a::b::Trait>::AssociatedItem); };
355         let generics = ident_set(vec!["T", "U"]);
356 
357         let bound_matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
358         assert_eq!(bound_matches.len(), 0);
359 
360         let declare_matches = input.data.uses_type_params(&Declare.into(), &generics);
361         assert_eq!(declare_matches.len(), 1);
362         assert!(declare_matches.contains::<Ident>(&parse_quote!(T)));
363     }
364 }
365