Skip to content

Commit acb4b6a

Browse files
committed
also add recover from over parsing for let
1 parent b1dcdd1 commit acb4b6a

7 files changed

Lines changed: 324 additions & 94 deletions

File tree

compiler/rustc_parse/src/parser/item.rs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use rustc_ast::ast::*;
66
use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind};
77
use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
88
use rustc_ast::util::case::Case;
9-
use rustc_ast::util::classify;
109
use rustc_ast::{
1110
attr, {self as ast},
1211
};
@@ -2654,8 +2653,13 @@ impl<'a> Parser<'a> {
26542653
*sig_hi = self.prev_token.span;
26552654
(AttrVec::new(), None)
26562655
} else if self.check(exp!(OpenBrace)) || self.token.is_metavar_block() {
2657-
self.parse_block_common(self.token.span, BlockCheckMode::Default, None)
2658-
.map(|(attrs, body)| (attrs, Some(body)))?
2656+
let prev_in_fn_body = self.in_fn_body;
2657+
self.in_fn_body = true;
2658+
let res = self
2659+
.parse_block_common(self.token.span, BlockCheckMode::Default, None)
2660+
.map(|(attrs, body)| (attrs, Some(body)));
2661+
self.in_fn_body = prev_in_fn_body;
2662+
res?
26592663
} else if self.token == token::Eq {
26602664
// Recover `fn foo() = $expr;`.
26612665
self.bump(); // `=`
@@ -3426,26 +3430,11 @@ impl<'a> Parser<'a> {
34263430
let Some(ConstItemRhs::Body(rhs)) = rhs else {
34273431
return None;
34283432
};
3429-
if !self.may_recover() || rhs.span.from_expansion() {
3433+
if !self.in_fn_body || !self.may_recover() || rhs.span.from_expansion() {
34303434
return None;
34313435
}
3432-
let sm = self.psess.source_map();
3433-
// Check if this is a binary expression that spans multiple lines
3434-
// and the RHS looks like it could be an independent expression.
3435-
if let ExprKind::Binary(op, lhs, rhs_inner) = &rhs.kind
3436-
&& sm.is_multiline(lhs.span.shrink_to_hi().until(rhs_inner.span.shrink_to_lo()))
3437-
&& matches!(op.node, BinOpKind::Mul | BinOpKind::BitAnd)
3438-
&& classify::expr_requires_semi_to_be_stmt(rhs_inner)
3439-
{
3440-
let lhs_end_span = lhs.span.shrink_to_hi();
3441-
let guar = self.dcx().emit_err(errors::ExpectedSemi {
3442-
span: lhs_end_span,
3443-
token: self.token,
3444-
unexpected_token_label: Some(self.token.span),
3445-
sugg: errors::ExpectedSemiSugg::AddSemi(lhs_end_span),
3446-
});
3447-
3448-
Some(self.mk_expr(lhs.span, ExprKind::Err(guar)))
3436+
if let Some((span, guar)) = self.missing_semi_from_binop("const", rhs) {
3437+
Some(self.mk_expr(span, ExprKind::Err(guar)))
34493438
} else {
34503439
None
34513440
}

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,18 @@ use rustc_ast::tokenstream::{
3434
ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, TokenTreeCursor,
3535
};
3636
use rustc_ast::util::case::Case;
37+
use rustc_ast::util::classify;
3738
use rustc_ast::{
38-
self as ast, AnonConst, AttrArgs, AttrId, BlockCheckMode, ByRef, Const, CoroutineKind,
39-
DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation,
40-
Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind,
39+
self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, BlockCheckMode, ByRef, Const,
40+
CoroutineKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens,
41+
MgcaDisambiguation, Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind,
4142
};
4243
use rustc_ast_pretty::pprust;
4344
use rustc_data_structures::fx::FxHashMap;
4445
use rustc_errors::{Applicability, Diag, FatalError, MultiSpan, PResult};
4546
use rustc_index::interval::IntervalSet;
4647
use rustc_session::parse::ParseSess;
47-
use rustc_span::{Ident, Span, Symbol, kw, sym};
48+
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym};
4849
use thin_vec::ThinVec;
4950
use token_type::TokenTypeSet;
5051
pub use token_type::{ExpKeywordPair, ExpTokenPair, TokenType};
@@ -223,6 +224,8 @@ pub struct Parser<'a> {
223224
/// Whether the parser is allowed to do recovery.
224225
/// This is disabled when parsing macro arguments, see #103534
225226
recovery: Recovery,
227+
/// Whether we're parsing a function body.
228+
in_fn_body: bool,
226229
}
227230

228231
// This type is used a lot, e.g. it's cloned when matching many declarative macro rules with
@@ -372,6 +375,7 @@ impl<'a> Parser<'a> {
372375
},
373376
current_closure: None,
374377
recovery: Recovery::Allowed,
378+
in_fn_body: false,
375379
};
376380

377381
// Make parser point to the first token.
@@ -1683,6 +1687,62 @@ impl<'a> Parser<'a> {
16831687
_ => self.prev_token.span,
16841688
}
16851689
}
1690+
1691+
fn missing_semi_from_binop(
1692+
&self,
1693+
kind_desc: &str,
1694+
expr: &Expr,
1695+
) -> Option<(Span, ErrorGuaranteed)> {
1696+
if self.token == TokenKind::Semi {
1697+
return None;
1698+
}
1699+
if !self.may_recover() || expr.span.from_expansion() {
1700+
return None;
1701+
}
1702+
let sm = self.psess.source_map();
1703+
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind
1704+
&& sm.is_multiline(lhs.span.shrink_to_hi().until(rhs.span.shrink_to_lo()))
1705+
&& matches!(op.node, BinOpKind::Mul | BinOpKind::BitAnd)
1706+
&& classify::expr_requires_semi_to_be_stmt(rhs)
1707+
{
1708+
let lhs_end_span = lhs.span.shrink_to_hi();
1709+
let token_str = token_descr(&self.token);
1710+
let mut err = self
1711+
.dcx()
1712+
.struct_span_err(lhs_end_span, format!("expected `;`, found {token_str}"));
1713+
err.span_label(self.token.span, "unexpected token");
1714+
1715+
let continuation_span = lhs_end_span.until(rhs.span.shrink_to_hi());
1716+
err.span_label(
1717+
continuation_span,
1718+
format!(
1719+
"to finish parsing this {kind_desc}, expected this to be followed by a `;`",
1720+
),
1721+
);
1722+
let op_desc = match op.node {
1723+
BinOpKind::BitAnd => "a bit-and",
1724+
BinOpKind::Mul => "a multiplication",
1725+
_ => "a binary",
1726+
};
1727+
let mut note_spans = MultiSpan::new();
1728+
note_spans.push_span_label(lhs.span, "parsed as the left-hand expression");
1729+
note_spans.push_span_label(rhs.span, "parsed as the right-hand expression");
1730+
note_spans.push_span_label(op.span, format!("this was parsed as {op_desc}"));
1731+
err.span_note(
1732+
note_spans,
1733+
format!("the {kind_desc} was parsed as having {op_desc} binary expression"),
1734+
);
1735+
1736+
err.span_suggestion(
1737+
lhs_end_span,
1738+
format!("you may have meant to write a `;` to terminate the {kind_desc} earlier"),
1739+
";",
1740+
Applicability::MaybeIncorrect,
1741+
);
1742+
return Some((lhs.span, err.emit()));
1743+
}
1744+
None
1745+
}
16861746
}
16871747

16881748
// Metavar captures of various kinds.

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,19 @@ impl<'a> Parser<'a> {
923923
}
924924
}
925925

926+
fn try_recover_let_missing_semi(&mut self, local: &mut Local) -> Option<ErrorGuaranteed> {
927+
let expr = match &mut local.kind {
928+
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => expr,
929+
LocalKind::Decl => return None,
930+
};
931+
if let Some((span, guar)) = self.missing_semi_from_binop("`let` binding", expr) {
932+
*expr = self.mk_expr(span, ExprKind::Err(guar));
933+
return Some(guar);
934+
}
935+
936+
None
937+
}
938+
926939
/// Parses a statement, including the trailing semicolon.
927940
pub fn parse_full_stmt(
928941
&mut self,
@@ -1065,71 +1078,74 @@ impl<'a> Parser<'a> {
10651078
}
10661079
}
10671080
StmtKind::Expr(_) | StmtKind::MacCall(_) => {}
1068-
StmtKind::Let(local) if let Err(mut e) = self.expect_semi() => {
1069-
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
1070-
match &mut local.kind {
1071-
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
1072-
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
1073-
|mut e| {
1074-
self.recover_missing_dot(&mut e);
1075-
self.recover_missing_let_else(&mut e, &local.pat, stmt.span);
1076-
e
1077-
},
1078-
)?;
1079-
// We found `foo<bar, baz>`, have we fully recovered?
1080-
self.expect_semi()?;
1081-
}
1082-
LocalKind::Decl => {
1083-
if let Some(colon_sp) = local.colon_sp {
1084-
e.span_label(
1085-
colon_sp,
1086-
format!(
1087-
"while parsing the type for {}",
1088-
local.pat.descr().map_or_else(
1089-
|| "the binding".to_string(),
1090-
|n| format!("`{n}`")
1091-
)
1092-
),
1093-
);
1094-
let suggest_eq = if self.token == token::Dot
1095-
&& let _ = self.bump()
1096-
&& let mut snapshot = self.create_snapshot_for_diagnostic()
1097-
&& let Ok(_) = snapshot
1098-
.parse_dot_suffix_expr(
1099-
colon_sp,
1100-
self.mk_expr_err(
1101-
colon_sp,
1102-
self.dcx()
1103-
.delayed_bug("error during `:` -> `=` recovery"),
1104-
),
1105-
)
1106-
.map_err(Diag::cancel)
1107-
{
1108-
true
1109-
} else if let Some(op) = self.check_assoc_op()
1110-
&& op.node.can_continue_expr_unambiguously()
1111-
{
1112-
true
1113-
} else {
1114-
false
1115-
};
1116-
if suggest_eq {
1117-
e.span_suggestion_short(
1081+
StmtKind::Let(local) => {
1082+
if self.try_recover_let_missing_semi(local).is_some() {
1083+
return Ok(Some(stmt));
1084+
}
1085+
if let Err(mut e) = self.expect_semi() {
1086+
// We might be at the `,` in `let x = foo<bar, baz>;`. Try to recover.
1087+
match &mut local.kind {
1088+
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
1089+
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)
1090+
.map_err(|mut e| {
1091+
self.recover_missing_dot(&mut e);
1092+
self.recover_missing_let_else(&mut e, &local.pat, stmt.span);
1093+
e
1094+
})?;
1095+
// We found `foo<bar, baz>`, have we fully recovered?
1096+
self.expect_semi()?;
1097+
}
1098+
LocalKind::Decl => {
1099+
if let Some(colon_sp) = local.colon_sp {
1100+
e.span_label(
11181101
colon_sp,
1119-
"use `=` if you meant to assign",
1120-
"=",
1121-
Applicability::MaybeIncorrect,
1102+
format!(
1103+
"while parsing the type for {}",
1104+
local.pat.descr().map_or_else(
1105+
|| "the binding".to_string(),
1106+
|n| format!("`{n}`")
1107+
)
1108+
),
11221109
);
1110+
let suggest_eq = if self.token == token::Dot
1111+
&& let _ = self.bump()
1112+
&& let mut snapshot = self.create_snapshot_for_diagnostic()
1113+
&& let Ok(_) = snapshot
1114+
.parse_dot_suffix_expr(
1115+
colon_sp,
1116+
self.mk_expr_err(
1117+
colon_sp,
1118+
self.dcx().delayed_bug(
1119+
"error during `:` -> `=` recovery",
1120+
),
1121+
),
1122+
)
1123+
.map_err(Diag::cancel)
1124+
{
1125+
true
1126+
} else if let Some(op) = self.check_assoc_op()
1127+
&& op.node.can_continue_expr_unambiguously()
1128+
{
1129+
true
1130+
} else {
1131+
false
1132+
};
1133+
if suggest_eq {
1134+
e.span_suggestion_short(
1135+
colon_sp,
1136+
"use `=` if you meant to assign",
1137+
"=",
1138+
Applicability::MaybeIncorrect,
1139+
);
1140+
}
11231141
}
1142+
return Err(e);
11241143
}
1125-
return Err(e);
11261144
}
11271145
}
11281146
eat_semi = false;
11291147
}
1130-
StmtKind::Empty | StmtKind::Item(_) | StmtKind::Let(_) | StmtKind::Semi(_) => {
1131-
eat_semi = false
1132-
}
1148+
StmtKind::Empty | StmtKind::Item(_) | StmtKind::Semi(_) => eat_semi = false,
11331149
}
11341150

11351151
if add_semi_to_stmt || (eat_semi && self.eat(exp!(Semi))) {

tests/ui/issues/const-recover-semi-issue-151149.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ const fn val() -> u8 {
1212
42
1313
}
1414

15+
const C: u8 = u8::const_default()
16+
&1 //~ ERROR expected `;`, found keyword `const`
17+
1518
const fn foo() -> &'static u8 { //~ ERROR mismatched types
1619
const C: u8 = u8::const_default() //~ ERROR expected `;`
1720
&C
1821
}
1922

2023
const fn bar() -> u8 { //~ ERROR mismatched types
2124
const C: u8 = 1
22-
+ 2 //~ ERROR expected `;`
25+
+ 2 //~ ERROR expected `;`, found `}`
2326
}
2427

2528
const fn baz() -> u8 { //~ ERROR mismatched types
2629
const C: u8 = 1
27-
+ val() //~ ERROR expected `;`
30+
+ val() //~ ERROR expected `;`, found `}`
2831
}
2932

3033
fn main() {}

0 commit comments

Comments
 (0)