Skip to main content

darling_core/usage/
type_params.rs

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            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
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(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| 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::AssocType, ty);
91uses_type_params!(syn::BareFnArg, 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::PredicateType, bounded_ty, bounds);
100uses_type_params!(syn::QSelf, ty);
101uses_type_params!(syn::TraitBound, path);
102uses_type_params!(syn::TypeArray, elem);
103uses_type_params!(syn::TypeBareFn, inputs, output);
104uses_type_params!(syn::TypeGroup, elem);
105uses_type_params!(syn::TypeImplTrait, bounds);
106uses_type_params!(syn::TypeParen, elem);
107uses_type_params!(syn::TypePtr, elem);
108uses_type_params!(syn::TypeReference, elem);
109uses_type_params!(syn::TypeSlice, elem);
110uses_type_params!(syn::TypeTuple, elems);
111uses_type_params!(syn::TypeTraitObject, bounds);
112uses_type_params!(syn::Variant, fields);
113
114impl 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) => 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
124impl 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.
131impl UsesTypeParams for Ident {
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
137impl 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) = *self {
140            ty.uses_type_params(options, type_set)
141        } else {
142            Default::default()
143        }
144    }
145}
146
147impl 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) => 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
169impl UsesTypeParams for syn::TypePath {
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
181impl 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 = 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
204impl 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) => v.uses_type_params(options, type_set),
209            syn::PathArguments::Parenthesized(ref v) => v.uses_type_params(options, type_set),
210        }
211    }
212}
213
214impl 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) => 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
226impl 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) => 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
242impl 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) => 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)]
255mod 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}