Skip to content

Detect missing => after match guard during parsing #116400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 7, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
@@ -226,6 +226,10 @@ parse_expected_semi_found_str = expected `;`, found `{$token}`

parse_expected_statement_after_outer_attr = expected statement after outer attribute

parse_expected_struct_field = expected one of `,`, `:`, or `{"}"}`, found `{$token}`
.label = expected one of `,`, `:`, or `{"}"}`
.ident_label = while parsing this struct field

parse_expected_trait_in_trait_impl_found_type = expected a trait, found type

parse_extern_crate_name_with_dashes = crate name using dashes are not valid in `extern crate` statements
11 changes: 11 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
@@ -430,6 +430,17 @@ pub(crate) struct ExpectedElseBlock {
pub condition_start: Span,
}

#[derive(Diagnostic)]
#[diag(parse_expected_struct_field)]
pub(crate) struct ExpectedStructField {
#[primary_span]
#[label]
pub span: Span,
pub token: Token,
#[label(parse_ident_label)]
pub ident_span: Span,
}

#[derive(Diagnostic)]
#[diag(parse_outer_attribute_not_allowed_on_if_else)]
pub(crate) struct OuterAttributeNotAllowedOnIfElse {
92 changes: 73 additions & 19 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
@@ -2834,7 +2834,7 @@ impl<'a> Parser<'a> {
)?;
let guard = if this.eat_keyword(kw::If) {
let if_span = this.prev_token.span;
let mut cond = this.parse_expr_res(Restrictions::ALLOW_LET, None)?;
let mut cond = this.parse_match_guard_condition()?;

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

@@ -2860,9 +2860,9 @@ impl<'a> Parser<'a> {
{
err.span_suggestion(
this.token.span,
"try using a fat arrow here",
"use a fat arrow to start a match arm",
"=>",
Applicability::MaybeIncorrect,
Applicability::MachineApplicable,
);
err.emit();
this.bump();
@@ -2979,6 +2979,33 @@ impl<'a> Parser<'a> {
})
}

fn parse_match_guard_condition(&mut self) -> PResult<'a, P<Expr>> {
self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, None).map_err(
|mut err| {
if self.prev_token == token::OpenDelim(Delimiter::Brace) {
let sugg_sp = self.prev_token.span.shrink_to_lo();
// Consume everything within the braces, let's avoid further parse
// errors.
self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
let msg = "you might have meant to start a match arm after the match guard";
if self.eat(&token::CloseDelim(Delimiter::Brace)) {
let applicability = if self.token.kind != token::FatArrow {
// We have high confidence that we indeed didn't have a struct
// literal in the match guard, but rather we had some operation
// that ended in a path, immediately followed by a block that was
// meant to be the match arm.
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
err.span_suggestion_verbose(sugg_sp, msg, "=> ".to_string(), applicability);
}
}
err
},
)
}

pub(crate) fn is_builtin(&self) -> bool {
self.token.is_keyword(kw::Builtin) && self.look_ahead(1, |t| *t == token::Pound)
}
@@ -3049,9 +3076,10 @@ impl<'a> Parser<'a> {
|| self.look_ahead(2, |t| t == &token::Colon)
&& (
// `{ ident: token, ` cannot start a block.
self.look_ahead(4, |t| t == &token::Comma) ||
// `{ ident: ` cannot start a block unless it's a type ascription `ident: Type`.
self.look_ahead(3, |t| !t.can_begin_type())
self.look_ahead(4, |t| t == &token::Comma)
// `{ ident: ` cannot start a block unless it's a type ascription
// `ident: Type`.
|| self.look_ahead(3, |t| !t.can_begin_type())
)
)
}
@@ -3091,6 +3119,7 @@ impl<'a> Parser<'a> {
let mut fields = ThinVec::new();
let mut base = ast::StructRest::None;
let mut recover_async = false;
let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD);

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

if let Some((ident, _)) = self.token.ident()
&& !self.token.is_reserved_ident()
&& self.look_ahead(1, |t| {
AssocOp::from_token(&t).is_some()
|| matches!(t.kind, token::OpenDelim(_))
|| t.kind == token::Dot
})
{
// Looks like they tried to write a shorthand, complex expression.
e.span_suggestion_verbose(
self.token.span.shrink_to_lo(),
"try naming a field",
&format!("{ident}: ", ),
Applicability::MaybeIncorrect,
);
}
if in_if_guard && close_delim == Delimiter::Brace {
return Err(e);
}

if !recover {
return Err(e);
}
@@ -3173,19 +3222,6 @@ impl<'a> Parser<'a> {
",",
Applicability::MachineApplicable,
);
} else if is_shorthand
&& (AssocOp::from_token(&self.token).is_some()
|| matches!(&self.token.kind, token::OpenDelim(_))
|| self.token.kind == token::Dot)
{
// Looks like they tried to write a shorthand, complex expression.
let ident = parsed_field.expect("is_shorthand implies Some").ident;
e.span_suggestion(
ident.span.shrink_to_lo(),
"try naming a field",
&format!("{ident}: "),
Applicability::HasPlaceholders,
);
}
}
if !recover {
@@ -3288,6 +3324,24 @@ impl<'a> Parser<'a> {

// Check if a colon exists one ahead. This means we're parsing a fieldname.
let is_shorthand = !this.look_ahead(1, |t| t == &token::Colon || t == &token::Eq);
// Proactively check whether parsing the field will be incorrect.
let is_wrong = this.token.is_ident()
&& !this.token.is_reserved_ident()
&& !this.look_ahead(1, |t| {
t == &token::Colon
|| t == &token::Eq
|| t == &token::Comma
|| t == &token::CloseDelim(Delimiter::Brace)
|| t == &token::CloseDelim(Delimiter::Parenthesis)
});
if is_wrong {
return Err(errors::ExpectedStructField {
span: this.look_ahead(1, |t| t.span),
ident_span: this.token.span,
token: this.look_ahead(1, |t| t.clone()),
}
.into_diagnostic(&self.sess.span_diagnostic));
}
let (ident, expr) = if is_shorthand {
// Mimic `x: x` for the `x` field shorthand.
let ident = this.parse_ident_common(false)?;
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ bitflags::bitflags! {
const NO_STRUCT_LITERAL = 1 << 1;
const CONST_EXPR = 1 << 2;
const ALLOW_LET = 1 << 3;
const IN_IF_GUARD = 1 << 4;
}
}

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 0 additions & 3 deletions tests/ui/parser/issues/issue-15980.rs
Original file line number Diff line number Diff line change
@@ -9,9 +9,6 @@ fn main(){
//~^ ERROR expected identifier, found keyword `return`
//~| NOTE expected identifier, found keyword
}
//~^ NOTE expected one of `.`, `=>`, `?`, or an operator
_ => {}
//~^ ERROR expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
//~| NOTE unexpected token
}
}
13 changes: 4 additions & 9 deletions tests/ui/parser/issues/issue-15980.stderr
Original file line number Diff line number Diff line change
@@ -11,15 +11,10 @@ help: escape `return` to use it as an identifier
|
LL | r#return
| ++

error: expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
--> $DIR/issue-15980.rs:13:9
help: you might have meant to start a match arm after the match guard
|
LL | }
| - expected one of `.`, `=>`, `?`, or an operator
LL |
LL | _ => {}
| ^ unexpected token
LL | Err(ref e) if e.kind == io::EndOfFile => {
| ++

error: aborting due to 2 previous errors
error: aborting due to previous error

9 changes: 7 additions & 2 deletions tests/ui/parser/issues/issue-52496.stderr
Original file line number Diff line number Diff line change
@@ -8,10 +8,15 @@ error: expected one of `,`, `:`, or `}`, found `.`
--> $DIR/issue-52496.rs:8:22
|
LL | let _ = Foo { bar.into(), bat: -1, . };
| --- - ^ expected one of `,`, `:`, or `}`
| --- ---^ expected one of `,`, `:`, or `}`
| | |
| | help: try naming a field: `bar:`
| | while parsing this struct field
| while parsing this struct
|
help: try naming a field
|
LL | let _ = Foo { bar: bar.into(), bat: -1, . };
| ++++

error: expected identifier, found `.`
--> $DIR/issue-52496.rs:8:40
4 changes: 2 additions & 2 deletions tests/ui/parser/issues/issue-89396.fixed
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@ fn main() {
let _ = match opt {
Some(_) => true,
//~^ ERROR: expected one of
//~| HELP: try using a fat arrow here
//~| HELP: use a fat arrow to start a match arm
None => false,
//~^ ERROR: expected one of
//~| HELP: try using a fat arrow here
//~| HELP: use a fat arrow to start a match arm
};
}
4 changes: 2 additions & 2 deletions tests/ui/parser/issues/issue-89396.rs
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@ fn main() {
let _ = match opt {
Some(_) = true,
//~^ ERROR: expected one of
//~| HELP: try using a fat arrow here
//~| HELP: use a fat arrow to start a match arm
None -> false,
//~^ ERROR: expected one of
//~| HELP: try using a fat arrow here
//~| HELP: use a fat arrow to start a match arm
};
}
4 changes: 2 additions & 2 deletions tests/ui/parser/issues/issue-89396.stderr
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ LL | Some(_) = true,
| ^
| |
| expected one of `=>`, `if`, or `|`
| help: try using a fat arrow here: `=>`
| help: use a fat arrow to start a match arm: `=>`

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

error: aborting due to 2 previous errors

38 changes: 38 additions & 0 deletions tests/ui/parser/missing-fat-arrow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
fn main() {
let x = 1;
let y = 2;
let value = 3;

match value {
Some(x) if x == y {
self.next_token()?; //~ ERROR expected identifier, found keyword `self`
},
_ => {}
}
let _: i32 = (); //~ ERROR mismatched types
}

struct Foo {
value: usize
}

fn foo(a: Option<&mut Foo>, b: usize) {
match a {
Some(a) if a.value == b {
a.value = 1; //~ ERROR expected one of `,`, `:`, or `}`, found `.`
},
_ => {}
}
let _: i32 = (); //~ ERROR mismatched types
}

fn bar(a: Option<&mut Foo>, b: usize) {
match a {
Some(a) if a.value == b {
a.value, //~ ERROR expected one of `,`, `:`, or `}`, found `.`
} => {
}
_ => {}
}
let _: i32 = (); //~ ERROR mismatched types
}
78 changes: 78 additions & 0 deletions tests/ui/parser/missing-fat-arrow.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
error: expected identifier, found keyword `self`
--> $DIR/missing-fat-arrow.rs:8:13
|
LL | Some(x) if x == y {
| - while parsing this struct
LL | self.next_token()?;
| ^^^^ expected identifier, found keyword
|
help: you might have meant to start a match arm after the match guard
|
LL | Some(x) if x == y => {
| ++

error: expected one of `,`, `:`, or `}`, found `.`
--> $DIR/missing-fat-arrow.rs:22: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 => {
| ++

error: expected one of `,`, `:`, or `}`, found `.`
--> $DIR/missing-fat-arrow.rs:32:14
|
LL | Some(a) if a.value == b {
| - while parsing this struct
LL | a.value,
| -^ expected one of `,`, `:`, or `}`
| |
| while parsing this struct field
|
help: try naming a field
|
LL | a: a.value,
| ++
help: you might have meant to start a match arm after the match guard
|
LL | Some(a) if a.value == b => {
| ++

error[E0308]: mismatched types
--> $DIR/missing-fat-arrow.rs:12:18
|
LL | let _: i32 = ();
| --- ^^ expected `i32`, found `()`
| |
| expected due to this

error[E0308]: mismatched types
--> $DIR/missing-fat-arrow.rs:26:18
|
LL | let _: i32 = ();
| --- ^^ expected `i32`, found `()`
| |
| expected due to this

error[E0308]: mismatched types
--> $DIR/missing-fat-arrow.rs:37:18
|
LL | let _: i32 = ();
| --- ^^ expected `i32`, found `()`
| |
| expected due to this

error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0308`.
Loading