From ea10584be62347a44cb82d1a780d1895a188a590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 31 Oct 2026 21:59:01 +0100 Subject: [PATCH 1/6] Integrate regression test case for issue 153567 into larger test file --- ...lias-type-where-suggest-issue-153567.fixed | 17 ------------- .../alias-type-where-suggest-issue-153567.rs | 17 ------------- ...ias-type-where-suggest-issue-153567.stderr | 16 ------------ ...-clause-placement-assoc-type-in-impl.fixed | 9 ++++++- ...ere-clause-placement-assoc-type-in-impl.rs | 9 ++++++- ...clause-placement-assoc-type-in-impl.stderr | 25 ++++++++++++++----- 6 files changed, 35 insertions(+), 58 deletions(-) delete mode 100644 tests/ui/where-clauses/alias-type-where-suggest-issue-153567.fixed delete mode 100644 tests/ui/where-clauses/alias-type-where-suggest-issue-153567.rs delete mode 100644 tests/ui/where-clauses/alias-type-where-suggest-issue-153567.stderr diff --git a/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.fixed b/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.fixed deleted file mode 100644 index 01da73716e884..0000000000000 --- a/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.fixed +++ /dev/null @@ -1,17 +0,0 @@ -//@ check-pass -//@ run-rustfix -//@ compile-flags: --force-warn deprecated_where_clause_location - - -#![allow(dead_code)] - -trait Trait { - type Assoc; -} - -impl Trait for i32 { - type Assoc = () where i32: Copy; - //~^ WARNING where clause not allowed here -} - -fn main() {} diff --git a/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.rs b/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.rs deleted file mode 100644 index cdee4c753b87d..0000000000000 --- a/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.rs +++ /dev/null @@ -1,17 +0,0 @@ -//@ check-pass -//@ run-rustfix -//@ compile-flags: --force-warn deprecated_where_clause_location - - -#![allow(dead_code)] - -trait Trait { - type Assoc; -} - -impl Trait for i32 { - type Assoc where i32: Copy = () where; - //~^ WARNING where clause not allowed here -} - -fn main() {} diff --git a/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.stderr b/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.stderr deleted file mode 100644 index d3421505bf5ca..0000000000000 --- a/tests/ui/where-clauses/alias-type-where-suggest-issue-153567.stderr +++ /dev/null @@ -1,16 +0,0 @@ -warning: where clause not allowed here - --> $DIR/alias-type-where-suggest-issue-153567.rs:13:16 - | -LL | type Assoc where i32: Copy = () where; - | ^^^^^^^^^^^^^^^ - | - = note: see issue #89122 for more information - = note: requested on the command line with `--force-warn deprecated-where-clause-location` -help: move it to the end of the type declaration - | -LL - type Assoc where i32: Copy = () where; -LL + type Assoc = () where i32: Copy; - | - -warning: 1 warning emitted - diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.fixed b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.fixed index 5f3882ccb412e..3671dd45b1c7b 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.fixed +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.fixed @@ -1,3 +1,6 @@ +// Ensure that we permit leading where clauses on associated types in traits +// (for backward compatibility) and lint on them. + //@ check-pass //@ run-rustfix @@ -8,8 +11,8 @@ trait Trait { type Assoc where u32: Copy; // Fine. type Assoc2 where u32: Copy, i32: Copy; - // type Assoc3; + type Assoc4; } impl Trait for u32 { @@ -21,6 +24,9 @@ impl Trait for u32 { //~^ WARNING where clause not allowed here type Assoc3 = () where; //~^ WARNING where clause not allowed here + // issue: + type Assoc4 = () where i32: Copy; + //~^ WARNING where clause not allowed here } impl Trait for i32 { @@ -31,6 +37,7 @@ impl Trait for i32 { //~^ WARNING where clause not allowed here type Assoc3 = () where; //~^ WARNING where clause not allowed here + type Assoc4 = (); } fn main() {} diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.rs b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.rs index b7a8ab3d4f782..142eb01fe2d03 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.rs +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.rs @@ -1,3 +1,6 @@ +// Ensure that we permit leading where clauses on associated types in traits +// (for backward compatibility) and lint on them. + //@ check-pass //@ run-rustfix @@ -8,8 +11,8 @@ trait Trait { type Assoc where u32: Copy; // Fine. type Assoc2 where u32: Copy, i32: Copy; - // type Assoc3; + type Assoc4; } impl Trait for u32 { @@ -21,6 +24,9 @@ impl Trait for u32 { //~^ WARNING where clause not allowed here type Assoc3 where = (); //~^ WARNING where clause not allowed here + // issue: + type Assoc4 where i32: Copy = () where; + //~^ WARNING where clause not allowed here } impl Trait for i32 { @@ -31,6 +37,7 @@ impl Trait for i32 { //~^ WARNING where clause not allowed here type Assoc3 where = () where; //~^ WARNING where clause not allowed here + type Assoc4 = (); } fn main() {} diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.stderr b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.stderr index f31c8b2dd8219..cb6ae3095c6c1 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.stderr +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-impl.stderr @@ -1,5 +1,5 @@ warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-impl.rs:17:16 + --> $DIR/where-clause-placement-assoc-type-in-impl.rs:20:16 | LL | type Assoc where u32: Copy = (); | ^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + type Assoc = () where u32: Copy; | warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-impl.rs:20:17 + --> $DIR/where-clause-placement-assoc-type-in-impl.rs:23:17 | LL | type Assoc2 where u32: Copy = () where i32: Copy; | ^^^^^^^^^^^^^^^ @@ -26,7 +26,7 @@ LL + type Assoc2 = () where i32: Copy, u32: Copy; | warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-impl.rs:22:17 + --> $DIR/where-clause-placement-assoc-type-in-impl.rs:25:17 | LL | type Assoc3 where = (); | ^^^^^ @@ -39,7 +39,20 @@ LL + type Assoc3 = () where; | warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-impl.rs:30:17 + --> $DIR/where-clause-placement-assoc-type-in-impl.rs:28:17 + | +LL | type Assoc4 where i32: Copy = () where; + | ^^^^^^^^^^^^^^^ + | + = note: see issue #89122 for more information +help: move it to the end of the type declaration + | +LL - type Assoc4 where i32: Copy = () where; +LL + type Assoc4 = () where i32: Copy; + | + +warning: where clause not allowed here + --> $DIR/where-clause-placement-assoc-type-in-impl.rs:36:17 | LL | type Assoc2 where u32: Copy, i32: Copy = (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,12 +65,12 @@ LL + type Assoc2 = () where u32: Copy, i32: Copy; | warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-impl.rs:32:17 + --> $DIR/where-clause-placement-assoc-type-in-impl.rs:38:17 | LL | type Assoc3 where = () where; | ^^^^^ help: remove this `where` | = note: see issue #89122 for more information -warning: 5 warnings emitted +warning: 6 warnings emitted From e63ee545679ed0dd5ed990f69adc10393c889e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 31 Oct 2026 21:25:11 +0100 Subject: [PATCH 2/6] Consolidate tests `parser/bounds-{type,lifetime}-where.rs` Previously they were separate because they each contained a fatal parse error. However, by introducing "barrier functions" we give the parser synchronization points to successfully recover from these syntax errors allowing us to put both test cases into the same file. We call the combined file `where-clause-predicates.rs` because it took me a while to realize that this test mainly means to test where clause predicates themselves and not *type aliases with where clauses*. See also PR RUST-39158 which introduced these files and which is about "bound parsing" in general. This makes it alot clearer which UI tests actually concern where clauses on type aliases *specifically* allowing me to more easily find an existing test file I can extend with test cases covering "double where clauses", namely `tests/ui/where-clauses/where-clause-placement-type-alias.rs`. --- tests/ui/parser/bounds-lifetime-where.rs | 10 ------- tests/ui/parser/bounds-lifetime-where.stderr | 8 ------ tests/ui/parser/bounds-type-where.rs | 11 -------- tests/ui/parser/bounds-type-where.stderr | 8 ------ tests/ui/parser/where-clause-predicates.rs | 26 +++++++++++++++++++ .../ui/parser/where-clause-predicates.stderr | 14 ++++++++++ 6 files changed, 40 insertions(+), 37 deletions(-) delete mode 100644 tests/ui/parser/bounds-lifetime-where.rs delete mode 100644 tests/ui/parser/bounds-lifetime-where.stderr delete mode 100644 tests/ui/parser/bounds-type-where.rs delete mode 100644 tests/ui/parser/bounds-type-where.stderr create mode 100644 tests/ui/parser/where-clause-predicates.rs create mode 100644 tests/ui/parser/where-clause-predicates.stderr diff --git a/tests/ui/parser/bounds-lifetime-where.rs b/tests/ui/parser/bounds-lifetime-where.rs deleted file mode 100644 index 730be7139bec8..0000000000000 --- a/tests/ui/parser/bounds-lifetime-where.rs +++ /dev/null @@ -1,10 +0,0 @@ -type A where 'a: 'b + 'c = u8; // OK -type A where 'a: 'b, = u8; // OK -type A where 'a: = u8; // OK -type A where 'a:, = u8; // OK -type A where 'a: 'b + 'c = u8; // OK -type A where = u8; // OK -type A where 'a: 'b + = u8; // OK -type A where , = u8; //~ ERROR expected one of `#`, `;`, `=`, `where`, lifetime, or type, found `,` - -fn main() {} diff --git a/tests/ui/parser/bounds-lifetime-where.stderr b/tests/ui/parser/bounds-lifetime-where.stderr deleted file mode 100644 index 5aadb5fff320b..0000000000000 --- a/tests/ui/parser/bounds-lifetime-where.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: expected one of `#`, `;`, `=`, `where`, lifetime, or type, found `,` - --> $DIR/bounds-lifetime-where.rs:8:14 - | -LL | type A where , = u8; - | ^ expected one of `#`, `;`, `=`, `where`, lifetime, or type - -error: aborting due to 1 previous error - diff --git a/tests/ui/parser/bounds-type-where.rs b/tests/ui/parser/bounds-type-where.rs deleted file mode 100644 index 2520ecb294c9a..0000000000000 --- a/tests/ui/parser/bounds-type-where.rs +++ /dev/null @@ -1,11 +0,0 @@ -type A where for<'a> for<'b> Trait1 + ?Trait2: 'a + Trait = u8; // OK -type A where T: Trait, = u8; // OK -type A where T: = u8; // OK -type A where T:, = u8; // OK -type A where T: Trait + Trait = u8; // OK -type A where = u8; // OK -type A where T: Trait + = u8; // OK -type A where T, = u8; -//~^ ERROR expected one of `!`, `(`, `+`, `::`, `:`, `<`, `==`, or `=`, found `,` - -fn main() {} diff --git a/tests/ui/parser/bounds-type-where.stderr b/tests/ui/parser/bounds-type-where.stderr deleted file mode 100644 index 709d31430984b..0000000000000 --- a/tests/ui/parser/bounds-type-where.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: expected one of `!`, `(`, `+`, `::`, `:`, `<`, `==`, or `=`, found `,` - --> $DIR/bounds-type-where.rs:8:15 - | -LL | type A where T, = u8; - | ^ expected one of 8 possible tokens - -error: aborting due to 1 previous error - diff --git a/tests/ui/parser/where-clause-predicates.rs b/tests/ui/parser/where-clause-predicates.rs new file mode 100644 index 0000000000000..99234889dbd7f --- /dev/null +++ b/tests/ui/parser/where-clause-predicates.rs @@ -0,0 +1,26 @@ +// Exercise the parsing of the "bounds" (predicates) that are found in where clauses. +// +// Check that we allow zero bounds after `:`, zero predicates after `where`, +// trailing `,` in predicate lists & trailing `+` in bound lists. +// +//@ compile-flags: -Zparse-crate-root-only + +type A where for<'a> for<'b> Trait1 + ?Trait2: 'a + Trait = u8; // OK +type A where T: Trait, = u8; // OK +type A where T: = u8; // OK +type A where T:, = u8; // OK +type A where T: Trait + Trait = u8; // OK +type A where = u8; // OK +type A where T: Trait + = u8; // OK +fn barrier() { type A where T, = u8; } +//~^ ERROR expected one of `!`, `(`, `+`, `::`, `:`, `<`, `==`, or `=`, found `,` + +type A where 'a: 'b + 'c = u8; // OK +type A where 'a: 'b, = u8; // OK +type A where 'a: = u8; // OK +type A where 'a:, = u8; // OK +type A where 'a: 'b + 'c = u8; // OK +type A where = u8; // OK +type A where 'a: 'b + = u8; // OK +fn barrier() { type A where , = u8; } +//~^ ERROR expected one of `#`, `;`, `=`, `where`, lifetime, or type, found `,` diff --git a/tests/ui/parser/where-clause-predicates.stderr b/tests/ui/parser/where-clause-predicates.stderr new file mode 100644 index 0000000000000..039130a971167 --- /dev/null +++ b/tests/ui/parser/where-clause-predicates.stderr @@ -0,0 +1,14 @@ +error: expected one of `!`, `(`, `+`, `::`, `:`, `<`, `==`, or `=`, found `,` + --> $DIR/where-clause-predicates.rs:15:30 + | +LL | fn barrier() { type A where T, = u8; } + | ^ expected one of 8 possible tokens + +error: expected one of `#`, `;`, `=`, `where`, lifetime, or type, found `,` + --> $DIR/where-clause-predicates.rs:25:29 + | +LL | fn barrier() { type A where , = u8; } + | ^ expected one of `#`, `;`, `=`, `where`, lifetime, or type + +error: aborting due to 2 previous errors + From f5ac608d5539423f8d3356e331022be602c32b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 25 Oct 2026 18:01:46 +0100 Subject: [PATCH 3/6] Construct `ItemKind` directly in `parse_{fn,const_item}` There's no reason to return a large tuple that the caller then has to convert into an `ItemKind`. None of the callers care about the concrete fields. In the case of `parse_const_item` it's likely a remnant of the time when it was used to parse (or rather finish parsing) const (`ItemKind::Const`) *and* static items (`ItemKind::Static`). --- compiler/rustc_parse/src/parser/function.rs | 16 ++++-- compiler/rustc_parse/src/parser/item.rs | 60 +++++++++------------ 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 443c7fae43a0a..7813a667ceaea 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -123,8 +123,9 @@ impl<'a> Parser<'a> { fn_parse_mode: FnParseMode, sig_lo: Span, vis: &Visibility, + defaultness: Defaultness, case: Case, - ) -> PResult<'a, (Ident, FnSig, Generics, Option>, Option>)> { + ) -> PResult<'a, ItemKind> { let fn_span = self.token.span; let header = self.parse_fn_front_matter(vis, case, FrontMatterParsingMode::Function)?; // `const ... fn` let ident = self.parse_ident()?; // `foo` @@ -161,8 +162,17 @@ impl<'a> Parser<'a> { // Either `;` or `{ ... }`. let body = self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?; - let fn_sig_span = sig_lo.to(sig_hi); - Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, contract, body)) + + Ok(ItemKind::Fn(Box::new(Fn { + defaultness, + ident, + sig: FnSig { header, decl, span: sig_lo.to(sig_hi) }, + generics, + contract, + body, + define_opaque: None, + eii_impl: None, + }))) } /// Provide diagnostics when function body is not found diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 27646bac1c21e..0941eb6177fec 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -277,18 +277,7 @@ impl<'a> Parser<'a> { self.psess.gated_spans.gate(sym::min_specialization, span); self.psess.gated_spans.ungate_last(sym::specialization, span); } - let (ident, sig, generics, contract, body) = - self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?; - ItemKind::Fn(Box::new(Fn { - defaultness, - ident, - sig, - generics, - contract, - body, - define_opaque: None, - eii_impl: None, - })) + self.parse_fn(attrs, fn_parse_mode, lo, vis, defaultness, case)? } else if self.eat_keyword_case(exp!(Extern), case) { if self.eat_keyword_case(exp!(Crate), case) { // EXTERN CRATE @@ -325,15 +314,7 @@ impl<'a> Parser<'a> { // CONST ITEM self.recover_const_mut(const_span); self.recover_missing_kw_before_item()?; - let (ident, generics, ty, body) = self.parse_const_item(const_span)?; - ItemKind::Const(Box::new(ConstItem { - defaultness: def_(), - ident, - generics, - ty, - body, - define_opaque: None, - })) + self.parse_const_item(const_span, def_())? } else if let Some(kind) = self.is_reuse_item() { self.parse_item_delegation(attrs, def_(), kind)? } else if self.check_keyword_case(exp!(Mod), case) @@ -1766,20 +1747,17 @@ impl<'a> Parser<'a> { /// Parse a constant item with the prefix `"const"` already parsed. /// - /// If `const_arg` is true, any expression assigned to the const will be parsed - /// as a const_arg instead of a body expression. - /// /// ```ebnf /// Const = "const" ($ident | "_") Generics ":" $ty (= $expr)? WhereClause ";" ; /// ``` fn parse_const_item( &mut self, const_span: Span, - ) -> PResult<'a, (Ident, Generics, Box, Option>)> { + defaultness: Defaultness, + ) -> PResult<'a, ItemKind> { let ident = self.parse_ident_or_underscore()?; let mut generics = self.parse_generics()?; - // Check the span for emptiness instead of the list of parameters in order to correctly // recognize and subsequently flag empty parameter lists (`<>`) as unstable. if !generics.span.is_empty() { @@ -1802,7 +1780,7 @@ impl<'a> Parser<'a> { let before_where_clause = if self.may_recover() { self.parse_where_clause()? } else { WhereClause::default() }; - let rhs = if self.eat(exp!(Eq)) { Some(self.parse_expr()?) } else { None }; + let body = if self.eat(exp!(Eq)) { Some(self.parse_expr()?) } else { None }; let after_where_clause = self.parse_where_clause()?; @@ -1810,18 +1788,18 @@ impl<'a> Parser<'a> { // Users may be tempted to write such code if they are still used to the deprecated // where-clause location on type aliases and associated types. See also #89122. if before_where_clause.has_where_token - && let Some(rhs) = &rhs + && let Some(body) = &body { self.dcx().emit_err(diagnostics::WhereClauseBeforeConstBody { span: before_where_clause.span, name: ident.span, - body: rhs.span, + body: body.span, sugg: if !after_where_clause.has_where_token { - self.psess.source_map().span_to_snippet(rhs.span).ok().map(|body_s| { + self.psess.source_map().span_to_snippet(body.span).ok().map(|body_s| { diagnostics::WhereClauseBeforeConstBodySugg { left: before_where_clause.span.shrink_to_lo(), snippet: body_s, - right: before_where_clause.span.shrink_to_hi().to(rhs.span), + right: before_where_clause.span.shrink_to_hi().to(body.span), } }) } else { @@ -1857,12 +1835,21 @@ impl<'a> Parser<'a> { generics.where_clause = where_clause; - if let Some(rhs) = self.try_recover_const_missing_semi(&rhs, const_span) { - return Ok((ident, generics, ty, Some(rhs))); - } - self.expect_semi()?; + let body = if let body @ Some(_) = self.try_recover_const_missing_semi(&body, const_span) { + body + } else { + self.expect_semi()?; + body + }; - Ok((ident, generics, ty, rhs)) + Ok(ItemKind::Const(Box::new(ConstItem { + defaultness, + ident, + generics, + ty, + body, + define_opaque: None, + }))) } /// We were supposed to parse `":" $ty` but the `:` or the type was missing. @@ -2533,6 +2520,7 @@ impl<'a> Parser<'a> { fn_parse_mode, lo, &inherited_vis, + Defaultness::Implicit, Case::Insensitive, ) { Ok(_) => self From b1d1ccc737f5dc3b2d841a02bd9deeb7084a5935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 28 Oct 2026 16:01:05 +0100 Subject: [PATCH 4/6] Don't artificially reject duplicate (non-empty) where clauses on bodiless type aliases / associated types. --- compiler/rustc_parse/src/parser/function.rs | 3 +- compiler/rustc_parse/src/parser/generics.rs | 25 ++++++++--- compiler/rustc_parse/src/parser/item.rs | 43 ++++++++++--------- tests/ui/parser/duplicate-where-clauses.rs | 7 +++ .../ui/parser/duplicate-where-clauses.stderr | 17 +++++++- tests/ui/type/type-alias-bounds.rs | 2 +- ...clause-placement-assoc-type-in-trait.fixed | 13 ++++-- ...re-clause-placement-assoc-type-in-trait.rs | 13 ++++-- ...lause-placement-assoc-type-in-trait.stderr | 12 +++--- ...re-clause-placement-type-alias.fail.stderr | 38 ++++++++++++++++ ...re-clause-placement-type-alias.pass.stderr | 18 ++++++++ .../where-clause-placement-type-alias.rs | 33 ++++++++++---- .../where-clause-placement-type-alias.stderr | 20 --------- 13 files changed, 173 insertions(+), 71 deletions(-) create mode 100644 tests/ui/where-clauses/where-clause-placement-type-alias.fail.stderr create mode 100644 tests/ui/where-clauses/where-clause-placement-type-alias.pass.stderr delete mode 100644 tests/ui/where-clauses/where-clause-placement-type-alias.stderr diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 7813a667ceaea..c72ff5e5457b7 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -12,6 +12,7 @@ use thin_vec::ThinVec; use tracing::debug; use super::diagnostics::dummy_arg; +use super::generics::RecoverDupeWhereClause; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{ ExpKeywordPair, FollowedByType, ForceCollect, Parser, Recovered, Trailing, UsePreAttrPos, @@ -152,7 +153,7 @@ impl<'a> Parser<'a> { let contract = self.parse_contract()?; - generics.where_clause = self.parse_where_clause()?; // `where T: Ord` + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; // `where T: Ord` // `fn_params_end` is needed only when it's followed by a where clause. let fn_params_end = diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 8dc679c3e62ca..60094aa2822b3 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -14,6 +14,11 @@ use crate::diagnostics::{ }; use crate::exp; +pub(super) enum RecoverDupeWhereClause { + Yes, + No, +} + enum PredicateKindOrStructBody { PredicateKind(ast::WherePredicateKind), StructBody(ThinVec), @@ -395,8 +400,11 @@ impl<'a> Parser<'a> { /// ```ignore (only-for-syntax-highlight) /// where T : Trait + 'b, 'a : 'b /// ``` - pub(super) fn parse_where_clause(&mut self) -> PResult<'a, WhereClause> { - self.parse_where_clause_common(None).map(|(clause, _)| clause) + pub(super) fn parse_where_clause( + &mut self, + recover_dupe: RecoverDupeWhereClause, + ) -> PResult<'a, WhereClause> { + self.parse_where_clause_common(None, recover_dupe).map(|(clause, _)| clause) } pub(super) fn parse_struct_where_clause( @@ -404,12 +412,16 @@ impl<'a> Parser<'a> { struct_name: Ident, body_insertion_point: Span, ) -> PResult<'a, (WhereClause, Option>)> { - self.parse_where_clause_common(Some((struct_name, body_insertion_point))) + self.parse_where_clause_common( + Some((struct_name, body_insertion_point)), + RecoverDupeWhereClause::Yes, + ) } fn parse_where_clause_common( &mut self, struct_: Option<(Ident, Span)>, + recover_dupe: RecoverDupeWhereClause, ) -> PResult<'a, (WhereClause, Option>)> { if !self.eat_keyword(exp!(Where)) { return Ok(( @@ -446,7 +458,7 @@ impl<'a> Parser<'a> { } let (predicates, tuple_struct_body) = - self.parse_where_clause_predicates(struct_, where_lo)?; + self.parse_where_clause_predicates(struct_, where_lo, recover_dupe)?; let where_clause = WhereClause { has_where_token: true, predicates, @@ -459,6 +471,7 @@ impl<'a> Parser<'a> { &mut self, struct_: Option<(Ident, Span)>, where_lo: Span, + recover_dupe: RecoverDupeWhereClause, ) -> PResult<'a, (ThinVec, Option>)> { let mut predicates = ThinVec::new(); let mut tuple_struct_body = None; @@ -522,7 +535,9 @@ impl<'a> Parser<'a> { let prev_token = self.prev_token.span; let ate_comma = self.eat(exp!(Comma)); - if self.eat_keyword_noexpect(kw::Where) { + if let RecoverDupeWhereClause::Yes = recover_dupe + && self.eat_keyword_noexpect(kw::Where) + { self.dcx().emit_err(MultipleWhereClauses { span: self.token.span, previous: pred_lo, diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 0941eb6177fec..3936fc61ea7e3 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -17,6 +17,7 @@ use thin_vec::{ThinVec, thin_vec}; use tracing::debug; use super::diagnostics::ConsumeClosingDelim; +use super::generics::RecoverDupeWhereClause; use super::{ AllowConstBlockItems, AttrWrapper, ExpTokenPair, FnContext, FnParseMode, FollowedByType, ForceCollect, IsDotDotDot, Parser, PathStyle, Recovered, Trailing, UsePreAttrPos, @@ -745,7 +746,7 @@ impl<'a> Parser<'a> { None }; - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; let impl_items = if is_reuse { Default::default() @@ -1173,7 +1174,7 @@ impl<'a> Parser<'a> { } let bounds = self.parse_generic_bounds()?; - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; self.expect_semi()?; let whole_span = lo.to(self.prev_token.span); @@ -1193,7 +1194,7 @@ impl<'a> Parser<'a> { Ok(ItemKind::TraitAlias(Box::new(TraitAlias { constness, ident, generics, bounds }))) } else { // It's a normal trait. - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; let items = self.parse_item_list(attrs, |p| p.parse_trait_item(ForceCollect::No))?; Ok(ItemKind::Trait(Box::new(Trait { impl_restriction, @@ -1286,14 +1287,16 @@ impl<'a> Parser<'a> { let ident = self.parse_ident()?; let mut generics = self.parse_generics()?; - // Parse optional colon and param bounds. let bounds = if self.eat(exp!(Colon)) { self.parse_generic_bounds()? } else { ThinVec::new() }; - generics.where_clause = self.parse_where_clause()?; + + // As a consequence of type aliases being allowed to have two where clauses & + // an optional body, we *must not* artificially reject duplicate where clauses! + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::No)?; let ty = if self.eat(exp!(Eq)) { Some(self.parse_ty()?) } else { None }; - let after_where_clause = self.parse_where_clause()?; + let after_where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; self.expect_semi()?; @@ -1777,12 +1780,15 @@ impl<'a> Parser<'a> { // Proactively parse a where-clause to be able to provide a good error message in case we // encounter the item body following it. - let before_where_clause = - if self.may_recover() { self.parse_where_clause()? } else { WhereClause::default() }; + let before_where_clause = if self.may_recover() { + self.parse_where_clause(RecoverDupeWhereClause::Yes)? + } else { + WhereClause::default() + }; let body = if self.eat(exp!(Eq)) { Some(self.parse_expr()?) } else { None }; - let after_where_clause = self.parse_where_clause()?; + let after_where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; // Provide a nice error message if the user placed a where-clause before the item body. // Users may be tempted to write such code if they are still used to the deprecated @@ -1897,7 +1903,7 @@ impl<'a> Parser<'a> { let prev_span = self.prev_token.span; let ident = self.parse_ident()?; let mut generics = self.parse_generics()?; - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; // Possibly recover `enum Foo;` instead of `enum Foo {}` let (variants, _) = if self.token == TokenKind::Semi { @@ -2090,7 +2096,7 @@ impl<'a> Parser<'a> { // Tuple-style struct definition with optional where-clause. } else if self.token == token::OpenParen { let body = VariantData::Tuple(self.parse_tuple_struct_body()?, DUMMY_NODE_ID); - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; self.expect_semi()?; body } else { @@ -2108,7 +2114,7 @@ impl<'a> Parser<'a> { let mut generics = self.parse_generics()?; let vdata = if self.token.is_keyword(kw::Where) { - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; let (fields, recovered) = self.parse_record_struct_body( "union", ident.span, @@ -2707,7 +2713,7 @@ impl<'a> Parser<'a> { pub fn parse_test_binder_constraints(&mut self) -> PResult<'a, Box> { self.expect_keyword(exp!(Impl))?; let mut generics = self.parse_generics()?; - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; let body = self.parse_test_binder_body()?; Ok(Box::new(TestBinderConstraints { generics, body: Box::new(body) })) } @@ -2720,12 +2726,9 @@ impl<'a> Parser<'a> { Some((Ident { name: sym::predicates, .. }, IdentKind::Normal)) => { let span = this.token.span; this.bump(); - items.extend( - this.parse_where_clause_predicates(None, span)? - .0 - .into_iter() - .map(TestBinderItem::Predicate), - ); + let (preds, _) = + this.parse_where_clause_predicates(None, span, RecoverDupeWhereClause::No)?; + items.extend(preds.into_iter().map(TestBinderItem::Predicate)); } Some((Ident { name: sym::forall, .. }, IdentKind::Normal)) => { items.push(TestBinderItem::Forall(this.parse_test_binder_forall()?)); @@ -2759,7 +2762,7 @@ impl<'a> Parser<'a> { self.bump(); let mut generics = self.parse_generics()?; - generics.where_clause = self.parse_where_clause()?; + generics.where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; let body = self.parse_test_binder_body()?; diff --git a/tests/ui/parser/duplicate-where-clauses.rs b/tests/ui/parser/duplicate-where-clauses.rs index 9eb2ffb06f02d..695456fe52806 100644 --- a/tests/ui/parser/duplicate-where-clauses.rs +++ b/tests/ui/parser/duplicate-where-clauses.rs @@ -16,4 +16,11 @@ fn e() where (): Sized, where (): Sized {} enum F where (): Sized, where (): Sized {} //~^ ERROR cannot define duplicate `where` clauses on an item +// For type aliases, two are fine but >two are not. +#[cfg(false)] +type G +where (): Sized +where (): Sized // OK +where (): Sized; //~ ERROR cannot define duplicate `where` clauses on an item + fn main() {} diff --git a/tests/ui/parser/duplicate-where-clauses.stderr b/tests/ui/parser/duplicate-where-clauses.stderr index 298cdab0c68c3..471c8a8ff6ed1 100644 --- a/tests/ui/parser/duplicate-where-clauses.stderr +++ b/tests/ui/parser/duplicate-where-clauses.stderr @@ -82,5 +82,20 @@ LL - enum F where (): Sized, where (): Sized {} LL + enum F where (): Sized, (): Sized {} | -error: aborting due to 6 previous errors +error: cannot define duplicate `where` clauses on an item + --> $DIR/duplicate-where-clauses.rs:24:7 + | +LL | where (): Sized // OK + | - previous `where` clause starts here +LL | where (): Sized; + | ^ + | +help: consider joining the two `where` clauses into one + | +LL - where (): Sized // OK +LL - where (): Sized; +LL + where (): Sized, (): Sized; + | + +error: aborting due to 7 previous errors diff --git a/tests/ui/type/type-alias-bounds.rs b/tests/ui/type/type-alias-bounds.rs index 37c073fe1f95d..d34422168e063 100644 --- a/tests/ui/type/type-alias-bounds.rs +++ b/tests/ui/type/type-alias-bounds.rs @@ -1,4 +1,4 @@ -// Test `ignored_generic_bounds` lint warning about bounds in type aliases. +// Test lint `type_alias_bounds` that's about bounds in type aliases. //@ check-pass #![allow(dead_code)] diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed index 02dba8f946d6c..387d18775b305 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed @@ -1,14 +1,19 @@ +// Ensure that we permit leading where clauses on associated types in traits +// (for backward compatibility) and lint on them. + //@ check-pass //@ run-rustfix +#![feature(associated_type_defaults)] // only used for some test cases #![allow(dead_code)] -#![feature(associated_type_defaults)] trait Trait { - // Not fine, suggests moving. - type Assoc = () where u32: Copy; + // FIXME(fmease): Emit `deprecated_where_clause_location` here, too: + type Assoc0 where T: Copy where T: Eq; + + type Assoc1 = () where u32: Copy; //~^ WARNING where clause not allowed here - // Not fine, suggests moving `u32: Copy` + type Assoc2 = () where i32: Copy, u32: Copy; //~^ WARNING where clause not allowed here } diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs index a76787413144c..698318bb0d871 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs @@ -1,14 +1,19 @@ +// Ensure that we permit leading where clauses on associated types in traits +// (for backward compatibility) and lint on them. + //@ check-pass //@ run-rustfix +#![feature(associated_type_defaults)] // only used for some test cases #![allow(dead_code)] -#![feature(associated_type_defaults)] trait Trait { - // Not fine, suggests moving. - type Assoc where u32: Copy = (); + // FIXME(fmease): Emit `deprecated_where_clause_location` here, too: + type Assoc0 where T: Copy where T: Eq; + + type Assoc1 where u32: Copy = (); //~^ WARNING where clause not allowed here - // Not fine, suggests moving `u32: Copy` + type Assoc2 where u32: Copy = () where i32: Copy; //~^ WARNING where clause not allowed here } diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr index 1b7c09897b7b8..88a8b2dcccd23 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr @@ -1,19 +1,19 @@ warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-trait.rs:9:16 + --> $DIR/where-clause-placement-assoc-type-in-trait.rs:14:17 | -LL | type Assoc where u32: Copy = (); - | ^^^^^^^^^^^^^^^ +LL | type Assoc1 where u32: Copy = (); + | ^^^^^^^^^^^^^^^ | = note: see issue #89122 for more information = note: `#[warn(deprecated_where_clause_location)]` on by default help: move it to the end of the type declaration | -LL - type Assoc where u32: Copy = (); -LL + type Assoc = () where u32: Copy; +LL - type Assoc1 where u32: Copy = (); +LL + type Assoc1 = () where u32: Copy; | warning: where clause not allowed here - --> $DIR/where-clause-placement-assoc-type-in-trait.rs:12:17 + --> $DIR/where-clause-placement-assoc-type-in-trait.rs:17:17 | LL | type Assoc2 where u32: Copy = () where i32: Copy; | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/where-clauses/where-clause-placement-type-alias.fail.stderr b/tests/ui/where-clauses/where-clause-placement-type-alias.fail.stderr new file mode 100644 index 0000000000000..715e2cfa3e747 --- /dev/null +++ b/tests/ui/where-clauses/where-clause-placement-type-alias.fail.stderr @@ -0,0 +1,38 @@ +error: where clauses are not allowed after the type for type aliases + --> $DIR/where-clause-placement-type-alias.rs:10:34 + | +LL | #[cfg(fail)] type Trailing0 = () where u32: Copy; + | ^^^^^^^^^^^^^^^ + | + = note: see issue #112792 for more information + = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable + +error: where clauses are not allowed after the type for type aliases + --> $DIR/where-clause-placement-type-alias.rs:12:34 + | +LL | #[cfg(fail)] type Trailing1 = () where; + | ^^^^^ + | + = note: see issue #112792 for more information + = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable + +error: where clauses are not allowed after the type for type aliases + --> $DIR/where-clause-placement-type-alias.rs:17:42 + | +LL | #[cfg(fail)] type Both0 where (): = bool where !: Sized; + | ^^^^^^^^^^^^^^ + | + = note: see issue #112792 for more information + = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable + +error: where clauses are not allowed after the type for type aliases + --> $DIR/where-clause-placement-type-alias.rs:19:38 + | +LL | #[cfg(fail)] type Both1 where = bool where; + | ^^^^^ + | + = note: see issue #112792 for more information + = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable + +error: aborting due to 4 previous errors + diff --git a/tests/ui/where-clauses/where-clause-placement-type-alias.pass.stderr b/tests/ui/where-clauses/where-clause-placement-type-alias.pass.stderr new file mode 100644 index 0000000000000..6044cc139db51 --- /dev/null +++ b/tests/ui/where-clauses/where-clause-placement-type-alias.pass.stderr @@ -0,0 +1,18 @@ +warning: where clauses on type aliases are not enforced + --> $DIR/where-clause-placement-type-alias.rs:5:21 + | +LL | type Leading0 where u32: Copy = (); + | ^^^^^^^^^ will not be checked at usage sites of the type alias + | + = note: this is a known limitation of the type checker that may be lifted in a future edition. + see issue #112792 for more information + = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable the desired semantics + = note: `#[warn(type_alias_bounds)]` on by default +help: remove this where clause + | +LL - type Leading0 where u32: Copy = (); +LL + type Leading0 = (); + | + +warning: 1 warning emitted + diff --git a/tests/ui/where-clauses/where-clause-placement-type-alias.rs b/tests/ui/where-clauses/where-clause-placement-type-alias.rs index 1f3b044c8119b..bb8fbc53edad0 100644 --- a/tests/ui/where-clauses/where-clause-placement-type-alias.rs +++ b/tests/ui/where-clauses/where-clause-placement-type-alias.rs @@ -1,11 +1,26 @@ -//@ check-fail - -// Fine, but lints as unused -type Foo where u32: Copy = (); -// Not fine. -type Bar = () where u32: Copy; -//~^ ERROR where clauses are not allowed -type Baz = () where; -//~^ ERROR where clauses are not allowed +// Test the parsing & basic semantic validation of where clauses on (free) type aliases. +//@ revisions: pass fail +//@[pass] check-pass + +type Leading0 where u32: Copy = (); //[pass]~ WARN [type_alias_bounds] +type Leading1 where = (); + +// Syntactically valid, semantically invalid: + +#[cfg(fail)] type Trailing0 = () where u32: Copy; +//[fail]~^ ERROR where clauses are not allowed +#[cfg(fail)] type Trailing1 = () where; +//[fail]~^ ERROR where clauses are not allowed + +// Indeed, we intentionally allow two where-clauses *at once* for backward compatibility. + +#[cfg(fail)] type Both0 where (): = bool where !: Sized; +//[fail]~^ ERROR where clauses are not allowed +#[cfg(fail)] type Both1 where = bool where; +//[fail]~^ ERROR where clauses are not allowed + +// We once used to incorrectly reject `BodilessBoth0` due to overzealous parse error recovery code. +#[cfg(false)] type BodilessBoth0 where [(); 1]: Copy where String: Clone; +#[cfg(false)] type BodilessBoth1 where where; fn main() {} diff --git a/tests/ui/where-clauses/where-clause-placement-type-alias.stderr b/tests/ui/where-clauses/where-clause-placement-type-alias.stderr deleted file mode 100644 index 23798e5fa6124..0000000000000 --- a/tests/ui/where-clauses/where-clause-placement-type-alias.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error: where clauses are not allowed after the type for type aliases - --> $DIR/where-clause-placement-type-alias.rs:6:15 - | -LL | type Bar = () where u32: Copy; - | ^^^^^^^^^^^^^^^ - | - = note: see issue #112792 for more information - = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable - -error: where clauses are not allowed after the type for type aliases - --> $DIR/where-clause-placement-type-alias.rs:8:15 - | -LL | type Baz = () where; - | ^^^^^ - | - = note: see issue #112792 for more information - = help: add `#![feature(checked_type_aliases)]` to the crate attributes to enable - -error: aborting due to 2 previous errors - From 53aa2ed0741e3adf577c88d7fe37988b8523c316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 6 Nov 2026 10:44:26 +0100 Subject: [PATCH 5/6] Make lint `deprecated_where_clause_location` detect leading where clauses on bodiless associated types --- compiler/rustc_ast_passes/src/ast_validation.rs | 10 ++++++---- ...e-clause-placement-assoc-type-in-trait.fixed | 4 ++-- ...here-clause-placement-assoc-type-in-trait.rs | 2 +- ...-clause-placement-assoc-type-in-trait.stderr | 17 +++++++++++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index a6e88f21ba74f..1868c739c34c1 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -192,11 +192,13 @@ impl<'a> AstValidator<'a> { self.disallow_tilde_const = old; } - fn check_type_alias_where_clause_location( + fn check_type_alias_where_clause_not_leading( &mut self, ty_alias: &TyAlias, ) -> Result<(), diagnostics::WhereClauseBeforeTypeAlias> { - if ty_alias.ty.is_none() || !ty_alias.generics.where_clause.has_where_token { + if !ty_alias.generics.where_clause.has_where_token + || ty_alias.ty.is_none() && !ty_alias.after_where_clause.has_where_token + { return Ok(()); } @@ -1722,7 +1724,7 @@ impl Visitor<'_> for AstValidator<'_> { self.check_type_no_bounds(bounds, "this context"); if self.features.checked_type_aliases() { - if let Err(err) = self.check_type_alias_where_clause_location(ty_alias) { + if let Err(err) = self.check_type_alias_where_clause_not_leading(ty_alias) { self.dcx().emit_err(err); } } else if after_where_clause.has_where_token { @@ -2106,7 +2108,7 @@ impl Visitor<'_> for AstValidator<'_> { } if let AssocItemKind::Type(ty_alias) = &item.kind - && let Err(err) = self.check_type_alias_where_clause_location(ty_alias) + && let Err(err) = self.check_type_alias_where_clause_not_leading(ty_alias) { let sugg = match err.sugg { diagnostics::WhereClauseBeforeTypeAliasSugg::Remove { .. } => None, diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed index 387d18775b305..0a4155189fce2 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.fixed @@ -8,8 +8,8 @@ #![allow(dead_code)] trait Trait { - // FIXME(fmease): Emit `deprecated_where_clause_location` here, too: - type Assoc0 where T: Copy where T: Eq; + type Assoc0 where T: Eq, T: Copy; + //~^ WARNING where clause not allowed here type Assoc1 = () where u32: Copy; //~^ WARNING where clause not allowed here diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs index 698318bb0d871..df7140e0ef867 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.rs @@ -8,8 +8,8 @@ #![allow(dead_code)] trait Trait { - // FIXME(fmease): Emit `deprecated_where_clause_location` here, too: type Assoc0 where T: Copy where T: Eq; + //~^ WARNING where clause not allowed here type Assoc1 where u32: Copy = (); //~^ WARNING where clause not allowed here diff --git a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr index 88a8b2dcccd23..415e4feb5c250 100644 --- a/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr +++ b/tests/ui/where-clauses/where-clause-placement-assoc-type-in-trait.stderr @@ -1,3 +1,17 @@ +warning: where clause not allowed here + --> $DIR/where-clause-placement-assoc-type-in-trait.rs:11:20 + | +LL | type Assoc0 where T: Copy where T: Eq; + | ^^^^^^^^^^^^^ + | + = note: see issue #89122 for more information + = note: `#[warn(deprecated_where_clause_location)]` on by default +help: move it to the end of the type declaration + | +LL - type Assoc0 where T: Copy where T: Eq; +LL + type Assoc0 where T: Eq, T: Copy; + | + warning: where clause not allowed here --> $DIR/where-clause-placement-assoc-type-in-trait.rs:14:17 | @@ -5,7 +19,6 @@ LL | type Assoc1 where u32: Copy = (); | ^^^^^^^^^^^^^^^ | = note: see issue #89122 for more information - = note: `#[warn(deprecated_where_clause_location)]` on by default help: move it to the end of the type declaration | LL - type Assoc1 where u32: Copy = (); @@ -25,5 +38,5 @@ LL - type Assoc2 where u32: Copy = () where i32: Copy; LL + type Assoc2 = () where i32: Copy, u32: Copy; | -warning: 2 warnings emitted +warning: 3 warnings emitted From 082df29907bb28ee1e853c5119d27fd419cc4d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 31 Oct 2026 21:58:26 +0100 Subject: [PATCH 6/6] GCI: Reject (literally) `where where` on const items --- compiler/rustc_parse/src/diagnostics.rs | 4 +- compiler/rustc_parse/src/parser/generics.rs | 18 ++++--- compiler/rustc_parse/src/parser/item.rs | 49 ++++++----------- .../duplicate-where-clause.rs | 8 +-- .../duplicate-where-clause.stderr | 20 ++++--- .../ui/parser/duplicate-where-clauses.stderr | 54 +++++++++---------- 6 files changed, 75 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 72947b1c576c8..8bade53ef9ec2 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -2624,7 +2624,7 @@ pub(crate) struct MultipleWhereClauses { code = ",", applicability = "maybe-incorrect" )] - pub between: Span, + pub between: Option, } #[derive(Diagnostic)] @@ -4088,7 +4088,7 @@ pub(crate) struct StaticWithGenerics { #[derive(Diagnostic)] #[diag("where clauses are not allowed before const item bodies")] -pub(crate) struct WhereClauseBeforeConstBody { +pub(crate) struct WhereClauseBeforeConstItemBody { #[primary_span] #[label("unexpected where clause")] pub span: Span, diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 60094aa2822b3..ce84446d986c1 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -527,21 +527,23 @@ impl<'a> Parser<'a> { }); Ok((predicate, Trailing::No, UsePreAttrPos::No)) })?; - match predicate { - Some(predicate) => predicates.push(predicate), - None => break, + + let has_parsed_pred = predicate.is_some(); + if let Some(pred) = predicate { + predicates.push(pred); } - let prev_token = self.prev_token.span; - let ate_comma = self.eat(exp!(Comma)); + let pred_hi = self.prev_token.span; + let ate_comma = has_parsed_pred && self.eat(exp!(Comma)); if let RecoverDupeWhereClause::Yes = recover_dupe && self.eat_keyword_noexpect(kw::Where) { self.dcx().emit_err(MultipleWhereClauses { - span: self.token.span, - previous: pred_lo, - between: prev_token.shrink_to_hi().to(self.prev_token.span), + span: self.prev_token.span, + previous: where_sp, + between: has_parsed_pred + .then(|| pred_hi.shrink_to_hi().to(self.prev_token.span)), }); } else if !ate_comma { break; diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 3936fc61ea7e3..e525dfc3dc29e 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1768,7 +1768,6 @@ impl<'a> Parser<'a> { } // Parse the type of a constant item. That is, the `":" $ty` fragment. - // FIXME: This could maybe benefit from `.may_recover()`? let ty = match ( self.eat(exp!(Colon)), self.check(exp!(Eq)) | self.check(exp!(Semi)) | self.check_keyword(exp!(Where)), @@ -1779,12 +1778,9 @@ impl<'a> Parser<'a> { }; // Proactively parse a where-clause to be able to provide a good error message in case we - // encounter the item body following it. - let before_where_clause = if self.may_recover() { - self.parse_where_clause(RecoverDupeWhereClause::Yes)? - } else { - WhereClause::default() - }; + // encounter the item body following it. We rely on the "duplicate where clause" parse + // error recovery code to reject duplicate where clauses when there's no body. + let mut before_where_clause = self.parse_where_clause(RecoverDupeWhereClause::Yes)?; let body = if self.eat(exp!(Eq)) { Some(self.parse_expr()?) } else { None }; @@ -1796,7 +1792,7 @@ impl<'a> Parser<'a> { if before_where_clause.has_where_token && let Some(body) = &body { - self.dcx().emit_err(diagnostics::WhereClauseBeforeConstBody { + self.dcx().emit_err(diagnostics::WhereClauseBeforeConstItemBody { span: before_where_clause.span, name: ident.span, body: body.span, @@ -1809,37 +1805,26 @@ impl<'a> Parser<'a> { } }) } else { - // FIXME(generic_const_items): Provide a structured suggestion to merge the first - // where-clause into the second one. + // FIXME(generic_const_items): Provide a structured suggestion to merge the + // first where-clause into the second one. None }, }); } - // Merge the predicates of both where-clauses since either one can be relevant. - // If we didn't parse a body (which is valid for associated consts in traits) and we were - // allowed to recover, `before_where_clause` contains the predicates, otherwise they are - // in `after_where_clause`. Further, both of them might contain predicates iff two - // where-clauses were provided which is syntactically ill-formed but we want to recover from - // it and treat them as one large where-clause. - let mut predicates = before_where_clause.predicates; - predicates.extend(after_where_clause.predicates); - let where_clause = WhereClause { - has_where_token: before_where_clause.has_where_token - || after_where_clause.has_where_token, - predicates, - span: if after_where_clause.has_where_token { - after_where_clause.span - } else { - before_where_clause.span - }, - }; - - if where_clause.has_where_token { - self.psess.gated_spans.gate(sym::generic_const_items, where_clause.span); + // Merge the predicates of both where-clauses since either one can be relevant. If we didn't + // parse a body, `before_where_clause` will contain *the* predicates, otherwise they are in + // `after_where_clause`. + before_where_clause.has_where_token |= after_where_clause.has_where_token; + before_where_clause.predicates.extend(after_where_clause.predicates); + if after_where_clause.has_where_token { + before_where_clause.span = after_where_clause.span; } + generics.where_clause = before_where_clause; - generics.where_clause = where_clause; + if generics.where_clause.has_where_token { + self.psess.gated_spans.gate(sym::generic_const_items, generics.where_clause.span); + } let body = if let body @ Some(_) = self.try_recover_const_missing_semi(&body, const_span) { body diff --git a/tests/ui/generic-const-items/duplicate-where-clause.rs b/tests/ui/generic-const-items/duplicate-where-clause.rs index 68da4073fc1f0..96bf536f70e98 100644 --- a/tests/ui/generic-const-items/duplicate-where-clause.rs +++ b/tests/ui/generic-const-items/duplicate-where-clause.rs @@ -1,13 +1,11 @@ #![feature(generic_const_items)] -#![allow(incomplete_features)] trait Tr

{ const K: () where P: Copy - where + where //~ ERROR cannot define duplicate `where` clauses on an item P: Eq; - //~^ ERROR cannot define duplicate `where` clauses on an item } // Test that we error on the first where-clause but also that we don't suggest to swap it with the @@ -24,4 +22,8 @@ impl

Tr

for () { //~^^^^^ ERROR where clauses are not allowed before const item bodies } +// We once used to accidentally accept this: +#[cfg(false)] +const _: () where where; //~ ERROR cannot define duplicate `where` clauses on an item + fn main() {} diff --git a/tests/ui/generic-const-items/duplicate-where-clause.stderr b/tests/ui/generic-const-items/duplicate-where-clause.stderr index 6b742dfafedaa..76751a3ede14c 100644 --- a/tests/ui/generic-const-items/duplicate-where-clause.stderr +++ b/tests/ui/generic-const-items/duplicate-where-clause.stderr @@ -1,11 +1,11 @@ error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clause.rs:9:9 + --> $DIR/duplicate-where-clause.rs:7:5 | +LL | where + | ----- previous `where` clause starts here LL | P: Copy - | - previous `where` clause starts here LL | where -LL | P: Eq; - | ^ + | ^^^^^ | help: consider joining the two `where` clauses into one | @@ -15,7 +15,7 @@ LL + P: Copy, | error: where clauses are not allowed before const item bodies - --> $DIR/duplicate-where-clause.rs:19:5 + --> $DIR/duplicate-where-clause.rs:17:5 | LL | const K: () | - while parsing this const item @@ -25,5 +25,13 @@ LL | | P: Eq LL | = () | -- the item body -error: aborting due to 2 previous errors +error: cannot define duplicate `where` clauses on an item + --> $DIR/duplicate-where-clause.rs:27:19 + | +LL | const _: () where where; + | ----- ^^^^^ + | | + | previous `where` clause starts here + +error: aborting due to 3 previous errors diff --git a/tests/ui/parser/duplicate-where-clauses.stderr b/tests/ui/parser/duplicate-where-clauses.stderr index 471c8a8ff6ed1..26968ec9db593 100644 --- a/tests/ui/parser/duplicate-where-clauses.stderr +++ b/tests/ui/parser/duplicate-where-clauses.stderr @@ -1,10 +1,10 @@ error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:1:32 + --> $DIR/duplicate-where-clauses.rs:1:26 | LL | struct A where (): Sized where (): Sized {} - | - ^ - | | - | previous `where` clause starts here + | ----- ^^^^^ + | | + | previous `where` clause starts here | help: consider joining the two `where` clauses into one | @@ -13,12 +13,12 @@ LL + struct A where (): Sized, (): Sized {} | error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:4:30 + --> $DIR/duplicate-where-clauses.rs:4:24 | LL | fn b() where (): Sized where (): Sized {} - | - ^ - | | - | previous `where` clause starts here + | ----- ^^^^^ + | | + | previous `where` clause starts here | help: consider joining the two `where` clauses into one | @@ -27,12 +27,12 @@ LL + fn b() where (): Sized, (): Sized {} | error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:7:30 + --> $DIR/duplicate-where-clauses.rs:7:24 | LL | enum C where (): Sized where (): Sized {} - | - ^ - | | - | previous `where` clause starts here + | ----- ^^^^^ + | | + | previous `where` clause starts here | help: consider joining the two `where` clauses into one | @@ -41,12 +41,12 @@ LL + enum C where (): Sized, (): Sized {} | error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:10:33 + --> $DIR/duplicate-where-clauses.rs:10:27 | LL | struct D where (): Sized, where (): Sized {} - | - ^ - | | - | previous `where` clause starts here + | ----- ^^^^^ + | | + | previous `where` clause starts here | help: consider joining the two `where` clauses into one | @@ -55,12 +55,12 @@ LL + struct D where (): Sized, (): Sized {} | error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:13:31 + --> $DIR/duplicate-where-clauses.rs:13:25 | LL | fn e() where (): Sized, where (): Sized {} - | - ^ - | | - | previous `where` clause starts here + | ----- ^^^^^ + | | + | previous `where` clause starts here | help: consider joining the two `where` clauses into one | @@ -69,12 +69,12 @@ LL + fn e() where (): Sized, (): Sized {} | error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:16:31 + --> $DIR/duplicate-where-clauses.rs:16:25 | LL | enum F where (): Sized, where (): Sized {} - | - ^ - | | - | previous `where` clause starts here + | ----- ^^^^^ + | | + | previous `where` clause starts here | help: consider joining the two `where` clauses into one | @@ -83,12 +83,12 @@ LL + enum F where (): Sized, (): Sized {} | error: cannot define duplicate `where` clauses on an item - --> $DIR/duplicate-where-clauses.rs:24:7 + --> $DIR/duplicate-where-clauses.rs:24:1 | LL | where (): Sized // OK - | - previous `where` clause starts here + | ----- previous `where` clause starts here LL | where (): Sized; - | ^ + | ^^^^^ | help: consider joining the two `where` clauses into one |