1use syn::punctuated::Punctuated;
2use syn::{Ident, Type};
3
4use 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.
8pub 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.
28pub 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
36impl<'i, T, I> CollectTypeParams for T
37where
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`.
57fn union_in_place<'a>(mut left: IdentRefSet<'a>, right: IdentRefSet<'a>) -> IdentRefSet<'a> {
58 left.extend(iter:right);
59
60 left
61}
62
63impl UsesTypeParams for () {
64 fn uses_type_params<'a>(&self, _options: &Options, _type_set: &'a IdentSet) -> IdentRefSet<'a> {
65 Default::default()
66 }
67}
68
69impl<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
77impl<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
83impl<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
89uses_type_params!(syn::AngleBracketedGenericArguments, args);
90uses_type_params!(syn::BareFnArg, ty);
91uses_type_params!(syn::Binding, ty);
92uses_type_params!(syn::Constraint, bounds);
93uses_type_params!(syn::DataEnum, variants);
94uses_type_params!(syn::DataStruct, fields);
95uses_type_params!(syn::DataUnion, fields);
96uses_type_params!(syn::Field, ty);
97uses_type_params!(syn::FieldsNamed, named);
98uses_type_params!(syn::ParenthesizedGenericArguments, inputs, output);
99uses_type_params!(syn::PredicateEq, lhs_ty, rhs_ty);
100uses_type_params!(syn::PredicateType, bounded_ty, bounds);
101uses_type_params!(syn::QSelf, ty);
102uses_type_params!(syn::TraitBound, path);
103uses_type_params!(syn::TypeArray, elem);
104uses_type_params!(syn::TypeBareFn, inputs, output);
105uses_type_params!(syn::TypeGroup, elem);
106uses_type_params!(syn::TypeImplTrait, bounds);
107uses_type_params!(syn::TypeParen, elem);
108uses_type_params!(syn::TypePtr, elem);
109uses_type_params!(syn::TypeReference, elem);
110uses_type_params!(syn::TypeSlice, elem);
111uses_type_params!(syn::TypeTuple, elems);
112uses_type_params!(syn::TypeTraitObject, bounds);
113uses_type_params!(syn::Variant, fields);
114
115impl 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
125impl 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.
132impl 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
138impl 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
148impl 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
170impl 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
182impl 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
205impl 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
215impl 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
225impl 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
238impl 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)]
248mod 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