Skip to content

Commit f37f3df

Browse files
committed
Auto merge of #151247 - chenyukang:yukang-fix-const-recover-151149, r=<try>
Try to recover from over-parsing in const item with missing semicolon
2 parents a1db344 + c639215 commit f37f3df

File tree

7 files changed

+317
-65
lines changed

7 files changed

+317
-65
lines changed

compiler/rustc_parse/src/errors.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1291,7 +1291,6 @@ pub(crate) struct HelpIdentifierStartsWithNumber {
12911291
pub(crate) struct ExpectedSemi {
12921292
pub span: Span,
12931293
pub token: Token,
1294-
12951294
pub unexpected_token_label: Option<Span>,
12961295
pub sugg: ExpectedSemiSugg,
12971296
}

compiler/rustc_parse/src/parser/item.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ impl<'a> Parser<'a> {
286286
// CONST ITEM
287287
self.recover_const_mut(const_span);
288288
self.recover_missing_kw_before_item()?;
289-
let (ident, generics, ty, rhs) = self.parse_const_item(attrs)?;
289+
let (ident, generics, ty, rhs) = self.parse_const_item(attrs, const_span)?;
290290
ItemKind::Const(Box::new(ConstItem {
291291
defaultness: def_(),
292292
ident,
@@ -1522,6 +1522,7 @@ impl<'a> Parser<'a> {
15221522
fn parse_const_item(
15231523
&mut self,
15241524
attrs: &[Attribute],
1525+
const_span: Span,
15251526
) -> PResult<'a, (Ident, Generics, Box<Ty>, Option<ast::ConstItemRhs>)> {
15261527
let ident = self.parse_ident_or_underscore()?;
15271528

@@ -1612,6 +1613,9 @@ impl<'a> Parser<'a> {
16121613

16131614
generics.where_clause = where_clause;
16141615

1616+
if let Some(recovered_rhs) = self.try_recover_const_missing_semi(&rhs, const_span) {
1617+
return Ok((ident, generics, ty, Some(ConstItemRhs::Body(recovered_rhs))));
1618+
}
16151619
self.expect_semi()?;
16161620

16171621
Ok((ident, generics, ty, rhs))
@@ -2694,8 +2698,21 @@ impl<'a> Parser<'a> {
26942698
*sig_hi = self.prev_token.span;
26952699
(AttrVec::new(), None)
26962700
} else if self.check(exp!(OpenBrace)) || self.token.is_metavar_block() {
2697-
self.parse_block_common(self.token.span, BlockCheckMode::Default, None)
2698-
.map(|(attrs, body)| (attrs, Some(body)))?
2701+
let prev_in_fn_body = self.in_fn_body;
2702+
self.in_fn_body = true;
2703+
let res = self.parse_block_common(self.token.span, BlockCheckMode::Default, None).map(
2704+
|(attrs, mut body)| {
2705+
if let Some(guar) = self.fn_body_missing_semi_guar.take() {
2706+
body.stmts.push(self.mk_stmt(
2707+
body.span,
2708+
StmtKind::Expr(self.mk_expr(body.span, ExprKind::Err(guar))),
2709+
));
2710+
}
2711+
(attrs, Some(body))
2712+
},
2713+
);
2714+
self.in_fn_body = prev_in_fn_body;
2715+
res?
26992716
} else if self.token == token::Eq {
27002717
// Recover `fn foo() = $expr;`.
27012718
self.bump(); // `=`
@@ -3451,6 +3468,37 @@ impl<'a> Parser<'a> {
34513468
Ok(Some(_))
34523469
)
34533470
}
3471+
3472+
/// Try to recover from over-parsing in const item when a semicolon is missing.
3473+
///
3474+
/// This detects cases where we parsed too much because a semicolon was missing
3475+
/// and the next line started an expression that the parser treated as a continuation
3476+
/// (e.g., `foo() \n &bar` was parsed as `foo() & bar`).
3477+
///
3478+
/// Returns a corrected expression if recovery is successful.
3479+
fn try_recover_const_missing_semi(
3480+
&mut self,
3481+
rhs: &Option<ConstItemRhs>,
3482+
const_span: Span,
3483+
) -> Option<Box<Expr>> {
3484+
if self.token == TokenKind::Semi {
3485+
return None;
3486+
}
3487+
let Some(ConstItemRhs::Body(rhs)) = rhs else {
3488+
return None;
3489+
};
3490+
if !self.in_fn_body || !self.may_recover() || rhs.span.from_expansion() {
3491+
return None;
3492+
}
3493+
if let Some((span, guar)) =
3494+
self.missing_semi_from_binop("const", rhs, Some(const_span.shrink_to_lo()))
3495+
{
3496+
self.fn_body_missing_semi_guar = Some(guar);
3497+
Some(self.mk_expr(span, ExprKind::Err(guar)))
3498+
} else {
3499+
None
3500+
}
3501+
}
34543502
}
34553503

34563504
enum IsMacroRulesItem {

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,19 @@ use rustc_ast::tokenstream::{
3333
ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, TokenTreeCursor,
3434
};
3535
use rustc_ast::util::case::Case;
36+
use rustc_ast::util::classify;
3637
use rustc_ast::{
37-
self as ast, AnonConst, AttrArgs, AttrId, BlockCheckMode, ByRef, Const, CoroutineKind,
38-
DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation,
39-
Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind,
38+
self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, BlockCheckMode, ByRef, Const,
39+
CoroutineKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens,
40+
MgcaDisambiguation, Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind,
4041
};
4142
use rustc_ast_pretty::pprust;
4243
use rustc_data_structures::debug_assert_matches;
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};
@@ -233,6 +234,10 @@ pub struct Parser<'a> {
233234
/// Whether the parser is allowed to do recovery.
234235
/// This is disabled when parsing macro arguments, see #103534
235236
recovery: Recovery = Recovery::Allowed,
237+
/// Whether we're parsing a function body.
238+
in_fn_body: bool = false,
239+
/// Whether we have detected a missing semicolon in the function body.
240+
pub fn_body_missing_semi_guar: Option<ErrorGuaranteed> = None,
236241
}
237242

238243
// This type is used a lot, e.g. it's cloned when matching many declarative macro rules with
@@ -1681,6 +1686,65 @@ impl<'a> Parser<'a> {
16811686
_ => self.prev_token.span,
16821687
}
16831688
}
1689+
1690+
fn missing_semi_from_binop(
1691+
&self,
1692+
kind_desc: &str,
1693+
expr: &Expr,
1694+
decl_lo: Option<Span>,
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+
// Use the declaration start if provided, otherwise fall back to lhs_end_span.
1716+
let continuation_start = decl_lo.unwrap_or(lhs_end_span);
1717+
let continuation_span = continuation_start.until(rhs.span.shrink_to_hi());
1718+
err.span_label(
1719+
continuation_span,
1720+
format!(
1721+
"to finish parsing this {kind_desc}, expected this to be followed by a `;`",
1722+
),
1723+
);
1724+
let op_desc = match op.node {
1725+
BinOpKind::BitAnd => "a bit-and",
1726+
BinOpKind::Mul => "a multiplication",
1727+
_ => "a binary",
1728+
};
1729+
let mut note_spans = MultiSpan::new();
1730+
note_spans.push_span_label(lhs.span, "parsed as the left-hand expression");
1731+
note_spans.push_span_label(rhs.span, "parsed as the right-hand expression");
1732+
note_spans.push_span_label(op.span, format!("this was parsed as {op_desc}"));
1733+
err.span_note(
1734+
note_spans,
1735+
format!("the {kind_desc} was parsed as having {op_desc} binary expression"),
1736+
);
1737+
1738+
err.span_suggestion(
1739+
lhs_end_span,
1740+
format!("you may have meant to write a `;` to terminate the {kind_desc} earlier"),
1741+
";",
1742+
Applicability::MaybeIncorrect,
1743+
);
1744+
return Some((lhs.span, err.emit()));
1745+
}
1746+
None
1747+
}
16841748
}
16851749

16861750
// Metavar captures of various kinds.

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 75 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,21 @@ impl<'a> Parser<'a> {
924924
}
925925
}
926926

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

11361154
if add_semi_to_stmt || (eat_semi && self.eat(exp!(Semi))) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@ run-rustfix
2+
#![feature(const_trait_impl)]
3+
#![allow(dead_code)]
4+
#![allow(unused)]
5+
6+
const trait ConstDefault {
7+
fn const_default() -> Self;
8+
}
9+
10+
impl const ConstDefault for u8 {
11+
fn const_default() -> Self { 0 }
12+
}
13+
14+
const fn val() -> u8 {
15+
42
16+
}
17+
18+
const C: u8 = u8::const_default()
19+
&1; //~ ERROR expected `;`, found keyword `const`
20+
21+
const fn foo() -> &'static u8 {
22+
const C: u8 = u8::const_default(); //~ ERROR expected `;`
23+
&C
24+
}
25+
26+
const fn bar() {
27+
const C: u8 = 1
28+
+ 2; //~ ERROR expected `;`, found `}`
29+
}
30+
31+
const fn baz() {
32+
const C: u8 = 1
33+
+ val(); //~ ERROR expected `;`, found `}`
34+
}
35+
36+
fn main() {}

0 commit comments

Comments
 (0)