Rollup merge of #155698 - fmease:no-struct-pat-tuple-index-shorthand, r=mu001999

Syntactically reject tuple index shorthands in struct patterns to fix a correctness regression

Split out of PR rust-lang/rust#154492. This fixes a correctness regression introduced in PR rust-lang/rust#81235 from 2021. Crater was run in my other PR and didn't report any real regressions (https://github.com/rust-lang/rust/pull/154492#issuecomment-4187544786); a rerun has been issued for a few spurious builds (https://github.com/rust-lang/rust/pull/154492#issuecomment-4237077272) but I'm certain it won't find anything either.

This is a theoretical breaking change that doesn't need any T-lang input IMHO since it's such a minute, niche and crystal clear bug that's not worth bothering them with (such a decision is not unprecedented). I'm adding it to the compatibility section of the release notes as is customary.

The Reference doesn't need updating since it didn't adopt this bug and thus accurately describes this part of the grammar as it used to be before 2021-02-23 and as it's meant to be.

The majority of the diff is doc comment additions & necessary UI test restructurings.
This commit is contained in:
Jacob Pratt
2026-04-24 02:42:52 -04:00
committed by GitHub
16 changed files with 160 additions and 106 deletions
+1 -1
View File
@@ -866,7 +866,7 @@ fn parse_borrow_modifiers(&mut self) -> (ast::BorrowKind, ast::Mutability) {
// `raw [ const | mut ]`.
let found_raw = self.eat_keyword(exp!(Raw));
assert!(found_raw);
let mutability = self.parse_const_or_mut().unwrap();
let mutability = self.parse_mut_or_const().unwrap();
(ast::BorrowKind::Raw, mutability)
} else {
match self.parse_pin_and_mut() {
+17 -4
View File
@@ -1302,12 +1302,16 @@ fn parse_const_block(&mut self, span: Span, pat: bool) -> PResult<'a, Box<Expr>>
Ok(self.mk_expr_with_attrs(span.to(blk_span), kind, attrs))
}
/// Parses mutability (`mut` or nothing).
/// Parse nothing or `mut`.
fn parse_mutability(&mut self) -> Mutability {
if self.eat_keyword(exp!(Mut)) { Mutability::Mut } else { Mutability::Not }
}
/// Parses reference binding mode (`ref`, `ref mut`, `ref pin const`, `ref pin mut`, or nothing).
/// Parse nothing or a by-reference mode.
///
/// ```ebnf
/// ByRef = "ref" PinAndMut?
/// ```
fn parse_byref(&mut self) -> ByRef {
if self.eat_keyword(exp!(Ref)) {
let (pinnedness, mutability) = self.parse_pin_and_mut();
@@ -1317,8 +1321,12 @@ fn parse_byref(&mut self) -> ByRef {
}
}
/// Possibly parses mutability (`const` or `mut`).
fn parse_const_or_mut(&mut self) -> Option<Mutability> {
/// Parse nothing or "explicit" mutability.
///
/// ```ebnf
/// MutOrConst = "mut" | "const"
/// ```
fn parse_mut_or_const(&mut self) -> Option<Mutability> {
if self.eat_keyword(exp!(Mut)) {
Some(Mutability::Mut)
} else if self.eat_keyword(exp!(Const)) {
@@ -1328,6 +1336,11 @@ fn parse_const_or_mut(&mut self) -> Option<Mutability> {
}
}
/// Parse a field name.
///
/// ```enbf
/// FieldName = IntLit | Ident
/// ```
fn parse_field_name(&mut self) -> PResult<'a, Ident> {
if let token::Literal(token::Lit { kind: token::Integer, symbol, suffix }) = self.token.kind
{
+6 -4
View File
@@ -1731,11 +1731,14 @@ fn recover_bad_dot_dot(&self) {
self.dcx().emit_err(DotDotDotForRemainingFields { span: self.token.span, token_str });
}
/// Parse a field in a struct pattern.
///
/// ```ebnf
/// PatField = FieldName ":" Pat | "box"? "mut"? ByRef? Ident
/// ```
fn parse_pat_field(&mut self, lo: Span, attrs: AttrVec) -> PResult<'a, PatField> {
// Check if a colon exists one ahead. This means we're parsing a fieldname.
let hi;
let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) {
// Parsing a pattern of the form `fieldname: pat`.
let fieldname = self.parse_field_name()?;
self.bump();
let pat = self.parse_pat_allow_top_guard(
@@ -1747,7 +1750,6 @@ fn parse_pat_field(&mut self, lo: Span, attrs: AttrVec) -> PResult<'a, PatField>
hi = pat.span;
(pat, fieldname, false)
} else {
// Parsing a pattern of the form `(box) (ref) (mut) fieldname`.
let is_box = self.eat_keyword(exp!(Box));
if is_box {
self.psess.gated_spans.gate(sym::box_patterns, self.prev_token.span);
@@ -1756,7 +1758,7 @@ fn parse_pat_field(&mut self, lo: Span, attrs: AttrVec) -> PResult<'a, PatField>
let mutability = self.parse_mutability();
let by_ref = self.parse_byref();
let fieldname = self.parse_field_name()?;
let fieldname = self.parse_ident_common(false)?;
hi = self.prev_token.span;
let ann = BindingMode(by_ref, mutability);
let fieldpat = self.mk_pat_ident(boxed_span.to(hi), ann, fieldname);
+7 -5
View File
@@ -596,7 +596,7 @@ fn parse_remaining_bounds(
/// Parses a raw pointer with a C-style typo
fn parse_ty_c_style_pointer(&mut self) -> PResult<'a, TyKind> {
let kw_span = self.token.span;
let mutbl = self.parse_const_or_mut();
let mutbl = self.parse_mut_or_const();
if let Some(mutbl) = mutbl
&& self.eat(exp!(Star))
@@ -630,7 +630,7 @@ fn parse_ty_c_style_pointer(&mut self) -> PResult<'a, TyKind> {
/// Parses a raw pointer type: `*[const | mut] $type`.
fn parse_ty_ptr(&mut self) -> PResult<'a, TyKind> {
let mutbl = self.parse_const_or_mut().unwrap_or_else(|| {
let mutbl = self.parse_mut_or_const().unwrap_or_else(|| {
let span = self.prev_token.span;
self.dcx().emit_err(ExpectedMutOrConstInRawPointerType {
span,
@@ -774,14 +774,16 @@ fn parse_borrowed_pointee(&mut self) -> PResult<'a, TyKind> {
})
}
/// Parses `pin` and `mut` annotations on references, patterns, or borrow modifiers.
/// Parse nothing, mutability or `pin` followed by "explicit" mutability.
///
/// It must be either `pin const`, `pin mut`, `mut`, or nothing (immutable).
/// ```ebnf
/// PinAndMut = "pin" MutOrConst | "mut"
/// ```
pub(crate) fn parse_pin_and_mut(&mut self) -> (Pinnedness, Mutability) {
if self.token.is_ident_named(sym::pin) && self.look_ahead(1, Token::is_mutability) {
self.psess.gated_spans.gate(sym::pin_ergonomics, self.token.span);
assert!(self.eat_keyword(exp!(Pin)));
let mutbl = self.parse_const_or_mut().unwrap();
let mutbl = self.parse_mut_or_const().unwrap();
(Pinnedness::Pinned, mutbl)
} else {
(Pinnedness::Not, self.parse_mutability())
@@ -0,0 +1,19 @@
// Check that tuple indices in struct exprs & pats don't have a shorthand.
// If they did it would be possible to bind and reference numeric identifiers
// which is undesirable.
struct Rgb(u8, u8, u8);
#[cfg(false)] // ensures that this is a *syntax* error, not just a semantic one!
fn scope() {
// FIXME: Better recover and also report a diagnostic for the other two fields.
let Rgb { 0, 1, 2 };
//~^ ERROR expected identifier, found `0`
let _ = Rgb { 0, 1, 2 };
//~^ ERROR expected identifier, found `0`
//~| ERROR expected identifier, found `1`
//~| ERROR expected identifier, found `2`
}
fn main() {}
@@ -1,5 +1,13 @@
error: expected identifier, found `0`
--> $DIR/struct-field-numeric-shorthand.rs:4:19
--> $DIR/struct-expr-pat-tuple-index-shorthand.rs:10:15
|
LL | let Rgb { 0, 1, 2 };
| --- ^ expected identifier
| |
| while parsing the fields for this pattern
error: expected identifier, found `0`
--> $DIR/struct-expr-pat-tuple-index-shorthand.rs:13:19
|
LL | let _ = Rgb { 0, 1, 2 };
| --- ^ expected identifier
@@ -7,7 +15,7 @@ LL | let _ = Rgb { 0, 1, 2 };
| while parsing this struct
error: expected identifier, found `1`
--> $DIR/struct-field-numeric-shorthand.rs:4:22
--> $DIR/struct-expr-pat-tuple-index-shorthand.rs:13:22
|
LL | let _ = Rgb { 0, 1, 2 };
| --- ^ expected identifier
@@ -15,12 +23,12 @@ LL | let _ = Rgb { 0, 1, 2 };
| while parsing this struct
error: expected identifier, found `2`
--> $DIR/struct-field-numeric-shorthand.rs:4:25
--> $DIR/struct-expr-pat-tuple-index-shorthand.rs:13:25
|
LL | let _ = Rgb { 0, 1, 2 };
| --- ^ expected identifier
| |
| while parsing this struct
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors
@@ -1,8 +0,0 @@
struct Rgb(u8, u8, u8);
fn main() {
let _ = Rgb { 0, 1, 2 };
//~^ ERROR expected identifier, found `0`
//~| ERROR expected identifier, found `1`
//~| ERROR expected identifier, found `2`
}
+3 -1
View File
@@ -2,7 +2,9 @@ error: expected identifier, found keyword `Self`
--> $DIR/self-ctor-133272.rs:17:21
|
LL | let S { ref Self } = todo!();
| ^^^^ expected identifier, found keyword
| - ^^^^ expected identifier, found keyword
| |
| while parsing the fields for this pattern
error[E0422]: cannot find struct, variant or union type `S` in this scope
--> $DIR/self-ctor-133272.rs:17:13
-1
View File
@@ -22,7 +22,6 @@ pub fn main() {
Foo { Self } => (),
//~^ ERROR expected identifier, found keyword `Self`
//~| ERROR mismatched types
//~| ERROR `Foo` does not have a field named `Self`
}
}
+9 -13
View File
@@ -39,22 +39,24 @@ error: expected identifier, found keyword `Self`
--> $DIR/self_type_keyword.rs:22:15
|
LL | Foo { Self } => (),
| ^^^^ expected identifier, found keyword
| --- ^^^^ expected identifier, found keyword
| |
| while parsing the fields for this pattern
error: expected identifier, found keyword `Self`
--> $DIR/self_type_keyword.rs:30:26
--> $DIR/self_type_keyword.rs:29:26
|
LL | extern crate core as Self;
| ^^^^ expected identifier, found keyword
error: expected identifier, found keyword `Self`
--> $DIR/self_type_keyword.rs:35:32
--> $DIR/self_type_keyword.rs:34:32
|
LL | use std::option::Option as Self;
| ^^^^ expected identifier, found keyword
error: expected identifier, found keyword `Self`
--> $DIR/self_type_keyword.rs:40:11
--> $DIR/self_type_keyword.rs:39:11
|
LL | trait Self {}
| ^^^^ expected identifier, found keyword
@@ -88,13 +90,7 @@ LL | match 15 {
LL | Foo { Self } => (),
| ^^^^^^^^^^^^ expected integer, found `Foo`
error[E0026]: struct `Foo` does not have a field named `Self`
--> $DIR/self_type_keyword.rs:22:15
|
LL | Foo { Self } => (),
| ^^^^ struct `Foo` does not have this field
error: aborting due to 12 previous errors
error: aborting due to 13 previous errors
Some errors have detailed explanations: E0026, E0308, E0392, E0531.
For more information about an error, try `rustc --explain E0026`.
Some errors have detailed explanations: E0308, E0392, E0531.
For more information about an error, try `rustc --explain E0308`.
@@ -0,0 +1,11 @@
// On unmentioned tuple indices in struct patterns, don't suggest turning the pattern into a
// tuple struct pattern and keep the struct pattern in the suggestion.
// issue: <https://github.com/rust-lang/rust/issues/108284>
struct S(i32, f32);
enum E { V(i32, f32) }
fn main() {
let S { 0: _ } = S(1, 2.2); //~ ERROR: pattern does not mention field `1`
let E::V { 0: _ } = E::V(1, 2.2); //~ ERROR: pattern does not mention field `1`
}
@@ -0,0 +1,41 @@
error[E0027]: pattern does not mention field `1`
--> $DIR/struct-pat-unmentioned-tuple-indices.rs:9:9
|
LL | let S { 0: _ } = S(1, 2.2);
| ^^^^^^^^^^ missing field `1`
|
help: include the missing field in the pattern
|
LL | let S { 0: _, 1: _ } = S(1, 2.2);
| ++++++
help: if you don't care about this missing field, you can explicitly ignore it
|
LL | let S { 0: _, 1: _ } = S(1, 2.2);
| ++++++
help: or always ignore missing fields here
|
LL | let S { 0: _, .. } = S(1, 2.2);
| ++++
error[E0027]: pattern does not mention field `1`
--> $DIR/struct-pat-unmentioned-tuple-indices.rs:10:9
|
LL | let E::V { 0: _ } = E::V(1, 2.2);
| ^^^^^^^^^^^^^ missing field `1`
|
help: include the missing field in the pattern
|
LL | let E::V { 0: _, 1: _ } = E::V(1, 2.2);
| ++++++
help: if you don't care about this missing field, you can explicitly ignore it
|
LL | let E::V { 0: _, 1: _ } = E::V(1, 2.2);
| ++++++
help: or always ignore missing fields here
|
LL | let E::V { 0: _, .. } = E::V(1, 2.2);
| ++++
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0027`.
@@ -1,18 +0,0 @@
struct S(i32, f32);
enum E {
S(i32, f32),
}
fn main() {
let x = E::S(1, 2.2);
match x {
E::S { 0, 1 } => {}
//~^ ERROR tuple variant `E::S` written as struct variant [E0769]
}
let y = S(1, 2.2);
match y {
S { } => {} //~ ERROR: tuple variant `S` written as struct variant [E0769]
}
if let E::S { 0: a } = x { //~ ERROR: pattern does not mention field `1`
}
}
@@ -1,47 +0,0 @@
error[E0769]: tuple variant `E::S` written as struct variant
--> $DIR/struct-tuple-field-names.rs:8:9
|
LL | E::S { 0, 1 } => {}
| ^^^^^^^^^^^^^
|
help: use the tuple variant pattern syntax instead
|
LL - E::S { 0, 1 } => {}
LL + E::S(_, _) => {}
|
error[E0769]: tuple variant `S` written as struct variant
--> $DIR/struct-tuple-field-names.rs:13:9
|
LL | S { } => {}
| ^^^^^
|
help: use the tuple variant pattern syntax instead
|
LL - S { } => {}
LL + S(_, _) => {}
|
error[E0027]: pattern does not mention field `1`
--> $DIR/struct-tuple-field-names.rs:16:12
|
LL | if let E::S { 0: a } = x {
| ^^^^^^^^^^^^^ missing field `1`
|
help: include the missing field in the pattern
|
LL | if let E::S { 0: a, 1: _ } = x {
| ++++++
help: if you don't care about this missing field, you can explicitly ignore it
|
LL | if let E::S { 0: a, 1: _ } = x {
| ++++++
help: or always ignore missing fields here
|
LL | if let E::S { 0: a, .. } = x {
| ++++
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0027, E0769.
For more information about an error, try `rustc --explain E0027`.
@@ -0,0 +1,7 @@
struct S(i32);
enum E { V(i32) }
fn main() {
let S {} = S(0); //~ ERROR tuple variant `S` written as struct variant
let E::V {} = E::V(0); //~ ERROR tuple variant `E::V` written as struct variant
}
@@ -0,0 +1,27 @@
error[E0769]: tuple variant `S` written as struct variant
--> $DIR/tuple-variant-written-as-empty-struct-variant.rs:5:9
|
LL | let S {} = S(0);
| ^^^^
|
help: use the tuple variant pattern syntax instead
|
LL - let S {} = S(0);
LL + let S(_) = S(0);
|
error[E0769]: tuple variant `E::V` written as struct variant
--> $DIR/tuple-variant-written-as-empty-struct-variant.rs:6:9
|
LL | let E::V {} = E::V(0);
| ^^^^^^^
|
help: use the tuple variant pattern syntax instead
|
LL - let E::V {} = E::V(0);
LL + let E::V(_) = E::V(0);
|
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0769`.