Skip to content

Commit 9fd60c8

Browse files
authored
Merge pull request #27 from LukasKalbertodt/keep-default-for
Add `auto_impl(keep_default_for(...))` attribute
2 parents 5a30b24 + ccfedfc commit 9fd60c8

10 files changed

+319
-36
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ nightly = ["proc-macro2/nightly"]
3030
[dependencies]
3131
proc-macro2 = { version = "0.4.6" }
3232
quote = "0.6.3"
33-
syn = { version = "0.14.4", features = ["full"] }
33+
syn = { version = "0.14.4", features = ["full", "visit-mut"] }
3434

3535
[dev-dependencies]
3636
build-plan = "0.1.1"

examples/keep_default_for.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! Example to demonstrate how to use the `keep_default_for` attribute.
2+
//!
3+
//! The generated `impl` blocks generate an item for each trait item by
4+
//! default. This means that default methods in traits are also implemented via
5+
//! the proxy type. Sometimes, this is not what you want. One special case is
6+
//! when the default method has where bounds that don't apply to the proxy
7+
//! type.
8+
use auto_impl::auto_impl;
9+
10+
11+
#[auto_impl(&, Box)]
12+
trait Foo {
13+
fn required(&self) -> String;
14+
15+
// The generated impl for `&T` will not override this method.
16+
#[auto_impl(keep_default_for(&))]
17+
fn provided(&self) {
18+
println!("Hello {}", self.required());
19+
}
20+
}
21+
22+
impl Foo for String {
23+
fn required(&self) -> String {
24+
self.clone()
25+
}
26+
27+
fn provided(&self) {
28+
println!("привет {}", self);
29+
}
30+
}
31+
32+
fn test_foo(x: impl Foo) {
33+
x.provided();
34+
}
35+
36+
fn main() {
37+
let s = String::from("Peter");
38+
39+
// Output: "привет Peter", because `String` has overwritten the default
40+
// method.
41+
test_foo(s.clone());
42+
43+
// Output: "Hello Peter", because the method is not overwritten for the
44+
// `&T` impl block.
45+
test_foo(&s);
46+
47+
// Output: "привет Peter", because the `Box<T>` impl overwrites the method
48+
// by default, if you don't specify `keep_default_for`.
49+
test_foo(Box::new(s));
50+
}

src/attr.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//! Internal attributes of the form `#[auto_impl(name(...))]` that can be
2+
//! attached to trait items.
3+
4+
use proc_macro2::{Delimiter, TokenTree};
5+
use syn::{
6+
Attribute, TraitItemMethod,
7+
visit_mut::{VisitMut, visit_item_trait_mut},
8+
};
9+
10+
use crate::{
11+
diag::{DiagnosticExt, SpanExt},
12+
proxy::{parse_types, ProxyType},
13+
spanned::Spanned
14+
};
15+
16+
17+
/// Removes all `#[auto_impl]` attributes that are attached to methods of the
18+
/// given trait.
19+
crate fn remove_our_attrs(trait_def: &mut syn::ItemTrait) {
20+
struct AttrRemover;
21+
impl VisitMut for AttrRemover {
22+
fn visit_trait_item_method_mut(&mut self, m: &mut TraitItemMethod) {
23+
m.attrs.retain(|a| !is_our_attr(a));
24+
}
25+
}
26+
27+
visit_item_trait_mut(&mut AttrRemover, trait_def);
28+
}
29+
30+
/// Checks if the given attribute is "our" attribute. That means that it's path
31+
/// is `auto_impl`.
32+
crate fn is_our_attr(attr: &Attribute) -> bool {
33+
attr.path.segments.len() == 1
34+
&& attr.path.segments.iter().next().map(|seg| {
35+
seg.ident == "auto_impl" && seg.arguments.is_empty()
36+
}).unwrap()
37+
}
38+
39+
/// Tries to parse the given attribute as one of our own `auto_impl`
40+
/// attributes. If it's invalid, an error is emitted and `Err(())` is returned.
41+
/// You have to make sure that `attr` is one of our attrs with `is_our_attr`
42+
/// before calling this function!
43+
crate fn parse_our_attr(attr: &Attribute) -> Result<OurAttr, ()> {
44+
assert!(is_our_attr(attr));
45+
46+
// Get the body of the attribute (which has to be a ground, because we
47+
// required the syntax `auto_impl(...)` and forbid stuff like
48+
// `auto_impl = ...`).
49+
let tokens = attr.tts.clone().into_iter().collect::<Vec<_>>();
50+
let body = match &*tokens {
51+
[TokenTree::Group(g)] => g.stream(),
52+
_ => {
53+
return attr.tts.span()
54+
.err(format!("expected single group delimitted by`()`, found '{:?}'", tokens))
55+
.emit_with_attr_note();
56+
}
57+
};
58+
59+
let mut it = body.clone().into_iter();
60+
61+
// Try to extract the name (we require the body to be `name(...)`).
62+
let name = match it.next() {
63+
Some(TokenTree::Ident(x)) => x,
64+
Some(other) => {
65+
return Spanned::span(&other)
66+
.err(format!("expected ident, found '{}'", other))
67+
.emit_with_attr_note();
68+
}
69+
None => {
70+
return attr.tts.span()
71+
.err("expected ident, found nothing")
72+
.emit_with_attr_note();
73+
}
74+
};
75+
76+
// Extract the parameters (which again, have to be a group delimitted by
77+
// `()`)
78+
let params = match it.next() {
79+
Some(TokenTree::Group(ref g)) if g.delimiter() == Delimiter::Parenthesis => {
80+
g.stream()
81+
}
82+
Some(other) => {
83+
let msg = format!(
84+
"expected arguments for '{}' in parenthesis `()`, found `{}`",
85+
name,
86+
other,
87+
);
88+
return Spanned::span(&other)
89+
.err(msg)
90+
.emit_with_attr_note();
91+
}
92+
None => {
93+
let msg = format!(
94+
"expected arguments for '{}' in parenthesis `()`, found nothing",
95+
name,
96+
);
97+
return body.span()
98+
.err(msg)
99+
.emit_with_attr_note();
100+
}
101+
};
102+
103+
// Finally match over the name of the attribute.
104+
let out = match () {
105+
() if name == "keep_default_for" => {
106+
let proxy_types = parse_types(params.into())?;
107+
OurAttr::KeepDefaultFor(proxy_types)
108+
}
109+
_ => {
110+
return Spanned::span(&name)
111+
.err(format!("invalid attribute '{}'", name))
112+
.emit_with_attr_note();
113+
}
114+
};
115+
116+
Ok(out)
117+
}
118+
119+
/// Attributes of the form `#[auto_impl(...)]` that can be attached to items of
120+
/// the trait.
121+
#[derive(Clone, PartialEq, Debug)]
122+
crate enum OurAttr {
123+
KeepDefaultFor(Vec<ProxyType>),
124+
}

src/gen.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use syn::{
88

99
use crate::{
1010
analyze::find_suitable_param_names,
11+
attr::{OurAttr, is_our_attr, parse_our_attr},
1112
diag::{DiagnosticExt, SpanExt},
1213
proxy::ProxyType,
1314
spanned::Spanned
@@ -20,7 +21,7 @@ use crate::{
2021
crate fn gen_impls(
2122
proxy_types: &[ProxyType],
2223
trait_def: &syn::ItemTrait,
23-
) -> Result<::proc_macro::TokenStream, ()> {
24+
) -> Result<TokenStream2, ()> {
2425
let mut tokens = TokenStream2::new();
2526

2627
let (proxy_ty_param, proxy_lt_param) = find_suitable_param_names(trait_def);
@@ -35,7 +36,7 @@ crate fn gen_impls(
3536
});
3637
}
3738

38-
Ok(tokens.into())
39+
Ok(tokens)
3940
}
4041

4142
/// Generates the header of the impl of the given trait for the given proxy
@@ -403,6 +404,22 @@ fn gen_method_item(
403404
trait_def: &ItemTrait,
404405
proxy_ty_param: &Ident,
405406
) -> Result<TokenStream2, ()> {
407+
// If this method has a `#[auto_impl(keep_default_for(...))]` attribute for
408+
// the given proxy type, we don't generate anything for this impl block.
409+
if should_keep_default_for(item, proxy_type)? {
410+
if item.default.is_some() {
411+
return Ok(TokenStream2::new());
412+
} else {
413+
return item.sig.span()
414+
.err(format!(
415+
"the method `{}` has the attribute `keep_default_for` but is not a default \
416+
method (no body is provided)",
417+
item.sig.ident,
418+
))
419+
.emit_with_attr_note();
420+
}
421+
}
422+
406423
// Determine the kind of the method, determined by the self type.
407424
let sig = &item.sig;
408425
let self_arg = SelfType::from_sig(sig);
@@ -591,3 +608,33 @@ fn get_arg_list<'a>(inputs: impl Iterator<Item = &'a FnArg>) -> Result<TokenStre
591608

592609
Ok(args)
593610
}
611+
612+
/// Checks if the given method has the attribute `#[auto_impl(keep_default_for(...))]`
613+
/// and if it contains the given proxy type.
614+
fn should_keep_default_for(m: &TraitItemMethod, proxy_type: &ProxyType) -> Result<bool, ()> {
615+
// Get an iterator of just the attribute we are interested in.
616+
let mut it = m.attrs.iter()
617+
.filter(|attr| is_our_attr(attr))
618+
.map(|attr| parse_our_attr(&attr));
619+
620+
// Check the first (and hopefully only) `keep_default_for` attribute.
621+
let out = match it.next() {
622+
Some(attr) => {
623+
// Check if the attribute lists the given proxy type.
624+
let OurAttr::KeepDefaultFor(proxy_types) = attr?;
625+
proxy_types.contains(proxy_type)
626+
}
627+
628+
// If there is no such attribute, we return `false`
629+
None => false,
630+
};
631+
632+
// Check if there is another such attribute (which we disallow)
633+
if it.next().is_some() {
634+
return m.sig.span()
635+
.err("found two `keep_default_for` attributes on one method")
636+
.emit_with_attr_note();
637+
}
638+
639+
Ok(out)
640+
}

src/lib.rs

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ extern crate quote;
99

1010
use proc_macro::TokenStream;
1111
use proc_macro2::TokenStream as TokenStream2;
12+
use quote::ToTokens;
1213

1314
mod analyze;
15+
mod attr;
1416
mod diag;
1517
mod gen;
1618
mod proxy;
@@ -25,42 +27,58 @@ use crate::{
2527
/// See crate documentation for more information.
2628
#[proc_macro_attribute]
2729
pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream {
28-
// We use the closure trick to catch errors until the `catch` syntax is
29-
// available. If an error occurs, we won't modify or add any tokens.
30-
let impls = || -> Result<TokenStream, ()> {
31-
// Try to parse the token stream from the attribute to get a list of
32-
// proxy types.
33-
let proxy_types = proxy::parse_types(args)?;
30+
// Try to parse the token stream from the attribute to get a list of proxy
31+
// types.
32+
let proxy_types = proxy::parse_types(args);
3433

35-
// Try to parse the item the `#[auto_impl]` attribute was applied to as
36-
// trait. Unfortunately, `parse()` consume the token stream, so we have
37-
// to clone it.
38-
match syn::parse::<syn::ItemTrait>(input.clone()) {
39-
// The attribute was applied to a valid trait. Now it's time to
40-
// execute the main step: generate a token stream which contains an
41-
// impl of the trait for each proxy type.
42-
Ok(trait_def) => Ok(gen::gen_impls(&proxy_types, &trait_def)?),
34+
// Try to parse the item the `#[auto_impl]` attribute was applied to as
35+
// trait. Unfortunately, `parse()` consume the token stream, so we have to
36+
// clone it.
37+
match syn::parse::<syn::ItemTrait>(input.clone()) {
38+
// The attribute was applied to a valid trait. Now it's time to execute
39+
// the main step: generate a token stream which contains an impl of the
40+
// trait for each proxy type.
41+
Ok(mut trait_def) => {
42+
let generated = proxy_types.and_then(|proxy_types| {
43+
gen::gen_impls(&proxy_types, &trait_def)
44+
});
4345

44-
// If the token stream could not be parsed as trait, this most
45-
// likely means that the attribute was applied to a non-trait item.
46-
// Even if the trait definition was syntactically incorrect, the
47-
// compiler usually does some kind of error recovery to proceed. We
48-
// get the recovered tokens.
49-
Err(e) => {
50-
// We have to take the detour through TokenStream2 to get a
51-
// good span for the error.
52-
TokenStream2::from(input.clone()).span()
53-
.err("couldn't parse trait item")
54-
.note(e.to_string())
55-
.note("the #[auto_impl] attribute can only be applied to traits!")
56-
.emit();
46+
// Before returning the trait definition, we have to remove all
47+
// `#[auto_impl(...)]` attributes on all methods.
48+
attr::remove_our_attrs(&mut trait_def);
5749

58-
Err(())
50+
match generated {
51+
// No errors at all => output the trait and our generated impls
52+
Ok(generated) => quote! { #trait_def #generated }.into(),
53+
Err(_) => {
54+
// We combine the token stream of the modified trait
55+
// definition with the generated errors (which are tokens
56+
// on stable due to the `compile_error!` hack).
57+
vec![
58+
TokenStream::from(trait_def.into_token_stream()),
59+
diag::error_tokens()
60+
].into_iter().collect()
61+
}
5962
}
60-
}
61-
}().unwrap_or_else(|_| diag::error_tokens());
63+
},
64+
65+
// If the token stream could not be parsed as trait, this most
66+
// likely means that the attribute was applied to a non-trait item.
67+
// Even if the trait definition was syntactically incorrect, the
68+
// compiler usually does some kind of error recovery to proceed. We
69+
// get the recovered tokens.
70+
Err(e) => {
71+
// We have to take the detour through TokenStream2 to get a
72+
// good span for the error.
73+
TokenStream2::from(input.clone()).span()
74+
.err("couldn't parse trait item")
75+
.note(e.to_string())
76+
.note("the #[auto_impl] attribute can only be applied to traits!")
77+
.emit();
6278

63-
// Combine the original token stream with the additional one containing the
64-
// generated impls (or nothing if an error occured).
65-
vec![input, impls].into_iter().collect()
79+
// We combine the original token stream with the generated errors
80+
// (which are tokens on stable due to the `compile_error!` hack).
81+
vec![input, diag::error_tokens()].into_iter().collect()
82+
}
83+
}
6684
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use auto_impl::auto_impl;
2+
3+
4+
#[auto_impl(&)]
5+
trait Foo {
6+
#[auto_impl(keep_default_for(&))]
7+
type Foo;
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use auto_impl::auto_impl;
2+
3+
4+
#[auto_impl(&)]
5+
trait Foo {
6+
#[auto_impl(keep_default_for(&))]
7+
fn required(&self);
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use auto_impl::auto_impl;
2+
3+
4+
#[auto_impl(&)]
5+
trait Foo {
6+
#[auto_impl(ferris_for_life)]
7+
fn a(&self);
8+
}

0 commit comments

Comments
 (0)