Skip to content

Commit 924e82b

Browse files
authored
Merge pull request #21 from LukasKalbertodt/better-generic-names
Better lifetime/type parameter names
2 parents 031b408 + c6230d1 commit 924e82b

File tree

5 files changed

+218
-47
lines changed

5 files changed

+218
-47
lines changed

examples/names.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//! Example to demonstrate how `auto_impl` chooses a name for the type and
2+
//! lifetime parameter.
3+
//!
4+
//! For documentation and compiler errors it would be nice to have very simple
5+
//! names for type and lifetime parameters:
6+
//!
7+
//! ```rust
8+
//! // not nice
9+
//! impl<'auto_impl_lifetime, AutoImplT> Foo for &'auto_impl_lifetime AutoImplT { ...}
10+
//!
11+
//! // better
12+
//! impl<'a, T> Foo for &'a T { ... }
13+
//! ```
14+
//!
15+
//! `auto_impl` tries the full alphabet, picking a name that is not yet taken.
16+
//! "Not taken" means that the name is not used anywhere in the `impl` block.
17+
//! Right now, we are a bit careful and mark all names as "taken" that are used
18+
//! in the trait def -- apart from names only appearing in the body of provided
19+
//! methods.
20+
//!
21+
//! In the trait below, a bunch of names are already "taken":
22+
//! - type names: T--Z and A--G (H is not taken, because it only appear in the
23+
//! default method body)
24+
//! - lifetime names: 'a--'c
25+
//!
26+
//! Thus, the names `H` and `'d` are used. Run `cargo expand --example names`
27+
//! to see the output.
28+
29+
30+
// This code is really ugly on purpose...
31+
#![allow(non_snake_case, dead_code, unused_variables)]
32+
#![feature(use_extern_macros)]
33+
34+
extern crate auto_impl;
35+
use auto_impl::auto_impl;
36+
37+
38+
39+
struct X {}
40+
trait Z {}
41+
42+
struct C {}
43+
struct E<T>(Vec<T>);
44+
struct F {}
45+
46+
struct G<T>(Vec<T>);
47+
struct H {}
48+
49+
#[auto_impl(&)]
50+
trait U<'a, V> {
51+
const W: Option<Box<&'static X>>;
52+
53+
type Y: Z;
54+
55+
fn A<'b, 'c>(&self, B: C, D: E<&[F; 1]>) -> G<fn((H,))> {
56+
let H = ();
57+
unimplemented!()
58+
}
59+
}
60+
61+
fn main() {}

src/analyze.rs

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

src/diag.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use proc_macro::{Diagnostic, Span};
2+
3+
4+
pub trait DiagnosticExt {
5+
/// Helper function to add a note to the diagnostic (with a span pointing
6+
/// to the `auto_impl` attribute) and emit the error. Additionally,
7+
/// `Err(())` is always returned.
8+
fn emit_with_attr_note<T>(self) -> Result<T, ()>;
9+
}
10+
11+
impl DiagnosticExt for Diagnostic {
12+
fn emit_with_attr_note<T>(self) -> Result<T, ()> {
13+
self.span_note(Span::call_site(), "auto-impl requested here")
14+
.emit();
15+
16+
Err(())
17+
}
18+
}

src/gen.rs

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
1-
use proc_macro::{Diagnostic, Span};
2-
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
1+
use proc_macro::Span;
2+
use proc_macro2::TokenStream as TokenStream2;
33
use quote::{ToTokens, TokenStreamExt};
44
use syn::{
55
FnArg, Ident, ItemTrait, Lifetime, MethodSig, Pat, PatIdent, TraitItem, TraitItemMethod,
66
TraitItemType, TraitItemConst,
77
};
88

9-
use crate::{proxy::ProxyType, spanned::Spanned};
10-
11-
/// The type parameter used in the proxy type. Usually, one would just use `T`,
12-
/// but this could conflict with type parameters on the trait.
13-
///
14-
/// Why do we have to care about this? Why about hygiene? In the first version
15-
/// of stable proc_macros, only call site spans are included. That means that
16-
/// we cannot generate spans that do not conflict with any other ident the user
17-
/// wrote. Once proper hygiene is available to proc_macros, this should be
18-
/// changed.
19-
const PROXY_TY_PARAM_NAME: &str = "__AutoImplProxyT";
9+
use crate::{
10+
analyze::{find_suitable_param_names},
11+
diag::DiagnosticExt,
12+
proxy::ProxyType,
13+
spanned::Spanned
14+
};
2015

21-
/// The lifetime parameter used in the proxy type if the proxy type is `&` or
22-
/// `&mut`. For more information see `PROXY_TY_PARAM_NAME`.
23-
const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime";
2416

2517

2618
/// Generates one complete impl of the given trait for each of the given proxy
@@ -31,10 +23,12 @@ pub(crate) fn gen_impls(
3123
) -> Result<::proc_macro::TokenStream, ()> {
3224
let mut tokens = TokenStream2::new();
3325

26+
let (proxy_ty_param, proxy_lt_param) = find_suitable_param_names(trait_def);
27+
3428
// One impl for each proxy type
3529
for proxy_type in proxy_types {
36-
let header = header(proxy_type, trait_def)?;
37-
let items = gen_items(proxy_type, trait_def)?;
30+
let header = header(proxy_type, trait_def, &proxy_ty_param, &proxy_lt_param)?;
31+
let items = gen_items(proxy_type, trait_def, &proxy_ty_param)?;
3832

3933
tokens.append_all(quote! {
4034
#header { #( #items )* }
@@ -46,10 +40,12 @@ pub(crate) fn gen_impls(
4640

4741
/// Generates the header of the impl of the given trait for the given proxy
4842
/// type.
49-
fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result<TokenStream2, ()> {
50-
let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site());
51-
let proxy_lt_param = &Lifetime::new(PROXY_LT_PARAM_NAME, Span2::call_site());
52-
43+
fn header(
44+
proxy_type: &ProxyType,
45+
trait_def: &ItemTrait,
46+
proxy_ty_param: &Ident,
47+
proxy_lt_param: &Lifetime,
48+
) -> Result<TokenStream2, ()> {
5349
// Generate generics for impl positions from trait generics.
5450
let (impl_generics, trait_generics, where_clause) = trait_def.generics.split_for_impl();
5551

@@ -63,10 +59,10 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result<TokenStream2,
6359
// one or two parameters added. For a trait `trait Foo<'x, 'y, A, B>`,
6460
// it will look like this:
6561
//
66-
// '__auto_impl_proxy_lifetime, 'x, 'y, A, B, __AutoImplProxyT
62+
// '{proxy_lt_param}, 'x, 'y, A, B, {proxy_ty_param}
6763
//
68-
// The `'__auto_impl_proxy_lifetime` in the beginning is only added when
69-
// the proxy type is `&` or `&mut`.
64+
// The `'{proxy_lt_param}` in the beginning is only added when the proxy
65+
// type is `&` or `&mut`.
7066
let impl_generics = {
7167
// Determine if our proxy type needs a lifetime parameter
7268
let (mut params, ty_bounds) = match proxy_type {
@@ -297,12 +293,19 @@ fn gen_fn_type_for_trait(
297293
fn gen_items(
298294
proxy_type: &ProxyType,
299295
trait_def: &ItemTrait,
296+
proxy_ty_param: &Ident,
300297
) -> Result<Vec<TokenStream2>, ()> {
301298
trait_def.items.iter().map(|item| {
302299
match item {
303-
TraitItem::Const(c) => gen_const_item(proxy_type, c, trait_def),
304-
TraitItem::Method(method) => gen_method_item(proxy_type, method, trait_def),
305-
TraitItem::Type(ty) => gen_type_item(proxy_type, ty, trait_def),
300+
TraitItem::Const(c) => {
301+
gen_const_item(proxy_type, c, trait_def, proxy_ty_param)
302+
}
303+
TraitItem::Method(method) => {
304+
gen_method_item(proxy_type, method, trait_def, proxy_ty_param)
305+
}
306+
TraitItem::Type(ty) => {
307+
gen_type_item(proxy_type, ty, trait_def, proxy_ty_param)
308+
}
306309
TraitItem::Macro(mac) => {
307310
// We cannot resolve the macro invocation and thus cannot know
308311
// if it adds additional items to the trait. Thus, we have to
@@ -334,6 +337,7 @@ fn gen_const_item(
334337
proxy_type: &ProxyType,
335338
item: &TraitItemConst,
336339
trait_def: &ItemTrait,
340+
proxy_ty_param: &Ident,
337341
) -> Result<TokenStream2, ()> {
338342
// A trait with associated consts cannot be implemented for Fn* types.
339343
if proxy_type.is_fn() {
@@ -350,7 +354,6 @@ fn gen_const_item(
350354
// We simply use the associated const from our type parameter.
351355
let const_name = &item.ident;
352356
let const_ty = &item.ty;
353-
let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site());
354357

355358
Ok(quote ! {
356359
const #const_name: #const_ty = #proxy_ty_param::#const_name;
@@ -366,6 +369,7 @@ fn gen_type_item(
366369
proxy_type: &ProxyType,
367370
item: &TraitItemType,
368371
trait_def: &ItemTrait,
372+
proxy_ty_param: &Ident,
369373
) -> Result<TokenStream2, ()> {
370374
// A trait with associated types cannot be implemented for Fn* types.
371375
if proxy_type.is_fn() {
@@ -381,7 +385,6 @@ fn gen_type_item(
381385

382386
// We simply use the associated type from our type parameter.
383387
let assoc_name = &item.ident;
384-
let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site());
385388

386389
Ok(quote ! {
387390
type #assoc_name = #proxy_ty_param::#assoc_name;
@@ -398,6 +401,7 @@ fn gen_method_item(
398401
proxy_type: &ProxyType,
399402
item: &TraitItemMethod,
400403
trait_def: &ItemTrait,
404+
proxy_ty_param: &Ident,
401405
) -> Result<TokenStream2, ()> {
402406
// Determine the kind of the method, determined by the self type.
403407
let sig = &item.sig;
@@ -421,7 +425,6 @@ fn gen_method_item(
421425
// No receiver
422426
SelfType::None => {
423427
// The proxy type is a reference, smartpointer or Box.
424-
let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site());
425428
quote! { #proxy_ty_param::#name(#args) }
426429
}
427430

@@ -588,19 +591,3 @@ fn get_arg_list(inputs: impl Iterator<Item = &'a FnArg>) -> Result<TokenStream2,
588591

589592
Ok(args)
590593
}
591-
592-
trait DiagnosticExt {
593-
/// Helper function to add a note to the diagnostic (with a span pointing
594-
/// to the `auto_impl` attribute) and emit the error. Additionally,
595-
/// `Err(())` is always returned.
596-
fn emit_with_attr_note<T>(self) -> Result<T, ()>;
597-
}
598-
599-
impl DiagnosticExt for Diagnostic {
600-
fn emit_with_attr_note<T>(self) -> Result<T, ()> {
601-
self.span_note(Span::call_site(), "auto-impl requested here")
602-
.emit();
603-
604-
Err(())
605-
}
606-
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ extern crate syn;
1616

1717
use proc_macro::{Diagnostic, Level, Span, TokenStream};
1818

19+
mod analyze;
20+
mod diag;
1921
mod gen;
2022
mod proxy;
2123
mod spanned;

0 commit comments

Comments
 (0)