Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -2460,6 +2455,52 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
});
}

fn build_test_binder_assumptions(
&self,
span: Span,
type_outlives: Vec<ty::PolyTypeOutlivesClause<'tcx>>,
region_outlives: Vec<ty::RegionOutlivesClause<'tcx>>,
) -> ty::region_constraint::Assumptions<TyCtxt<'tcx>> {
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::Clause<'tcx>> =
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 &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.
ty::region_constraint::Assumptions::new_unelaborated(type_outlives, builder.freeze())
}

#[instrument(level = "debug", skip(self))]
fn check_test_binder_region_constraints(
&self,
Expand Down Expand Up @@ -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<ty::Binder<'tcx, ty::OutlivesClause<'tcx, Ty<'tcx>>>>,
pub region_outlives: Vec<(ty::Region<'tcx>, ty::Region<'tcx>)>,
pub type_outlives: Vec<ty::PolyTypeOutlivesClause<'tcx>>,
pub region_outlives: Vec<ty::RegionOutlivesClause<'tcx>>,
}
80 changes: 34 additions & 46 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(

@khyperia khyperia Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fyi to reviewers: there are no changes to this function, I just deduped the Trait/Use bound handling into a helper fn, and changed the wording to remove the word "yet".

View changes since the review

&self,
predicate: &hir::WherePredicate<'tcx>,
type_outlives: &mut Vec<ty::Binder<'tcx, ty::OutlivesClause<'tcx, Ty<'tcx>>>>,
region_outlives: &mut Vec<(ty::Region<'tcx>, ty::Region<'tcx>)>,
type_outlives: &mut Vec<ty::PolyTypeOutlivesClause<'tcx>>,
region_outlives: &mut Vec<ty::RegionOutlivesClause<'tcx>>,
) {
fn outlives_bounds<'tcx>(
dcx: DiagCtxtHandle<'tcx>,
bounds: hir::GenericBounds<'tcx>,
) -> impl Iterator<Item = &'tcx hir::Lifetime> {
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));
}
}
}
Expand Down
16 changes: 13 additions & 3 deletions tests/ui/assumptions_on_binders/test-infra-fails-properly.rs
Original file line number Diff line number Diff line change
@@ -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> {
Expand All @@ -27,8 +29,6 @@ core::test_binder_constraints! {
}
}

trait Trait<'a> {}

core::test_binder_constraints! {
impl<'a> {
dyn for<'b> Trait<'b>: 'a,
Expand All @@ -47,6 +47,7 @@ core::test_binder_constraints! {
impl<'a> {
forall<T> 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
}
Expand Down Expand Up @@ -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 {
Expand Down
60 changes: 42 additions & 18 deletions tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr
Original file line number Diff line number Diff line change
@@ -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 `<`
Expand All @@ -27,7 +27,7 @@ LL | forall<T> 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
| ^^^^^^
Expand All @@ -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<T> 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,
| ^^^^^
Expand All @@ -51,43 +59,59 @@ 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
| ^^^^^^^^^
|
= 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),
),
],
)
Expand All @@ -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 {
| ^^^^^^
Expand All @@ -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`.
8 changes: 8 additions & 0 deletions tests/ui/assumptions_on_binders/test-infra-works.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,12 @@ core::test_binder_constraints! {
}
}

core::test_binder_constraints! {
impl<TParam: Trait> {
forall<'a> where TParam: 'a { },
forall<'a> where TParam::Assoc: 'a { },
forall<'a, T> where T: 'a { },
}
}

fn main() {}
Loading