Skip to content

Commit

Permalink
Properly set spans on introduced lifetimes.
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioBenitez committed Feb 13, 2021
1 parent c509624 commit 7b437f6
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 40 deletions.
77 changes: 55 additions & 22 deletions src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ use syn::{
WhereClause,
};

macro_rules! parse_quote_spanned {
($span:expr => $($t:tt)*) => (
syn::parse2(quote_spanned!($span => $($t)*)).unwrap()
)
}

impl ToTokens for Item {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Expand All @@ -21,7 +27,7 @@ impl ToTokens for Item {
}
}

#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
enum Context<'a> {
Trait {
generics: &'a Generics,
Expand All @@ -46,6 +52,13 @@ impl Context<'_> {
}
})
}

fn generics_span(&self) -> Span {
match self {
Context::Trait { generics, .. } => generics.span(),
Context::Impl { impl_generics } => impl_generics.span(),
}
}
}

type Supertraits = Punctuated<TypeParamBound, Token![+]>;
Expand Down Expand Up @@ -78,7 +91,7 @@ pub fn expand(input: &mut Item, is_local: bool) {
}
}
Item::Impl(input) => {
let mut lifetimes = CollectLifetimes::new("'impl");
let mut lifetimes = CollectLifetimes::new("'impl", input.generics.span());
lifetimes.visit_type_mut(&mut *input.self_ty);
lifetimes.visit_path_mut(&mut input.trait_.as_mut().unwrap().1);
let params = &input.generics.params;
Expand Down Expand Up @@ -133,21 +146,18 @@ fn transform_sig(
ReturnType::Type(_, ret) => quote!(#ret),
};

let mut lifetimes = CollectLifetimes::new("'life");
let default_span = sig.ident.span()
.join(sig.paren_token.span)
.unwrap_or_else(|| sig.ident.span());

let mut lifetimes = CollectLifetimes::new("'life", default_span);
for arg in sig.inputs.iter_mut() {
match arg {
FnArg::Receiver(arg) => lifetimes.visit_receiver_mut(arg),
FnArg::Typed(arg) => lifetimes.visit_type_mut(&mut arg.ty),
}
}

let where_clause = sig
.generics
.where_clause
.get_or_insert_with(|| WhereClause {
where_token: Default::default(),
predicates: Punctuated::new(),
});
for param in sig
.generics
.params
Expand All @@ -157,26 +167,31 @@ fn transform_sig(
match param {
GenericParam::Type(param) => {
let param = &param.ident;
where_clause
let span = param.span();
where_clause_or_default(&mut sig.generics.where_clause)
.predicates
.push(parse_quote!(#param: 'async_trait));
.push(parse_quote_spanned!(span => #param: 'async_trait));
}
GenericParam::Lifetime(param) => {
let param = &param.lifetime;
where_clause
let span = param.span();
where_clause_or_default(&mut sig.generics.where_clause)
.predicates
.push(parse_quote!(#param: 'async_trait));
.push(parse_quote_spanned!(span => #param: 'async_trait));
}
GenericParam::Const(_) => {}
}
}

for elided in lifetimes.elided {
sig.generics.params.push(parse_quote!(#elided));
where_clause
push_param(&mut sig.generics, parse_quote!(#elided));
where_clause_or_default(&mut sig.generics.where_clause)
.predicates
.push(parse_quote!(#elided: 'async_trait));
.push(parse_quote_spanned!(elided.span() => #elided: 'async_trait));
}
sig.generics.params.push(parse_quote!('async_trait));

push_param(&mut sig.generics, parse_quote_spanned!(default_span => 'async_trait));

if has_self {
let bound: Ident = match sig.inputs.iter().next() {
Some(FnArg::Receiver(Receiver {
Expand All @@ -200,10 +215,11 @@ fn transform_sig(
Context::Trait { supertraits, .. } => !has_default || has_bound(supertraits, &bound),
Context::Impl { .. } => true,
};
let where_clause = where_clause_or_default(&mut sig.generics.where_clause);
where_clause.predicates.push(if assume_bound || is_local {
parse_quote!(Self: 'async_trait)
parse_quote_spanned!(where_clause.span() => Self: 'async_trait)
} else {
parse_quote!(Self: ::core::marker::#bound + 'async_trait)
parse_quote_spanned!(where_clause.span() => Self: ::core::marker::#bound + 'async_trait)
});
}

Expand All @@ -228,9 +244,9 @@ fn transform_sig(
}

let bounds = if is_local {
quote!('async_trait)
quote_spanned!(context.generics_span() => 'async_trait)
} else {
quote!(::core::marker::Send + 'async_trait)
quote_spanned!(context.generics_span() => ::core::marker::Send + 'async_trait)
};

sig.output = parse_quote! {
Expand Down Expand Up @@ -333,3 +349,20 @@ fn has_bound(supertraits: &Supertraits, marker: &Ident) -> bool {
}
false
}

fn where_clause_or_default(clause: &mut Option<WhereClause>) -> &mut WhereClause {
clause.get_or_insert_with(|| WhereClause {
where_token: Default::default(),
predicates: Punctuated::new(),
})
}

fn push_param(generics: &mut Generics, param: GenericParam) {
let span = param.span();
if generics.params.is_empty() {
generics.lt_token = parse_quote_spanned!(span => <);
generics.gt_token = parse_quote_spanned!(span => >);
}

generics.params.push(param);
}
14 changes: 9 additions & 5 deletions src/lifetime.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::visit_mut::{self, VisitMut};
use syn::{GenericArgument, Lifetime, Receiver, TypeReference};

pub struct CollectLifetimes {
pub elided: Vec<Lifetime>,
pub explicit: Vec<Lifetime>,
pub name: &'static str,
pub default_span: Span,
}

impl CollectLifetimes {
pub fn new(name: &'static str) -> Self {
pub fn new(name: &'static str, default_span: Span) -> Self {
CollectLifetimes {
elided: Vec::new(),
explicit: Vec::new(),
name,
default_span,
}
}

fn visit_opt_lifetime(&mut self, lifetime: &mut Option<Lifetime>) {
match lifetime {
None => *lifetime = Some(self.next_lifetime()),
None => *lifetime = Some(self.next_lifetime(None)),
Some(lifetime) => self.visit_lifetime(lifetime),
}
}

fn visit_lifetime(&mut self, lifetime: &mut Lifetime) {
if lifetime.ident == "_" {
*lifetime = self.next_lifetime();
*lifetime = self.next_lifetime(lifetime.span());
} else {
self.explicit.push(lifetime.clone());
}
}

fn next_lifetime(&mut self) -> Lifetime {
fn next_lifetime<S: Into<Option<Span>>>(&mut self, span: S) -> Lifetime {
let name = format!("{}{}", self.name, self.elided.len());
let life = Lifetime::new(&name, Span::call_site());
let span = span.into().unwrap_or(self.default_span);
let life = Lifetime::new(&name, span);
self.elided.push(life.clone());
life
}
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/lifetime-span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,19 @@ impl<'r> Trait<'r> for B {
async fn method(&self) { }
}

#[async_trait]
pub trait Trait2 {
async fn method<'r>(&'r self);
}

#[async_trait]
impl Trait2 for A {
async fn method(&self) { }
}

#[async_trait]
impl<'r> Trait2<'r> for B {
async fn method(&'r self) { }
}

fn main() {}
45 changes: 32 additions & 13 deletions tests/ui/lifetime-span.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,43 @@ error[E0726]: implicit elided lifetime not allowed here
12 | impl Trait for A {
| ^^^^^- help: indicate the anonymous lifetime: `<'_>`

error[E0107]: this trait takes 0 lifetime arguments but 1 lifetime argument was supplied
--> $DIR/lifetime-span.rs:32:10
|
32 | impl<'r> Trait2<'r> for B {
| ^^^^^^---- help: remove these generics
| |
| expected 0 lifetime arguments
|
note: trait defined here, with 0 lifetime parameters
--> $DIR/lifetime-span.rs:22:11
|
22 | pub trait Trait2 {
| ^^^^^^

error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration
--> $DIR/lifetime-span.rs:11:1
--> $DIR/lifetime-span.rs:13:14
|
6 | #[async_trait]
| -------------- lifetimes in impl do not match this method in trait
8 | async fn method(&'r self);
| ---------------- lifetimes in impl do not match this method in trait
...
11 | #[async_trait]
| ^^^^^^^^^^^^^^ lifetimes do not match method in trait
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
13 | async fn method(&self) { }
| ^^^^^^^^^^^^^ lifetimes do not match method in trait

error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration
--> $DIR/lifetime-span.rs:16:1
--> $DIR/lifetime-span.rs:18:14
|
6 | #[async_trait]
| -------------- lifetimes in impl do not match this method in trait
8 | async fn method(&'r self);
| ---------------- lifetimes in impl do not match this method in trait
...
16 | #[async_trait]
| ^^^^^^^^^^^^^^ lifetimes do not match method in trait
18 | async fn method(&self) { }
| ^^^^^^^^^^^^^ lifetimes do not match method in trait

error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration
--> $DIR/lifetime-span.rs:33:14
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
23 | async fn method<'r>(&'r self);
| ---- lifetimes in impl do not match this method in trait
...
33 | async fn method(&'r self) { }
| ^^^^^^^^^^^^^^^^ lifetimes do not match method in trait

0 comments on commit 7b437f6

Please sign in to comment.