Skip to content
Draft
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
60 changes: 34 additions & 26 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use rustc_ast::*;
use rustc_ast_pretty::pprust::{self, State};
use rustc_attr_parsing::validate_attr;
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::{DiagCtxtHandle, LintBuffer};
use rustc_errors::{Diag, DiagCtxtHandle, LintBuffer, StashKey, Subdiagnostic as _};
use rustc_feature::Features;
use rustc_session::Session;
use rustc_session::lint::BuiltinLintDiag;
Expand Down Expand Up @@ -1559,14 +1559,8 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
}

validate_generic_param_order(self.dcx(), &generics.params, generics.span);

for predicate in &generics.where_clause.predicates {
let span = predicate.span;
if let WherePredicateKind::EqPredicate(predicate) = &predicate.kind {
deny_equality_constraints(self, predicate, span, generics);
}
}
walk_list!(self, visit_generic_param, &generics.params);

for predicate in &generics.where_clause.predicates {
match &predicate.kind {
WherePredicateKind::BoundPredicate(bound_pred) => {
Expand All @@ -1590,7 +1584,24 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
}
}
}
_ => {}
WherePredicateKind::RegionPredicate(_) => {}
WherePredicateKind::EqPredicate(eq_predicate) => {
// NOTE: We compute and attach the suggestion here instead of in the parser as
// it needs access to `generics.params`. While those could be passed to
// the where-clause parser, it would worsen its many callsites!
self.dcx().try_steal_modify_and_emit_err(
eq_predicate.lhs_ty.span,
StashKey::EqualityPredicate,
|diag| {
suggest_replacing_eq_pred_with_assoc_ty_binding(
eq_predicate,
predicate.span,
generics,
diag,
)
},
);
}
}
self.visit_where_predicate(predicate);
}
Expand Down Expand Up @@ -1880,24 +1891,20 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
}
}

/// When encountering an equality constraint in a `where` clause, emit an error. If the code seems
/// like it's setting an associated type, provide an appropriate suggestion.
fn deny_equality_constraints(
this: &AstValidator<'_>,
fn suggest_replacing_eq_pred_with_assoc_ty_binding(
predicate: &WhereEqPredicate,
predicate_span: Span,
generics: &Generics,
diag: &mut Diag<'_>,
) {
let mut err = errors::EqualityInWhere { span: predicate_span, assoc: None, assoc2: None };

// Given `<A as Foo>::Bar = RhsTy`, suggest `A: Foo<Bar = RhsTy>`.
if let TyKind::Path(Some(qself), full_path) = &predicate.lhs_ty.kind
&& let TyKind::Path(None, path) = &qself.ty.kind
&& let [PathSegment { ident, args: None, .. }] = &path.segments[..]
&& let [PathSegment { ident, args: None, .. }] = path.segments[..]
{
for param in &generics.params {
if param.ident == *ident
&& let [PathSegment { ident, args, .. }] = &full_path.segments[qself.position..]
if param.ident == ident
&& let [PathSegment { ident, ref args, .. }] = full_path.segments[qself.position..]
{
// Make a new `Path` from `foo::Bar` to `Foo<Bar = RhsTy>`.
let mut assoc_path = full_path.clone();
Expand All @@ -1908,7 +1915,7 @@ fn deny_equality_constraints(
// Build `<Bar = RhsTy>`.
let arg = AngleBracketedArg::Constraint(AssocItemConstraint {
id: rustc_ast::node_id::DUMMY_NODE_ID,
ident: *ident,
ident,
gen_args,
kind: AssocItemConstraintKind::Equality {
term: predicate.rhs_ty.clone().into(),
Expand All @@ -1931,12 +1938,13 @@ fn deny_equality_constraints(
);
}
}
err.assoc = Some(errors::AssociatedSuggestion {
errors::UseAssocTypeBindingSyntaxSugg {
span: predicate_span,
ident: *ident,
ident,
param: param.ident,
path: pprust::path_to_string(&assoc_path),
})
}
.add_to_diag(diag);
}
}
}
Expand All @@ -1959,7 +1967,7 @@ fn deny_equality_constraints(
None => (format!("<{assoc} = {ty}>"), trait_segment.span().shrink_to_hi()),
};
let removal_span = if generics.where_clause.predicates.len() == 1 {
// We're removing th eonly where bound left, remove the whole thing.
// We're removing the only where bound left, remove the whole thing.
generics.where_clause.span
} else {
let mut span = predicate_span;
Expand All @@ -1982,13 +1990,14 @@ fn deny_equality_constraints(
}
span
};
err.assoc2 = Some(errors::AssociatedSuggestion2 {
errors::UseAssocTypeBindingSyntaxMultipartSugg {
span,
args,
predicate: removal_span,
trait_segment: trait_segment.ident,
potential_assoc: potential_assoc.ident,
});
}
.add_to_diag(diag);
}
};

Expand Down Expand Up @@ -2041,7 +2050,6 @@ fn deny_equality_constraints(
}
}
}
this.dcx().emit_err(err);
}

pub fn check_crate(
Expand Down
21 changes: 4 additions & 17 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,27 +879,14 @@ pub(crate) struct PatternInBodiless {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("equality constraints are not yet supported in `where` clauses")]
#[note("see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information")]
pub(crate) struct EqualityInWhere {
#[primary_span]
#[label("not supported")]
pub span: Span,
#[subdiagnostic]
pub assoc: Option<AssociatedSuggestion>,
#[subdiagnostic]
pub assoc2: Option<AssociatedSuggestion2>,
}

#[derive(Subdiagnostic)]
#[suggestion(
"if `{$ident}` is an associated type you're trying to set, use the associated type binding syntax",
"use the associated type binding syntax if `{$ident}` is an associated type you're trying to set",
code = "{param}: {path}",
style = "verbose",
applicability = "maybe-incorrect"
)]
pub(crate) struct AssociatedSuggestion {
pub(crate) struct UseAssocTypeBindingSyntaxSugg {
#[primary_span]
pub span: Span,
pub ident: Ident,
Expand All @@ -909,10 +896,10 @@ pub(crate) struct AssociatedSuggestion {

#[derive(Subdiagnostic)]
#[multipart_suggestion(
"if `{$trait_segment}::{$potential_assoc}` is an associated type you're trying to set, use the associated type binding syntax",
"use the associated type binding syntax if `{$trait_segment}::{$potential_assoc}` is an associated type you're trying to set",
applicability = "maybe-incorrect"
)]
pub(crate) struct AssociatedSuggestion2 {
pub(crate) struct UseAssocTypeBindingSyntaxMultipartSugg {
#[suggestion_part(code = "{args}")]
pub span: Span,
pub args: String,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ pub enum StashKey {
/// it's a method call without parens. If later on in `hir_typeck` we find out that this is
/// the case we suppress this message and we give a better suggestion.
GenericInFieldExpr,
EqualityPredicate,
}

fn default_track_diagnostic<R>(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4470,3 +4470,12 @@ impl TokenDescription {
}
}
}

#[derive(Diagnostic)]
#[diag("equality constraints are not supported in where-clauses")]
#[note("see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information")]
pub(crate) struct EqualityConstraintInWhereClause {
#[primary_span]
#[label("not supported")]
pub span: Span,
}
38 changes: 27 additions & 11 deletions compiler/rustc_parse/src/parser/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,25 +581,41 @@ impl<'a> Parser<'a> {
// * `for<'a> for<'b> Trait1<'a, 'b>: Trait2<'a /* ok */, 'b /* not ok */>`
let (bound_vars, _) = self.parse_higher_ranked_binder()?;

// Parse type with mandatory colon and (possibly empty) bounds,
// or with mandatory equality sign and the second type.
let ty = self.parse_ty_for_where_clause()?;

if self.eat(exp!(Colon)) {
// The bounds may be empty; we intentionally accept predicates like `Ty:`.
let bounds = self.parse_generic_bounds()?;
Ok(ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate {

return Ok(ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate {
bound_generic_params: bound_vars,
bounded_ty: ty,
bounds,
}))
// FIXME: Decide what should be used here, `=` or `==`.
// FIXME: We are just dropping the binders in lifetime_defs on the floor here.
} else if self.eat(exp!(Eq)) || self.eat(exp!(EqEq)) {
}));
}

// NOTE: If we ever end up impl'ing and stabilizing equality predicates,
// we need to pick between `=` and `==`, both is not an option!
if self.eat(exp!(Eq)) || self.eat(exp!(EqEq)) {
let rhs_ty = self.parse_ty()?;
Ok(ast::WherePredicateKind::EqPredicate(ast::WhereEqPredicate { lhs_ty: ty, rhs_ty }))
} else {
self.maybe_recover_bounds_doubled_colon(&ty)?;
self.unexpected_any()

let diag = self.dcx().create_err(errors::EqualityConstraintInWhereClause {
span: ty.span.to(rhs_ty.span),
});
diag.stash(ty.span, rustc_errors::StashKey::EqualityPredicate);

// NOTE: If we ever end up impl'ing equality predicates,
// we ought to track the binder in the AST node!
let _ = bound_vars;

return Ok(ast::WherePredicateKind::EqPredicate(ast::WhereEqPredicate {
Copy link
Copy Markdown
Member Author

@fmease fmease Mar 16, 2026

Choose a reason for hiding this comment

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

For now, I'm not removing WherePredicateKind::EqPredicate since I don't know yet if T-lang+T-types actually want to officially endorse an in-tree experiment of the restricted form as loosely proposed in #20041 (comment) (there are some questions that need to be answered first, too, see the PR description).

Removing the AST node would mean removing the lowering, HIR node, name resolution, rustfmt support, rustdoc and Clippy code.

lhs_ty: ty,
rhs_ty,
}));
}

self.maybe_recover_bounds_doubled_colon(&ty)?;
self.unexpected_any()
}

pub(super) fn choose_generics_over_qpath(&self, start: usize) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn paint<C:BoxCar>(c: C, d: C::Color) {

fn dent_object_2<COLOR>(c: &dyn BoxCar) where <dyn BoxCar as Vehicle>::Color = COLOR {
//~^ ERROR the value of the associated types
//~| ERROR equality constraints are not yet supported in `where` clauses
//~| ERROR equality constraints are not supported in where-clauses
}

fn dent_object_3<X, COLOR>(c: X)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: equality constraints are not yet supported in `where` clauses
error: equality constraints are not supported in where-clauses
--> $DIR/associated-type-projection-from-multiple-supertraits.rs:32:47
|
LL | fn dent_object_2<COLOR>(c: &dyn BoxCar) where <dyn BoxCar as Vehicle>::Color = COLOR {
Expand Down
18 changes: 9 additions & 9 deletions tests/ui/generic-associated-types/equality-bound.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
fn sum<I: Iterator<Item = ()>>(i: I) -> i32 where I::Item = i32 {
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
panic!()
}
fn sum2<I: Iterator>(i: I) -> i32 where I::Item = i32 {
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
panic!()
}
fn sum3<J: Iterator>(i: J) -> i32 where I::Item = i32 {
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `I`
panic!()
}
Expand All @@ -18,7 +18,7 @@ struct X {}

impl FromIterator<bool> for X {
fn from_iter<T>(_: T) -> Self where T: IntoIterator, IntoIterator::Item = A,
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `A` in this scope
{
todo!()
Expand All @@ -29,7 +29,7 @@ struct Y {}

impl FromIterator<bool> for Y {
fn from_iter<T>(_: T) -> Self where T: IntoIterator, T::Item = A,
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `A` in this scope
{
todo!()
Expand All @@ -40,7 +40,7 @@ struct Z {}

impl FromIterator<bool> for Z {
fn from_iter<T: IntoIterator>(_: T) -> Self where IntoIterator::Item = A,
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `A` in this scope
{
todo!()
Expand All @@ -51,7 +51,7 @@ struct K {}

impl FromIterator<bool> for K {
fn from_iter<T: IntoIterator>(_: T) -> Self where T::Item = A,
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `A` in this scope
{
todo!()
Expand All @@ -62,7 +62,7 @@ struct L {}

impl FromIterator<bool> for L {
fn from_iter<T>(_: T) -> Self where IntoIterator::Item = A, T: IntoIterator,
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `A` in this scope
{
todo!()
Expand All @@ -73,7 +73,7 @@ struct M {}

impl FromIterator<bool> for M {
fn from_iter<T>(_: T) -> Self where T::Item = A, T: IntoIterator,
//~^ ERROR equality constraints are not yet supported in `where` clauses
//~^ ERROR equality constraints are not supported in where-clauses
//~| ERROR cannot find type `A` in this scope
{
todo!()
Expand Down
Loading
Loading