Rollup merge of #133087 - estebank:stmt-misparse, r=chenyukang
Detect missing `.` in method chain in `let` bindings and statements On parse errors where an ident is found where one wasn't expected, see if the next elements might have been meant as method call or field access. ``` error: expected one of `.`, `;`, `?`, `else`, or an operator, found `map` --> $DIR/missing-dot-on-statement-expression.rs:7:29 | LL | let _ = [1, 2, 3].iter()map(|x| x); | ^^^ expected one of `.`, `;`, `?`, `else`, or an operator | help: you might have meant to write a method call | LL | let _ = [1, 2, 3].iter().map(|x| x); | + ```
This commit is contained in:
commit
36485acdac
5 changed files with 160 additions and 2 deletions
|
@ -745,6 +745,51 @@ impl<'a> Parser<'a> {
|
||||||
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
|
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
|
||||||
|
let Some((ident, _)) = self.token.ident() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(c) = ident.name.as_str().chars().next()
|
||||||
|
&& c.is_uppercase()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.token.is_reserved_ident() && !self.token.is_ident_named(kw::Await) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.prev_token.is_reserved_ident() && self.prev_token.is_ident_named(kw::Await) {
|
||||||
|
// Likely `foo.await bar`
|
||||||
|
} else if !self.prev_token.is_reserved_ident() && self.prev_token.is_ident() {
|
||||||
|
// Likely `foo bar`
|
||||||
|
} else if self.prev_token.kind == token::Question {
|
||||||
|
// `foo? bar`
|
||||||
|
} else if self.prev_token.kind == token::CloseDelim(Delimiter::Parenthesis) {
|
||||||
|
// `foo() bar`
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.token.span == self.prev_token.span {
|
||||||
|
// Account for syntax errors in proc-macros.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.look_ahead(1, |t| [token::Semi, token::Question, token::Dot].contains(&t.kind)) {
|
||||||
|
err.span_suggestion_verbose(
|
||||||
|
self.prev_token.span.between(self.token.span),
|
||||||
|
"you might have meant to write a field access",
|
||||||
|
".".to_string(),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Parenthesis)) {
|
||||||
|
err.span_suggestion_verbose(
|
||||||
|
self.prev_token.span.between(self.token.span),
|
||||||
|
"you might have meant to write a method call",
|
||||||
|
".".to_string(),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a statement, including the trailing semicolon.
|
/// Parses a statement, including the trailing semicolon.
|
||||||
pub fn parse_full_stmt(
|
pub fn parse_full_stmt(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -851,7 +896,8 @@ impl<'a> Parser<'a> {
|
||||||
Some(if recover.no() {
|
Some(if recover.no() {
|
||||||
res?
|
res?
|
||||||
} else {
|
} else {
|
||||||
res.unwrap_or_else(|e| {
|
res.unwrap_or_else(|mut e| {
|
||||||
|
self.recover_missing_dot(&mut e);
|
||||||
let guar = e.emit();
|
let guar = e.emit();
|
||||||
self.recover_stmt();
|
self.recover_stmt();
|
||||||
guar
|
guar
|
||||||
|
@ -872,7 +918,12 @@ impl<'a> Parser<'a> {
|
||||||
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
|
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
|
||||||
match &mut local.kind {
|
match &mut local.kind {
|
||||||
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
|
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
|
||||||
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
|
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
|
||||||
|
|mut e| {
|
||||||
|
self.recover_missing_dot(&mut e);
|
||||||
|
e
|
||||||
|
},
|
||||||
|
)?;
|
||||||
// We found `foo<bar, baz>`, have we fully recovered?
|
// We found `foo<bar, baz>`, have we fully recovered?
|
||||||
self.expect_semi()?;
|
self.expect_semi()?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
//@ run-rustfix
|
||||||
|
#![allow(unused_must_use, dead_code)]
|
||||||
|
struct S {
|
||||||
|
field: (),
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let _ = [1, 2, 3].iter().map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `map`
|
||||||
|
//~^ HELP you might have meant to write a method call
|
||||||
|
}
|
||||||
|
fn foo() {
|
||||||
|
let baz = S {
|
||||||
|
field: ()
|
||||||
|
};
|
||||||
|
let _ = baz.field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field`
|
||||||
|
//~^ HELP you might have meant to write a field
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar() {
|
||||||
|
[1, 2, 3].iter().map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `map`
|
||||||
|
//~^ HELP you might have meant to write a method call
|
||||||
|
}
|
||||||
|
fn baz() {
|
||||||
|
let baz = S {
|
||||||
|
field: ()
|
||||||
|
};
|
||||||
|
baz.field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field`
|
||||||
|
//~^ HELP you might have meant to write a field
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
//@ run-rustfix
|
||||||
|
#![allow(unused_must_use, dead_code)]
|
||||||
|
struct S {
|
||||||
|
field: (),
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let _ = [1, 2, 3].iter()map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `map`
|
||||||
|
//~^ HELP you might have meant to write a method call
|
||||||
|
}
|
||||||
|
fn foo() {
|
||||||
|
let baz = S {
|
||||||
|
field: ()
|
||||||
|
};
|
||||||
|
let _ = baz field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field`
|
||||||
|
//~^ HELP you might have meant to write a field
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar() {
|
||||||
|
[1, 2, 3].iter()map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `map`
|
||||||
|
//~^ HELP you might have meant to write a method call
|
||||||
|
}
|
||||||
|
fn baz() {
|
||||||
|
let baz = S {
|
||||||
|
field: ()
|
||||||
|
};
|
||||||
|
baz field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field`
|
||||||
|
//~^ HELP you might have meant to write a field
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `map`
|
||||||
|
--> $DIR/missing-dot-on-statement-expression.rs:7:29
|
||||||
|
|
|
||||||
|
LL | let _ = [1, 2, 3].iter()map(|x| x);
|
||||||
|
| ^^^ expected one of `.`, `;`, `?`, `else`, or an operator
|
||||||
|
|
|
||||||
|
help: you might have meant to write a method call
|
||||||
|
|
|
||||||
|
LL | let _ = [1, 2, 3].iter().map(|x| x);
|
||||||
|
| +
|
||||||
|
|
||||||
|
error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field`
|
||||||
|
--> $DIR/missing-dot-on-statement-expression.rs:14:17
|
||||||
|
|
|
||||||
|
LL | let _ = baz field;
|
||||||
|
| ^^^^^ expected one of 8 possible tokens
|
||||||
|
|
|
||||||
|
help: you might have meant to write a field access
|
||||||
|
|
|
||||||
|
LL | let _ = baz.field;
|
||||||
|
| +
|
||||||
|
|
||||||
|
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `map`
|
||||||
|
--> $DIR/missing-dot-on-statement-expression.rs:19:21
|
||||||
|
|
|
||||||
|
LL | [1, 2, 3].iter()map(|x| x);
|
||||||
|
| ^^^ expected one of `.`, `;`, `?`, `}`, or an operator
|
||||||
|
|
|
||||||
|
help: you might have meant to write a method call
|
||||||
|
|
|
||||||
|
LL | [1, 2, 3].iter().map(|x| x);
|
||||||
|
| +
|
||||||
|
|
||||||
|
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field`
|
||||||
|
--> $DIR/missing-dot-on-statement-expression.rs:26:9
|
||||||
|
|
|
||||||
|
LL | baz field;
|
||||||
|
| ^^^^^ expected one of 8 possible tokens
|
||||||
|
|
|
||||||
|
help: you might have meant to write a field access
|
||||||
|
|
|
||||||
|
LL | baz.field;
|
||||||
|
| +
|
||||||
|
|
||||||
|
error: aborting due to 4 previous errors
|
||||||
|
|
|
@ -3,6 +3,11 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found
|
||||||
|
|
|
|
||||||
LL | not rust;
|
LL | not rust;
|
||||||
| ^^^^ expected one of 8 possible tokens
|
| ^^^^ expected one of 8 possible tokens
|
||||||
|
|
|
||||||
|
help: you might have meant to write a field access
|
||||||
|
|
|
||||||
|
LL | not.rust;
|
||||||
|
| +
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue