Skip to content

Commit 3879927

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 203a175 commit 3879927

7 files changed

+165
-2
lines changed

compiler/rustc_parse/src/parser/stmt.rs

+44-2
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,42 @@ impl<'a> Parser<'a> {
730730
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
731731
}
732732

733+
fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
734+
if !self.token.is_ident() {
735+
return;
736+
}
737+
if self.token.is_reserved_ident() && !self.token.is_ident_named(kw::Await) {
738+
return;
739+
}
740+
if self.prev_token.is_reserved_ident() && self.prev_token.is_ident_named(kw::Await) {
741+
// Likely `foo.await bar`
742+
} else if !self.prev_token.is_reserved_ident() && self.prev_token.is_ident() {
743+
// Likely `foo bar`
744+
} else if self.prev_token.kind == token::Question {
745+
// `foo? bar`
746+
} else if self.prev_token.kind == token::CloseDelim(Delimiter::Parenthesis) {
747+
// `foo() bar`
748+
} else {
749+
return;
750+
}
751+
if self.look_ahead(1, |t| [token::Semi, token::Question, token::Dot].contains(&t.kind)) {
752+
err.span_suggestion_verbose(
753+
self.prev_token.span.between(self.token.span),
754+
"you might have meant to write a field access",
755+
".".to_string(),
756+
Applicability::MaybeIncorrect,
757+
);
758+
}
759+
if self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Parenthesis)) {
760+
err.span_suggestion_verbose(
761+
self.prev_token.span.between(self.token.span),
762+
"you might have meant to write a method call",
763+
".".to_string(),
764+
Applicability::MaybeIncorrect,
765+
);
766+
}
767+
}
768+
733769
/// Parses a statement, including the trailing semicolon.
734770
pub fn parse_full_stmt(
735771
&mut self,
@@ -837,7 +873,8 @@ impl<'a> Parser<'a> {
837873
Some(if recover.no() {
838874
res?
839875
} else {
840-
res.unwrap_or_else(|e| {
876+
res.unwrap_or_else(|mut e| {
877+
self.recover_missing_dot(&mut e);
841878
let guar = e.emit();
842879
self.recover_stmt();
843880
guar
@@ -858,7 +895,12 @@ impl<'a> Parser<'a> {
858895
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
859896
match &mut local.kind {
860897
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
861-
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
898+
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
899+
|mut e| {
900+
self.recover_missing_dot(&mut e);
901+
e
902+
},
903+
)?;
862904
// We found `foo<bar, baz>`, have we fully recovered?
863905
self.expect_semi()?;
864906
}

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

+10
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
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+
}
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+
}
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

+4
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

+5
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)