From cceb5cfdadba9d869936dfd77923ed9473f42fbd Mon Sep 17 00:00:00 2001 From: Xiaoyi Shi Date: Mon, 8 Jun 2026 13:21:30 +0800 Subject: [PATCH] Tie boxed future to the receiver lifetime for &self-only methods When the receiver is the only borrowed input and the method has no generic parameters, emit fn m<'life0>(&'life0 self) -> Pin> instead of introducing 'async_trait with 'life0: 'async_trait and Self: 'async_trait outlives bounds. Those bounds defeat the trait solver's global Send/Sync cache, so the captured state is re-proven once per impl (rust-lang/rust#157595). Eligibility is decided from the signature alone because the trait and its impls expand in separate macro invocations and must agree on whether 'life0 is late-bound (E0195). --- src/expand.rs | 84 ++++++++++++++++++++++++++++------- src/lifetime.rs | 13 ++++++ tests/test.rs | 47 ++++++++++++++++++++ tests/ui/lifetime-span.stderr | 38 ++++++++++++++++ 4 files changed, 165 insertions(+), 17 deletions(-) diff --git a/src/expand.rs b/src/expand.rs index 939708a..7c61dfa 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -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; @@ -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 + Send + 'life0>>; fn transform_sig( context: Context, sig: &mut Signature, @@ -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) => { @@ -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 { @@ -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() { @@ -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 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); + } +} diff --git a/tests/ui/lifetime-span.stderr b/tests/ui/lifetime-span.stderr index a3c4e7b..440e738 100644 --- a/tests/ui/lifetime-span.stderr +++ b/tests/ui/lifetime-span.stderr @@ -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 |