Skip to content
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

Detect missing => after match guard during parsing #116400

Merged
merged 3 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
92 changes: 73 additions & 19 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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();
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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`.
notriddle marked this conversation as resolved.
Show resolved Hide resolved
|| self.look_ahead(3, |t| !t.can_begin_type())
)
)
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)?;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
3 changes: 0 additions & 3 deletions tests/ui/parser/issues/issue-15980.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/parser/issues/issue-89396.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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
Expand All @@ -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
WaffleLapkin marked this conversation as resolved.
Show resolved Hide resolved
}

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;
| ++
WaffleLapkin marked this conversation as resolved.
Show resolved Hide resolved
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
Loading