1
Fork 0

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:
Esteban Küber 2024-11-16 00:07:58 +00:00
parent 9e136a30a9
commit 1ce0fa98c7
7 changed files with 165 additions and 2 deletions

View file

@ -745,6 +745,42 @@ 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<'_>) {
if !self.token.is_ident() {
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.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 +887,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 +909,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()?;
} }

View file

@ -9,12 +9,22 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found
| |
LL | r#struct Test; LL | r#struct Test;
| ^^^^ expected one of 8 possible tokens | ^^^^ expected one of 8 possible tokens
|
help: you might have meant to write a field access
|
LL | r#struct.Test;
| +
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `Test` error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `Test`
--> $DIR/raw-literal-keywords.rs:10:13 --> $DIR/raw-literal-keywords.rs:10:13
| |
LL | r#union Test; LL | r#union Test;
| ^^^^ expected one of 8 possible tokens | ^^^^ expected one of 8 possible tokens
|
help: you might have meant to write a field access
|
LL | r#union.Test;
| +
error[E0425]: cannot find value `r#if` in this scope error[E0425]: cannot find value `r#if` in this scope
--> $DIR/raw-literal-keywords.rs:14:13 --> $DIR/raw-literal-keywords.rs:14:13

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -5,6 +5,10 @@ LL | make_bad_struct!(S);
| ^^^^^^^^^^^^^^^^^^^ expected one of 8 possible tokens | ^^^^^^^^^^^^^^^^^^^ expected one of 8 possible tokens
| |
= note: this error originates in the macro `make_bad_struct` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `make_bad_struct` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to write a field access
|
LL | .;
| ~
error: aborting due to 1 previous error error: aborting due to 1 previous error

View file

@ -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