auto_impl/
analyze.rs

1use std::collections::HashSet;
2
3use proc_macro2::Span as Span2;
4use syn::{
5    visit::{visit_item_trait, Visit},
6    Block, Ident, ItemTrait, Lifetime,
7};
8
9/// The type parameter used in the proxy type. Usually, one would just use `T`,
10/// but this could conflict with type parameters on the trait.
11///
12/// Why do we have to care about this? Why about hygiene? In the first version
13/// of stable proc_macros, only call site spans are included. That means that
14/// we cannot generate spans that do not conflict with any other ident the user
15/// wrote. Once proper hygiene is available to proc_macros, this should be
16/// changed.
17const PROXY_TY_PARAM_NAME: &str = "__AutoImplProxyT";
18
19/// The lifetime parameter used in the proxy type if the proxy type is `&` or
20/// `&mut`. For more information see `PROXY_TY_PARAM_NAME`.
21const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime";
22
23/// We need to introduce our own type and lifetime parameter. Regardless of
24/// what kind of hygiene we use for the parameter, it would be nice (for docs
25/// and compiler errors) if the names are as simple as possible ('a and T, for
26/// example).
27///
28/// This function searches for names that we can use. Such a name must not
29/// conflict with any other name we'll use in the `impl` block. Luckily, we
30/// know all those names in advance.
31///
32/// The idea is to collect all names that might conflict with our names, store
33/// them in a set and later check which name we can use. If we can't use a simple
34/// name, we'll use the ugly `PROXY_TY_PARAM_NAME` and `PROXY_LT_PARAM_NAME`.
35///
36/// This method returns two idents: (type_parameter, lifetime_parameter).
37pub(crate) fn find_suitable_param_names(trait_def: &ItemTrait) -> (Ident, Lifetime) {
38    // Define the visitor that just collects names
39    struct IdentCollector<'ast> {
40        ty_names: HashSet<&'ast Ident>,
41        lt_names: HashSet<&'ast Ident>,
42    }
43
44    impl<'ast> Visit<'ast> for IdentCollector<'ast> {
45        fn visit_ident(&mut self, i: &'ast Ident) {
46            self.ty_names.insert(i);
47        }
48
49        // We overwrite this to make sure to put lifetime names into
50        // `lt_names`. We also don't recurse, so `visit_ident` won't be called
51        // for lifetime names.
52        fn visit_lifetime(&mut self, lt: &'ast Lifetime) {
53            self.lt_names.insert(&lt.ident);
54        }
55
56        // Visiting a block just does nothing. It is the default body of a method
57        // in the trait. But since that block won't be in the impl block, we can
58        // just ignore it.
59        fn visit_block(&mut self, _: &'ast Block) {}
60    }
61
62    // Create the visitor and visit the trait
63    let mut visitor = IdentCollector {
64        ty_names: HashSet::new(),
65        lt_names: HashSet::new(),
66    };
67    visit_item_trait(&mut visitor, trait_def);
68
69    fn char_to_ident(c: u8) -> Ident {
70        let arr = [c];
71        let s = ::std::str::from_utf8(&arr).unwrap();
72        Ident::new(s, param_span())
73    }
74
75    // Find suitable type name (T..=Z and A..=S)
76    let ty_name = (b'T'..=b'Z')
77        .chain(b'A'..=b'S')
78        .map(char_to_ident)
79        .find(|i| !visitor.ty_names.contains(i))
80        .unwrap_or_else(|| Ident::new(PROXY_TY_PARAM_NAME, param_span()));
81
82    // Find suitable lifetime name ('a..='z)
83    let lt_name = (b'a'..=b'z')
84        .map(char_to_ident)
85        .find(|i| !visitor.lt_names.contains(i))
86        .unwrap_or_else(|| Ident::new(PROXY_LT_PARAM_NAME, param_span()));
87    let lt = Lifetime {
88        apostrophe: param_span(),
89        ident: lt_name,
90    };
91
92    (ty_name, lt)
93}
94
95fn param_span() -> Span2 {
96    Span2::call_site()
97}