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(<.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}