From ac1b201344c2872d8dd4cc54db49bd6cb8b454b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 18 Apr 2025 13:16:49 +0200 Subject: [PATCH 1/5] Propagate the object lifetime defaults of GAT's own params Notably, this excludes the self ty. This automatically fixes object lifetime defaulting for trait refs, too. These used to be broken because the index calculation for going from middle generic args back to HIR ones didn't take into account the implicit self ty param present of traits. --- .../src/collect/resolve_bound_vars.rs | 143 +++++++++++++----- src/librustdoc/clean/mod.rs | 3 + tests/ui/deriving/issue-89188-gat-hrtb.rs | 3 +- tests/ui/deriving/issue-89188-gat-hrtb.stderr | 25 +++ tests/ui/did_you_mean/bad-assoc-ty.rs | 2 +- tests/ui/did_you_mean/bad-assoc-ty.stderr | 15 +- ...ifetime-default-assoc-ty-self-ty-static.rs | 13 ++ ...ime-default-assoc-ty-self-ty-static.stderr | 9 ++ ...bject-lifetime-default-assoc-ty-self-ty.rs | 13 ++ ...t-lifetime-default-assoc-ty-self-ty.stderr | 9 ++ .../object-lifetime-default-gat-resolved.rs | 33 ++++ ...ject-lifetime-default-gat-type-relative.rs | 10 ++ ...-lifetime-default-gat-type-relative.stderr | 9 ++ .../object-lifetime-default-trait-ref.rs | 26 ++++ 14 files changed, 257 insertions(+), 56 deletions(-) create mode 100644 tests/ui/deriving/issue-89188-gat-hrtb.stderr create mode 100644 tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs create mode 100644 tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr create mode 100644 tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs create mode 100644 tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr create mode 100644 tests/ui/object-lifetime/object-lifetime-default-gat-resolved.rs create mode 100644 tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs create mode 100644 tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr create mode 100644 tests/ui/object-lifetime/object-lifetime-default-trait-ref.rs diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 59ab36d98fdab..962bd3d287b0a 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -780,7 +780,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { // use the object lifetime defaulting // rules. So e.g., `Box` becomes // `Box`. - self.resolve_object_lifetime_default(&*lifetime) + self.resolve_object_lifetime_default(&*lifetime); } LifetimeKind::Infer => { // If the user writes `'_`, we use the *ordinary* elision @@ -799,7 +799,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { hir::TyKind::Ref(lifetime_ref, ref mt) => { self.visit_lifetime(lifetime_ref); let scope = Scope::ObjectLifetimeDefault { - lifetime: self.rbv.defs.get(&lifetime_ref.hir_id.local_id).cloned(), + lifetime: self.rbv.defs.get(&lifetime_ref.hir_id.local_id).copied(), s: self.scope, }; self.with(scope, |this| this.visit_ty_unambig(mt.ty)); @@ -887,11 +887,36 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { } } + fn visit_qpath(&mut self, qpath: &'tcx hir::QPath<'tcx>, id: HirId, _: Span) { + match qpath { + hir::QPath::Resolved(maybe_qself, path) => { + if let Some(qself) = maybe_qself { + // FIXME: Actually determine the ambient object lifetime defaults for the self ty! + let scope = Scope::ObjectLifetimeDefault { lifetime: None, s: self.scope }; + self.with(scope, |this| this.visit_ty_unambig(qself)); + } + self.visit_path(path, id); + } + hir::QPath::TypeRelative(qself, segment) => { + // Resolving object lifetime defaults for type-relative paths requires full + // type-dependent resolution as performed by HIR ty lowering whose results + // we don't have access to (and we'd results for both FnCtxts and ItemCtxts). + // FIXME: Figure out if there's a feasible way to obtain the map of + // type-dependent definitions. + let scope = Scope::ObjectLifetimeDefault { lifetime: None, s: self.scope }; + self.with(scope, |this| { + this.visit_ty_unambig(qself); + this.visit_path_segment(segment) + }); + } + hir::QPath::LangItem(..) => {} + } + } + fn visit_path(&mut self, path: &hir::Path<'tcx>, hir_id: HirId) { - for (i, segment) in path.segments.iter().enumerate() { - let depth = path.segments.len() - i - 1; + for (index, segment) in path.segments.iter().enumerate() { if let Some(args) = segment.args { - self.visit_segment_args(path.res, depth, args); + self.visit_segment_args(path, index, args); } } if let Res::Def(DefKind::TyParam | DefKind::ConstParam, param_def_id) = path.res { @@ -1611,8 +1636,8 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { #[instrument(level = "debug", skip(self))] fn visit_segment_args( &mut self, - res: Res, - depth: usize, + path: &hir::Path<'tcx>, + index: usize, generic_args: &'tcx hir::GenericArgs<'tcx>, ) { if let Some((inputs, output)) = generic_args.paren_sugar_inputs_output() { @@ -1620,35 +1645,53 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { return; } + // Let's first resolve all lifetime arguments because we need their resolution + // for computing the ambient object lifetime defaults. for arg in generic_args.args { if let hir::GenericArg::Lifetime(lt) = arg { self.visit_lifetime(lt); } } - // Figure out if this is a type/trait segment, - // which requires object lifetime defaults. - let type_def_id = match res { - Res::Def(DefKind::AssocTy, def_id) if depth == 1 => Some(self.tcx.parent(def_id)), - Res::Def(DefKind::Variant, def_id) if depth == 0 => Some(self.tcx.parent(def_id)), - Res::Def( - DefKind::Struct - | DefKind::Union - | DefKind::Enum - | DefKind::TyAlias - | DefKind::Trait, - def_id, - ) if depth == 0 => Some(def_id), + // Figure out if this is an "eligible generic container" that brings along ambient object + // lifetime defaults for trait object types contained in any of the type arguments passed to + // it (any inner generic containers will of course end up shadowing that the default). + let depth = path.segments.len() - index - 1; + let container = match (path.res, depth) { + (Res::Def(DefKind::AssocTy, def_id), 1) => { + Some((self.tcx.parent(def_id), &path.segments[..=index])) + } + (Res::Def(DefKind::Variant, def_id), 0) => { + Some((self.tcx.parent(def_id), path.segments)) + } + // FIXME(trait_alias): Arguably, trait aliases are eligible generic containers. + ( + Res::Def( + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::TyAlias + | DefKind::Trait + | DefKind::AssocTy, + def_id, + ), + 0, + ) => Some((def_id, path.segments)), + // Note: We don't need to care about definition kinds that may have generics if they + // can only ever appear in positions where we can perform type inference (i.e., bodies). + // FIXME(mgca): @fmease thinks that under (m)GCA we now also need to care about e.g., + // type-level Consts (GCI) and AssocConsts (maybe also Fns, AssocFns) here + // since they appear outside of bodies (once the feature is more complete). _ => None, }; - debug!(?type_def_id); + debug!(?container); - // Compute a vector of defaults, one for each type parameter, - // per the rules given in RFCs 599 and 1156. Example: + // Compute a vector of ambient object lifetime defaults, one for each type parameter, + // per the rules initially given in RFCs 599 and 1156. Example: // // ```rust - // struct Foo<'a, T: 'a, U> { } + // struct Foo<'a, T: 'a + ?Sized, U: ?Sized>(&'a T, &'a U); // ``` // // If you have `Foo<'x, dyn Bar, dyn Baz>`, we want to default @@ -1656,10 +1699,9 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { // and `dyn Baz` to `dyn Baz + 'static` (because there is no // such bound). // - // Therefore, we would compute `object_lifetime_defaults` to a - // vector like `['x, 'static]`. Note that the vector only - // includes type parameters. - let object_lifetime_defaults = type_def_id.map_or_else(Vec::new, |def_id| { + // Therefore, we would compute a vector like `['x, 'static]`. + // Note that the vector only includes type parameters. + let object_lifetime_defaults = container.map_or_else(Vec::new, |(def_id, segments)| { let in_body = { let mut scope = self.scope; loop { @@ -1683,9 +1725,6 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { let rbv = &self.rbv; let generics = self.tcx.generics_of(def_id); - // `type_def_id` points to an item, so there is nothing to inherit generics from. - debug_assert_eq!(generics.parent_count, 0); - let set_to_region = |set: ObjectLifetimeDefault| match set { ObjectLifetimeDefault::Empty => { if in_body { @@ -1696,12 +1735,31 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { } ObjectLifetimeDefault::Static => Some(ResolvedArg::StaticLifetime), ObjectLifetimeDefault::Param(param_def_id) => { - // This index can be used with `generic_args` since `parent_count == 0`. - let index = generics.param_def_id_to_index[¶m_def_id] as usize; - generic_args.args.get(index).and_then(|arg| match arg { - GenericArg::Lifetime(lt) => rbv.defs.get(<.hir_id.local_id).copied(), - _ => None, - }) + fn param_to_depth_and_index( + generics: &ty::Generics, + tcx: TyCtxt<'_>, + def_id: DefId, + ) -> (usize, usize) { + if let Some(&index) = generics.param_def_id_to_index.get(&def_id) { + let has_self = generics.parent.is_none() && generics.has_self; + (0, index as usize - generics.parent_count - has_self as usize) + } else if let Some(parent) = generics.parent { + let parent = tcx.generics_of(parent); + let (depth, index) = param_to_depth_and_index(parent, tcx, def_id); + (depth + 1, index) + } else { + unreachable!() + } + } + + let (depth, index) = param_to_depth_and_index(generics, self.tcx, param_def_id); + segments[segments.len() - depth - 1] + .args + .and_then(|args| args.args.get(index)) + .and_then(|arg| match arg { + GenericArg::Lifetime(lt) => rbv.defs.get(<.hir_id.local_id).copied(), + _ => None, + }) } ObjectLifetimeDefault::Ambiguous => None, }; @@ -1731,6 +1789,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { let mut i = 0; for arg in generic_args.args { match arg { + // We've already visited all lifetime arguments at the start. GenericArg::Lifetime(_) => {} GenericArg::Type(ty) => { if let Some(<) = object_lifetime_defaults.get(i) { @@ -1805,11 +1864,11 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { // `for<'a> for<'r> >::x::<'r, T>::{opaque#0}: for<'b> Other<'b>`. if constraint.gen_args.parenthesized == hir::GenericArgsParentheses::ReturnTypeNotation { - let bound_vars = if let Some(type_def_id) = type_def_id - && self.tcx.def_kind(type_def_id) == DefKind::Trait + let bound_vars = if let Some((container_def_id, _)) = container + && self.tcx.def_kind(container_def_id) == DefKind::Trait && let Some((mut bound_vars, assoc_fn)) = BoundVarContext::supertrait_hrtb_vars( self.tcx, - type_def_id, + container_def_id, constraint.ident, ty::AssocTag::Fn, ) { @@ -1838,10 +1897,10 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { this.visit_assoc_item_constraint(constraint) }); }); - } else if let Some(type_def_id) = type_def_id { + } else if let Some((container_def_id, _)) = container { let bound_vars = BoundVarContext::supertrait_hrtb_vars( self.tcx, - type_def_id, + container_def_id, constraint.ident, ty::AssocTag::Type, ) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 6ecb67c776ca9..dc938a5ca0131 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1994,6 +1994,9 @@ impl<'tcx> ContainerTy<'_, 'tcx> { match self { Self::Ref(region) => ObjectLifetimeDefault::Arg(region), Self::Regular { ty: container, args, arg: index } => { + // FIXME(fmease): rustc now also computes ambient object lifetime defaults for + // `AssocTy`s. Re-elide these, too! + let (DefKind::Struct | DefKind::Union | DefKind::Enum diff --git a/tests/ui/deriving/issue-89188-gat-hrtb.rs b/tests/ui/deriving/issue-89188-gat-hrtb.rs index a7b43159f16fd..79609be45b740 100644 --- a/tests/ui/deriving/issue-89188-gat-hrtb.rs +++ b/tests/ui/deriving/issue-89188-gat-hrtb.rs @@ -1,4 +1,5 @@ -//@ check-pass +// FIXME(fmease): I've regressed this one since we now reject TypeRelative paths as too complex. +//@ known-bug: unknown trait CallWithShim: Sized { type Shim<'s> diff --git a/tests/ui/deriving/issue-89188-gat-hrtb.stderr b/tests/ui/deriving/issue-89188-gat-hrtb.stderr new file mode 100644 index 0000000000000..ec8ea32a6d779 --- /dev/null +++ b/tests/ui/deriving/issue-89188-gat-hrtb.stderr @@ -0,0 +1,25 @@ +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound + --> $DIR/issue-89188-gat-hrtb.rs:27:56 + | +LL | &'s mut T::Shim Fn(&'s mut T::Shim Trait<'s, 't, 'u>>)>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound + --> $DIR/issue-89188-gat-hrtb.rs:27:56 + | +LL | &'s mut T::Shim Fn(&'s mut T::Shim Trait<'s, 't, 'u>>)>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound + --> $DIR/issue-89188-gat-hrtb.rs:27:56 + | +LL | &'s mut T::Shim Fn(&'s mut T::Shim Trait<'s, 't, 'u>>)>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0228`. diff --git a/tests/ui/did_you_mean/bad-assoc-ty.rs b/tests/ui/did_you_mean/bad-assoc-ty.rs index 5a559b01ea281..da0111d949282 100644 --- a/tests/ui/did_you_mean/bad-assoc-ty.rs +++ b/tests/ui/did_you_mean/bad-assoc-ty.rs @@ -31,7 +31,7 @@ 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; -//~^ ERROR ambiguous associated type +//~^ ERROR the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound //~| WARN trait objects without an explicit `dyn` are deprecated //~| WARN this is accepted in the current edition diff --git a/tests/ui/did_you_mean/bad-assoc-ty.stderr b/tests/ui/did_you_mean/bad-assoc-ty.stderr index 7e34f4d35b4e6..49465781ef4b4 100644 --- a/tests/ui/did_you_mean/bad-assoc-ty.stderr +++ b/tests/ui/did_you_mean/bad-assoc-ty.stderr @@ -193,20 +193,11 @@ help: if this is a dyn-compatible trait, use `dyn` LL | type H = (u8)>::Output; | ++++ + -error[E0223]: ambiguous associated type +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound --> $DIR/bad-assoc-ty.rs:33: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; - | + | ^^^^^^^^^^^^^^ error[E0223]: ambiguous associated type --> $DIR/bad-assoc-ty.rs:39:19 @@ -354,5 +345,5 @@ LL + trait P where F: Fn() -> T { error: aborting due to 29 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`. diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs new file mode 100644 index 0000000000000..86a0ac86e4459 --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs @@ -0,0 +1,13 @@ +// FIXME: Explainer. +//@ known-bug: unknown + +trait Outer { type Ty; } +trait Inner {} + +impl<'a> Outer for dyn Inner + 'a { type Ty = &'a (); } + +// FIXME: Deduce `dyn Inner + 'static` from absence of any bounds on self ty param of trait `Outer`. +fn f<'r>(x: &'r ::Ty) { g(x) } +fn g<'r>(x: &'r ::Ty) {} + +fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr new file mode 100644 index 0000000000000..93c531b3d65ca --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr @@ -0,0 +1,9 @@ +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound + --> $DIR/object-lifetime-default-assoc-ty-self-ty-static.rs:10:18 + | +LL | fn f<'r>(x: &'r ::Ty) { g(x) } + | ^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0228`. diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs new file mode 100644 index 0000000000000..aef7dbe020b6d --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs @@ -0,0 +1,13 @@ +// FIXME: Explainer. +//@ known-bug: unknown + +trait Outer<'a>: 'a { type Ty; } +trait Inner {} + +impl<'a> Outer<'a> for dyn Inner + 'a { type Ty = &'a (); } + +fn f<'r>(x: >::Ty) { g(x) } +// FIXME: Deduce `dyn Inner + 'r` from bound `'a` on self ty param of trait `Outer`. +fn g<'r>(x: >::Ty) {} + +fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr new file mode 100644 index 0000000000000..0fc886f787a80 --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr @@ -0,0 +1,9 @@ +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound + --> $DIR/object-lifetime-default-assoc-ty-self-ty.rs:11:14 + | +LL | fn g<'r>(x: >::Ty) {} + | ^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0228`. diff --git a/tests/ui/object-lifetime/object-lifetime-default-gat-resolved.rs b/tests/ui/object-lifetime/object-lifetime-default-gat-resolved.rs new file mode 100644 index 0000000000000..e35e60e304c3f --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-gat-resolved.rs @@ -0,0 +1,33 @@ +// Check that we correctly deduce object lifetime defaults inside resolved GAT *paths*. +// issue: +//@ check-pass + +mod own { // the object lifetime default comes from the own generics + trait Outer { + type Ty<'a, T: ?Sized + 'a>; + } + impl Outer for () { + type Ty<'a, T: ?Sized + 'a> = &'a T; + } + trait Inner {} + + fn f<'r>(x: <() as Outer>::Ty<'r, dyn Inner + 'r>) { g(x) } + // We deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of assoc ty `Ty`. + fn g<'r>(_: <() as Outer>::Ty<'r, dyn Inner>) {} +} + +mod parent { // the object lifetime default comes from the parent generics + trait Outer<'a> { + type Ty; + } + impl<'a> Outer<'a> for () { + type Ty = &'a T; + } + trait Inner {} + + fn f<'r>(x: <() as Outer<'r>>::Ty) { g(x) } + // We deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of assoc ty `Ty`. + fn g<'r>(_: <() as Outer<'r>>::Ty) {} +} + +fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs new file mode 100644 index 0000000000000..4c204abfaf99a --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs @@ -0,0 +1,10 @@ +trait Outer { type Ty<'a, T: 'a + ?Sized>; } +trait Inner {} + +// FIXME: Ideally, we would deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of assoc ty `Ty` +// but for that we'd need to somehow obtain the resolution of the type-relative path `T::Ty` +// from HIR ty lowering (it resolves to `::Ty`). +fn f<'r, T: Outer>(x: T::Ty<'r, dyn Inner>) {} +//~^ ERROR lifetime bound for this object type cannot be deduced from context + +fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr new file mode 100644 index 0000000000000..9c535da22700a --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr @@ -0,0 +1,9 @@ +error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound + --> $DIR/object-lifetime-default-gat-type-relative.rs:7:33 + | +LL | fn f<'r, T: Outer>(x: T::Ty<'r, dyn Inner>) {} + | ^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0228`. diff --git a/tests/ui/object-lifetime/object-lifetime-default-trait-ref.rs b/tests/ui/object-lifetime/object-lifetime-default-trait-ref.rs new file mode 100644 index 0000000000000..88cef0810d671 --- /dev/null +++ b/tests/ui/object-lifetime/object-lifetime-default-trait-ref.rs @@ -0,0 +1,26 @@ +// Check that we correctly deduce object lifetime defaults inside *trait refs*! +// For the longest time these examples used to get rejected as "inderminate" due to an off-by-one. +//@ check-pass + +trait Inner {} +trait Outer<'a, T: 'a + ?Sized> { type Project where Self: Sized; } + +fn bound0<'r, T>() where T: Outer<'r, dyn Inner + 'r> { bound1::<'r, T>() } +// We deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of trait `Outer`. +fn bound1<'r, T>() where T: Outer<'r, dyn Inner> {} + +fn dyn0<'r>(x: Box>) { dyn1(x) } +// We deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of trait `Outer`. +fn dyn1<'r>(_: Box>) {} + +fn impl0<'r>(x: impl Outer<'r, dyn Inner + 'r>) { impl1(x) } +// We deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of trait `Outer`. +fn impl1<'r>(_: impl Outer<'r, dyn Inner>) {} // deduce `dyn Inner + 'r` + +fn proj<'r>(x: <() as Outer<'r, dyn Inner + 'r>>::Project) { proj1(x) } +// We deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of trait `Outer`. +fn proj1<'r>(_: <() as Outer<'r, dyn Inner>>::Project) {} + +impl<'a, T: 'a + ?Sized> Outer<'a, T> for () { type Project = &'a T; } + +fn main() {} From 0d034e1249423dd1e96038d016d4b97fc26eb632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 19 Apr 2025 15:10:55 +0200 Subject: [PATCH 2/5] Don't needlessly search for already-found HIR generic param --- .../rustc_hir_analysis/src/collect/resolve_bound_vars.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 962bd3d287b0a..6b2536a3b2118 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -1060,7 +1060,6 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { } fn object_lifetime_default(tcx: TyCtxt<'_>, param_def_id: LocalDefId) -> ObjectLifetimeDefault { - debug_assert_eq!(tcx.def_kind(param_def_id), DefKind::TyParam); let hir::Node::GenericParam(param) = tcx.hir_node_by_def_id(param_def_id) else { bug!("expected GenericParam for object_lifetime_default"); }; @@ -1068,8 +1067,6 @@ fn object_lifetime_default(tcx: TyCtxt<'_>, param_def_id: LocalDefId) -> ObjectL hir::GenericParamSource::Generics => { let parent_def_id = tcx.local_parent(param_def_id); let generics = tcx.hir_get_generics(parent_def_id).unwrap(); - let param_hir_id = tcx.local_def_id_to_hir_id(param_def_id); - let param = generics.params.iter().find(|p| p.hir_id == param_hir_id).unwrap(); // Scan the bounds and where-clauses on parameters to extract bounds // of the form `T:'a` so as to determine the `ObjectLifetimeDefault` @@ -1103,7 +1100,7 @@ fn object_lifetime_default(tcx: TyCtxt<'_>, param_def_id: LocalDefId) -> ObjectL } } _ => { - bug!("object_lifetime_default_raw must only be called on a type parameter") + bug!("object_lifetime_default must only be called on a type parameter") } } } From a0c12b48d271f25a0fd7571721eeec598cceacb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 20 Apr 2025 22:21:01 +0200 Subject: [PATCH 3/5] Propagate the object lifetime default to the self ty of resolved projections --- .../src/collect/resolve_bound_vars.rs | 313 ++++++++++-------- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- ...ifetime-default-assoc-ty-self-ty-static.rs | 6 +- ...ime-default-assoc-ty-self-ty-static.stderr | 9 - ...bject-lifetime-default-assoc-ty-self-ty.rs | 10 +- ...t-lifetime-default-assoc-ty-self-ty.stderr | 9 - 6 files changed, 183 insertions(+), 166 deletions(-) delete mode 100644 tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr delete mode 100644 tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 6b2536a3b2118..9eed6e254fe72 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -6,6 +6,7 @@ //! the types in HIR to identify late-bound lifetimes and assign their Debruijn indices. This file //! is also responsible for assigning their semantics to implicit lifetimes in trait objects. +use std::assert_matches::assert_matches; use std::cell::RefCell; use std::fmt; use std::ops::ControlFlow; @@ -890,12 +891,28 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { fn visit_qpath(&mut self, qpath: &'tcx hir::QPath<'tcx>, id: HirId, _: Span) { match qpath { hir::QPath::Resolved(maybe_qself, path) => { + // Visit the path before the self type since computing the ambient object lifetime default + // for the latter requires all lifetime arguments of the trait ref to be already resolved. + self.visit_path(path, id); if let Some(qself) = maybe_qself { - // FIXME: Actually determine the ambient object lifetime defaults for the self ty! - let scope = Scope::ObjectLifetimeDefault { lifetime: None, s: self.scope }; - self.with(scope, |this| this.visit_ty_unambig(qself)); + let container = match path.res { + Res::Def(DefKind::AssocTy, def_id) => Some(( + self.tcx.parent(def_id), + &path.segments[..path.segments.len() - 1], + )), + _ => None, + }; + let object_lifetime_defaults = + container.map_or(Vec::new(), |(def_id, segs)| { + self.compute_ambient_object_lifetime_defaults(def_id, segs) + }); + if let Some(<) = object_lifetime_defaults.get(0) { + let scope = Scope::ObjectLifetimeDefault { lifetime: lt, s: self.scope }; + self.with(scope, |this| this.visit_ty_unambig(qself)); + } else { + self.visit_ty_unambig(qself); + } } - self.visit_path(path, id); } hir::QPath::TypeRelative(qself, segment) => { // Resolving object lifetime defaults for type-relative paths requires full @@ -1060,51 +1077,53 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { } fn object_lifetime_default(tcx: TyCtxt<'_>, param_def_id: LocalDefId) -> ObjectLifetimeDefault { - let hir::Node::GenericParam(param) = tcx.hir_node_by_def_id(param_def_id) else { - bug!("expected GenericParam for object_lifetime_default"); + // Scan the bounds and where-clauses on parameters to extract bounds of the form `T: 'a` + // so as to determine the `ObjectLifetimeDefault` for each type parameter. + + let (generics, bounds) = match tcx.hir_node_by_def_id(param_def_id) { + hir::Node::GenericParam(param) => match param.source { + hir::GenericParamSource::Generics => { + assert_matches!(param.kind, GenericParamKind::Type { .. }); + (tcx.hir_get_generics(tcx.local_parent(param_def_id)).unwrap(), &[][..]) + } + hir::GenericParamSource::Binder => return ObjectLifetimeDefault::Empty, + }, + // For Self ty params + hir::Node::Item(&hir::Item { + kind: hir::ItemKind::Trait(_, _, _, generics, bounds, _), + .. + }) => (generics, bounds), + _ => bug!("`object_lifetime_default` must only be called on type parameters"), }; - match param.source { - hir::GenericParamSource::Generics => { - let parent_def_id = tcx.local_parent(param_def_id); - let generics = tcx.hir_get_generics(parent_def_id).unwrap(); - - // Scan the bounds and where-clauses on parameters to extract bounds - // of the form `T:'a` so as to determine the `ObjectLifetimeDefault` - // for each type parameter. - match param.kind { - GenericParamKind::Type { .. } => { - let mut set = Set1::Empty; - - // Look for `type: ...` where clauses. - for bound in generics.bounds_for_param(param_def_id) { - // Ignore `for<'a> type: ...` as they can change what - // lifetimes mean (although we could "just" handle it). - if !bound.bound_generic_params.is_empty() { - continue; - } - for bound in bound.bounds { - if let hir::GenericBound::Outlives(lifetime) = bound { - set.insert(lifetime.kind); - } - } - } + let mut set = Set1::Empty; - match set { - Set1::Empty => ObjectLifetimeDefault::Empty, - Set1::One(hir::LifetimeKind::Static) => ObjectLifetimeDefault::Static, - Set1::One(hir::LifetimeKind::Param(param_def_id)) => { - ObjectLifetimeDefault::Param(param_def_id.to_def_id()) - } - _ => ObjectLifetimeDefault::Ambiguous, - } - } - _ => { - bug!("object_lifetime_default must only be called on a type parameter") - } + let mut add_outlives_bounds = |bounds: &[hir::GenericBound<'_>]| { + for bound in bounds { + if let hir::GenericBound::Outlives(lifetime) = bound { + set.insert(lifetime.kind); } } - hir::GenericParamSource::Binder => ObjectLifetimeDefault::Empty, + }; + + add_outlives_bounds(bounds); + + // Look for `Type: ...` where clauses. + for bound in generics.bounds_for_param(param_def_id) { + // Ignore `for<'a> Type: ...` as they can change what + // lifetimes mean (although we could "just" handle it). + if bound.bound_generic_params.is_empty() { + add_outlives_bounds(&bound.bounds); + } + } + + match set { + Set1::Empty => ObjectLifetimeDefault::Empty, + Set1::One(hir::LifetimeKind::Static) => ObjectLifetimeDefault::Static, + Set1::One(hir::LifetimeKind::Param(param_def_id)) => { + ObjectLifetimeDefault::Param(param_def_id.to_def_id()) + } + _ => ObjectLifetimeDefault::Ambiguous, } } @@ -1684,106 +1703,16 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { debug!(?container); - // Compute a vector of ambient object lifetime defaults, one for each type parameter, - // per the rules initially given in RFCs 599 and 1156. Example: - // - // ```rust - // struct Foo<'a, T: 'a + ?Sized, U: ?Sized>(&'a T, &'a U); - // ``` - // - // If you have `Foo<'x, dyn Bar, dyn Baz>`, we want to default - // `dyn Bar` to `dyn Bar + 'x` (because of the `T: 'a` bound) - // and `dyn Baz` to `dyn Baz + 'static` (because there is no - // such bound). - // - // Therefore, we would compute a vector like `['x, 'static]`. - // Note that the vector only includes type parameters. - let object_lifetime_defaults = container.map_or_else(Vec::new, |(def_id, segments)| { - let in_body = { - let mut scope = self.scope; - loop { - match *scope { - Scope::Root { .. } => break false, - - Scope::Body { .. } => break true, - - Scope::Binder { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } - | Scope::Opaque { s, .. } - | Scope::Supertrait { s, .. } - | Scope::TraitRefBoundary { s, .. } - | Scope::LateBoundary { s, .. } => { - scope = s; - } - } - } - }; - - let rbv = &self.rbv; - let generics = self.tcx.generics_of(def_id); - - let set_to_region = |set: ObjectLifetimeDefault| match set { - ObjectLifetimeDefault::Empty => { - if in_body { - None - } else { - Some(ResolvedArg::StaticLifetime) - } - } - ObjectLifetimeDefault::Static => Some(ResolvedArg::StaticLifetime), - ObjectLifetimeDefault::Param(param_def_id) => { - fn param_to_depth_and_index( - generics: &ty::Generics, - tcx: TyCtxt<'_>, - def_id: DefId, - ) -> (usize, usize) { - if let Some(&index) = generics.param_def_id_to_index.get(&def_id) { - let has_self = generics.parent.is_none() && generics.has_self; - (0, index as usize - generics.parent_count - has_self as usize) - } else if let Some(parent) = generics.parent { - let parent = tcx.generics_of(parent); - let (depth, index) = param_to_depth_and_index(parent, tcx, def_id); - (depth + 1, index) - } else { - unreachable!() - } - } - - let (depth, index) = param_to_depth_and_index(generics, self.tcx, param_def_id); - segments[segments.len() - depth - 1] - .args - .and_then(|args| args.args.get(index)) - .and_then(|arg| match arg { - GenericArg::Lifetime(lt) => rbv.defs.get(<.hir_id.local_id).copied(), - _ => None, - }) - } - ObjectLifetimeDefault::Ambiguous => None, - }; - generics - .own_params - .iter() - .filter_map(|param| { - match self.tcx.def_kind(param.def_id) { - // Generic consts don't impose any constraints. - // - // We still store a dummy value here to allow generic parameters - // in an arbitrary order. - DefKind::ConstParam => Some(ObjectLifetimeDefault::Empty), - DefKind::TyParam => Some(self.tcx.object_lifetime_default(param.def_id)), - // We may also get a `Trait` or `TraitAlias` because of how generics `Self` parameter - // works. Ignore it because it can't have a meaningful lifetime default. - DefKind::LifetimeParam | DefKind::Trait | DefKind::TraitAlias => None, - dk => bug!("unexpected def_kind {:?}", dk), - } - }) - .map(set_to_region) - .collect() + let object_lifetime_defaults = container.map_or_else(Vec::new, |(def_id, segs)| { + self.compute_ambient_object_lifetime_defaults(def_id, segs) }); debug!(?object_lifetime_defaults); - let mut i = 0; + let has_self = container + .map(|(def_id, _)| self.tcx.generics_of(def_id)) + .is_some_and(|generics| generics.parent.is_none() && generics.has_self); + let mut i = has_self as usize; for arg in generic_args.args { match arg { // We've already visited all lifetime arguments at the start. @@ -1915,6 +1844,110 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { } } + /// Compute a vector of ambient object lifetime defaults, one for each type parameter, + /// per the rules initially given in RFCs 599 and 1156. + /// + /// # Example + /// + /// ```ignore (illustrative) + /// struct Foo<'a, T: 'a + ?Sized, U: ?Sized>(&'a T, &'a U); + /// ``` + /// + /// If you have `Foo<'x, dyn Bar, dyn Baz>`, we want to default + /// `dyn Bar` to `dyn Bar + 'x` (because of the `T: 'a` bound) + /// and `dyn Baz` to `dyn Baz + 'static` (because there is no + /// such bound). + /// + /// Therefore, we would compute a vector like `['x, 'static]`. + /// Note that the vector only includes type parameters. + fn compute_ambient_object_lifetime_defaults( + &self, + def_id: DefId, + segments: &[hir::PathSegment<'_>], + ) -> Vec> { + let in_body = { + let mut scope = self.scope; + loop { + match *scope { + Scope::Root { .. } => break false, + + Scope::Body { .. } => break true, + + Scope::Binder { s, .. } + | Scope::ObjectLifetimeDefault { s, .. } + | Scope::Opaque { s, .. } + | Scope::Supertrait { s, .. } + | Scope::TraitRefBoundary { s, .. } + | Scope::LateBoundary { s, .. } => { + scope = s; + } + } + } + }; + + let generics = self.tcx.generics_of(def_id); + + let set_to_region = |set: ObjectLifetimeDefault| match set { + ObjectLifetimeDefault::Empty => { + if in_body { + None + } else { + Some(ResolvedArg::StaticLifetime) + } + } + ObjectLifetimeDefault::Static => Some(ResolvedArg::StaticLifetime), + ObjectLifetimeDefault::Param(param_def_id) => { + fn param_to_depth_and_index( + generics: &ty::Generics, + tcx: TyCtxt<'_>, + def_id: DefId, + ) -> (usize, usize) { + if let Some(&index) = generics.param_def_id_to_index.get(&def_id) { + let has_self = generics.parent.is_none() && generics.has_self; + (0, index as usize - generics.parent_count - has_self as usize) + } else if let Some(parent) = generics.parent { + let parent = tcx.generics_of(parent); + let (depth, index) = param_to_depth_and_index(parent, tcx, def_id); + (depth + 1, index) + } else { + unreachable!() + } + } + + let (depth, index) = param_to_depth_and_index(generics, self.tcx, param_def_id); + segments[segments.len() - depth - 1] + .args + .and_then(|args| args.args.get(index)) + .and_then(|arg| match arg { + GenericArg::Lifetime(lt) => self.rbv.defs.get(<.hir_id.local_id).copied(), + _ => None, + }) + } + ObjectLifetimeDefault::Ambiguous => None, + }; + generics + .own_params + .iter() + .filter_map(|param| { + match self.tcx.def_kind(param.def_id) { + // Generic consts don't impose any constraints. + // + // We still store a dummy value here to allow generic parameters + // in an arbitrary order. + DefKind::ConstParam => Some(ObjectLifetimeDefault::Empty), + // `Self` type params share the `DefId` of the corresp. trait. + DefKind::TyParam | DefKind::Trait => { + Some(self.tcx.object_lifetime_default(param.def_id)) + } + // `Self` type params of trait aliases may show up here, ignore them. + DefKind::LifetimeParam | DefKind::TraitAlias => None, + dk => bug!("unexpected def_kind {:?}", dk), + } + }) + .map(set_to_region) + .collect() + } + /// Returns all the late-bound vars that come into scope from supertrait HRTBs, based on the /// associated type name and starting trait. /// For example, imagine we have diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 3ea61d1b40a60..86de2970483e4 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1476,7 +1476,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); } diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs index 86a0ac86e4459..466b81561f4e8 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.rs @@ -1,12 +1,12 @@ -// FIXME: Explainer. -//@ known-bug: unknown +// Check that we correctly deduce object lifetime defaults inside self types of qualified paths. +//@ check-pass trait Outer { type Ty; } trait Inner {} impl<'a> Outer for dyn Inner + 'a { type Ty = &'a (); } -// FIXME: Deduce `dyn Inner + 'static` from absence of any bounds on self ty param of trait `Outer`. +// We deduce `dyn Inner + 'static` from absence of any bounds on self ty param of trait `Outer`. fn f<'r>(x: &'r ::Ty) { g(x) } fn g<'r>(x: &'r ::Ty) {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr deleted file mode 100644 index 93c531b3d65ca..0000000000000 --- a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty-static.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound - --> $DIR/object-lifetime-default-assoc-ty-self-ty-static.rs:10:18 - | -LL | fn f<'r>(x: &'r ::Ty) { g(x) } - | ^^^^^^^^^ - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0228`. diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs index aef7dbe020b6d..21921a98bf986 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.rs @@ -1,13 +1,15 @@ -// FIXME: Explainer. -//@ known-bug: unknown +// Check that we correctly deduce object lifetime defaults inside self types of qualified paths. +//@ check-pass +//@ revisions: bound clause -trait Outer<'a>: 'a { type Ty; } +#[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: >::Ty) { g(x) } -// FIXME: Deduce `dyn Inner + 'r` from bound `'a` on self ty param of trait `Outer`. +// We deduce `dyn Inner + 'r` from bound `'a` on self ty param of trait `Outer`. fn g<'r>(x: >::Ty) {} fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr b/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr deleted file mode 100644 index 0fc886f787a80..0000000000000 --- a/tests/ui/object-lifetime/object-lifetime-default-assoc-ty-self-ty.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound - --> $DIR/object-lifetime-default-assoc-ty-self-ty.rs:11:14 - | -LL | fn g<'r>(x: >::Ty) {} - | ^^^^^^^^^ - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0228`. From b31ee558b26e5f9631dda1cf66d8ef0dbe0d5888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 18 Apr 2025 14:59:15 +0200 Subject: [PATCH 4/5] Tweak output of `#[rustc_object_lifetime_default]` * Print `Empty` as it's called in code, otherwise it's unnecessarily confusing * Go through the middle::ty Generics instead of the HIR ones, so we can dump the default for the implicit `Self` type parameter of traits, too. --- compiler/rustc_passes/src/check_attr.rs | 26 +++++++++---------- .../object-lifetime-default.rs | 12 +++++++-- .../object-lifetime-default.stderr | 22 +++++++++++++--- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index cbe5058b55191..9996b653985be 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -710,20 +710,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// Debugging aid for `object_lifetime_default` query. fn check_object_lifetime_default(&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().emit_err(errors::ObjectLifetimeErr { span: 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() + .emit_err(errors::ObjectLifetimeErr { span: tcx.def_span(param.def_id), repr }); } } diff --git a/tests/ui/object-lifetime/object-lifetime-default.rs b/tests/ui/object-lifetime/object-lifetime-default.rs index 74f5bb7ddb0ec..034928cbd5165 100644 --- a/tests/ui/object-lifetime/object-lifetime-default.rs +++ b/tests/ui/object-lifetime/object-lifetime-default.rs @@ -2,13 +2,13 @@ #[rustc_object_lifetime_default] struct A< - T, //~ ERROR BaseDefault + T, //~ ERROR Empty >(T); #[rustc_object_lifetime_default] struct B< 'a, - T, //~ ERROR BaseDefault + T, //~ ERROR Empty >(&'a (), T); #[rustc_object_lifetime_default] @@ -47,4 +47,12 @@ struct G< U: 'a + 'b, //~ ERROR Ambiguous >(&'a T, &'b U); +// Check that we also dump the default for the implicit `Self` type param of traits. +#[rustc_object_lifetime_default] +trait H< //~ ERROR 'a + 'a, + 'b, + T: 'b, //~ ERROR 'b +>: 'a {} + fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default.stderr b/tests/ui/object-lifetime/object-lifetime-default.stderr index a58afad3ef2be..6606676c69f72 100644 --- a/tests/ui/object-lifetime/object-lifetime-default.stderr +++ b/tests/ui/object-lifetime/object-lifetime-default.stderr @@ -1,10 +1,10 @@ -error: BaseDefault +error: Empty --> $DIR/object-lifetime-default.rs:5:5 | LL | T, | ^ -error: BaseDefault +error: Empty --> $DIR/object-lifetime-default.rs:11:5 | LL | T, @@ -52,5 +52,21 @@ error: Ambiguous LL | U: 'a + 'b, | ^ -error: aborting due to 9 previous errors +error: 'a + --> $DIR/object-lifetime-default.rs:52:1 + | +LL | / trait H< +LL | | 'a, +LL | | 'b, +LL | | T: 'b, +LL | | >: 'a {} + | |_____^ + +error: 'b + --> $DIR/object-lifetime-default.rs:55:5 + | +LL | T: 'b, + | ^ + +error: aborting due to 11 previous errors From 352dfcb1112a2a289d9ec92225269cb86c600385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 7 May 2025 17:20:59 +0200 Subject: [PATCH 5/5] Compute object lifetime defaults for type-relative assoc ty paths, too --- .../src/collect/resolve_bound_vars.rs | 249 ++++++++++-------- tests/ui/deriving/issue-89188-gat-hrtb.rs | 3 +- tests/ui/deriving/issue-89188-gat-hrtb.stderr | 25 -- ...ject-lifetime-default-gat-type-relative.rs | 12 +- ...-lifetime-default-gat-type-relative.stderr | 9 - 5 files changed, 146 insertions(+), 152 deletions(-) delete mode 100644 tests/ui/deriving/issue-89188-gat-hrtb.stderr delete mode 100644 tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 9eed6e254fe72..7db79d955a0b1 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -900,6 +900,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { self.tcx.parent(def_id), &path.segments[..path.segments.len() - 1], )), + // FIXME(mgca): @fmease thinks we also need to handle AssocConsts here. _ => None, }; let object_lifetime_defaults = @@ -914,17 +915,27 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { } } } - hir::QPath::TypeRelative(qself, segment) => { - // Resolving object lifetime defaults for type-relative paths requires full - // type-dependent resolution as performed by HIR ty lowering whose results - // we don't have access to (and we'd results for both FnCtxts and ItemCtxts). - // FIXME: Figure out if there's a feasible way to obtain the map of - // type-dependent definitions. + &hir::QPath::TypeRelative(qself, segment) => { + if let Some(args) = segment.args { + // FIXME(mgca): @fmease thinks we also need to handle AssocConsts here. + let container = self + .limited_resolve_type_relative_path( + ty::AssocTag::Type, + qself, + segment, + true, + ) + .map(|(_, assoc_item)| (assoc_item.def_id, std::slice::from_ref(segment))); + self.visit_segment_args(container, args); + } + + // For forward compatibility we reject elided object lifetimes in the self type as + // "indeterminate" by passing `None`. `limited_resolve_type_relative_path` is not + // complete compared to HIR ty lowering's `lower_assoc_path_shared`, so we need to + // be conservative. Consider paths like `::X` which may resolve in the + // future (under IATs or mGCA (IACs)). let scope = Scope::ObjectLifetimeDefault { lifetime: None, s: self.scope }; - self.with(scope, |this| { - this.visit_ty_unambig(qself); - this.visit_path_segment(segment) - }); + self.with(scope, |this| this.visit_ty_unambig(qself)); } hir::QPath::LangItem(..) => {} } @@ -933,7 +944,38 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { fn visit_path(&mut self, path: &hir::Path<'tcx>, hir_id: HirId) { for (index, segment) in path.segments.iter().enumerate() { if let Some(args) = segment.args { - self.visit_segment_args(path, index, args); + // Figure out if this is an "eligible generic container" that brings along ambient object + // lifetime defaults for trait object types contained in any of the type arguments passed to + // it (any inner generic containers will of course end up shadowing that the default). + let depth = path.segments.len() - index - 1; + let container = match (path.res, depth) { + (Res::Def(DefKind::AssocTy, def_id), 1) => { + Some((self.tcx.parent(def_id), &path.segments[..=index])) + } + (Res::Def(DefKind::Variant, def_id), 0) => { + Some((self.tcx.parent(def_id), path.segments)) + } + // FIXME(trait_alias): Arguably, trait aliases are eligible generic containers. + ( + Res::Def( + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::TyAlias + | DefKind::Trait + | DefKind::AssocTy, + def_id, + ), + 0, + ) => Some((def_id, path.segments)), + // Note: We don't need to care about definition kinds that may have generics if they + // can only ever appear in positions where we can perform type inference (i.e., bodies). + // FIXME(mgca): @fmease thinks that under (m)GCA we now also need to care about e.g., + // type-level Consts (GCI) and AssocConsts (maybe also Fns, AssocFns) here + // since they appear outside of bodies (once the feature is more complete). + _ => None, + }; + self.visit_segment_args(container, args); } } if let Res::Def(DefKind::TyParam | DefKind::ConstParam, param_def_id) = path.res { @@ -1652,8 +1694,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { #[instrument(level = "debug", skip(self))] fn visit_segment_args( &mut self, - path: &hir::Path<'tcx>, - index: usize, + container: Option<(DefId, &[hir::PathSegment<'tcx>])>, generic_args: &'tcx hir::GenericArgs<'tcx>, ) { if let Some((inputs, output)) = generic_args.paren_sugar_inputs_output() { @@ -1669,40 +1710,6 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { } } - // Figure out if this is an "eligible generic container" that brings along ambient object - // lifetime defaults for trait object types contained in any of the type arguments passed to - // it (any inner generic containers will of course end up shadowing that the default). - let depth = path.segments.len() - index - 1; - let container = match (path.res, depth) { - (Res::Def(DefKind::AssocTy, def_id), 1) => { - Some((self.tcx.parent(def_id), &path.segments[..=index])) - } - (Res::Def(DefKind::Variant, def_id), 0) => { - Some((self.tcx.parent(def_id), path.segments)) - } - // FIXME(trait_alias): Arguably, trait aliases are eligible generic containers. - ( - Res::Def( - DefKind::Struct - | DefKind::Union - | DefKind::Enum - | DefKind::TyAlias - | DefKind::Trait - | DefKind::AssocTy, - def_id, - ), - 0, - ) => Some((def_id, path.segments)), - // Note: We don't need to care about definition kinds that may have generics if they - // can only ever appear in positions where we can perform type inference (i.e., bodies). - // FIXME(mgca): @fmease thinks that under (m)GCA we now also need to care about e.g., - // type-level Consts (GCI) and AssocConsts (maybe also Fns, AssocFns) here - // since they appear outside of bodies (once the feature is more complete). - _ => None, - }; - - debug!(?container); - let object_lifetime_defaults = container.map_or_else(Vec::new, |(def_id, segs)| { self.compute_ambient_object_lifetime_defaults(def_id, segs) }); @@ -2143,72 +2150,15 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation) }) => { - // First, ignore a qself that isn't a type or `Self` param. Those are the - // only ones that support `T::Assoc` anyways in HIR lowering. - let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = qself.kind else { + let Some((bound_vars, assoc_item)) = self.limited_resolve_type_relative_path( + ty::AssocTag::Fn, + qself, + item_segment, + false, + ) else { return; }; - match path.res { - Res::Def(DefKind::TyParam, _) | Res::SelfTyParam { trait_: _ } => { - let mut bounds = - self.for_each_trait_bound_on_res(path.res).filter_map(|trait_def_id| { - BoundVarContext::supertrait_hrtb_vars( - self.tcx, - trait_def_id, - item_segment.ident, - ty::AssocTag::Fn, - ) - }); - - let Some((bound_vars, assoc_item)) = bounds.next() else { - // This will error in HIR lowering. - self.tcx - .dcx() - .span_delayed_bug(path.span, "no resolution for RTN path"); - return; - }; - - // Don't bail if we have identical bounds, which may be collected from - // something like `T: Bound + Bound`, or via elaborating supertraits. - for (second_vars, second_assoc_item) in bounds { - if second_vars != bound_vars || second_assoc_item != assoc_item { - // This will error in HIR lowering. - self.tcx.dcx().span_delayed_bug( - path.span, - "ambiguous resolution for RTN path", - ); - return; - } - } - - (bound_vars, assoc_item.def_id, item_segment) - } - // If we have a self type alias (in an impl), try to resolve an - // associated item from one of the supertraits of the impl's trait. - Res::SelfTyAlias { alias_to: impl_def_id, is_trait_impl: true, .. } => { - let hir::ItemKind::Impl(hir::Impl { of_trait: Some(trait_ref), .. }) = self - .tcx - .hir_node_by_def_id(impl_def_id.expect_local()) - .expect_item() - .kind - else { - return; - }; - let Some(trait_def_id) = trait_ref.trait_def_id() else { - return; - }; - let Some((bound_vars, assoc_item)) = BoundVarContext::supertrait_hrtb_vars( - self.tcx, - trait_def_id, - item_segment.ident, - ty::AssocTag::Fn, - ) else { - return; - }; - (bound_vars, assoc_item.def_id, item_segment) - } - _ => return, - } + (bound_vars, assoc_item.def_id, item_segment) } _ => return, @@ -2247,6 +2197,83 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { self.record_late_bound_vars(item_segment.hir_id, existing_bound_vars_saved); } + /// In a limited fashion, try to resolve the given type-relative path of the given kind. + fn limited_resolve_type_relative_path( + &self, + tag: ty::AssocTag, + qself: &'tcx hir::Ty<'tcx>, + segment: &'tcx hir::PathSegment<'tcx>, + speculative: bool, + ) -> Option<(Vec, &'tcx ty::AssocItem)> { + // This mimics HIR ty lowering's `lower_assoc_path_shared`. + // FIXME: Duplicating efforts is not robust or sustainable/maintainable. + // Ideally, we'd simply obtain the resulting type-dependent defs from + // HIR ty lowering (not only in FnCtxts but also in ItemCtxts!). + + // First, ignore a qself that isn't a type or `Self` param. Those are the only ones + // that support `T::Assoc` anyways in HIR ty lowering at the time of writing. + let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = qself.kind else { + return None; + }; + + match path.res { + Res::Def(DefKind::TyParam, _) | Res::SelfTyParam { trait_: _ } => { + let mut bounds = + self.for_each_trait_bound_on_res(path.res).filter_map(|trait_def_id| { + BoundVarContext::supertrait_hrtb_vars( + self.tcx, + trait_def_id, + segment.ident, + tag, + ) + }); + + let Some((bound_vars, assoc_item)) = bounds.next() else { + if !speculative { + // This will error in HIR ty lowering. + self.tcx + .dcx() + .span_delayed_bug(path.span, "no resolution for type-relative path"); + } + return None; + }; + + // Don't bail if we have identical bounds, which may be collected from + // something like `T: Bound + Bound`, or via elaborating supertraits. + for (second_vars, second_assoc_item) in bounds { + if second_vars != bound_vars || second_assoc_item != assoc_item { + if !speculative { + // This will error in HIR ty lowering. + self.tcx.dcx().span_delayed_bug( + path.span, + "ambiguous resolution for type-relative path", + ); + } + return None; + } + } + + Some((bound_vars, assoc_item)) + } + // If we have a self type alias (in an impl), try to resolve an + // associated item from one of the supertraits of the impl's trait. + Res::SelfTyAlias { alias_to: impl_def_id, is_trait_impl: true, .. } => { + let hir::ItemKind::Impl(hir::Impl { of_trait: Some(trait_ref), .. }) = + self.tcx.hir_node_by_def_id(impl_def_id.expect_local()).expect_item().kind + else { + return None; + }; + BoundVarContext::supertrait_hrtb_vars( + self.tcx, + trait_ref.trait_def_id()?, + segment.ident, + tag, + ) + } + _ => None, + } + } + /// Walk the generics of the item for a trait bound whose self type /// corresponds to the expected res, and return the trait def id. fn for_each_trait_bound_on_res(&self, expected_res: Res) -> impl Iterator { diff --git a/tests/ui/deriving/issue-89188-gat-hrtb.rs b/tests/ui/deriving/issue-89188-gat-hrtb.rs index 79609be45b740..a7b43159f16fd 100644 --- a/tests/ui/deriving/issue-89188-gat-hrtb.rs +++ b/tests/ui/deriving/issue-89188-gat-hrtb.rs @@ -1,5 +1,4 @@ -// FIXME(fmease): I've regressed this one since we now reject TypeRelative paths as too complex. -//@ known-bug: unknown +//@ check-pass trait CallWithShim: Sized { type Shim<'s> diff --git a/tests/ui/deriving/issue-89188-gat-hrtb.stderr b/tests/ui/deriving/issue-89188-gat-hrtb.stderr deleted file mode 100644 index ec8ea32a6d779..0000000000000 --- a/tests/ui/deriving/issue-89188-gat-hrtb.stderr +++ /dev/null @@ -1,25 +0,0 @@ -error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound - --> $DIR/issue-89188-gat-hrtb.rs:27:56 - | -LL | &'s mut T::Shim Fn(&'s mut T::Shim Trait<'s, 't, 'u>>)>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound - --> $DIR/issue-89188-gat-hrtb.rs:27:56 - | -LL | &'s mut T::Shim Fn(&'s mut T::Shim Trait<'s, 't, 'u>>)>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound - --> $DIR/issue-89188-gat-hrtb.rs:27:56 - | -LL | &'s mut T::Shim Fn(&'s mut T::Shim Trait<'s, 't, 'u>>)>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: aborting due to 3 previous errors - -For more information about this error, try `rustc --explain E0228`. diff --git a/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs index 4c204abfaf99a..7b5ff95faf054 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.rs @@ -1,10 +1,12 @@ +// Properly deduce the object lifetime default in type-relative generic associated type *paths*. +// issue: +//@ check-pass + trait Outer { type Ty<'a, T: 'a + ?Sized>; } trait Inner {} -// FIXME: Ideally, we would deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of assoc ty `Ty` -// but for that we'd need to somehow obtain the resolution of the type-relative path `T::Ty` -// from HIR ty lowering (it resolves to `::Ty`). -fn f<'r, T: Outer>(x: T::Ty<'r, dyn Inner>) {} -//~^ ERROR lifetime bound for this object type cannot be deduced from context +fn f<'r, T: Outer>(x: T::Ty<'r, dyn Inner + 'r>) { g::(x) } +// Deduce `dyn Inner + 'r` from bound `'a` on ty param `T` of assoc ty `Ty` +fn g<'r, T: Outer>(x: T::Ty<'r, dyn Inner>) {} fn main() {} diff --git a/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr b/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr deleted file mode 100644 index 9c535da22700a..0000000000000 --- a/tests/ui/object-lifetime/object-lifetime-default-gat-type-relative.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound - --> $DIR/object-lifetime-default-gat-type-relative.rs:7:33 - | -LL | fn f<'r, T: Outer>(x: T::Ty<'r, dyn Inner>) {} - | ^^^^^^^^^ - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0228`.