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

This commit is contained in:
León Orell Valerian Liehr
2026-04-23 21:45:54 +02:00
parent 827651f220
commit 07d015e566
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`.