Skip to content

Commit 745c1ea

Browse files
committed
Detect missing => after match guard during parsing
``` error: expected one of `,`, `:`, or `}`, found `.` --> $DIR/missing-fat-arrow.rs:25:14 | LL | Some(a) if a.value == b { | - while parsing this struct LL | a.value = 1; | -^ expected one of `,`, `:`, or `}` | | | while parsing this struct field | help: try naming a field | LL | a: a.value = 1; | ++ help: you might have meant to start a match arm after the match guard | LL | Some(a) if a.value == b => { | ++ ``` Fix rust-lang#78585.
1 parent a6dfd89 commit 745c1ea

13 files changed

+229
-41
lines changed

compiler/rustc_parse/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ parse_expected_else_block = expected `{"{"}`, found {$first_tok}
195195
.label = expected an `if` or a block after this `else`
196196
.suggestion = add an `if` if this is the condition of a chained `else if` statement
197197
198+
parse_expected_struct_field = expected one of `,`, `:`, or `{"}"}`, found `{$token}`
199+
.label = expected one of `,`, `:`, or `{"}"}`
200+
.ident_label = while parsing this struct field
201+
198202
parse_expected_expression_found_let = expected expression, found `let` statement
199203
.note = only supported directly in conditions of `if` and `while` expressions
200204
.not_supported_or = `||` operators are not supported in let chain expressions

compiler/rustc_parse/src/errors.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,17 @@ pub(crate) struct ExpectedElseBlock {
430430
pub condition_start: Span,
431431
}
432432

433+
#[derive(Diagnostic)]
434+
#[diag(parse_expected_struct_field)]
435+
pub(crate) struct ExpectedStructField {
436+
#[primary_span]
437+
#[label]
438+
pub span: Span,
439+
pub token: Token,
440+
#[label(parse_ident_label)]
441+
pub ident_span: Span,
442+
}
443+
433444
#[derive(Diagnostic)]
434445
#[diag(parse_outer_attribute_not_allowed_on_if_else)]
435446
pub(crate) struct OuterAttributeNotAllowedOnIfElse {

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2834,7 +2834,7 @@ impl<'a> Parser<'a> {
28342834
)?;
28352835
let guard = if this.eat_keyword(kw::If) {
28362836
let if_span = this.prev_token.span;
2837-
let mut cond = this.parse_expr_res(Restrictions::ALLOW_LET, None)?;
2837+
let mut cond = this.parse_match_guard_condition()?;
28382838

28392839
CondChecker { parser: this, forbid_let_reason: None }.visit_expr(&mut cond);
28402840

@@ -2860,9 +2860,9 @@ impl<'a> Parser<'a> {
28602860
{
28612861
err.span_suggestion(
28622862
this.token.span,
2863-
"try using a fat arrow here",
2863+
"use a fat arrow to start a match arm",
28642864
"=>",
2865-
Applicability::MaybeIncorrect,
2865+
Applicability::MachineApplicable,
28662866
);
28672867
err.emit();
28682868
this.bump();
@@ -2979,6 +2979,34 @@ impl<'a> Parser<'a> {
29792979
})
29802980
}
29812981

2982+
fn parse_match_guard_condition(&mut self) -> PResult<'a, P<Expr>> {
2983+
self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, None).map_err(
2984+
|mut err| {
2985+
if self.prev_token == token::OpenDelim(Delimiter::Brace) {
2986+
let sugg_sp = self.prev_token.span.shrink_to_lo();
2987+
// Consume everything within the braces, let's avoid further parse
2988+
// errors.
2989+
self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
2990+
let msg = "you might have meant to start a match arm after the match guard";
2991+
if self.eat(&token::CloseDelim(Delimiter::Brace)) {
2992+
let applicability = if self.token.kind != token::FatArrow {
2993+
// We have high confidence that we indeed didn't have a struct
2994+
// literal in the match guard, but rather we had some operation
2995+
// that ended in a path, immediately followed by a block that was
2996+
// meant to be the match arm.
2997+
Applicability::MachineApplicable
2998+
} else {
2999+
Applicability::MaybeIncorrect
3000+
};
3001+
// self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
3002+
err.span_suggestion_verbose(sugg_sp, msg, "=> ".to_string(), applicability);
3003+
}
3004+
}
3005+
err
3006+
},
3007+
)
3008+
}
3009+
29823010
pub(crate) fn is_builtin(&self) -> bool {
29833011
self.token.is_keyword(kw::Builtin) && self.look_ahead(1, |t| *t == token::Pound)
29843012
}
@@ -3049,9 +3077,10 @@ impl<'a> Parser<'a> {
30493077
|| self.look_ahead(2, |t| t == &token::Colon)
30503078
&& (
30513079
// `{ ident: token, ` cannot start a block.
3052-
self.look_ahead(4, |t| t == &token::Comma) ||
3053-
// `{ ident: ` cannot start a block unless it's a type ascription `ident: Type`.
3054-
self.look_ahead(3, |t| !t.can_begin_type())
3080+
self.look_ahead(4, |t| t == &token::Comma)
3081+
// `{ ident: ` cannot start a block unless it's a type ascription
3082+
// `ident: Type`.
3083+
|| self.look_ahead(3, |t| !t.can_begin_type())
30553084
)
30563085
)
30573086
}
@@ -3091,6 +3120,7 @@ impl<'a> Parser<'a> {
30913120
let mut fields = ThinVec::new();
30923121
let mut base = ast::StructRest::None;
30933122
let mut recover_async = false;
3123+
let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD);
30943124

30953125
let mut async_block_err = |e: &mut Diagnostic, span: Span| {
30963126
recover_async = true;
@@ -3128,6 +3158,26 @@ impl<'a> Parser<'a> {
31283158
e.span_label(pth.span, "while parsing this struct");
31293159
}
31303160

3161+
if let Some((ident, _)) = self.token.ident()
3162+
&& !self.token.is_reserved_ident()
3163+
&& self.look_ahead(1, |t| {
3164+
AssocOp::from_token(&t).is_some()
3165+
|| matches!(t.kind, token::OpenDelim(_))
3166+
|| t.kind == token::Dot
3167+
})
3168+
{
3169+
// Looks like they tried to write a shorthand, complex expression.
3170+
e.span_suggestion_verbose(
3171+
self.token.span.shrink_to_lo(),
3172+
"try naming a field",
3173+
&format!("{ident}: ", ),
3174+
Applicability::HasPlaceholders,
3175+
);
3176+
}
3177+
if in_if_guard && close_delim == Delimiter::Brace {
3178+
return Err(e);
3179+
}
3180+
31313181
if !recover {
31323182
return Err(e);
31333183
}
@@ -3173,19 +3223,6 @@ impl<'a> Parser<'a> {
31733223
",",
31743224
Applicability::MachineApplicable,
31753225
);
3176-
} else if is_shorthand
3177-
&& (AssocOp::from_token(&self.token).is_some()
3178-
|| matches!(&self.token.kind, token::OpenDelim(_))
3179-
|| self.token.kind == token::Dot)
3180-
{
3181-
// Looks like they tried to write a shorthand, complex expression.
3182-
let ident = parsed_field.expect("is_shorthand implies Some").ident;
3183-
e.span_suggestion(
3184-
ident.span.shrink_to_lo(),
3185-
"try naming a field",
3186-
&format!("{ident}: "),
3187-
Applicability::HasPlaceholders,
3188-
);
31893226
}
31903227
}
31913228
if !recover {
@@ -3288,6 +3325,24 @@ impl<'a> Parser<'a> {
32883325

32893326
// Check if a colon exists one ahead. This means we're parsing a fieldname.
32903327
let is_shorthand = !this.look_ahead(1, |t| t == &token::Colon || t == &token::Eq);
3328+
// Proactively check whether parsing the field will be correct.
3329+
let is_wrong = this.token.is_ident()
3330+
&& !this.token.is_reserved_ident()
3331+
&& !this.look_ahead(1, |t| {
3332+
t == &token::Colon
3333+
|| t == &token::Eq
3334+
|| t == &token::Comma
3335+
|| t == &token::CloseDelim(Delimiter::Brace)
3336+
|| t == &token::CloseDelim(Delimiter::Parenthesis)
3337+
});
3338+
if is_wrong {
3339+
return Err(errors::ExpectedStructField {
3340+
span: this.look_ahead(1, |t| t.span),
3341+
ident_span: this.token.span,
3342+
token: this.look_ahead(1, |t| t.clone()),
3343+
}
3344+
.into_diagnostic(&self.sess.span_diagnostic));
3345+
}
32913346
let (ident, expr) = if is_shorthand {
32923347
// Mimic `x: x` for the `x` field shorthand.
32933348
let ident = this.parse_ident_common(false)?;

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ bitflags::bitflags! {
5252
const NO_STRUCT_LITERAL = 1 << 1;
5353
const CONST_EXPR = 1 << 2;
5454
const ALLOW_LET = 1 << 3;
55+
const IN_IF_GUARD = 1 << 4;
5556
}
5657
}
5758

tests/ui/parser/issues/issue-15980.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ fn main(){
99
//~^ ERROR expected identifier, found keyword `return`
1010
//~| NOTE expected identifier, found keyword
1111
}
12-
//~^ NOTE expected one of `.`, `=>`, `?`, or an operator
1312
_ => {}
14-
//~^ ERROR expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
15-
//~| NOTE unexpected token
1613
}
1714
}

tests/ui/parser/issues/issue-15980.stderr

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,10 @@ help: escape `return` to use it as an identifier
1111
|
1212
LL | r#return
1313
| ++
14-
15-
error: expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
16-
--> $DIR/issue-15980.rs:13:9
14+
help: you might have meant to start a match arm after the match guard
1715
|
18-
LL | }
19-
| - expected one of `.`, `=>`, `?`, or an operator
20-
LL |
21-
LL | _ => {}
22-
| ^ unexpected token
16+
LL | Err(ref e) if e.kind == io::EndOfFile => {
17+
| ++
2318

24-
error: aborting due to 2 previous errors
19+
error: aborting due to previous error
2520

tests/ui/parser/issues/issue-52496.stderr

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ error: expected one of `,`, `:`, or `}`, found `.`
88
--> $DIR/issue-52496.rs:8:22
99
|
1010
LL | let _ = Foo { bar.into(), bat: -1, . };
11-
| --- - ^ expected one of `,`, `:`, or `}`
11+
| --- ---^ expected one of `,`, `:`, or `}`
1212
| | |
13-
| | help: try naming a field: `bar:`
13+
| | while parsing this struct field
1414
| while parsing this struct
15+
|
16+
help: try naming a field
17+
|
18+
LL | let _ = Foo { bar: bar.into(), bat: -1, . };
19+
| ++++
1520

1621
error: expected identifier, found `.`
1722
--> $DIR/issue-52496.rs:8:40

tests/ui/parser/issues/issue-89396.fixed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ fn main() {
88
let _ = match opt {
99
Some(_) => true,
1010
//~^ ERROR: expected one of
11-
//~| HELP: try using a fat arrow here
11+
//~| HELP: use a fat arrow to start a match arm
1212
None => false,
1313
//~^ ERROR: expected one of
14-
//~| HELP: try using a fat arrow here
14+
//~| HELP: use a fat arrow to start a match arm
1515
};
1616
}

tests/ui/parser/issues/issue-89396.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ fn main() {
88
let _ = match opt {
99
Some(_) = true,
1010
//~^ ERROR: expected one of
11-
//~| HELP: try using a fat arrow here
11+
//~| HELP: use a fat arrow to start a match arm
1212
None -> false,
1313
//~^ ERROR: expected one of
14-
//~| HELP: try using a fat arrow here
14+
//~| HELP: use a fat arrow to start a match arm
1515
};
1616
}

tests/ui/parser/issues/issue-89396.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ LL | Some(_) = true,
55
| ^
66
| |
77
| expected one of `=>`, `if`, or `|`
8-
| help: try using a fat arrow here: `=>`
8+
| help: use a fat arrow to start a match arm: `=>`
99

1010
error: expected one of `=>`, `@`, `if`, or `|`, found `->`
1111
--> $DIR/issue-89396.rs:12:14
@@ -14,7 +14,7 @@ LL | None -> false,
1414
| ^^
1515
| |
1616
| expected one of `=>`, `@`, `if`, or `|`
17-
| help: try using a fat arrow here: `=>`
17+
| help: use a fat arrow to start a match arm: `=>`
1818

1919
error: aborting due to 2 previous errors
2020

tests/ui/parser/missing-fat-arrow.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
fn main() {
2+
let x = 1;
3+
let y = 2;
4+
let value = 3;
5+
6+
match value {
7+
Some(x) if x == y {
8+
self.next_token()?; //~ ERROR expected identifier, found keyword `self`
9+
Ok(true)
10+
},
11+
_ => {
12+
Ok(false)
13+
}
14+
}
15+
let _: i32 = (); //~ ERROR mismatched types
16+
}
17+
18+
struct Foo {
19+
value: usize
20+
}
21+
22+
fn foo(a: Option<&mut Foo>, b: usize) {
23+
match a {
24+
Some(a) if a.value == b {
25+
a.value = 1; //~ ERROR expected one of `,`, `:`, or `}`, found `.`
26+
},
27+
_ => {}
28+
}
29+
let _: i32 = (); //~ ERROR mismatched types
30+
}
31+
32+
fn bar(a: Option<&mut Foo>, b: usize) {
33+
match a {
34+
Some(a) if a.value == b {
35+
a.value, //~ ERROR expected one of `,`, `:`, or `}`, found `.`
36+
} => {
37+
}
38+
_ => {}
39+
}
40+
let _: i32 = (); //~ ERROR mismatched types
41+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
error: expected identifier, found keyword `self`
2+
--> $DIR/missing-fat-arrow.rs:8:13
3+
|
4+
LL | Some(x) if x == y {
5+
| - while parsing this struct
6+
LL | self.next_token()?;
7+
| ^^^^ expected identifier, found keyword
8+
|
9+
help: you might have meant to start a match arm after the match guard
10+
|
11+
LL | Some(x) if x == y => {
12+
| ++
13+
14+
error: expected one of `,`, `:`, or `}`, found `.`
15+
--> $DIR/missing-fat-arrow.rs:25:14
16+
|
17+
LL | Some(a) if a.value == b {
18+
| - while parsing this struct
19+
LL | a.value = 1;
20+
| -^ expected one of `,`, `:`, or `}`
21+
| |
22+
| while parsing this struct field
23+
|
24+
help: try naming a field
25+
|
26+
LL | a: a.value = 1;
27+
| ++
28+
help: you might have meant to start a match arm after the match guard
29+
|
30+
LL | Some(a) if a.value == b => {
31+
| ++
32+
33+
error: expected one of `,`, `:`, or `}`, found `.`
34+
--> $DIR/missing-fat-arrow.rs:35:14
35+
|
36+
LL | Some(a) if a.value == b {
37+
| - while parsing this struct
38+
LL | a.value,
39+
| -^ expected one of `,`, `:`, or `}`
40+
| |
41+
| while parsing this struct field
42+
|
43+
help: try naming a field
44+
|
45+
LL | a: a.value,
46+
| ++
47+
help: you might have meant to start a match arm after the match guard
48+
|
49+
LL | Some(a) if a.value == b => {
50+
| ++
51+
52+
error[E0308]: mismatched types
53+
--> $DIR/missing-fat-arrow.rs:15:18
54+
|
55+
LL | let _: i32 = ();
56+
| --- ^^ expected `i32`, found `()`
57+
| |
58+
| expected due to this
59+
60+
error[E0308]: mismatched types
61+
--> $DIR/missing-fat-arrow.rs:29:18
62+
|
63+
LL | let _: i32 = ();
64+
| --- ^^ expected `i32`, found `()`
65+
| |
66+
| expected due to this
67+
68+
error[E0308]: mismatched types
69+
--> $DIR/missing-fat-arrow.rs:40:18
70+
|
71+
LL | let _: i32 = ();
72+
| --- ^^ expected `i32`, found `()`
73+
| |
74+
| expected due to this
75+
76+
error: aborting due to 6 previous errors
77+
78+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)