From caa90376104350ca31958721dc5404b107999a79 Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:44:12 +0200 Subject: [PATCH] abby: ensure forall where clause lhs is alias, placeholder, or lifetime --- .../rustc_hir_analysis/src/check/wfcheck.rs | 61 +++++++++++--- compiler/rustc_hir_analysis/src/collect.rs | 80 ++++++++----------- .../test-infra-fails-properly.rs | 16 +++- .../test-infra-fails-properly.stderr | 60 +++++++++----- .../test-infra-works.rs | 8 ++ 5 files changed, 148 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 0af884207ddd6..043e3fad05056 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2434,15 +2434,10 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { self.infcx.universe() }; - let mut builder = TransitiveRelationBuilder::default(); - for &(r1, r2) in &body.region_outlives { - builder.add(r1, r2); - } - // Deliberately unelaborated: the assumptions of a `forall` are exactly the ones - // written down in the test, no extra ones hidden behind the scenes. - let assumptions = ty::region_constraint::Assumptions::new_unelaborated( + let assumptions = self.build_test_binder_assumptions( + forall.span, body.type_outlives, - builder.freeze(), + body.region_outlives, ); self.infcx.insert_placeholder_assumptions(u, assumptions); self.check_test_binder_body(body.value); @@ -2460,6 +2455,52 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { }); } + fn build_test_binder_assumptions( + &self, + span: Span, + type_outlives: Vec>, + region_outlives: Vec>, + ) -> ty::region_constraint::Assumptions> { + for clause in &type_outlives { + if !matches!( + clause.skip_binder().0.kind(), + ty::Alias(..) | ty::Param(..) | ty::Placeholder(..) + ) { + let mut err = self.tcx().dcx().struct_span_err( + span, + "the lhs of a forall where clause must be \ + an alias, placeholder, or lifetime", + ); + err.note(format!("the bad clause is {clause}")); + err.emit(); + } + } + let ty = type_outlives.iter().map(|c| c.map_bound(ty::ClauseKind::TypeOutlives)); + let reg = + region_outlives.iter().map(|&c| ty::Binder::dummy(ty::ClauseKind::RegionOutlives(c))); + let explicit_clauses: Vec> = + ty.chain(reg).map(|c| c.upcast(self.tcx())).collect(); + for elaborated in ty::elaborate::elaborate(self.tcx(), explicit_clauses.iter().copied()) { + if !explicit_clauses.contains(&elaborated) { + let mut err = self.tcx().dcx().struct_span_err( + span, + "all implied bounds of clauses must themselves be \ + included in the where clause of a forall", + ); + err.note(format!("missing clause is {elaborated}")); + err.emit(); + } + } + + let mut builder = TransitiveRelationBuilder::default(); + for &ty::OutlivesClause(r1, r2) in ®ion_outlives { + builder.add(r1, r2); + } + // Deliberately unelaborated: the assumptions of a `forall` are exactly the ones + // written down in the test, no extra ones hidden behind the scenes. + ty::region_constraint::Assumptions::new_unelaborated(type_outlives, builder.freeze()) + } + #[instrument(level = "debug", skip(self))] fn check_test_binder_region_constraints( &self, @@ -2753,6 +2794,6 @@ pub(crate) struct WithWhereClauses<'tcx, T> { // The where clauses on the forall. These eventually will probably get stored inside // `ty::Binder` but they're here for now. - pub type_outlives: Vec>>>, - pub region_outlives: Vec<(ty::Region<'tcx>, ty::Region<'tcx>)>, + pub type_outlives: Vec>, + pub region_outlives: Vec>, } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 51a45eb53aa27..b81b7aafb8c1d 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -372,62 +372,50 @@ impl<'tcx> ItemCtxt<'tcx> { TestBinderExists { span: exists.span, binder } } - // FIXME: this is likely too basic, and we'll want to evolve/make this more advanced over time. - // For example, right now, if the user writes `forall<'a> where Foo<'a>: 'b`, that's not gonna - // work - that should be destructured into `where 'a: 'b`, whether by hand (and checked it was - // indeed done so, via compiler) or automatically by the test framework, unsure, but something. fn lower_test_binder_assumptions( &self, predicate: &hir::WherePredicate<'tcx>, - type_outlives: &mut Vec>>>, - region_outlives: &mut Vec<(ty::Region<'tcx>, ty::Region<'tcx>)>, + type_outlives: &mut Vec>, + region_outlives: &mut Vec>, ) { + fn outlives_bounds<'tcx>( + dcx: DiagCtxtHandle<'tcx>, + bounds: hir::GenericBounds<'tcx>, + ) -> impl Iterator { + bounds.iter().flat_map(move |bound| match *bound { + rustc_hir::GenericBound::Trait(poly_trait_ref) => { + dcx.span_err( + poly_trait_ref.span, + "trait bounds aren't supported in forall where clauses", + ); + None + } + rustc_hir::GenericBound::Outlives(lifetime) => Some(lifetime), + rustc_hir::GenericBound::Use(_, span) => { + dcx.span_err(span, "use bounds aren't supported in forall where clauses"); + None + } + }) + } match predicate.kind { hir::WherePredicateKind::BoundPredicate(p) => { let bound_vars = self.tcx.late_bound_vars(predicate.hir_id); let ty = self.lower_ty(p.bounded_ty); - for bound in p.bounds { - match bound { - hir::GenericBound::Trait(poly_trait_ref) => { - self.dcx() - .span_err(poly_trait_ref.span, "trait bounds aren't supported yet"); - } - hir::GenericBound::Outlives(lifetime) => { - let region = self - .lowerer() - .lower_lifetime(lifetime, RegionInferReason::RegionPredicate); - let binder = ty::Binder::bind_with_vars( - ty::OutlivesClause(ty, region), - bound_vars, - ); - type_outlives.push(binder); - } - hir::GenericBound::Use(_, span) => { - self.dcx().span_err(*span, "use bounds aren't supported yet"); - } - } + for lifetime in outlives_bounds(self.dcx(), p.bounds) { + let region = + self.lowerer().lower_lifetime(lifetime, RegionInferReason::RegionPredicate); + let binder = + ty::Binder::bind_with_vars(ty::OutlivesClause(ty, region), bound_vars); + type_outlives.push(binder); } } - hir::WherePredicateKind::RegionPredicate(predicate) => { - let lhs = self - .lowerer() - .lower_lifetime(predicate.lifetime, RegionInferReason::RegionPredicate); - for bound in predicate.bounds { - match bound { - hir::GenericBound::Trait(poly_trait_ref) => { - self.dcx() - .span_err(poly_trait_ref.span, "trait bounds aren't supported yet"); - } - hir::GenericBound::Outlives(lifetime) => { - let rhs = self - .lowerer() - .lower_lifetime(lifetime, RegionInferReason::RegionPredicate); - region_outlives.push((lhs, rhs)); - } - hir::GenericBound::Use(_, span) => { - self.dcx().span_err(*span, "use bounds aren't supported yet"); - } - } + hir::WherePredicateKind::RegionPredicate(p) => { + let lhs = + self.lowerer().lower_lifetime(p.lifetime, RegionInferReason::RegionPredicate); + for lifetime in outlives_bounds(self.dcx(), p.bounds) { + let rhs = + self.lowerer().lower_lifetime(lifetime, RegionInferReason::RegionPredicate); + region_outlives.push(ty::OutlivesClause(lhs, rhs)); } } } diff --git a/tests/ui/assumptions_on_binders/test-infra-fails-properly.rs b/tests/ui/assumptions_on_binders/test-infra-fails-properly.rs index 6e57c780658da..2d3b399985655 100644 --- a/tests/ui/assumptions_on_binders/test-infra-fails-properly.rs +++ b/tests/ui/assumptions_on_binders/test-infra-fails-properly.rs @@ -1,6 +1,8 @@ //@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally #![feature(test_binder_constraints)] -#![expect(incomplete_features)] + +trait Trait<'a> {} +struct Struct<'a>(&'a u32); core::test_binder_constraints! { impl<'a, 'b> { @@ -27,8 +29,6 @@ core::test_binder_constraints! { } } -trait Trait<'a> {} - core::test_binder_constraints! { impl<'a> { dyn for<'b> Trait<'b>: 'a, @@ -47,6 +47,7 @@ core::test_binder_constraints! { impl<'a> { forall where T: 'a { //~^ ERROR only lifetime parameters can be used in this context + //~| ERROR the lhs of a forall where clause must be an alias, placeholder, or lifetime T: 'a, //~^ ERROR the lhs of a ty outlives must be a placeholder } @@ -74,6 +75,15 @@ core::test_binder_constraints! { } } +core::test_binder_constraints! { + impl<'a> { + forall<'b> where Struct<'a>: 'b { + //~^ ERROR the lhs of a forall where clause must be an alias, placeholder, or lifetime + //~| ERROR all implied bounds of clauses must themselves be included in the where clause of a forall + } + } +} + core::test_binder_constraints! { impl { forall<'a> where 'a: 'static { diff --git a/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr b/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr index 2236a5d95ca89..04eb93f7fe03c 100644 --- a/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr +++ b/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr @@ -1,11 +1,11 @@ error: expected one of `!`, `(`, `+`, `::`, `:`, or `<`, found `{` - --> $DIR/test-infra-fails-properly.rs:15:20 + --> $DIR/test-infra-fails-properly.rs:17:20 | LL | forall { } | ^ expected one of `!`, `(`, `+`, `::`, `:`, or `<` error: expected one of `!`, `(`, `+`, `::`, `:`, or `<`, found `{` - --> $DIR/test-infra-fails-properly.rs:24:20 + --> $DIR/test-infra-fails-properly.rs:26:20 | LL | forall { } | ^ expected one of `!`, `(`, `+`, `::`, `:`, or `<` @@ -27,7 +27,7 @@ LL | forall where T: 'a { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: higher-ranked lifetime bound could not be satisfied - --> $DIR/test-infra-fails-properly.rs:7:9 + --> $DIR/test-infra-fails-properly.rs:9:9 | LL | 'a: 'b | ^^^^^^ @@ -39,10 +39,18 @@ LL | dyn for<'b> Trait<'b>: 'a, | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: it is a (dyn for<'b> Trait<'b> + 'static) - = note: and here it is `Debug`ged :3 dyn [Binder { value: Trait(Trait<'b>), bound_vars: [Region(BrNamed(DefId(0:11 ~ test_infra_fails_properly[2377]::{test_binder_constraints#1}::'b)))] }] + 'static + = note: and here it is `Debug`ged :3 dyn [Binder { value: Trait(Trait<'b>), bound_vars: [Region(BrNamed(DefId(0:15 ~ test_infra_fails_properly[2377]::{test_binder_constraints#1}::'b)))] }] + 'static + +error: the lhs of a forall where clause must be an alias, placeholder, or lifetime + --> $DIR/test-infra-fails-properly.rs:48:9 + | +LL | forall where T: 'a { + | ^^^^^^ + | + = note: the bad clause is {type error}: 'a error: the lhs of a ty outlives must be a placeholder - --> $DIR/test-infra-fails-properly.rs:50:13 + --> $DIR/test-infra-fails-properly.rs:51:13 | LL | T: 'a, | ^^^^^ @@ -51,35 +59,51 @@ LL | T: 'a, = note: and here it is `Debug`ged :3 {type error} error: forall expect clause failed - --> $DIR/test-infra-fails-properly.rs:63:17 + --> $DIR/test-infra-fails-properly.rs:64:17 | LL | 'b: 'c, | ^^^^^^ | note: constraint from here - --> $DIR/test-infra-fails-properly.rs:58:9 + --> $DIR/test-infra-fails-properly.rs:59:9 | LL | forall<'a> where 'b: 'a { | ^^^^^^ = note: expected: RegionOutlives( 'b/#0, 'c/#1, - $DIR/test-infra-fails-properly.rs:63:17: 63:23 (#0), + $DIR/test-infra-fails-properly.rs:64:17: 64:23 (#0), ) = note: actual: RegionOutlives( 'c/#1, 'static, - $DIR/test-infra-fails-properly.rs:58:9: 58:15 (#0), + $DIR/test-infra-fails-properly.rs:59:9: 59:15 (#0), ) error: bound type test binder constraint must be alias (it's a AliasTyOutlivesViaEnv) - --> $DIR/test-infra-fails-properly.rs:72:15 + --> $DIR/test-infra-fails-properly.rs:73:15 | LL | for<> T: 'a | ^ +error: the lhs of a forall where clause must be an alias, placeholder, or lifetime + --> $DIR/test-infra-fails-properly.rs:80:9 + | +LL | forall<'b> where Struct<'a>: 'b { + | ^^^^^^ + | + = note: the bad clause is for<'b> Struct<'a>: 'b + +error: all implied bounds of clauses must themselves be included in the where clause of a forall + --> $DIR/test-infra-fails-properly.rs:80:9 + | +LL | forall<'b> where Struct<'a>: 'b { + | ^^^^^^ + | + = note: missing clause is for<'b> 'a: 'b + error: forall expect clause failed - --> $DIR/test-infra-fails-properly.rs:81:13 + --> $DIR/test-infra-fails-properly.rs:91:13 | LL | ambiguity | ^^^^^^^^^ @@ -87,7 +111,7 @@ LL | ambiguity = note: expected: And( [ Ambiguity( - $DIR/test-infra-fails-properly.rs:81:13: 81:22 (#0), + $DIR/test-infra-fails-properly.rs:91:13: 91:22 (#0), ), ], ) @@ -96,13 +120,13 @@ LL | ambiguity ) error: forall expect clause failed - --> $DIR/test-infra-fails-properly.rs:89:9 + --> $DIR/test-infra-fails-properly.rs:99:9 | LL | forall<'a> where 'a: 'static { | ^^^^^^ | note: constraint from here - --> $DIR/test-infra-fails-properly.rs:89:9 + --> $DIR/test-infra-fails-properly.rs:99:9 | LL | forall<'a> where 'a: 'static { | ^^^^^^ @@ -112,23 +136,23 @@ LL | forall<'a> where 'a: 'static { = note: actual: And( [ Ambiguity( - $DIR/test-infra-fails-properly.rs:89:9: 89:15 (#0), + $DIR/test-infra-fails-properly.rs:99:9: 99:15 (#0), ), ], ) error: unable to satisfy constraints involving placeholders due to unknown implied bounds - --> $DIR/test-infra-fails-properly.rs:89:9 + --> $DIR/test-infra-fails-properly.rs:99:9 | LL | forall<'a> where 'a: 'static { | ^^^^^^ error: unable to satisfy constraints involving placeholders due to unknown implied bounds - --> $DIR/test-infra-fails-properly.rs:100:9 + --> $DIR/test-infra-fails-properly.rs:110:9 | LL | forall<'a> where 'a: 'static { | ^^^^^^ -error: aborting due to 13 previous errors +error: aborting due to 16 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/assumptions_on_binders/test-infra-works.rs b/tests/ui/assumptions_on_binders/test-infra-works.rs index f7d2a01d936b6..f2582a3131a56 100644 --- a/tests/ui/assumptions_on_binders/test-infra-works.rs +++ b/tests/ui/assumptions_on_binders/test-infra-works.rs @@ -82,4 +82,12 @@ core::test_binder_constraints! { } } +core::test_binder_constraints! { + impl { + forall<'a> where TParam: 'a { }, + forall<'a> where TParam::Assoc: 'a { }, + forall<'a, T> where T: 'a { }, + } +} + fn main() {}