From 07d015e566dc55235094ee4a48407253ca8246c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 23 Apr 2026 21:45:54 +0200 Subject: [PATCH] Syntactically reject tuple index shorthands in struct patterns to fix a correctness regression --- compiler/rustc_parse/src/parser/expr.rs | 2 +- compiler/rustc_parse/src/parser/mod.rs | 21 +++++++-- compiler/rustc_parse/src/parser/pat.rs | 10 ++-- compiler/rustc_parse/src/parser/ty.rs | 12 +++-- .../struct-expr-pat-tuple-index-shorthand.rs | 19 ++++++++ ...uct-expr-pat-tuple-index-shorthand.stderr} | 16 +++++-- .../parser/struct-field-numeric-shorthand.rs | 8 ---- tests/ui/pattern/self-ctor-133272.stderr | 4 +- tests/ui/self/self_type_keyword.rs | 1 - tests/ui/self/self_type_keyword.stderr | 22 ++++----- .../struct-pat-unmentioned-tuple-indices.rs | 11 +++++ ...truct-pat-unmentioned-tuple-indices.stderr | 41 ++++++++++++++++ tests/ui/structs/struct-tuple-field-names.rs | 18 ------- .../structs/struct-tuple-field-names.stderr | 47 ------------------- ...variant-written-as-empty-struct-variant.rs | 7 +++ ...ant-written-as-empty-struct-variant.stderr | 27 +++++++++++ 16 files changed, 160 insertions(+), 106 deletions(-) create mode 100644 tests/ui/parser/struct-expr-pat-tuple-index-shorthand.rs rename tests/ui/parser/{struct-field-numeric-shorthand.stderr => struct-expr-pat-tuple-index-shorthand.stderr} (55%) delete mode 100644 tests/ui/parser/struct-field-numeric-shorthand.rs create mode 100644 tests/ui/structs/struct-pat-unmentioned-tuple-indices.rs create mode 100644 tests/ui/structs/struct-pat-unmentioned-tuple-indices.stderr delete mode 100644 tests/ui/structs/struct-tuple-field-names.rs delete mode 100644 tests/ui/structs/struct-tuple-field-names.stderr create mode 100644 tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.rs create mode 100644 tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.stderr diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index a0601cc71d03..d55548dd7218 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -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() { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 8c1c3c7025f5..9303be8ff0cc 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -1302,12 +1302,16 @@ fn parse_const_block(&mut self, span: Span, pat: bool) -> PResult<'a, Box> 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 { + /// Parse nothing or "explicit" mutability. + /// + /// ```ebnf + /// MutOrConst = "mut" | "const" + /// ``` + fn parse_mut_or_const(&mut self) -> Option { 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 { } } + /// 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 { diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index bdcbb1eb42a8..b5c33d740872 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -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); diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 2a5cd4cd55ae..072975f445bf 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -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()) diff --git a/tests/ui/parser/struct-expr-pat-tuple-index-shorthand.rs b/tests/ui/parser/struct-expr-pat-tuple-index-shorthand.rs new file mode 100644 index 000000000000..fde5563cd4b8 --- /dev/null +++ b/tests/ui/parser/struct-expr-pat-tuple-index-shorthand.rs @@ -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() {} diff --git a/tests/ui/parser/struct-field-numeric-shorthand.stderr b/tests/ui/parser/struct-expr-pat-tuple-index-shorthand.stderr similarity index 55% rename from tests/ui/parser/struct-field-numeric-shorthand.stderr rename to tests/ui/parser/struct-expr-pat-tuple-index-shorthand.stderr index 9878cfbbdceb..5a618f1d3383 100644 --- a/tests/ui/parser/struct-field-numeric-shorthand.stderr +++ b/tests/ui/parser/struct-expr-pat-tuple-index-shorthand.stderr @@ -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 diff --git a/tests/ui/parser/struct-field-numeric-shorthand.rs b/tests/ui/parser/struct-field-numeric-shorthand.rs deleted file mode 100644 index aa342eb02a91..000000000000 --- a/tests/ui/parser/struct-field-numeric-shorthand.rs +++ /dev/null @@ -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` -} diff --git a/tests/ui/pattern/self-ctor-133272.stderr b/tests/ui/pattern/self-ctor-133272.stderr index bca55a43d9c0..f3053a3e7d79 100644 --- a/tests/ui/pattern/self-ctor-133272.stderr +++ b/tests/ui/pattern/self-ctor-133272.stderr @@ -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 diff --git a/tests/ui/self/self_type_keyword.rs b/tests/ui/self/self_type_keyword.rs index 463872585f94..70eb539bb1fd 100644 --- a/tests/ui/self/self_type_keyword.rs +++ b/tests/ui/self/self_type_keyword.rs @@ -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` } } diff --git a/tests/ui/self/self_type_keyword.stderr b/tests/ui/self/self_type_keyword.stderr index 06761bb40c03..b839bec0b8b7 100644 --- a/tests/ui/self/self_type_keyword.stderr +++ b/tests/ui/self/self_type_keyword.stderr @@ -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`. diff --git a/tests/ui/structs/struct-pat-unmentioned-tuple-indices.rs b/tests/ui/structs/struct-pat-unmentioned-tuple-indices.rs new file mode 100644 index 000000000000..1f637d34c1ab --- /dev/null +++ b/tests/ui/structs/struct-pat-unmentioned-tuple-indices.rs @@ -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: + +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` +} diff --git a/tests/ui/structs/struct-pat-unmentioned-tuple-indices.stderr b/tests/ui/structs/struct-pat-unmentioned-tuple-indices.stderr new file mode 100644 index 000000000000..bc5480c26bbd --- /dev/null +++ b/tests/ui/structs/struct-pat-unmentioned-tuple-indices.stderr @@ -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`. diff --git a/tests/ui/structs/struct-tuple-field-names.rs b/tests/ui/structs/struct-tuple-field-names.rs deleted file mode 100644 index 33f264aa2509..000000000000 --- a/tests/ui/structs/struct-tuple-field-names.rs +++ /dev/null @@ -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` - } -} diff --git a/tests/ui/structs/struct-tuple-field-names.stderr b/tests/ui/structs/struct-tuple-field-names.stderr deleted file mode 100644 index 953f01e1fb6c..000000000000 --- a/tests/ui/structs/struct-tuple-field-names.stderr +++ /dev/null @@ -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`. diff --git a/tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.rs b/tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.rs new file mode 100644 index 000000000000..3382ac9e868a --- /dev/null +++ b/tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.rs @@ -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 +} diff --git a/tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.stderr b/tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.stderr new file mode 100644 index 000000000000..ef63238b0040 --- /dev/null +++ b/tests/ui/tuple/tuple-variant-written-as-empty-struct-variant.stderr @@ -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`.