Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 67 additions & 17 deletions src/expand.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::bound::{has_bound, InferredBound, Supertraits};
use crate::lifetime::{AddLifetimeToImplTrait, CollectLifetimes};
use crate::lifetime::{AddLifetimeToImplTrait, CollectLifetimes, NeedsAsyncTrait};
use crate::parse::Item;
use crate::receiver::{has_self_in_block, has_self_in_sig, mut_pat, ReplaceSelf};
use crate::verbatim::VerbatimFn;
Expand Down Expand Up @@ -162,6 +162,20 @@ fn lint_suppress_without_body() -> Attribute {
// 'life1: 'async_trait,
// T: 'async_trait,
// Self: Sync + 'async_trait;
//
// If the receiver is the only borrowed input and there are no generic
// parameters, the future is tied to the receiver's lifetime instead and no
// `'async_trait` is emitted. The `Self: 'async_trait` and `'lifeN:
// 'async_trait` outlives bounds this avoids defeat the trait solver's global
// `Send`/`Sync` cache (rust-lang/rust#157595).
//
// Input:
// async fn f(&self) -> Ret;
//
// Output:
// fn f<'life0>(
// &'life0 self,
// ) -> Pin<Box<dyn Future<Output = Ret> + Send + 'life0>>;
fn transform_sig(
context: Context,
sig: &mut Signature,
Expand All @@ -184,6 +198,25 @@ fn transform_sig(
}
}

// Decided from the signature alone: the trait and its impls expand in
// separate macro invocations and must agree on whether `'life0` is
// late-bound (E0195).
let receiver_is_reference = sig.receiver().is_some_and(|receiver| {
receiver.reference.is_some() || matches!(*receiver.ty, Type::Reference(_))
});
let mut needs_async_trait = NeedsAsyncTrait(false);
needs_async_trait.visit_signature_mut(sig);
let receiver_lifetime = if receiver_is_reference
&& sig.generics.params.is_empty()
&& lifetimes.explicit.is_empty()
&& lifetimes.elided.len() == 1
&& !needs_async_trait.0
{
Some(lifetimes.elided[0].clone())
} else {
None
};

for param in &mut sig.generics.params {
match param {
GenericParam::Type(param) => {
Expand Down Expand Up @@ -237,12 +270,16 @@ fn transform_sig(

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

sig.generics.params.push(parse_quote!('async_trait));
if receiver_lifetime.is_none() {
sig.generics.params.push(parse_quote!('async_trait));
}

if has_self {
let bounds: &[InferredBound] = if is_local {
Expand Down Expand Up @@ -278,16 +315,28 @@ fn transform_sig(
&[InferredBound::Send]
};

let bounds = bounds.iter().filter(|bound| match context {
Context::Trait { supertraits, .. } => has_default && !has_bound(supertraits, bound),
Context::Impl { .. } => false,
});

where_clause_or_default(&mut sig.generics.where_clause)
.predicates
.push(parse_quote! {
Self: #(#bounds +)* 'async_trait
});
let bounds: Vec<&InferredBound> = bounds
.iter()
.filter(|bound| match context {
Context::Trait { supertraits, .. } => has_default && !has_bound(supertraits, bound),
Context::Impl { .. } => false,
})
.collect();

if receiver_lifetime.is_none() {
where_clause_or_default(&mut sig.generics.where_clause)
.predicates
.push(parse_quote! {
Self: #(#bounds +)* 'async_trait
});
} else if !bounds.is_empty() {
// `Self: 'life0` is already implied by `&'life0 self`
where_clause_or_default(&mut sig.generics.where_clause)
.predicates
.push(parse_quote! {
Self: #(#bounds)+*
});
}
}

for (i, arg) in sig.inputs.iter_mut().enumerate() {
Expand Down Expand Up @@ -316,10 +365,11 @@ fn transform_sig(
}
}

let lifetime = receiver_lifetime.unwrap_or_else(|| parse_quote!('async_trait));
let bounds = if is_local {
quote!('async_trait)
quote!(#lifetime)
} else {
quote!(::core::marker::Send + 'async_trait)
quote!(::core::marker::Send + #lifetime)
};
sig.output = parse_quote! {
#ret_arrow ::core::pin::Pin<Box<
Expand Down
13 changes: 13 additions & 0 deletions src/lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ impl VisitMut for CollectLifetimes {
}
}

pub struct NeedsAsyncTrait(pub bool);

impl VisitMut for NeedsAsyncTrait {
fn visit_type_impl_trait_mut(&mut self, _ty: &mut TypeImplTrait) {
// the general lowering bounds impl Trait arguments by `+ 'async_trait`
self.0 = true;
}

fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
self.0 |= lifetime.ident == "async_trait";
}
}

pub struct AddLifetimeToImplTrait;

impl VisitMut for AddLifetimeToImplTrait {
Expand Down
47 changes: 47 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1727,3 +1727,50 @@ pub mod issue288 {
}
}
}

// rust-lang/rust#157595: methods whose only borrowed input is the receiver
// lower to a future tied to the receiver lifetime, with no `'async_trait` and
// no outlives bounds. Pins the eligible forms.
pub mod region_free_receiver_lifetime {
use crate::executor;
use async_trait::async_trait;

#[async_trait]
pub trait Svc {
async fn ref_self(&self) -> u64;
async fn mut_self(&mut self) -> u64;
async fn ret_borrow(&self) -> &u64;
async fn owned_arg(&self, n: u64) -> u64;
async fn defaulted(&self) -> u64 {
1
}
}

pub struct S(u64);

#[async_trait]
impl Svc for S {
async fn ref_self(&self) -> u64 {
self.0
}
async fn mut_self(&mut self) -> u64 {
self.0
}
async fn ret_borrow(&self) -> &u64 {
&self.0
}
async fn owned_arg(&self, n: u64) -> u64 {
self.0 + n
}
}

#[test]
fn test() {
let mut s = S(40);
assert_eq!(executor::block_on_simple(s.ref_self()), 40);
assert_eq!(executor::block_on_simple(s.mut_self()), 40);
assert_eq!(*executor::block_on_simple(s.ret_borrow()), 40);
assert_eq!(executor::block_on_simple(s.owned_arg(2)), 42);
assert_eq!(executor::block_on_simple(s.defaulted()), 1);
}
}
38 changes: 38 additions & 0 deletions tests/ui/lifetime-span.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,44 @@ note: trait defined here, with 0 lifetime parameters
22 | pub trait Trait2 {
| ^^^^^^

error[E0195]: lifetime parameters do not match the trait definition
--> tests/ui/lifetime-span.rs:28:21
|
28 | async fn method(&self) {}
| ^
|
= note: lifetime parameters differ in whether they are early- or late-bound
note: `'life0` differs between the trait and impl
--> tests/ui/lifetime-span.rs:23:21
|
22 | pub trait Trait2 {
| ---------------- in this trait...
23 | async fn method<'r>(&'r self);
| ^^
| |
| `'r` is early-bound
| this lifetime bound makes `'r` early-bound
...
27 | impl Trait2 for A {
| ----------------- in this impl...
28 | async fn method(&self) {}
| ^ `'life0` is late-bound
note: `'life0` differs between the trait and impl
--> tests/ui/lifetime-span.rs:21:1
|
21 | #[async_trait]
| ^^^^^^^^^^^^^^ `'async_trait` is early-bound
22 | pub trait Trait2 {
| ---------------- in this trait...
23 | async fn method<'r>(&'r self);
| -- this lifetime bound makes `'async_trait` early-bound
...
27 | impl Trait2 for A {
| ----------------- in this impl...
28 | async fn method(&self) {}
| ^ `'life0` is late-bound
= note: this error originates in the attribute macro `async_trait` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration
--> tests/ui/lifetime-span.rs:33:14
|
Expand Down