Skip to content

Commit 8b66579

Browse files
committed
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); | + ```
1 parent 6503543 commit 8b66579

File tree

7 files changed

+165
-2
lines changed

7 files changed

+165
-2
lines changed

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,42 @@ impl<'a> Parser<'a> {
650650
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
651651
}
652652

653+
fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
654+
if !self.token.is_ident() {
655+
return;
656+
}
657+
if self.token.is_reserved_ident() && !self.token.is_ident_named(kw::Await) {
658+
return;
659+
}
660+
if self.prev_token.is_reserved_ident() && self.prev_token.is_ident_named(kw::Await) {
661+
// Likely `foo.await bar`
662+
} else if !self.prev_token.is_reserved_ident() && self.prev_token.is_ident() {
663+
// Likely `foo bar`
664+
} else if self.prev_token.kind == token::Question {
665+
// `foo? bar`
666+
} else if self.prev_token.kind == token::CloseDelim(Delimiter::Parenthesis) {
667+
// `foo() bar`
668+
} else {
669+
return;
670+
}
671+
if self.look_ahead(1, |t| [token::Semi, token::Question, token::Dot].contains(&t.kind)) {
672+
err.span_suggestion_verbose(
673+
self.prev_token.span.between(self.token.span),
674+
"you might have meant to write a field access",
675+
".".to_string(),
676+
Applicability::MaybeIncorrect,
677+
);
678+
}
679+
if self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Parenthesis)) {
680+
err.span_suggestion_verbose(
681+
self.prev_token.span.between(self.token.span),
682+
"you might have meant to write a method call",
683+
".".to_string(),
684+
Applicability::MaybeIncorrect,
685+
);
686+
}
687+
}
688+
653689
/// Parses a statement, including the trailing semicolon.
654690
pub fn parse_full_stmt(
655691
&mut self,
@@ -757,7 +793,8 @@ impl<'a> Parser<'a> {
757793
Some(if recover.no() {
758794
res?
759795
} else {
760-
res.unwrap_or_else(|e| {
796+
res.unwrap_or_else(|mut e| {
797+
self.recover_missing_dot(&mut e);
761798
let guar = e.emit();
762799
self.recover_stmt();
763800
guar
@@ -778,7 +815,12 @@ impl<'a> Parser<'a> {
778815
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
779816
match &mut local.kind {
780817
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
781-
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
818+
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
819+
|mut e| {
820+
self.recover_missing_dot(&mut e);
821+
e
822+
},
823+
)?;
782824
// We found `foo<bar, baz>`, have we fully recovered?
783825
self.expect_semi()?;
784826
}

tests/ui/parser/raw/raw-literal-keywords.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,22 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found
99
|
1010
LL | r#struct Test;
1111
| ^^^^ expected one of 8 possible tokens
12+
|
13+
help: you might have meant to write a field access
14+
|
15+
LL | r#struct.Test;
16+
| +
1217

1318
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `Test`
1419
--> $DIR/raw-literal-keywords.rs:10:13
1520
|
1621
LL | r#union Test;
1722
| ^^^^ expected one of 8 possible tokens
23+
|
24+
help: you might have meant to write a field access
25+
|
26+
LL | r#union.Test;
27+
| +
1828

1929
error[E0425]: cannot find value `r#if` in this scope
2030
--> $DIR/raw-literal-keywords.rs:14:13
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ run-rustfix
2+
#![allow(unused_must_use, dead_code)]
3+
struct S {
4+
field: (),
5+
}
6+
fn main() {
7+
let _ = [1, 2, 3].iter().map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `map`
8+
//~^ HELP you might have meant to write a method call
9+
}
10+
fn foo() {
11+
let baz = S {
12+
field: ()
13+
};
14+
let _ = baz.field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field`
15+
//~^ HELP you might have meant to write a field
16+
}
17+
18+
fn bar() {
19+
[1, 2, 3].iter().map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `map`
20+
//~^ HELP you might have meant to write a method call
21+
}
22+
fn baz() {
23+
let baz = S {
24+
field: ()
25+
};
26+
baz.field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field`
27+
//~^ HELP you might have meant to write a field
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ run-rustfix
2+
#![allow(unused_must_use, dead_code)]
3+
struct S {
4+
field: (),
5+
}
6+
fn main() {
7+
let _ = [1, 2, 3].iter()map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `map`
8+
//~^ HELP you might have meant to write a method call
9+
}
10+
fn foo() {
11+
let baz = S {
12+
field: ()
13+
};
14+
let _ = baz field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field`
15+
//~^ HELP you might have meant to write a field
16+
}
17+
18+
fn bar() {
19+
[1, 2, 3].iter()map(|x| x); //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `map`
20+
//~^ HELP you might have meant to write a method call
21+
}
22+
fn baz() {
23+
let baz = S {
24+
field: ()
25+
};
26+
baz field; //~ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field`
27+
//~^ HELP you might have meant to write a field
28+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `map`
2+
--> $DIR/missing-dot-on-statement-expression.rs:7:29
3+
|
4+
LL | let _ = [1, 2, 3].iter()map(|x| x);
5+
| ^^^ expected one of `.`, `;`, `?`, `else`, or an operator
6+
|
7+
help: you might have meant to write a method call
8+
|
9+
LL | let _ = [1, 2, 3].iter().map(|x| x);
10+
| +
11+
12+
error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `field`
13+
--> $DIR/missing-dot-on-statement-expression.rs:14:17
14+
|
15+
LL | let _ = baz field;
16+
| ^^^^^ expected one of 8 possible tokens
17+
|
18+
help: you might have meant to write a field access
19+
|
20+
LL | let _ = baz.field;
21+
| +
22+
23+
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `map`
24+
--> $DIR/missing-dot-on-statement-expression.rs:19:21
25+
|
26+
LL | [1, 2, 3].iter()map(|x| x);
27+
| ^^^ expected one of `.`, `;`, `?`, `}`, or an operator
28+
|
29+
help: you might have meant to write a method call
30+
|
31+
LL | [1, 2, 3].iter().map(|x| x);
32+
| +
33+
34+
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `field`
35+
--> $DIR/missing-dot-on-statement-expression.rs:26:9
36+
|
37+
LL | baz field;
38+
| ^^^^^ expected one of 8 possible tokens
39+
|
40+
help: you might have meant to write a field access
41+
|
42+
LL | baz.field;
43+
| +
44+
45+
error: aborting due to 4 previous errors
46+

tests/ui/proc-macro/raw-ident.stderr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ LL | make_bad_struct!(S);
55
| ^^^^^^^^^^^^^^^^^^^ expected one of 8 possible tokens
66
|
77
= note: this error originates in the macro `make_bad_struct` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
help: you might have meant to write a field access
9+
|
10+
LL | .;
11+
| ~
812

913
error: aborting due to 1 previous error
1014

tests/ui/suggestions/type-ascription-and-other-error.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found
33
|
44
LL | not rust;
55
| ^^^^ expected one of 8 possible tokens
6+
|
7+
help: you might have meant to write a field access
8+
|
9+
LL | not.rust;
10+
| +
611

712
error: aborting due to 1 previous error
813

0 commit comments

Comments
 (0)