Skip to content

Commit 36485ac

Browse files
authored
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); | + ```
2 parents 13170cd + 1549af2 commit 36485ac

File tree

5 files changed

+160
-2
lines changed

5 files changed

+160
-2
lines changed

compiler/rustc_parse/src/parser/stmt.rs

+53-2
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,51 @@ impl<'a> Parser<'a> {
745745
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
746746
}
747747

748+
fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
749+
let Some((ident, _)) = self.token.ident() else {
750+
return;
751+
};
752+
if let Some(c) = ident.name.as_str().chars().next()
753+
&& c.is_uppercase()
754+
{
755+
return;
756+
}
757+
if self.token.is_reserved_ident() && !self.token.is_ident_named(kw::Await) {
758+
return;
759+
}
760+
if self.prev_token.is_reserved_ident() && self.prev_token.is_ident_named(kw::Await) {
761+
// Likely `foo.await bar`
762+
} else if !self.prev_token.is_reserved_ident() && self.prev_token.is_ident() {
763+
// Likely `foo bar`
764+
} else if self.prev_token.kind == token::Question {
765+
// `foo? bar`
766+
} else if self.prev_token.kind == token::CloseDelim(Delimiter::Parenthesis) {
767+
// `foo() bar`
768+
} else {
769+
return;
770+
}
771+
if self.token.span == self.prev_token.span {
772+
// Account for syntax errors in proc-macros.
773+
return;
774+
}
775+
if self.look_ahead(1, |t| [token::Semi, token::Question, token::Dot].contains(&t.kind)) {
776+
err.span_suggestion_verbose(
777+
self.prev_token.span.between(self.token.span),
778+
"you might have meant to write a field access",
779+
".".to_string(),
780+
Applicability::MaybeIncorrect,
781+
);
782+
}
783+
if self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Parenthesis)) {
784+
err.span_suggestion_verbose(
785+
self.prev_token.span.between(self.token.span),
786+
"you might have meant to write a method call",
787+
".".to_string(),
788+
Applicability::MaybeIncorrect,
789+
);
790+
}
791+
}
792+
748793
/// Parses a statement, including the trailing semicolon.
749794
pub fn parse_full_stmt(
750795
&mut self,
@@ -851,7 +896,8 @@ impl<'a> Parser<'a> {
851896
Some(if recover.no() {
852897
res?
853898
} else {
854-
res.unwrap_or_else(|e| {
899+
res.unwrap_or_else(|mut e| {
900+
self.recover_missing_dot(&mut e);
855901
let guar = e.emit();
856902
self.recover_stmt();
857903
guar
@@ -872,7 +918,12 @@ impl<'a> Parser<'a> {
872918
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
873919
match &mut local.kind {
874920
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
875-
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
921+
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
922+
|mut e| {
923+
self.recover_missing_dot(&mut e);
924+
e
925+
},
926+
)?;
876927
// We found `foo<bar, baz>`, have we fully recovered?
877928
self.expect_semi()?;
878929
}
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/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)