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::BareFnArg, ty); |
91 | uses_type_params!(syn::Binding, 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::PredicateEq, lhs_ty, rhs_ty); |
100 | uses_type_params!(syn::PredicateType, bounded_ty, bounds); |
101 | uses_type_params!(syn::QSelf, ty); |
102 | uses_type_params!(syn::TraitBound, path); |
103 | uses_type_params!(syn::TypeArray, elem); |
104 | uses_type_params!(syn::TypeBareFn, inputs, output); |
105 | uses_type_params!(syn::TypeGroup, elem); |
106 | uses_type_params!(syn::TypeImplTrait, bounds); |
107 | uses_type_params!(syn::TypeParen, elem); |
108 | uses_type_params!(syn::TypePtr, elem); |
109 | uses_type_params!(syn::TypeReference, elem); |
110 | uses_type_params!(syn::TypeSlice, elem); |
111 | uses_type_params!(syn::TypeTuple, elems); |
112 | uses_type_params!(syn::TypeTraitObject, bounds); |
113 | uses_type_params!(syn::Variant, fields); |
114 | |
115 | impl UsesTypeParams for syn::Data { |
116 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
117 | match *self { |
118 | syn::Data::Struct(ref v: &DataStruct) => v.uses_type_params(options, type_set), |
119 | syn::Data::Enum(ref v: &DataEnum) => v.uses_type_params(options, type_set), |
120 | syn::Data::Union(ref v: &DataUnion) => v.uses_type_params(options, type_set), |
121 | } |
122 | } |
123 | } |
124 | |
125 | impl UsesTypeParams for syn::Fields { |
126 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
127 | self.collect_type_params(options, type_set) |
128 | } |
129 | } |
130 | |
131 | /// Check if an Ident exactly matches one of the sought-after type parameters. |
132 | impl UsesTypeParams for Ident { |
133 | fn uses_type_params<'a>(&self, _options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
134 | type_set.iter().filter(|v: &&Ident| *v == self).collect() |
135 | } |
136 | } |
137 | |
138 | impl UsesTypeParams for syn::ReturnType { |
139 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
140 | if let syn::ReturnType::Type(_, ref ty: &Box) = *self { |
141 | ty.uses_type_params(options, type_set) |
142 | } else { |
143 | Default::default() |
144 | } |
145 | } |
146 | } |
147 | |
148 | impl UsesTypeParams for Type { |
149 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
150 | match *self { |
151 | Type::Slice(ref v: &TypeSlice) => v.uses_type_params(options, type_set), |
152 | Type::Array(ref v: &TypeArray) => v.uses_type_params(options, type_set), |
153 | Type::Ptr(ref v: &TypePtr) => v.uses_type_params(options, type_set), |
154 | Type::Reference(ref v: &TypeReference) => v.uses_type_params(options, type_set), |
155 | Type::BareFn(ref v: &TypeBareFn) => v.uses_type_params(options, type_set), |
156 | Type::Tuple(ref v: &TypeTuple) => v.uses_type_params(options, type_set), |
157 | Type::Path(ref v: &TypePath) => v.uses_type_params(options, type_set), |
158 | Type::Paren(ref v: &TypeParen) => v.uses_type_params(options, type_set), |
159 | Type::Group(ref v: &TypeGroup) => v.uses_type_params(options, type_set), |
160 | Type::TraitObject(ref v: &TypeTraitObject) => v.uses_type_params(options, type_set), |
161 | Type::ImplTrait(ref v: &TypeImplTrait) => v.uses_type_params(options, type_set), |
162 | Type::Macro(_) | Type::Verbatim(_) | Type::Infer(_) | Type::Never(_) => { |
163 | Default::default() |
164 | } |
165 | _ => panic!("Unknown syn::Type: {:?}" , self), |
166 | } |
167 | } |
168 | } |
169 | |
170 | impl UsesTypeParams for syn::TypePath { |
171 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
172 | let hits: HashSet<&Ident, BuildHasherDefault<…>> = self.path.uses_type_params(options, type_set); |
173 | |
174 | if options.include_type_path_qself() { |
175 | union_in_place(left:hits, self.qself.uses_type_params(options, type_set)) |
176 | } else { |
177 | hits |
178 | } |
179 | } |
180 | } |
181 | |
182 | impl UsesTypeParams for syn::Path { |
183 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
184 | // Not sure if this is even possible, but a path with no segments definitely |
185 | // can't use type parameters. |
186 | if self.segments.is_empty() { |
187 | return Default::default(); |
188 | } |
189 | |
190 | // A path segment ident can only match if it is not global and it is the first segment |
191 | // in the path. |
192 | let ident_hits: HashSet<&Ident, BuildHasherDefault<…>> = if self.leading_colon.is_none() { |
193 | self.segments[0].ident.uses_type_params(options, type_set) |
194 | } else { |
195 | Default::default() |
196 | }; |
197 | |
198 | // Merge ident hit, if any, with all hits from path arguments |
199 | self.segments.iter().fold(init:ident_hits, |state: HashSet<&Ident, BuildHasherDefault<…>>, segment: &PathSegment| { |
200 | union_in_place(left:state, right:segment.arguments.uses_type_params(options, type_set)) |
201 | }) |
202 | } |
203 | } |
204 | |
205 | impl UsesTypeParams for syn::PathArguments { |
206 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
207 | match *self { |
208 | syn::PathArguments::None => Default::default(), |
209 | syn::PathArguments::AngleBracketed(ref v: &AngleBracketedGenericArguments) => v.uses_type_params(options, type_set), |
210 | syn::PathArguments::Parenthesized(ref v: &ParenthesizedGenericArguments) => v.uses_type_params(options, type_set), |
211 | } |
212 | } |
213 | } |
214 | |
215 | impl UsesTypeParams for syn::WherePredicate { |
216 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
217 | match *self { |
218 | syn::WherePredicate::Lifetime(_) => Default::default(), |
219 | syn::WherePredicate::Type(ref v: &PredicateType) => v.uses_type_params(options, type_set), |
220 | syn::WherePredicate::Eq(ref v: &PredicateEq) => v.uses_type_params(options, type_set), |
221 | } |
222 | } |
223 | } |
224 | |
225 | impl UsesTypeParams for syn::GenericArgument { |
226 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
227 | match *self { |
228 | syn::GenericArgument::Type(ref v: &Type) => v.uses_type_params(options, type_set), |
229 | syn::GenericArgument::Binding(ref v: &Binding) => v.uses_type_params(options, type_set), |
230 | syn::GenericArgument::Constraint(ref v: &Constraint) => v.uses_type_params(options, type_set), |
231 | syn::GenericArgument::Const(_) | syn::GenericArgument::Lifetime(_) => { |
232 | Default::default() |
233 | } |
234 | } |
235 | } |
236 | } |
237 | |
238 | impl UsesTypeParams for syn::TypeParamBound { |
239 | fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { |
240 | match *self { |
241 | syn::TypeParamBound::Trait(ref v: &TraitBound) => v.uses_type_params(options, type_set), |
242 | syn::TypeParamBound::Lifetime(_) => Default::default(), |
243 | } |
244 | } |
245 | } |
246 | |
247 | #[cfg (test)] |
248 | mod tests { |
249 | use proc_macro2::Span; |
250 | use syn::{parse_quote, DeriveInput, Ident}; |
251 | |
252 | use super::UsesTypeParams; |
253 | use crate::usage::IdentSet; |
254 | use crate::usage::Purpose::*; |
255 | |
256 | fn ident_set(idents: Vec<&str>) -> IdentSet { |
257 | idents |
258 | .into_iter() |
259 | .map(|s| Ident::new(s, Span::call_site())) |
260 | .collect() |
261 | } |
262 | |
263 | #[test ] |
264 | fn finds_simple() { |
265 | let input: DeriveInput = parse_quote! { struct Foo<T, U>(T, i32, A, U); }; |
266 | let generics = ident_set(vec!["T" , "U" , "X" ]); |
267 | let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
268 | assert_eq!(matches.len(), 2); |
269 | assert!(matches.contains::<Ident>(&parse_quote!(T))); |
270 | assert!(matches.contains::<Ident>(&parse_quote!(U))); |
271 | assert!(!matches.contains::<Ident>(&parse_quote!(X))); |
272 | assert!(!matches.contains::<Ident>(&parse_quote!(A))); |
273 | } |
274 | |
275 | #[test ] |
276 | fn finds_named() { |
277 | let input: DeriveInput = parse_quote! { |
278 | struct Foo<T, U = usize> { |
279 | bar: T, |
280 | world: U, |
281 | } |
282 | }; |
283 | |
284 | let generics = ident_set(vec!["T" , "U" , "X" ]); |
285 | |
286 | let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
287 | |
288 | assert_eq!(matches.len(), 2); |
289 | assert!(matches.contains::<Ident>(&parse_quote!(T))); |
290 | assert!(matches.contains::<Ident>(&parse_quote!(U))); |
291 | assert!(!matches.contains::<Ident>(&parse_quote!(X))); |
292 | assert!(!matches.contains::<Ident>(&parse_quote!(A))); |
293 | } |
294 | |
295 | #[test ] |
296 | fn finds_as_type_arg() { |
297 | let input: DeriveInput = parse_quote! { |
298 | struct Foo<T, U> { |
299 | bar: T, |
300 | world: Vec<U>, |
301 | } |
302 | }; |
303 | |
304 | let generics = ident_set(vec!["T" , "U" , "X" ]); |
305 | |
306 | let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
307 | |
308 | assert_eq!(matches.len(), 2); |
309 | assert!(matches.contains::<Ident>(&parse_quote!(T))); |
310 | assert!(matches.contains::<Ident>(&parse_quote!(U))); |
311 | assert!(!matches.contains::<Ident>(&parse_quote!(X))); |
312 | assert!(!matches.contains::<Ident>(&parse_quote!(A))); |
313 | } |
314 | |
315 | #[test ] |
316 | fn associated_type() { |
317 | let input: DeriveInput = |
318 | parse_quote! { struct Foo<'a, T> where T: Iterator { peek: T::Item } }; |
319 | let generics = ident_set(vec!["T" , "INTO" ]); |
320 | let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
321 | assert_eq!(matches.len(), 1); |
322 | } |
323 | |
324 | #[test ] |
325 | fn box_fn_output() { |
326 | let input: DeriveInput = parse_quote! { struct Foo<T>(Box<Fn() -> T>); }; |
327 | let generics = ident_set(vec!["T" ]); |
328 | let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
329 | assert_eq!(matches.len(), 1); |
330 | assert!(matches.contains::<Ident>(&parse_quote!(T))); |
331 | } |
332 | |
333 | #[test ] |
334 | fn box_fn_input() { |
335 | let input: DeriveInput = parse_quote! { struct Foo<T>(Box<Fn(&T) -> ()>); }; |
336 | let generics = ident_set(vec!["T" ]); |
337 | let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
338 | assert_eq!(matches.len(), 1); |
339 | assert!(matches.contains::<Ident>(&parse_quote!(T))); |
340 | } |
341 | |
342 | /// Test that `syn::TypePath` is correctly honoring the different modes a |
343 | /// search can execute in. |
344 | #[test ] |
345 | fn qself_vec() { |
346 | let input: DeriveInput = |
347 | parse_quote! { struct Foo<T>(<Vec<T> as a::b::Trait>::AssociatedItem); }; |
348 | let generics = ident_set(vec!["T" , "U" ]); |
349 | |
350 | let bound_matches = input.data.uses_type_params(&BoundImpl.into(), &generics); |
351 | assert_eq!(bound_matches.len(), 0); |
352 | |
353 | let declare_matches = input.data.uses_type_params(&Declare.into(), &generics); |
354 | assert_eq!(declare_matches.len(), 1); |
355 | assert!(declare_matches.contains::<Ident>(&parse_quote!(T))); |
356 | } |
357 | } |
358 | |