Skip to content

Commit f007e6f

Browse files
committed
Identify when a stmt could have been parsed as an expr
There are some expressions that can be parsed as a statement without a trailing semicolon depending on the context, which can lead to confusing errors due to the same looking code being accepted in some places and not others. Identify these cases and suggest enclosing in parenthesis making the parse non-ambiguous without changing the accepted grammar.
1 parent a55c2eb commit f007e6f

File tree

10 files changed

+270
-11
lines changed

10 files changed

+270
-11
lines changed

src/librustc_typeck/check/mod.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -4165,9 +4165,31 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
41654165
oprnd_t = self.make_overloaded_place_return_type(method).ty;
41664166
self.write_method_call(expr.hir_id, method);
41674167
} else {
4168-
type_error_struct!(tcx.sess, expr.span, oprnd_t, E0614,
4169-
"type `{}` cannot be dereferenced",
4170-
oprnd_t).emit();
4168+
let mut err = type_error_struct!(
4169+
tcx.sess,
4170+
expr.span,
4171+
oprnd_t,
4172+
E0614,
4173+
"type `{}` cannot be dereferenced",
4174+
oprnd_t,
4175+
);
4176+
let sp = tcx.sess.source_map().start_point(expr.span);
4177+
if let Some(sp) = tcx.sess.parse_sess.abiguous_block_expr_parse
4178+
.borrow().get(&sp)
4179+
{
4180+
if let Ok(snippet) = tcx.sess.source_map()
4181+
.span_to_snippet(*sp)
4182+
{
4183+
err.span_suggestion(
4184+
*sp,
4185+
"parenthesis are required to parse this \
4186+
as an expression",
4187+
format!("({})", snippet),
4188+
Applicability::MachineApplicable,
4189+
);
4190+
}
4191+
}
4192+
err.emit();
41714193
oprnd_t = tcx.types.err;
41724194
}
41734195
}

src/libsyntax/parse/lexer/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1899,7 +1899,7 @@ mod tests {
18991899
use std::io;
19001900
use std::path::PathBuf;
19011901
use syntax_pos::{BytePos, Span, NO_EXPANSION};
1902-
use rustc_data_structures::fx::FxHashSet;
1902+
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
19031903
use rustc_data_structures::sync::Lock;
19041904

19051905
fn mk_sess(sm: Lrc<SourceMap>) -> ParseSess {
@@ -1918,6 +1918,7 @@ mod tests {
19181918
raw_identifier_spans: Lock::new(Vec::new()),
19191919
registered_diagnostics: Lock::new(ErrorMap::new()),
19201920
buffered_lints: Lock::new(vec![]),
1921+
abiguous_block_expr_parse: Lock::new(FxHashMap::default()),
19211922
}
19221923
}
19231924

src/libsyntax/parse/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_data_structures::sync::{Lrc, Lock};
1616
use syntax_pos::{Span, SourceFile, FileName, MultiSpan};
1717
use log::debug;
1818

19-
use rustc_data_structures::fx::FxHashSet;
19+
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
2020
use std::borrow::Cow;
2121
use std::iter;
2222
use std::path::{Path, PathBuf};
@@ -47,6 +47,7 @@ pub struct ParseSess {
4747
included_mod_stack: Lock<Vec<PathBuf>>,
4848
source_map: Lrc<SourceMap>,
4949
pub buffered_lints: Lock<Vec<BufferedEarlyLint>>,
50+
pub abiguous_block_expr_parse: Lock<FxHashMap<Span, Span>>,
5051
}
5152

5253
impl ParseSess {
@@ -70,6 +71,7 @@ impl ParseSess {
7071
included_mod_stack: Lock::new(vec![]),
7172
source_map,
7273
buffered_lints: Lock::new(vec![]),
74+
abiguous_block_expr_parse: Lock::new(FxHashMap::default()),
7375
}
7476
}
7577

src/libsyntax/parse/parser.rs

+61-3
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ enum PrevTokenKind {
186186
Interpolated,
187187
Eof,
188188
Ident,
189+
BitOr,
189190
Other,
190191
}
191192

@@ -1410,6 +1411,7 @@ impl<'a> Parser<'a> {
14101411
token::DocComment(..) => PrevTokenKind::DocComment,
14111412
token::Comma => PrevTokenKind::Comma,
14121413
token::BinOp(token::Plus) => PrevTokenKind::Plus,
1414+
token::BinOp(token::Or) => PrevTokenKind::BitOr,
14131415
token::Interpolated(..) => PrevTokenKind::Interpolated,
14141416
token::Eof => PrevTokenKind::Eof,
14151417
token::Ident(..) => PrevTokenKind::Ident,
@@ -2925,6 +2927,19 @@ impl<'a> Parser<'a> {
29252927
let msg = format!("expected expression, found {}",
29262928
self.this_token_descr());
29272929
let mut err = self.fatal(&msg);
2930+
let sp = self.sess.source_map().start_point(self.span);
2931+
if let Some(sp) = self.sess.abiguous_block_expr_parse.borrow()
2932+
.get(&sp)
2933+
{
2934+
if let Ok(snippet) = self.sess.source_map().span_to_snippet(*sp) {
2935+
err.span_suggestion(
2936+
*sp,
2937+
"parenthesis are required to parse this as an expression",
2938+
format!("({})", snippet),
2939+
Applicability::MachineApplicable,
2940+
);
2941+
}
2942+
}
29282943
err.span_label(self.span, "expected expression");
29292944
return Err(err);
29302945
}
@@ -3616,9 +3631,41 @@ impl<'a> Parser<'a> {
36163631
}
36173632
};
36183633

3619-
if self.expr_is_complete(&lhs) {
3620-
// Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
3621-
return Ok(lhs);
3634+
match (self.expr_is_complete(&lhs), AssocOp::from_token(&self.token)) {
3635+
(true, None) => {
3636+
// Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
3637+
return Ok(lhs);
3638+
}
3639+
(false, _) => {} // continue parsing the expression
3640+
(true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;`
3641+
(true, Some(AssocOp::Subtract)) | // `{ 42 } -5`
3642+
(true, Some(AssocOp::Add)) => { // `{ 42 } + 42
3643+
// These cases are ambiguous and can't be identified in the parser alone
3644+
let sp = self.sess.source_map().start_point(self.span);
3645+
self.sess.abiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
3646+
return Ok(lhs);
3647+
}
3648+
(true, Some(ref op)) if !op.can_continue_expr_unambiguously() => {
3649+
return Ok(lhs);
3650+
}
3651+
(true, Some(_)) => {
3652+
// #54186, #54482, #59975
3653+
// We've found an expression that would be parsed as a statement, but the next
3654+
// token implies this should be parsed as an expression.
3655+
let mut err = self.sess.span_diagnostic.struct_span_err(
3656+
self.span,
3657+
"ambiguous parse",
3658+
);
3659+
let snippet = self.sess.source_map().span_to_snippet(lhs.span)
3660+
.unwrap_or_else(|_| pprust::expr_to_string(&lhs));
3661+
err.span_suggestion(
3662+
lhs.span,
3663+
"parenthesis are required to parse this as an expression",
3664+
format!("({})", snippet),
3665+
Applicability::MachineApplicable,
3666+
);
3667+
err.emit();
3668+
}
36223669
}
36233670
self.expected_tokens.push(TokenType::Operator);
36243671
while let Some(op) = AssocOp::from_token(&self.token) {
@@ -4929,6 +4976,17 @@ impl<'a> Parser<'a> {
49294976
);
49304977
let mut err = self.fatal(&msg);
49314978
err.span_label(self.span, format!("expected {}", expected));
4979+
let sp = self.sess.source_map().start_point(self.span);
4980+
if let Some(sp) = self.sess.abiguous_block_expr_parse.borrow().get(&sp) {
4981+
if let Ok(snippet) = self.sess.source_map().span_to_snippet(*sp) {
4982+
err.span_suggestion(
4983+
*sp,
4984+
"parenthesis are required to parse this as an expression",
4985+
format!("({})", snippet),
4986+
Applicability::MachineApplicable,
4987+
);
4988+
}
4989+
}
49324990
return Err(err);
49334991
}
49344992
}

src/libsyntax/util/parser.rs

+22
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,28 @@ impl AssocOp {
207207
ObsoleteInPlace | Assign | AssignOp(_) | As | DotDot | DotDotEq | Colon => None
208208
}
209209
}
210+
211+
pub fn can_continue_expr_unambiguously(&self) -> bool {
212+
use AssocOp::*;
213+
match self {
214+
BitXor | // `{ 42 } ^ 3`
215+
Assign | // `{ 42 } = { 42 }`
216+
Divide | // `{ 42 } / 42`
217+
Modulus | // `{ 42 } % 2`
218+
ShiftRight | // `{ 42 } >> 2`
219+
LessEqual | // `{ 42 } <= 3`
220+
Greater | // `{ 42 } > 3`
221+
GreaterEqual | // `{ 42 } >= 3`
222+
AssignOp(_) | // `{ 42 } +=`
223+
LAnd | // `{ 42 } &&foo`
224+
As | // `{ 42 } as usize`
225+
// Equal | // `{ 42 } == { 42 }` Accepting these here would regress incorrect
226+
// NotEqual | // `{ 42 } != { 42 } struct literals parser recovery.
227+
Colon => true, // `{ 42 }: usize`
228+
_ => false,
229+
}
230+
231+
}
210232
}
211233

212234
pub const PREC_RESET: i8 = -100;

src/test/ui/parser/expr-as-stmt.fixed

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// run-rustfix
2+
#![allow(unused_variables)]
3+
#![allow(dead_code)]
4+
#![allow(unused_must_use)]
5+
6+
fn foo() -> i32 {
7+
({2}) + {2} //~ ERROR expected expression, found `+`
8+
//~^ ERROR mismatched types
9+
}
10+
11+
fn bar() -> i32 {
12+
({2}) + 2 //~ ERROR expected expression, found `+`
13+
//~^ ERROR mismatched types
14+
}
15+
16+
fn zul() -> u32 {
17+
let foo = 3;
18+
({ 42 }) + foo; //~ ERROR expected expression, found `+`
19+
//~^ ERROR mismatched types
20+
32
21+
}
22+
23+
fn baz() -> i32 {
24+
({ 3 }) * 3 //~ ERROR type `{integer}` cannot be dereferenced
25+
//~^ ERROR mismatched types
26+
}
27+
28+
fn qux(a: Option<u32>, b: Option<u32>) -> bool {
29+
(if let Some(x) = a { true } else { false })
30+
&& //~ ERROR ambiguous parse
31+
if let Some(y) = a { true } else { false }
32+
}
33+
34+
fn main() {}

src/test/ui/parser/expr-as-stmt.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// run-rustfix
2+
#![allow(unused_variables)]
3+
#![allow(dead_code)]
4+
#![allow(unused_must_use)]
5+
6+
fn foo() -> i32 {
7+
{2} + {2} //~ ERROR expected expression, found `+`
8+
//~^ ERROR mismatched types
9+
}
10+
11+
fn bar() -> i32 {
12+
{2} + 2 //~ ERROR expected expression, found `+`
13+
//~^ ERROR mismatched types
14+
}
15+
16+
fn zul() -> u32 {
17+
let foo = 3;
18+
{ 42 } + foo; //~ ERROR expected expression, found `+`
19+
//~^ ERROR mismatched types
20+
32
21+
}
22+
23+
fn baz() -> i32 {
24+
{ 3 } * 3 //~ ERROR type `{integer}` cannot be dereferenced
25+
//~^ ERROR mismatched types
26+
}
27+
28+
fn qux(a: Option<u32>, b: Option<u32>) -> bool {
29+
if let Some(x) = a { true } else { false }
30+
&& //~ ERROR ambiguous parse
31+
if let Some(y) = a { true } else { false }
32+
}
33+
34+
fn main() {}
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
error: expected expression, found `+`
2+
--> $DIR/expr-as-stmt.rs:7:9
3+
|
4+
LL | {2} + {2}
5+
| --- ^ expected expression
6+
| |
7+
| help: parenthesis are required to parse this as an expression: `({2})`
8+
9+
error: expected expression, found `+`
10+
--> $DIR/expr-as-stmt.rs:12:9
11+
|
12+
LL | {2} + 2
13+
| --- ^ expected expression
14+
| |
15+
| help: parenthesis are required to parse this as an expression: `({2})`
16+
17+
error: expected expression, found `+`
18+
--> $DIR/expr-as-stmt.rs:18:12
19+
|
20+
LL | { 42 } + foo;
21+
| ------ ^ expected expression
22+
| |
23+
| help: parenthesis are required to parse this as an expression: `({ 42 })`
24+
25+
error: ambiguous parse
26+
--> $DIR/expr-as-stmt.rs:30:5
27+
|
28+
LL | if let Some(x) = a { true } else { false }
29+
| ------------------------------------------ help: parenthesis are required to parse this as an expression: `(if let Some(x) = a { true } else { false })`
30+
LL | &&
31+
| ^^
32+
33+
error[E0308]: mismatched types
34+
--> $DIR/expr-as-stmt.rs:7:6
35+
|
36+
LL | {2} + {2}
37+
| ^ expected (), found integer
38+
|
39+
= note: expected type `()`
40+
found type `{integer}`
41+
42+
error[E0308]: mismatched types
43+
--> $DIR/expr-as-stmt.rs:12:6
44+
|
45+
LL | {2} + 2
46+
| ^ expected (), found integer
47+
|
48+
= note: expected type `()`
49+
found type `{integer}`
50+
51+
error[E0308]: mismatched types
52+
--> $DIR/expr-as-stmt.rs:18:7
53+
|
54+
LL | { 42 } + foo;
55+
| ^^ expected (), found integer
56+
|
57+
= note: expected type `()`
58+
found type `{integer}`
59+
60+
error[E0308]: mismatched types
61+
--> $DIR/expr-as-stmt.rs:24:7
62+
|
63+
LL | { 3 } * 3
64+
| ^ expected (), found integer
65+
|
66+
= note: expected type `()`
67+
found type `{integer}`
68+
69+
error[E0614]: type `{integer}` cannot be dereferenced
70+
--> $DIR/expr-as-stmt.rs:24:11
71+
|
72+
LL | { 3 } * 3
73+
| ----- ^^^
74+
| |
75+
| help: parenthesis are required to parse this as an expression: `({ 3 })`
76+
77+
error: aborting due to 9 previous errors
78+
79+
Some errors have detailed explanations: E0308, E0614.
80+
For more information about an error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
fn main() {
2-
3-
match 0 {
2+
let _ = match 0 {
43
0 => {
4+
0
55
} + 5 //~ ERROR expected pattern, found `+`
6-
}
6+
};
77
}

src/test/ui/parser/match-arrows-block-then-binop.stderr

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ error: expected pattern, found `+`
33
|
44
LL | } + 5
55
| ^ expected pattern
6+
help: parenthesis are required to parse this as an expression
7+
|
8+
LL | 0 => ({
9+
LL | 0
10+
LL | }) + 5
11+
|
612

713
error: aborting due to previous error
814

0 commit comments

Comments
 (0)