Skip to content

Commit 39edc68

Browse files
authored
Rollup merge of #60188 - estebank:recover-block, r=varkor
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. Fix #54186, cc #54482, fix #59975, fix #47287.
2 parents 62ab971 + 54430ad commit 39edc68

17 files changed

+312
-24
lines changed

src/librustc_typeck/check/mod.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4168,9 +4168,25 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
41684168
oprnd_t = self.make_overloaded_place_return_type(method).ty;
41694169
self.write_method_call(expr.hir_id, method);
41704170
} else {
4171-
type_error_struct!(tcx.sess, expr.span, oprnd_t, E0614,
4172-
"type `{}` cannot be dereferenced",
4173-
oprnd_t).emit();
4171+
let mut err = type_error_struct!(
4172+
tcx.sess,
4173+
expr.span,
4174+
oprnd_t,
4175+
E0614,
4176+
"type `{}` cannot be dereferenced",
4177+
oprnd_t,
4178+
);
4179+
let sp = tcx.sess.source_map().start_point(expr.span);
4180+
if let Some(sp) = tcx.sess.parse_sess.ambiguous_block_expr_parse
4181+
.borrow().get(&sp)
4182+
{
4183+
tcx.sess.parse_sess.expr_parentheses_needed(
4184+
&mut err,
4185+
*sp,
4186+
None,
4187+
);
4188+
}
4189+
err.emit();
41744190
oprnd_t = tcx.types.err;
41754191
}
41764192
}

src/libsyntax/parse/lexer/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1598,7 +1598,7 @@ mod tests {
15981598
use std::io;
15991599
use std::path::PathBuf;
16001600
use syntax_pos::{BytePos, Span, NO_EXPANSION};
1601-
use rustc_data_structures::fx::FxHashSet;
1601+
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
16021602
use rustc_data_structures::sync::Lock;
16031603

16041604
fn mk_sess(sm: Lrc<SourceMap>) -> ParseSess {
@@ -1617,6 +1617,7 @@ mod tests {
16171617
raw_identifier_spans: Lock::new(Vec::new()),
16181618
registered_diagnostics: Lock::new(ErrorMap::new()),
16191619
buffered_lints: Lock::new(vec![]),
1620+
ambiguous_block_expr_parse: Lock::new(FxHashMap::default()),
16201621
}
16211622
}
16221623

src/libsyntax/parse/mod.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ use crate::tokenstream::{TokenStream, TokenTree};
1111
use crate::diagnostics::plugin::ErrorMap;
1212
use crate::print::pprust::token_to_string;
1313

14-
use errors::{FatalError, Level, Handler, ColorConfig, Diagnostic, DiagnosticBuilder};
14+
use errors::{Applicability, FatalError, Level, Handler, ColorConfig, Diagnostic, DiagnosticBuilder};
1515
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::path::{Path, PathBuf};
2222
use std::str;
@@ -52,6 +52,10 @@ pub struct ParseSess {
5252
included_mod_stack: Lock<Vec<PathBuf>>,
5353
source_map: Lrc<SourceMap>,
5454
pub buffered_lints: Lock<Vec<BufferedEarlyLint>>,
55+
/// Contains the spans of block expressions that could have been incomplete based on the
56+
/// operation token that followed it, but that the parser cannot identify without further
57+
/// analysis.
58+
pub ambiguous_block_expr_parse: Lock<FxHashMap<Span, Span>>,
5559
}
5660

5761
impl ParseSess {
@@ -75,6 +79,7 @@ impl ParseSess {
7579
included_mod_stack: Lock::new(vec![]),
7680
source_map,
7781
buffered_lints: Lock::new(vec![]),
82+
ambiguous_block_expr_parse: Lock::new(FxHashMap::default()),
7883
}
7984
}
8085

@@ -98,6 +103,24 @@ impl ParseSess {
98103
});
99104
});
100105
}
106+
107+
/// Extend an error with a suggestion to wrap an expression with parentheses to allow the
108+
/// parser to continue parsing the following operation as part of the same expression.
109+
pub fn expr_parentheses_needed(
110+
&self,
111+
err: &mut DiagnosticBuilder<'_>,
112+
span: Span,
113+
alt_snippet: Option<String>,
114+
) {
115+
if let Some(snippet) = self.source_map().span_to_snippet(span).ok().or(alt_snippet) {
116+
err.span_suggestion(
117+
span,
118+
"parentheses are required to parse this as an expression",
119+
format!("({})", snippet),
120+
Applicability::MachineApplicable,
121+
);
122+
}
123+
}
101124
}
102125

103126
#[derive(Clone)]

src/libsyntax/parse/parser.rs

Lines changed: 49 additions & 4 deletions
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

@@ -1375,6 +1376,7 @@ impl<'a> Parser<'a> {
13751376
token::DocComment(..) => PrevTokenKind::DocComment,
13761377
token::Comma => PrevTokenKind::Comma,
13771378
token::BinOp(token::Plus) => PrevTokenKind::Plus,
1379+
token::BinOp(token::Or) => PrevTokenKind::BitOr,
13781380
token::Interpolated(..) => PrevTokenKind::Interpolated,
13791381
token::Eof => PrevTokenKind::Eof,
13801382
token::Ident(..) => PrevTokenKind::Ident,
@@ -2806,6 +2808,12 @@ impl<'a> Parser<'a> {
28062808
let msg = format!("expected expression, found {}",
28072809
self.this_token_descr());
28082810
let mut err = self.fatal(&msg);
2811+
let sp = self.sess.source_map().start_point(self.span);
2812+
if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow()
2813+
.get(&sp)
2814+
{
2815+
self.sess.expr_parentheses_needed(&mut err, *sp, None);
2816+
}
28092817
err.span_label(self.span, "expected expression");
28102818
return Err(err);
28112819
}
@@ -2845,7 +2853,7 @@ impl<'a> Parser<'a> {
28452853
"struct literals are not allowed here",
28462854
);
28472855
err.multipart_suggestion(
2848-
"surround the struct literal with parenthesis",
2856+
"surround the struct literal with parentheses",
28492857
vec![
28502858
(lo.shrink_to_lo(), "(".to_string()),
28512859
(expr.span.shrink_to_hi(), ")".to_string()),
@@ -3506,9 +3514,42 @@ impl<'a> Parser<'a> {
35063514
}
35073515
};
35083516

3509-
if self.expr_is_complete(&lhs) {
3510-
// Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
3511-
return Ok(lhs);
3517+
match (self.expr_is_complete(&lhs), AssocOp::from_token(&self.token)) {
3518+
(true, None) => {
3519+
// Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
3520+
return Ok(lhs);
3521+
}
3522+
(false, _) => {} // continue parsing the expression
3523+
// An exhaustive check is done in the following block, but these are checked first
3524+
// because they *are* ambiguous but also reasonable looking incorrect syntax, so we
3525+
// want to keep their span info to improve diagnostics in these cases in a later stage.
3526+
(true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
3527+
(true, Some(AssocOp::Subtract)) | // `{ 42 } -5`
3528+
(true, Some(AssocOp::Add)) => { // `{ 42 } + 42
3529+
// These cases are ambiguous and can't be identified in the parser alone
3530+
let sp = self.sess.source_map().start_point(self.span);
3531+
self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
3532+
return Ok(lhs);
3533+
}
3534+
(true, Some(ref op)) if !op.can_continue_expr_unambiguously() => {
3535+
return Ok(lhs);
3536+
}
3537+
(true, Some(_)) => {
3538+
// We've found an expression that would be parsed as a statement, but the next
3539+
// token implies this should be parsed as an expression.
3540+
// For example: `if let Some(x) = x { x } else { 0 } / 2`
3541+
let mut err = self.sess.span_diagnostic.struct_span_err(self.span, &format!(
3542+
"expected expression, found `{}`",
3543+
pprust::token_to_string(&self.token),
3544+
));
3545+
err.span_label(self.span, "expected expression");
3546+
self.sess.expr_parentheses_needed(
3547+
&mut err,
3548+
lhs.span,
3549+
Some(pprust::expr_to_string(&lhs),
3550+
));
3551+
err.emit();
3552+
}
35123553
}
35133554
self.expected_tokens.push(TokenType::Operator);
35143555
while let Some(op) = AssocOp::from_token(&self.token) {
@@ -4819,6 +4860,10 @@ impl<'a> Parser<'a> {
48194860
);
48204861
let mut err = self.fatal(&msg);
48214862
err.span_label(self.span, format!("expected {}", expected));
4863+
let sp = self.sess.source_map().start_point(self.span);
4864+
if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) {
4865+
self.sess.expr_parentheses_needed(&mut err, *sp, None);
4866+
}
48224867
return Err(err);
48234868
}
48244869
}

src/libsyntax/util/parser.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,31 @@ impl AssocOp {
207207
ObsoleteInPlace | Assign | AssignOp(_) | As | DotDot | DotDotEq | Colon => None
208208
}
209209
}
210+
211+
/// This operator could be used to follow a block unambiguously.
212+
///
213+
/// This is used for error recovery at the moment, providing a suggestion to wrap blocks with
214+
/// parentheses while having a high degree of confidence on the correctness of the suggestion.
215+
pub fn can_continue_expr_unambiguously(&self) -> bool {
216+
use AssocOp::*;
217+
match self {
218+
BitXor | // `{ 42 } ^ 3`
219+
Assign | // `{ 42 } = { 42 }`
220+
Divide | // `{ 42 } / 42`
221+
Modulus | // `{ 42 } % 2`
222+
ShiftRight | // `{ 42 } >> 2`
223+
LessEqual | // `{ 42 } <= 3`
224+
Greater | // `{ 42 } > 3`
225+
GreaterEqual | // `{ 42 } >= 3`
226+
AssignOp(_) | // `{ 42 } +=`
227+
LAnd | // `{ 42 } &&foo`
228+
As | // `{ 42 } as usize`
229+
// Equal | // `{ 42 } == { 42 }` Accepting these here would regress incorrect
230+
// NotEqual | // `{ 42 } != { 42 } struct literals parser recovery.
231+
Colon => true, // `{ 42 }: usize`
232+
_ => false,
233+
}
234+
}
210235
}
211236

212237
pub const PREC_RESET: i8 = -100;

src/test/ui/error-codes/E0423.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ error: struct literals are not allowed here
33
|
44
LL | if let S { x: _x, y: 2 } = S { x: 1, y: 2 } { println!("Ok"); }
55
| ^^^^^^^^^^^^^^^^
6-
help: surround the struct literal with parenthesis
6+
help: surround the struct literal with parentheses
77
|
88
LL | if let S { x: _x, y: 2 } = (S { x: 1, y: 2 }) { println!("Ok"); }
99
| ^ ^
@@ -19,7 +19,7 @@ error: struct literals are not allowed here
1919
|
2020
LL | for _ in std::ops::Range { start: 0, end: 10 } {}
2121
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22-
help: surround the struct literal with parenthesis
22+
help: surround the struct literal with parentheses
2323
|
2424
LL | for _ in (std::ops::Range { start: 0, end: 10 }) {}
2525
| ^ ^

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 expected expression
31+
if let Some(y) = a { true } else { false }
32+
}
33+
34+
fn moo(x: u32) -> bool {
35+
(match x {
36+
_ => 1,
37+
}) > 0 //~ ERROR expected expression
38+
}
39+
40+
fn main() {}

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 expected expression
31+
if let Some(y) = a { true } else { false }
32+
}
33+
34+
fn moo(x: u32) -> bool {
35+
match x {
36+
_ => 1,
37+
} > 0 //~ ERROR expected expression
38+
}
39+
40+
fn main() {}

0 commit comments

Comments
 (0)