Skip to main content

yoke_derive/
visitor.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Visitor for determining whether a type has type and non-static lifetime parameters
6//! (except for type macros, which are largely ignored).
7//!
8//! Note that poor handling of type macros in `derive(Yokeable)` can only result in
9//! compiler errors at worst, not unsoundness.
10
11use std::collections::HashSet;
12use syn::ext::IdentExt as _;
13use syn::visit::{
14    visit_bound_lifetimes, visit_generic_param, visit_lifetime, visit_type, visit_type_path,
15    visit_where_clause, Visit,
16};
17use syn::{GenericParam, Ident, Lifetime, Type, TypePath, WhereClause};
18
19use super::lifetimes::ignored_lifetime_ident;
20
21struct Visitor<'a> {
22    /// The lifetime parameter of the yokeable, stripped of any leading `r#`
23    lt_param: &'a Ident,
24    /// Whether we found a usage of the `lt_param` lifetime (or its `raw` form)
25    found_lt_param_usage: bool,
26    /// The type parameters in scope, stripped of any leading `r#`s
27    typarams: &'a HashSet<Ident>,
28    /// Whether we found a type parameter (or its `raw` form)
29    found_typaram_usage: bool,
30    /// How many underscores should be added before "yoke" in the `'__[underscores]__yoke`
31    /// lifetime used by the derive.
32    ///
33    /// This is one more than the maximum number of underscores in
34    /// (possibly raw) `'__[underscores]__yoke` lifetimes bound by `for<>` binders,
35    /// or 0 if no such bound lifetimes were found.
36    min_underscores_for_yoke_lt: usize,
37}
38
39impl<'ast> Visit<'ast> for Visitor<'_> {
40    fn visit_lifetime(&mut self, lt: &'ast Lifetime) {
41        if lt.ident.unraw() == *self.lt_param {
42            // Note that `for<>` binders cannot introduce a lifetime already in scope,
43            // so `lt.ident` necessarily refers to the lifetime parameter of the yokeable.
44            self.found_lt_param_usage = true;
45        }
46        visit_lifetime(self, lt)
47    }
48
49    fn visit_type_path(&mut self, ty: &'ast TypePath) {
50        // We only need to check ty.path.get_ident() and not ty.qself or any
51        // generics in ty.path because the visitor will eventually visit those
52        // types on its own
53        if let Some(ident) = ty.path.get_ident() {
54            if self.typarams.contains(&ident.unraw()) {
55                self.found_typaram_usage = true;
56            }
57        }
58        visit_type_path(self, ty)
59    }
60
61    fn visit_bound_lifetimes(&mut self, lts: &'ast syn::BoundLifetimes) {
62        for lt in &lts.lifetimes {
63            if let GenericParam::Lifetime(lt) = lt {
64                let maybe_underscores_yoke = lt.lifetime.ident.unraw().to_string();
65
66                // Check if the unraw'd lifetime ident is `__[underscores]__yoke`
67                if let Some(underscores) = maybe_underscores_yoke.strip_suffix("yoke") {
68                    if underscores.as_bytes().iter().all(|byte| *byte == b'_') {
69                        // Since `_` is ASCII, `underscores` consists entirely of `_` characters
70                        // iff it consists entirely of `b'_'` bytes, which holds iff
71                        // `underscores.len()` is the number of underscores.
72                        self.min_underscores_for_yoke_lt = self.min_underscores_for_yoke_lt.max(
73                            // 1 more underscore, so as not to conflict with this bound lt.
74                            underscores.len() + 1,
75                        );
76                    }
77                }
78            }
79        }
80        visit_bound_lifetimes(self, lts);
81    }
82    // Type macros are ignored/skipped by default.
83}
84
85#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CheckResult {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "CheckResult",
            "uses_lifetime_param", &self.uses_lifetime_param,
            "uses_type_params", &self.uses_type_params,
            "min_underscores_for_yoke_lt", &&self.min_underscores_for_yoke_lt)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for CheckResult {
    #[inline]
    fn eq(&self, other: &CheckResult) -> bool {
        self.uses_lifetime_param == other.uses_lifetime_param &&
                self.uses_type_params == other.uses_type_params &&
            self.min_underscores_for_yoke_lt ==
                other.min_underscores_for_yoke_lt
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CheckResult {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<bool>;
        let _: ::core::cmp::AssertParamIsEq<usize>;
    }
}Eq)]
86pub struct CheckResult {
87    /// Whether the checked type uses the given `lt_param`
88    /// (possibly in its raw form).
89    pub uses_lifetime_param: bool,
90    /// Whether the checked type uses one of the type parameters in scope
91    /// (possibly in its raw form).
92    pub uses_type_params: bool,
93    /// How many underscores should be added before "yoke" in the `'__[underscores]__yoke`
94    /// lifetime used by the derive.
95    ///
96    /// This is one more than the maximum number of underscores in
97    /// (possibly raw) `'__[underscores]__yoke` lifetimes bound by `for<>` binders,
98    /// or 0 if no such bound lifetimes were found.
99    pub min_underscores_for_yoke_lt: usize,
100}
101
102/// Checks if a type uses the yokeable type's lifetime parameter or type parameters,
103/// given the local context of named type parameters and the lifetime parameter.
104///
105/// Crucially, the idents in `lt_param` and `typarams` are required to not have leading `r#`s.
106///
107/// Usage of const generic parameters is not checked.
108pub fn check_type_for_parameters(
109    lt_param: &Ident,
110    typarams: &HashSet<Ident>,
111    ty: &Type,
112) -> CheckResult {
113    let mut visit = Visitor {
114        lt_param,
115        found_lt_param_usage: false,
116        typarams,
117        found_typaram_usage: false,
118        min_underscores_for_yoke_lt: 0,
119    };
120    visit_type(&mut visit, ty);
121
122    CheckResult {
123        uses_lifetime_param: visit.found_lt_param_usage,
124        uses_type_params: visit.found_typaram_usage,
125        min_underscores_for_yoke_lt: visit.min_underscores_for_yoke_lt,
126    }
127}
128
129/// Check a generic parameter of a yokeable type for usage of lifetimes like `'yoke` or `'_yoke`
130/// bound in for-binders.
131///
132/// Returns [`min_underscores_for_yoke_lt`](CheckResult::min_underscores_for_yoke_lt).
133pub fn check_parameter_for_bound_lts(param: &GenericParam) -> usize {
134    // Note that `lt_param` does not impact `min_underscores_for_yoke_lt`.
135    let mut visit = Visitor {
136        lt_param: &ignored_lifetime_ident(),
137        found_lt_param_usage: false,
138        typarams: &HashSet::new(),
139        found_typaram_usage: false,
140        min_underscores_for_yoke_lt: 0,
141    };
142
143    visit_generic_param(&mut visit, param);
144
145    visit.min_underscores_for_yoke_lt
146}
147
148/// Check a where-clause for usage of lifetimes like `'yoke` or `'_yoke` bound in for-binders.
149///
150/// Returns [`min_underscores_for_yoke_lt`](CheckResult::min_underscores_for_yoke_lt).
151pub fn check_where_clause_for_bound_lts(where_clause: &WhereClause) -> usize {
152    // Note that `lt_param` does not impact `min_underscores_for_yoke_lt`.
153    let mut visit = Visitor {
154        lt_param: &ignored_lifetime_ident(),
155        found_lt_param_usage: false,
156        typarams: &HashSet::new(),
157        found_typaram_usage: false,
158        min_underscores_for_yoke_lt: 0,
159    };
160
161    visit_where_clause(&mut visit, where_clause);
162
163    visit.min_underscores_for_yoke_lt
164}
165
166#[cfg(test)]
167mod tests {
168    use proc_macro2::Span;
169    use std::collections::HashSet;
170    use syn::{parse_quote, Ident};
171
172    use super::{check_type_for_parameters, CheckResult};
173
174    fn a_ident() -> Ident {
175        Ident::new("a", Span::call_site())
176    }
177
178    fn yoke_ident() -> Ident {
179        Ident::new("yoke", Span::call_site())
180    }
181
182    fn make_typarams(params: &[&str]) -> HashSet<Ident> {
183        params
184            .iter()
185            .map(|x| Ident::new(x, Span::call_site()))
186            .collect()
187    }
188
189    fn uses(lifetime: bool, typarams: bool) -> CheckResult {
190        CheckResult {
191            uses_lifetime_param: lifetime,
192            uses_type_params: typarams,
193            min_underscores_for_yoke_lt: 0,
194        }
195    }
196
197    #[test]
198    fn test_simple_type() {
199        let environment = make_typarams(&["T", "U", "V"]);
200
201        let ty = parse_quote!(Foo<'a, T>);
202        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
203        assert_eq!(check, uses(true, true));
204
205        let ty = parse_quote!(Foo<T>);
206        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
207        assert_eq!(check, uses(false, true));
208
209        let ty = parse_quote!(Foo<'static, T>);
210        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
211        assert_eq!(check, uses(false, true));
212
213        let ty = parse_quote!(Foo<'a>);
214        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
215        assert_eq!(check, uses(true, false));
216
217        let ty = parse_quote!(Foo<'a, Bar<U>, Baz<(V, u8)>>);
218        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
219        assert_eq!(check, uses(true, true));
220
221        let ty = parse_quote!(Foo<'a, W>);
222        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
223        assert_eq!(check, uses(true, false));
224    }
225
226    #[test]
227    fn test_assoc_types() {
228        let environment = make_typarams(&["T"]);
229
230        let ty = parse_quote!(<Foo as SomeTrait<'a, T>>::Output);
231        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
232        assert_eq!(check, uses(true, true));
233
234        let ty = parse_quote!(<Foo as SomeTrait<'static, T>>::Output);
235        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
236        assert_eq!(check, uses(false, true));
237
238        let ty = parse_quote!(<T as SomeTrait<'static, Foo>>::Output);
239        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
240        assert_eq!(check, uses(false, true));
241    }
242
243    #[test]
244    fn test_macro_types() {
245        let environment = make_typarams(&["T"]);
246
247        // Macro types are opaque. Note that the environment doesn't contain `U` or `V`,
248        // and the `T` is basically ignored.
249
250        let ty = parse_quote!(foo!(Foo<'a, T>));
251        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
252        assert_eq!(check, uses(false, false));
253
254        let ty = parse_quote!(foo!(Foo<T>));
255        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
256        assert_eq!(check, uses(false, false));
257
258        let ty = parse_quote!(foo!(Foo<'static, T>));
259        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
260        assert_eq!(check, uses(false, false));
261
262        let ty = parse_quote!(foo!(Foo<'a>));
263        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
264        assert_eq!(check, uses(false, false));
265
266        let ty = parse_quote!(foo!(Foo<'a, Bar<U>, Baz<(V, u8)>>));
267        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
268        assert_eq!(check, uses(false, false));
269
270        let ty = parse_quote!(foo!(Foo<'a, W>));
271        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
272        assert_eq!(check, uses(false, false));
273    }
274
275    #[test]
276    fn test_raw_types() {
277        let environment = make_typarams(&["T", "U", "V"]);
278
279        let ty = parse_quote!(Foo<'a, r#T>);
280        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
281        assert_eq!(check, uses(true, true));
282
283        let ty = parse_quote!(Foo<r#T>);
284        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
285        assert_eq!(check, uses(false, true));
286
287        let ty = parse_quote!(Foo<'static, r#T>);
288        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
289        assert_eq!(check, uses(false, true));
290
291        let ty = parse_quote!(Foo<'a>);
292        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
293        assert_eq!(check, uses(true, false));
294
295        let ty = parse_quote!(Foo<'a, Bar<r#U>, Baz<(r#V, u8)>>);
296        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
297        assert_eq!(check, uses(true, true));
298
299        let ty = parse_quote!(Foo<'a, r#W>);
300        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
301        assert_eq!(check, uses(true, false));
302    }
303
304    #[test]
305    fn test_yoke_lifetime() {
306        let environment = make_typarams(&["T", "U", "V"]);
307
308        let ty = parse_quote!(Foo<'yoke, r#T>);
309        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
310        assert_eq!(check.min_underscores_for_yoke_lt, 0);
311
312        let ty = parse_quote!(for<'yoke> fn(&'yoke ()));
313        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
314        assert_eq!(check.min_underscores_for_yoke_lt, 1);
315
316        let ty = parse_quote!(for<'_yoke> fn(&'_yoke ()));
317        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
318        assert_eq!(check.min_underscores_for_yoke_lt, 2);
319
320        let ty = parse_quote!(for<'_yoke_> fn(&'_yoke_ ()));
321        let check = check_type_for_parameters(&yoke_ident(), &environment, &ty);
322        assert_eq!(check.min_underscores_for_yoke_lt, 0);
323
324        let ty = parse_quote!(for<'_yoke, '___yoke> fn(&'_yoke (), &'___yoke ()));
325        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
326        assert_eq!(check.min_underscores_for_yoke_lt, 4);
327
328        let ty = parse_quote!(for<'___yoke> fn(for<'_yoke> fn(&'_yoke (), &'___yoke ())));
329        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
330        assert_eq!(check.min_underscores_for_yoke_lt, 4);
331
332        let ty = parse_quote! {
333            for<'yoke> fn(for<'_yoke> fn(for<'b> fn(&'b (), &'_yoke (), &'yoke ())))
334        };
335        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
336        assert_eq!(check.min_underscores_for_yoke_lt, 2);
337
338        let ty = parse_quote! {
339            for<'yoke> fn(for<'r#_yoke> fn(for<'b> fn(&'b (), &'_yoke (), &'yoke ())))
340        };
341        let check = check_type_for_parameters(&a_ident(), &environment, &ty);
342        assert_eq!(check.min_underscores_for_yoke_lt, 2);
343    }
344}