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. |
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. |
16 | 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`. |
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. |
33 | 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 | { |
41 | fn collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
42 | self.into_iter().fold( |
43 | init:IdentRefSet::with_capacity_and_hasher(type_set.len(), Default::default()), |
44 | |state: HashSet<&Ident, BuildHasherDefault<…>>, value: &I| union_in_place(left:state, right:value.uses_type_params(options, type_set)), |
45 | ) |
46 | } |
47 | |
48 | 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`. |
57 | fn union_in_place<'a>(mut left: IdentRefSet<'a>, right: IdentRefSet<'a>) -> IdentRefSet<'a> { |
58 | left.extend(iter:right); |
59 | |
60 | left |
61 | } |
62 | |
63 | impl UsesTypeParams for () { |
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> { |
70 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
71 | self.as_ref() |
72 | .map(|v: &T| v.uses_type_params(options, type_set)) |
73 | .unwrap_or_default() |
74 | } |
75 | } |
76 | |
77 | impl<T: UsesTypeParams> UsesTypeParams for Vec<T> { |
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> { |
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 { |
115 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
116 | match *self { |
117 | syn::Data::Struct(ref v: &DataStruct) => v.uses_type_params(options, type_set), |
118 | syn::Data::Enum(ref v: &DataEnum) => v.uses_type_params(options, type_set), |
119 | syn::Data::Union(ref v: &DataUnion) => v.uses_type_params(options, type_set), |
120 | } |
121 | } |
122 | } |
123 | |
124 | impl UsesTypeParams for syn::Fields { |
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 { |
132 | fn uses_type_params<'a>(&self, _options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
133 | type_set.iter().filter(|v: &&Ident| *v == self).collect() |
134 | } |
135 | } |
136 | |
137 | impl UsesTypeParams for syn::ReturnType { |
138 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
139 | if let syn::ReturnType::Type(_, ref ty: &Box) = *self { |
140 | ty.uses_type_params(options, type_set) |
141 | } else { |
142 | Default::default() |
143 | } |
144 | } |
145 | } |
146 | |
147 | impl UsesTypeParams for Type { |
148 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
149 | match *self { |
150 | Type::Slice(ref v: &TypeSlice) => v.uses_type_params(options, type_set), |
151 | Type::Array(ref v: &TypeArray) => v.uses_type_params(options, type_set), |
152 | Type::Ptr(ref v: &TypePtr) => v.uses_type_params(options, type_set), |
153 | Type::Reference(ref v: &TypeReference) => v.uses_type_params(options, type_set), |
154 | Type::BareFn(ref v: &TypeBareFn) => v.uses_type_params(options, type_set), |
155 | Type::Tuple(ref v: &TypeTuple) => v.uses_type_params(options, type_set), |
156 | Type::Path(ref v: &TypePath) => v.uses_type_params(options, type_set), |
157 | Type::Paren(ref v: &TypeParen) => v.uses_type_params(options, type_set), |
158 | Type::Group(ref v: &TypeGroup) => v.uses_type_params(options, type_set), |
159 | Type::TraitObject(ref v: &TypeTraitObject) => v.uses_type_params(options, type_set), |
160 | Type::ImplTrait(ref v: &TypeImplTrait) => 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 { |
170 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
171 | let hits: HashSet<&Ident, BuildHasherDefault<…>> = self.path.uses_type_params(options, type_set); |
172 | |
173 | if options.include_type_path_qself() { |
174 | union_in_place(left:hits, self.qself.uses_type_params(options, type_set)) |
175 | } else { |
176 | hits |
177 | } |
178 | } |
179 | } |
180 | |
181 | impl UsesTypeParams for syn::Path { |
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: HashSet<&Ident, BuildHasherDefault<…>> = 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(init:ident_hits, |state: HashSet<&Ident, BuildHasherDefault<…>>, segment: &PathSegment| { |
199 | union_in_place(left:state, right:segment.arguments.uses_type_params(options, type_set)) |
200 | }) |
201 | } |
202 | } |
203 | |
204 | impl UsesTypeParams for syn::PathArguments { |
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: &AngleBracketedGenericArguments) => v.uses_type_params(options, type_set), |
209 | syn::PathArguments::Parenthesized(ref v: &ParenthesizedGenericArguments) => v.uses_type_params(options, type_set), |
210 | } |
211 | } |
212 | } |
213 | |
214 | impl UsesTypeParams for syn::WherePredicate { |
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: &PredicateType) => 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 { |
227 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
228 | match *self { |
229 | syn::GenericArgument::Type(ref v: &Type) => v.uses_type_params(options, type_set), |
230 | syn::GenericArgument::AssocType(ref v: &AssocType) => v.uses_type_params(options, type_set), |
231 | syn::GenericArgument::Constraint(ref v: &Constraint) => 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 { |
243 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
244 | match *self { |
245 | syn::TypeParamBound::Trait(ref v: &TraitBound) => 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 | |
263 | 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 ] |
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 ] |
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 ] |
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 ] |
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 ] |
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 ] |
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 ] |
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 | |