Skip to content
4 changes: 2 additions & 2 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,12 +699,12 @@ impl<'hir> GenericArgs<'hir> {
}

#[inline]
pub fn num_lifetime_params(&self) -> usize {
pub fn num_lifetime_args(&self) -> usize {
self.args.iter().filter(|arg| matches!(arg, GenericArg::Lifetime(_))).count()
}

#[inline]
pub fn has_lifetime_params(&self) -> bool {
pub fn has_lifetime_args(&self) -> bool {
self.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
}

Expand Down
543 changes: 353 additions & 190 deletions compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> {
AngleBrackets::Missing => 0,
// Only lifetime arguments can be implied
AngleBrackets::Implied => self.gen_args.args.len(),
AngleBrackets::Available => self.gen_args.num_lifetime_params(),
AngleBrackets::Available => self.gen_args.num_lifetime_args(),
}
}

Expand Down
12 changes: 6 additions & 6 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ pub(crate) fn check_generic_arg_count(
let named_type_param_count = param_counts.types - has_self as usize - synth_type_param_count;
let named_const_param_count = param_counts.consts;
let infer_lifetimes =
(gen_pos != GenericArgPosition::Type || seg.infer_args) && !gen_args.has_lifetime_params();
(gen_pos != GenericArgPosition::Type || seg.infer_args) && !gen_args.has_lifetime_args();

if gen_pos != GenericArgPosition::Type
&& let Some(c) = gen_args.constraints.first()
Expand Down Expand Up @@ -471,7 +471,7 @@ pub(crate) fn check_generic_arg_count(

let min_expected_lifetime_args = if infer_lifetimes { 0 } else { param_counts.lifetimes };
let max_expected_lifetime_args = param_counts.lifetimes;
let num_provided_lifetime_args = gen_args.num_lifetime_params();
let num_provided_lifetime_args = gen_args.num_lifetime_args();

let lifetimes_correct = check_lifetime_args(
min_expected_lifetime_args,
Expand Down Expand Up @@ -595,7 +595,7 @@ pub(crate) fn check_generic_arg_count(
- default_counts.consts
};
debug!(?expected_min);
debug!(arg_counts.lifetimes=?gen_args.num_lifetime_params());
debug!(arg_counts.lifetimes=?gen_args.num_lifetime_args());

let provided = gen_args.num_generic_params();

Expand All @@ -605,7 +605,7 @@ pub(crate) fn check_generic_arg_count(
named_const_param_count + named_type_param_count + synth_type_param_count,
provided,
param_counts.lifetimes + has_self as usize,
gen_args.num_lifetime_params(),
gen_args.num_lifetime_args(),
)
};

Expand Down Expand Up @@ -639,15 +639,15 @@ pub(crate) fn prohibit_explicit_late_bound_lifetimes(
let param_counts = def.own_counts();

if let Some(span_late) = def.has_late_bound_regions
&& args.has_lifetime_params()
&& args.has_lifetime_args()
{
let msg = "cannot specify lifetime arguments explicitly \
if late bound lifetime parameters are present";
let note = "the late bound lifetime parameter is introduced here";
let span = args.args[0].span();

if position == GenericArgPosition::Value(IsMethodCall::No)
&& args.num_lifetime_params() != param_counts.lifetimes
&& args.num_lifetime_args() != param_counts.lifetimes
{
struct_span_code_err!(cx.dcx(), span, E0794, "{}", msg)
.with_span_note(span_late, note)
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
if let Some(name) = tcx.intrinsic(def_id) {
record!(self.tables.intrinsic[def_id] <- name);
}
if let DefKind::TyParam = def_kind {
if let DefKind::TyParam | DefKind::Trait = def_kind {
let default = self.tcx.object_lifetime_default(def_id);
record!(self.tables.object_lifetime_default[def_id] <- default);
}
Expand Down
25 changes: 11 additions & 14 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,20 +849,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
/// Debugging aid for the `object_lifetime_default` query.
fn check_dump_object_lifetime_defaults(&self, hir_id: HirId) {
let tcx = self.tcx;
if let Some(owner_id) = hir_id.as_owner()
&& let Some(generics) = tcx.hir_get_generics(owner_id.def_id)
{
for p in generics.params {
let hir::GenericParamKind::Type { .. } = p.kind else { continue };
let default = tcx.object_lifetime_default(p.def_id);
let repr = match default {
ObjectLifetimeDefault::Empty => "BaseDefault".to_owned(),
ObjectLifetimeDefault::Static => "'static".to_owned(),
ObjectLifetimeDefault::Param(def_id) => tcx.item_name(def_id).to_string(),
ObjectLifetimeDefault::Ambiguous => "Ambiguous".to_owned(),
};
tcx.dcx().span_err(p.span, repr);
}
let Some(owner_id) = hir_id.as_owner() else { return };
for param in &tcx.generics_of(owner_id.def_id).own_params {
let ty::GenericParamDefKind::Type { .. } = param.kind else { continue };
let default = tcx.object_lifetime_default(param.def_id);
let repr = match default {
ObjectLifetimeDefault::Empty => "Empty".to_owned(),
ObjectLifetimeDefault::Static => "'static".to_owned(),
ObjectLifetimeDefault::Param(def_id) => tcx.item_name(def_id).to_string(),
ObjectLifetimeDefault::Ambiguous => "Ambiguous".to_owned(),
};
tcx.dcx().span_err(tcx.def_span(param.def_id), repr);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
&& let Some(idx) =
argument_index.checked_sub(generics.own_counts().lifetimes)
&& let Some(arg) =
hir_args.args.get(hir_args.num_lifetime_params() + idx) =>
hir_args.args.get(hir_args.num_lifetime_args() + idx) =>
{
arg.span()
}
Expand Down
3 changes: 3 additions & 0 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2036,6 +2036,9 @@ impl<'tcx> ContainerTy<'_, 'tcx> {
match self {
Self::Ref(region) => ObjectLifetimeDefault::Arg(region),
Self::Regular { ty: container, args, arg: index } => {
// FIXME(fmease): Since #129543 assoc tys can now also induce trait object
// lifetime defaults. Re-elide these, too!

let (DefKind::Struct
| DefKind::Union
| DefKind::Enum
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/deriving/issue-89188-gat-hrtb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ trait Trait<'s, 't, 'u> {}
#[derive(Clone)]
struct ShimMethod3<T: CallWithShim2 + 'static>(
pub &'static dyn for<'s> Fn(
&'s mut T::Shim<dyn for<'t> Fn(&'s mut T::Shim<dyn for<'u> Trait<'s, 't, 'u>>)>,
&'s mut T::Shim<dyn for<'t> Fn(&'s mut T::Shim<dyn for<'u> Trait<'s, 't, 'u> + 's>) + 's>,
),
);

Expand Down
16 changes: 6 additions & 10 deletions tests/ui/did_you_mean/bad-assoc-ty.edition2015.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,16 @@ help: if this is a dyn-compatible trait, use `dyn`
LL | type H = <dyn Fn(u8) -> (u8)>::Output;
| ++++ +

error[E0223]: ambiguous associated type
error[E0228]: cannot deduce the lifetime bound for this trait object type from context
--> $DIR/bad-assoc-ty.rs:37:10
|
LL | type H = Fn(u8) -> (u8)::Output;
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: use fully-qualified syntax
|
LL - type H = Fn(u8) -> (u8)::Output;
LL + type H = <(dyn Fn(u8) -> u8 + 'static) as BitOr>::Output;
| ^^^^^^^^^^^^^^
|
LL - type H = Fn(u8) -> (u8)::Output;
LL + type H = <(dyn Fn(u8) -> u8 + 'static) as IntoFuture>::Output;
help: please supply an explicit bound
|
LL | type H = Fn(u8) -> (u8) + /* 'a */::Output;
| ++++++++++

error[E0223]: ambiguous associated type
--> $DIR/bad-assoc-ty.rs:44:19
Expand Down Expand Up @@ -310,5 +306,5 @@ LL | fn foo<F>(_: F) where F: Fn() -> _ {}

error: aborting due to 30 previous errors; 1 warning emitted

Some errors have detailed explanations: E0121, E0223, E0740.
Some errors have detailed explanations: E0121, E0223, E0228, E0740.
For more information about an error, try `rustc --explain E0121`.
4 changes: 2 additions & 2 deletions tests/ui/did_you_mean/bad-assoc-ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ type G = dyn 'static + (Send)::AssocTy;
// This is actually a legal path with fn-like generic arguments in the middle!
// Recovery should not apply in this context.
type H = Fn(u8) -> (u8)::Output;
//[edition2015]~^ ERROR ambiguous associated type
//[edition2021]~^ ERROR expected a type, found a trait
//[edition2015]~^^ ERROR cannot deduce the lifetime bound for this trait object type from context
//[edition2015]~| WARN trait objects without an explicit `dyn` are deprecated
//[edition2015]~| WARN this is accepted in the current edition
//[edition2021]~^^^^ ERROR expected a type, found a trait

macro_rules! ty {
($ty: ty) => ($ty::AssocTy);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Ideally, given an assoc type binding `dyn Trait<AssocTy = Ty>`, we'd factor in the item bounds of
// assoc type `AssocTy` when computing the trait object lifetime default for type `Ty`.
//
// However, since the current implementation can't handle this we instead conservatively and hackily
// treat the trait object lifetime default of the RHS as indeterminate if any lifetime arguments are
// passed to the trait ref (or the GAT) thus rejecting any implicit trait object lifetime bounds.
// This way, we can still implement the desired behavior in the future.

trait Foo<'a> {
type Item: 'a + ?Sized;

fn item(&self) -> Box<Self::Item> { panic!() }
}

trait Bar {}

impl<T> Foo<'_> for T {
type Item = dyn Bar;
}

fn is_static<T>(_: T) where T: 'static {}

// FIXME: Ideally, we'd elaborate `dyn Bar` to `dyn Bar + 'a` instead of rejecting it.
fn bar<'a>(x: &'a str) -> &'a dyn Foo<'a, Item = dyn Bar> { &() }
//~^ ERROR cannot deduce the lifetime bound for this trait object type from context

fn main() {
let s = format!("foo");
let r = bar(&s);

// If it weren't for the conservative path above, we'd expect an error here.
is_static(r.item());
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0228]: cannot deduce the lifetime bound for this trait object type from context
--> $DIR/object-lifetime-default-dyn-binding-nonstatic1.rs:20:50
--> $DIR/object-lifetime-default-assoc-ty-binding-item-bounds-non-static.rs:24:50
|
LL | fn bar<'a>(x: &'a str) -> &'a dyn Foo<'a, Item = dyn Bar> { &() }
| ^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Check that assoc item bindings correctly induce trait object lifetime defaults `'static` if the
// the trait & assoc ty doesn't have any lifetime params & the assoc ty isn't bounded by a lifetime.
//
//@ check-pass

trait Foo {
type Item: ?Sized;

fn item(&self) -> Box<Self::Item> { loop {} }
}

trait Bar {}

impl<T> Foo for T {
type Item = dyn Bar;
}

fn is_static<T>(_: T) where T: 'static {}

// We elaborate `dyn Bar` to `dyn Bar + 'static` since the assoc ty isn't bounded by any lifetime.
// Notably, we don't elaborate it to `dyn Bar + 'r` since the trait object lifetime default induced
// by `Foo` (i.e., `'static`) shadows the one induced by `&` (`'r`).
fn bar<'r>(x: &'r str) -> &'r dyn Foo<Item = dyn Bar> { &() }

fn main() {
let s = format!("foo");
let r = bar(&s);

is_static(r.item());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Ideally, given an assoc type binding `dyn Trait<AssocTy = Ty>`, we'd factor in the item bounds of
// assoc type `AssocTy` when computing the trait object lifetime default for type `Ty`.
//
// However, since the current implementation can't handle this we instead conservatively and hackily
// treat the trait object lifetime default of the RHS as indeterminate if any lifetime arguments are
// passed to the trait ref (or the GAT) thus rejecting any implicit trait object lifetime bounds.
// This way, we can still implement the desired behavior in the future.

trait Foo<'a> {
type Item: ?Sized;

fn item(&self) -> Box<Self::Item> { panic!() }
}

trait Bar {}

impl<T> Foo<'_> for T {
type Item = dyn Bar;
}

fn is_static<T>(_: T) where T: 'static {}

// FIXME: Ideally, we'd elaborate `dyn Bar` to `dyn Bar + 'static` instead of rejecting it.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we? 😅 I am not even sure whether this is desirable or not.

Copy link
Copy Markdown
Member Author

@fmease fmease Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify? Which semantics would you desire?

AFAICT, there are 5 options for how we could make associated type bindings behave: The associated type binding could...

  1. ...artificially induce "indeterminacy" unconditionally
    • i.e., reject implicit object lifetimes in item signatures
  2. ...artificially induce 'static unconditionally
  3. ...induce a lifetime default that's determined by the item bounds
    • here that would be 'static since OLD(Foo::Item) would be Empty which turns into 'static in item signatures
    • unlikely to be implemented anytime soon since RBV & HIR ty lowering would need to be consolidated or be able to interlock smh.
    • not sure if it's "desirable" but it's consistent / not surprising:
      • type arguments induce a default determined by the parameter bounds
      • assoc type bindings would induce a default determined by the item bounds
  4. ...not induce any object lifetime default
    • aka it's pass-through
    • here that would mean elab'ing dyn Bar to dyn Bar + 'a since it'd take on the default induced by the outer &
    • that'd be surprising I think à la "why are type arguments 'solid' (not pass through) but assoc type bindings are not despite being in the same <…> list?"
    • (although I have to admit that we do already have containers on stable that "look" eligible but actually aren't (apart from the things that are fixed in this PR, of course), most notably tuple type constructors which are pass-through contrary to ADTs (Solo<·> != (·,) wrt. obj lt defaulting); still, using that as a counterargument would probably be a stretch)
  5. ...artificially induce *indeterminacy" if any generic arg list contains lifetime args, otherwise artificially induce 'static
    • that's the behavior on main, stable & this branch
    • it's a HACK for reservation purposes (namely transitioning to option (3))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. ...induce a lifetime default that's determined by the item bounds
    • here that would be 'staticindeterminate since OLD(Foo::Item) would be Empty which turns into 'staticindeterminate for associated type bindings in item signatures

is what I'd expect

Copy link
Copy Markdown
Member Author

@fmease fmease Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Well, as a matter of fact in all other contexts Empty just like Static leads to a 'static lifetime default in item signatures, so that would be a bit inconsistent. I mean maybe it's fine if type args and assoc type bindings get treated differently, idk.

For comparison, &'r Rc<dyn Trait> gets elab'ed to &'r Rc<dyn Trait + 'static> in item signatures since Rc's T is Empty. So it's neither indeterminate nor pass-through.

fn bar<'a>(x: &'a str) -> &'a dyn Foo<'a, Item = dyn Bar> { &() }
//~^ ERROR cannot deduce the lifetime bound for this trait object type from context

// FIXME: Ideally, we'd elaborate `dyn Bar` to `dyn Bar + 'static` instead of rejecting it.
fn baz(x: &str) -> &dyn Foo<Item = dyn Bar> { &() }
//~^ ERROR cannot deduce the lifetime bound for this trait object type from context

fn main() {
let s = format!("foo");
let r = bar(&s);
is_static(r.item());
let r = baz(&s);
is_static(r.item());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error[E0228]: cannot deduce the lifetime bound for this trait object type from context
--> $DIR/object-lifetime-default-assoc-ty-binding-item-bounds.rs:24:50
|
LL | fn bar<'a>(x: &'a str) -> &'a dyn Foo<'a, Item = dyn Bar> { &() }
| ^^^^^^^
|
help: please supply an explicit bound
|
LL | fn bar<'a>(x: &'a str) -> &'a dyn Foo<'a, Item = dyn Bar + /* 'a */> { &() }
| ++++++++++

error[E0228]: cannot deduce the lifetime bound for this trait object type from context
--> $DIR/object-lifetime-default-assoc-ty-binding-item-bounds.rs:28:36
|
LL | fn baz(x: &str) -> &dyn Foo<Item = dyn Bar> { &() }
| ^^^^^^^
|
help: please supply an explicit bound
|
LL | fn baz(x: &str) -> &dyn Foo<Item = dyn Bar + /* 'a */> { &() }
| ++++++++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0228`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Check that resolved associated type paths induce the correct
// trait object lifetime default for the self type.

//@ check-pass

trait Outer { type Ty; }
trait Inner {}

impl<'a> Outer for dyn Inner + 'a { type Ty = &'a (); }

// We deduce `dyn Inner + 'static` from absence of any bounds on self ty param of trait `Outer`.
//
// Prior to PR rust-lang/rust#129543, assoc tys weren't considered eligible *containers* and
// thus we'd use the *trait object lifetime default* induced by the reference type ctor `&`,
// namely `'r`. Now however, the assoc ty overrides that default to be `'static`.
fn f<'r>(x: &'r <dyn Inner as Outer>::Ty) { /*check*/ g(x) }
fn g<'r>(x: &'r <dyn Inner + 'static as Outer>::Ty) {}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Check that resolved associated type paths induce the correct
// trait object lifetime default for the self type.

//@ check-pass
//@ revisions: bound clause

// RBV works on the HIR where where-clauses and item bounds of traits aren't merged yet.
// It's therefore wise to check both forms and make sure both are treated the same by RBV.
#[cfg(bound)] trait Outer<'a>: 'a { type Ty; }
#[cfg(clause)] trait Outer<'a> where Self: 'a { type Ty; }
trait Inner {}

impl<'a> Outer<'a> for dyn Inner + 'a { type Ty = &'a (); }

fn f<'r>(x: <dyn Inner + 'r as Outer<'r>>::Ty) { /*check*/ g(x) }
// We deduce `dyn Inner + 'r` from bound `'a` on self ty param of trait `Outer`.
fn g<'r>(x: <dyn Inner as Outer<'r>>::Ty) {}

fn main() {}

This file was deleted.

Loading
Loading