Skip to content

Commit d56483a

Browse files
committed
Auto merge of #129543 - fmease:obj-lt-def-gat, r=lcnr
Make trait refs & assoc ty paths properly induce trait object lifetime defaults ## Trait Object Lifetime Defaults ### Primer & Definitions You could read [this section](https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes) in the Reference but it has several issues (see rust-lang/reference#1407). Here's a small explainer by me that only mentions the parts relevant to this PR: Basically, given `dyn Trait` (≠ `dyn Trait + '_`) we want to deduce its *trait object lifetime bound* from context without relying on normal region inference as we might not be in a body[^1]. The "context" means the closest – what I call – *(eligible) container* `C<X0, …, Xn>` that wraps this trait object type. A *container* is to be understood as a use site of a "parametrized definition" (more general than type constructors). Currently *eligible* are ADTs, type aliases, traits and enum variants. So if we have `C<dyn Trait>` (e.g., `&'r dyn Trait` or `Struct<'r, dyn Trait>`), `D<C<dyn Trait>>` or `C<N<dyn Trait>>` (e.g., `Struct<'r, (dyn Trait,)>`), we use the explicit[^2] outlives-bounds on the corresponding type parameter of `C` to determine the trait object lifetime bound. Here, `C` & `D` denote (eligible) containers and `N` denotes a generic type that is **not** an eligible container. E.g., given `struct Struct<'a, T: 'a + ?Sized>(…);`, we elaborate `Struct<'r, dyn Trait>` to `Struct<'r, dyn Trait + 'r>`. Finally, we call lifetime bounds used as the default for *constituent* trait object types of an eligible container `C` the *trait object lifetime defaults* (*induced by* `C`). These defaults may of course end up getting shadowed in parts of the type by the defaults induced by any inner eligible containers. ### Changes Made **These changes are theoretically breaking**. 1. Make *resolved* associated type paths / projections eligible containers. * `<Y0 as TraitRef<X0, …, Xn>>::AssocTy<Y1, …, Ym>` now induces *trait object lifetime defaults* for constituents `Y0` to `Ym` (`TraitRef` is considered a separate container, see also list item **(3)**). * Notably, for the self type `Y0` of (resolved) projections we now look at the bounds on the `Self` type param of the relevant trait (e.g., given `trait Outer<'a>: 'a { type Proj; }` or `trait Outer<'a> where Self: 'a { type Proj; }` we elaborate `<dyn Inner as Outer<'r>>::Proj` to `<dyn Inner + 'r as Outer<'r>>::Proj`). * Example breakages: ```rs trait Outer<'a> { type Ty<T: ?Sized + 'a>; } impl<'a> Outer<'a> for () { type Ty<T: ?Sized + 'a> = &'a T; } trait Inner {} fn f<'r>(x: <() as Outer<'r>>::Ty<dyn Inner>) { // ~~~~~~~~~ // this branch: dyn Inner + 'r (due to bound `'a` on `T`) // stable/main: dyn Inner + 'static (due to item signature fallback) let _: <() as Outer<'r>>::Ty<dyn Inner + 'static> = x; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // this branch: error: lifetime may not live long enough // `'r` must outlive `'static` // stable/main: OK } ``` ```rs trait Outer { type Ty; } trait Inner {} impl<'a> Outer for dyn Inner + 'a { type Ty = &'a (); } fn f<'r>(x: *mut &'r <dyn Inner as Outer>::Ty) { // ~~~~~~~~~ // this branch: dyn Inner + 'static (due to lack of bounds on `Outer`; the assoc type path shadows the default induced by the type ctor `&`) // stable/main: dyn Inner + 'r (due to bound `'a` on `T` in (pseudo) `builtin type &<'a, T: 'a + ?Sized>;`) let _: *mut &'r <dyn Inner + 'r as Outer>::Ty = x; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // this branch: error: lifetime may not live long enough // `'r` must outlive `'static` // stable/main: OK } ``` 2. In *type-relative* paths `Y0::Name<Y1, …, Ym>` consider the trait object lifetime default **indeterminate** * Meaning if we're in an "item context" / "item signature" / "non-body" (& the principal trait isn't bounded by any outlives-bounds which would take precedence over the default) we will **reject** any implicit trait object lifetime bounds that would take on that default * Reason: Limitations of the current implementation which can't be easily overcome * RBV (which resolves trait object lifetime defaults by recursing into the local crate "in one sitting") would require the resolution of *type-relative* paths in order to look up the generics but these paths are only resolved in HIR ty lowering (that can selectively lower local items) which depends on the results of RBV (cyclic dependency!) * While one might be able to resolve type-relative paths in RBV in an ad-hoc fashion, it would require a lot of duplication with HIR ty lowering and its impl would be very brittle (RTN does something like that in RBV but we require a more sophisticated resolver) * I did attempt that but it got too gnarly and brittle and would've likely been incomplete anyway * See also [this GH thread](#129543 (comment)) * See also [#t-types/meetings > 2025-09-16 weekly @ 💬](https://rust-lang.zulipchat.com/#narrow/channel/326132-t-types.2Fmeetings/topic/2025-09-16.20weekly/near/539889059) * This should still be maximally forward compatible and allow us to implement the desired behavior in the future. * Example breakage: ```rs trait Outer { type Ty<'a, T: 'a + ?Sized>; } trait Inner {} fn f<'r, T: Outer>(x: T::Ty<'r, dyn Inner>) {} // ~~~~~~~~~ // this branch: error: indeterminate (reservation) // stable/main: dyn Inner + 'static (due to item signature fallback) ``` 3. Fixes trait object lifetime defaults inside trait refs `TraitRef<X0, …, Xn>` (this fell out from the previous changes). They used to be completely broken due to a nasty off-by-one error for not accounting for the implicit `Self` type param of traits which lead to cases like * `Outer<'r, dyn Inner>` (with `trait Outer<'a, T: 'a + ?Sized> {}`) getting rejected as *indeterminate* (it tries to access a *lifetime* at index 1 instead 0) ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=0069c89b2313f0f447ff8b6f7de9adfa)) * `Outer<'r, 's, dyn Inner>` (with `trait Outer<'a, 'b, T: 'a + ?Sized> {}`) elaborating `dyn Inner` to `dyn Inner + 's` instead of `dyn Inner + 'r`(!) which subsequently gets rejected of course since `'s` isn't known to outlive `'r` ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=9c521165e0ac0d868a8087cd7ca861fe)) * The same applies to trait *alias* refs (feature `trait_alias`) * Example breakage: ```rs trait Outer<'a, 'b, T: 'a + ?Sized> {} trait Inner {} struct F<'r, T> where T: Outer<'r, 'static, dyn Inner> // ~~~~~~~~~ // this branch: dyn Inner + 'r (correctly mapping `'a` => `'r`) // stable/main: dyn Inner + 'static (incorrectly mapping `'a` => `'static` due to off-by-one) { g: G<'r, T>, // ~~~~~~~~ // this branch: error: mismatched types // expected: `Outer<'r, 'static, (dyn Inner + 'static)>` // found: `Outer<'r, 'static, (dyn Inner + 'r)>` // stable/main: OK } struct G<'r, T>(&'r (), T) where T: Outer<'r, 'static, dyn Inner + 'static>; ``` 4. In associated type binding `TraitRef<AssocTy<X0, …, Xn> = Y>` consider the trait object lifetime default **indeterminate** (in `X0`, …, `Xn` and `Y`) if `X0`, …, `Xn` contains any lifetime arguments. * Meaning if we're in an item context (& the principal trait isn't bounded) we will **reject** any implicit trait object lifetime bounds that would take on that default * This reserves us the right to (1) take into account the *item bounds* of `AssocTy` in the future when computing the default for `Y` (2) take into account the parameter bounds of `AssocTy` in the future when computing the defaults for `X0`, …, `Xn`. * This extends a preexisting hack that – given `TraitRef<X0, …, Xn, AssocTy<Y0, …, Ym> = Z>` – treats the default indeterminate in `Y0`, …, `Ym` and `Z` if `X0`, …, `Xn` contains any lifetime arguments. * Rephrased, this hack / reservation previously didn't account for GAT args, only trait ref args, which is insufficient * See also [this GH comment of mine](#115379 (comment)) * Example breakages: ```rs trait Outer { type Ty<'a>: ?Sized; } trait Inner {} fn f<'r>(_: impl Outer<Ty<'r> = dyn Inner>) {} // ~~~~~~~~~ // this branch: error: indeterminate (reservation) // stable/main: dyn Inner + 'static (forced) ``` ```rs trait Outer { type Ty<'a, T: ?Sized + 'a>; } trait Inner {} fn f<'r>(_: impl Outer<Ty<'r, dyn Inner> = ()>) {} // ~~~~~~~~~ // this branch: error: indeterminate (reservation) // stable/main: dyn Inner + 'static (forced) ``` #### Motivation Both trait object lifetime default RFCs ([599](https://rust-lang.github.io/rfcs/0599-default-object-bound.html) and [1156](https://rust-lang.github.io/rfcs/1156-adjust-default-object-bounds.html)) never explicitly specify what constitutes a — what I call — *(eligible) container* but it only makes sense to include anything that can be parametrized by generics and can be mentioned in places where we don't perform full region inference … like associated types. So it's only *consistent* to make this change. #### Breakages **These changes are theoretically breaking** because they can lead to different trait object lifetime bounds getting deduced compared to main which is obviously user observable. Moreover, we're now explicitly rejecting implicit trait object lifetime bounds inside type-relative paths (excl. the self type) and on the RHS of assoc type bindings if the assoc type has lifetime params. However, the latest crater run found 0 non-spurious regressions (see [here](#129543 (comment)) and [here](#129543 (comment))). --- Fixes #115379. Fixes #140710. Fixes #141997. [^1]: If we *are* in a body we do use to normal region inference as a fallback. [^2]: Indeed, we don't consider implied bounds (inferred outlives-bounds).
2 parents beae781 + 102434c commit d56483a

42 files changed

Lines changed: 1107 additions & 405 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/rustc_hir/src/hir.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,12 +700,12 @@ impl<'hir> GenericArgs<'hir> {
700700
}
701701

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

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

compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs

Lines changed: 376 additions & 192 deletions
Large diffs are not rendered by default.

compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> {
180180
AngleBrackets::Missing => 0,
181181
// Only lifetime arguments can be implied
182182
AngleBrackets::Implied => self.gen_args.args.len(),
183-
AngleBrackets::Available => self.gen_args.num_lifetime_params(),
183+
AngleBrackets::Available => self.gen_args.num_lifetime_args(),
184184
}
185185
}
186186

compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ pub(crate) fn check_generic_arg_count(
422422
let named_type_param_count = param_counts.types - has_self as usize - synth_type_param_count;
423423
let named_const_param_count = param_counts.consts;
424424
let infer_lifetimes =
425-
(gen_pos != GenericArgPosition::Type || seg.infer_args) && !gen_args.has_lifetime_params();
425+
(gen_pos != GenericArgPosition::Type || seg.infer_args) && !gen_args.has_lifetime_args();
426426

427427
if gen_pos != GenericArgPosition::Type
428428
&& let Some(c) = gen_args.constraints.first()
@@ -472,7 +472,7 @@ pub(crate) fn check_generic_arg_count(
472472

473473
let min_expected_lifetime_args = if infer_lifetimes { 0 } else { param_counts.lifetimes };
474474
let max_expected_lifetime_args = param_counts.lifetimes;
475-
let num_provided_lifetime_args = gen_args.num_lifetime_params();
475+
let num_provided_lifetime_args = gen_args.num_lifetime_args();
476476

477477
let lifetimes_correct = check_lifetime_args(
478478
min_expected_lifetime_args,
@@ -596,7 +596,7 @@ pub(crate) fn check_generic_arg_count(
596596
- default_counts.consts
597597
};
598598
debug!(?expected_min);
599-
debug!(arg_counts.lifetimes=?gen_args.num_lifetime_params());
599+
debug!(arg_counts.lifetimes=?gen_args.num_lifetime_args());
600600

601601
let provided = gen_args.num_generic_params();
602602

@@ -606,7 +606,7 @@ pub(crate) fn check_generic_arg_count(
606606
named_const_param_count + named_type_param_count + synth_type_param_count,
607607
provided,
608608
param_counts.lifetimes + has_self as usize,
609-
gen_args.num_lifetime_params(),
609+
gen_args.num_lifetime_args(),
610610
)
611611
};
612612

@@ -640,15 +640,15 @@ pub(crate) fn prohibit_explicit_late_bound_lifetimes(
640640
let param_counts = def.own_counts();
641641

642642
if let Some(span_late) = def.has_late_bound_regions
643-
&& args.has_lifetime_params()
643+
&& args.has_lifetime_args()
644644
{
645645
let msg = "cannot specify lifetime arguments explicitly \
646646
if late bound lifetime parameters are present";
647647
let note = "the late bound lifetime parameter is introduced here";
648648
let span = args.args[0].span();
649649

650650
if position == GenericArgPosition::Value(IsMethodCall::No)
651-
&& args.num_lifetime_params() != param_counts.lifetimes
651+
&& args.num_lifetime_args() != param_counts.lifetimes
652652
{
653653
struct_span_code_err!(cx.dcx(), span, E0794, "{}", msg)
654654
.with_span_note(span_late, note)

compiler/rustc_metadata/src/rmeta/encoder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1533,7 +1533,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
15331533
if let Some(name) = tcx.intrinsic(def_id) {
15341534
record!(self.tables.intrinsic[def_id] <- name);
15351535
}
1536-
if let DefKind::TyParam = def_kind {
1536+
if let DefKind::TyParam | DefKind::Trait = def_kind {
15371537
let default = self.tcx.object_lifetime_default(def_id);
15381538
record!(self.tables.object_lifetime_default[def_id] <- default);
15391539
}

compiler/rustc_passes/src/check_attr.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -654,20 +654,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
654654
/// Debugging aid for the `object_lifetime_default` query.
655655
fn check_dump_object_lifetime_defaults(&self, hir_id: HirId) {
656656
let tcx = self.tcx;
657-
if let Some(owner_id) = hir_id.as_owner()
658-
&& let Some(generics) = tcx.hir_get_generics(owner_id.def_id)
659-
{
660-
for p in generics.params {
661-
let hir::GenericParamKind::Type { .. } = p.kind else { continue };
662-
let default = tcx.object_lifetime_default(p.def_id);
663-
let repr = match default {
664-
ObjectLifetimeDefault::Empty => "BaseDefault".to_owned(),
665-
ObjectLifetimeDefault::Static => "'static".to_owned(),
666-
ObjectLifetimeDefault::Param(def_id) => tcx.item_name(def_id).to_string(),
667-
ObjectLifetimeDefault::Ambiguous => "Ambiguous".to_owned(),
668-
};
669-
tcx.dcx().span_err(p.span, repr);
670-
}
657+
let Some(owner_id) = hir_id.as_owner() else { return };
658+
for param in &tcx.generics_of(owner_id.def_id).own_params {
659+
let ty::GenericParamDefKind::Type { .. } = param.kind else { continue };
660+
let default = tcx.object_lifetime_default(param.def_id);
661+
let repr = match default {
662+
ObjectLifetimeDefault::Empty => "Empty".to_owned(),
663+
ObjectLifetimeDefault::Static => "'static".to_owned(),
664+
ObjectLifetimeDefault::Param(def_id) => tcx.item_name(def_id).to_string(),
665+
ObjectLifetimeDefault::Ambiguous => "Ambiguous".to_owned(),
666+
};
667+
tcx.dcx().span_err(tcx.def_span(param.def_id), repr);
671668
}
672669
}
673670

compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
14111411
&& let Some(idx) =
14121412
argument_index.checked_sub(generics.own_counts().lifetimes)
14131413
&& let Some(arg) =
1414-
hir_args.args.get(hir_args.num_lifetime_params() + idx) =>
1414+
hir_args.args.get(hir_args.num_lifetime_args() + idx) =>
14151415
{
14161416
arg.span()
14171417
}

src/librustdoc/clean/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2088,6 +2088,9 @@ impl<'tcx> ContainerTy<'_, 'tcx> {
20882088
match self {
20892089
Self::Ref(region) => ObjectLifetimeDefault::Arg(region),
20902090
Self::Regular { ty: container, args, arg: index } => {
2091+
// FIXME(fmease): Since #129543 assoc tys can now also induce trait object
2092+
// lifetime defaults. Re-elide these, too!
2093+
20912094
let (DefKind::Struct
20922095
| DefKind::Union
20932096
| DefKind::Enum

tests/ui/derives/clone-include-gat-hrtb.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ trait Trait<'s, 't, 'u> {}
2424
#[derive(Clone)]
2525
struct ShimMethod3<T: CallWithShim2 + 'static>(
2626
pub &'static dyn for<'s> Fn(
27-
&'s mut T::Shim<dyn for<'t> Fn(&'s mut T::Shim<dyn for<'u> Trait<'s, 't, 'u>>)>,
27+
&'s mut T::Shim<dyn for<'t> Fn(&'s mut T::Shim<dyn for<'u> Trait<'s, 't, 'u> + 's>) + 's>,
2828
),
2929
);
3030

tests/ui/did_you_mean/bad-assoc-ty.edition2015.stderr

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,20 +193,16 @@ help: if this is a dyn-compatible trait, use `dyn`
193193
LL | type H = <dyn Fn(u8) -> (u8)>::Output;
194194
| ++++ +
195195

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

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

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

313-
Some errors have detailed explanations: E0121, E0223, E0740.
309+
Some errors have detailed explanations: E0121, E0223, E0228, E0740.
314310
For more information about an error, try `rustc --explain E0121`.

0 commit comments

Comments
 (0)