Skip to content

Commit 6f54d59

Browse files
committed
Auto merge of #151247 - chenyukang:yukang-fix-const-recover-151149, r=estebank
Try to recover from over-parsing in const item with missing semicolon Fixes #151149 r? @estebank
2 parents 0ee5907 + ba0e1c9 commit 6f54d59

7 files changed

Lines changed: 318 additions & 66 deletions

File tree

compiler/rustc_parse/src/errors.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1514,7 +1514,6 @@ pub(crate) struct HelpIdentifierStartsWithNumber {
15141514
pub(crate) struct ExpectedSemi {
15151515
pub span: Span,
15161516
pub token: Token,
1517-
15181517
pub unexpected_token_label: Option<Span>,
15191518
pub sugg: ExpectedSemiSugg,
15201519
}

compiler/rustc_parse/src/parser/item.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ impl<'a> Parser<'a> {
296296
// CONST ITEM
297297
self.recover_const_mut(const_span);
298298
self.recover_missing_kw_before_item()?;
299-
let (ident, generics, ty, rhs_kind) = self.parse_const_item(false)?;
299+
let (ident, generics, ty, rhs_kind) = self.parse_const_item(false, const_span)?;
300300
ItemKind::Const(Box::new(ConstItem {
301301
defaultness: def_(),
302302
ident,
@@ -317,7 +317,7 @@ impl<'a> Parser<'a> {
317317
// TYPE CONST (mgca)
318318
self.recover_const_mut(const_span);
319319
self.recover_missing_kw_before_item()?;
320-
let (ident, generics, ty, rhs_kind) = self.parse_const_item(true)?;
320+
let (ident, generics, ty, rhs_kind) = self.parse_const_item(true, const_span)?;
321321
// Make sure this is only allowed if the feature gate is enabled.
322322
// #![feature(mgca_type_const_syntax)]
323323
self.psess.gated_spans.gate(sym::mgca_type_const_syntax, lo.to(const_span));
@@ -1558,6 +1558,7 @@ impl<'a> Parser<'a> {
15581558
fn parse_const_item(
15591559
&mut self,
15601560
const_arg: bool,
1561+
const_span: Span,
15611562
) -> PResult<'a, (Ident, Generics, Box<Ty>, ConstItemRhsKind)> {
15621563
let ident = self.parse_ident_or_underscore()?;
15631564

@@ -1647,6 +1648,9 @@ impl<'a> Parser<'a> {
16471648

16481649
generics.where_clause = where_clause;
16491650

1651+
if let Some(rhs) = self.try_recover_const_missing_semi(&rhs, const_span) {
1652+
return Ok((ident, generics, ty, ConstItemRhsKind::Body { rhs: Some(rhs) }));
1653+
}
16501654
self.expect_semi()?;
16511655

16521656
Ok((ident, generics, ty, rhs))
@@ -2744,8 +2748,21 @@ impl<'a> Parser<'a> {
27442748
*sig_hi = self.prev_token.span;
27452749
(AttrVec::new(), None)
27462750
} else if self.check(exp!(OpenBrace)) || self.token.is_metavar_block() {
2747-
self.parse_block_common(self.token.span, BlockCheckMode::Default, None)
2748-
.map(|(attrs, body)| (attrs, Some(body)))?
2751+
let prev_in_fn_body = self.in_fn_body;
2752+
self.in_fn_body = true;
2753+
let res = self.parse_block_common(self.token.span, BlockCheckMode::Default, None).map(
2754+
|(attrs, mut body)| {
2755+
if let Some(guar) = self.fn_body_missing_semi_guar.take() {
2756+
body.stmts.push(self.mk_stmt(
2757+
body.span,
2758+
StmtKind::Expr(self.mk_expr(body.span, ExprKind::Err(guar))),
2759+
));
2760+
}
2761+
(attrs, Some(body))
2762+
},
2763+
);
2764+
self.in_fn_body = prev_in_fn_body;
2765+
res?
27492766
} else if self.token == token::Eq {
27502767
// Recover `fn foo() = $expr;`.
27512768
self.bump(); // `=`
@@ -3501,6 +3518,37 @@ impl<'a> Parser<'a> {
35013518
Ok(Some(_))
35023519
)
35033520
}
3521+
3522+
/// Try to recover from over-parsing in const item when a semicolon is missing.
3523+
///
3524+
/// This detects cases where we parsed too much because a semicolon was missing
3525+
/// and the next line started an expression that the parser treated as a continuation
3526+
/// (e.g., `foo() \n &bar` was parsed as `foo() & bar`).
3527+
///
3528+
/// Returns a corrected expression if recovery is successful.
3529+
fn try_recover_const_missing_semi(
3530+
&mut self,
3531+
rhs: &ConstItemRhsKind,
3532+
const_span: Span,
3533+
) -> Option<Box<Expr>> {
3534+
if self.token == TokenKind::Semi {
3535+
return None;
3536+
}
3537+
let ConstItemRhsKind::Body { rhs: Some(rhs) } = rhs else {
3538+
return None;
3539+
};
3540+
if !self.in_fn_body || !self.may_recover() || rhs.span.from_expansion() {
3541+
return None;
3542+
}
3543+
if let Some((span, guar)) =
3544+
self.missing_semi_from_binop("const", rhs, Some(const_span.shrink_to_lo()))
3545+
{
3546+
self.fn_body_missing_semi_guar = Some(guar);
3547+
Some(self.mk_expr(span, ExprKind::Err(guar)))
3548+
} else {
3549+
None
3550+
}
3551+
}
35043552
}
35053553

35063554
enum IsMacroRulesItem {

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@ use rustc_ast::tokenstream::{
3232
ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, TokenTreeCursor,
3333
};
3434
use rustc_ast::util::case::Case;
35+
use rustc_ast::util::classify;
3536
use rustc_ast::{
36-
self as ast, AnonConst, AttrArgs, AttrId, ByRef, Const, CoroutineKind, DUMMY_NODE_ID,
37-
DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation, Mutability,
38-
Recovered, Safety, StrLit, Visibility, VisibilityKind,
37+
self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, ByRef, Const, CoroutineKind,
38+
DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation,
39+
Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind,
3940
};
4041
use rustc_ast_pretty::pprust;
4142
use rustc_data_structures::debug_assert_matches;
4243
use rustc_data_structures::fx::FxHashMap;
4344
use rustc_errors::{Applicability, Diag, FatalError, MultiSpan, PResult};
4445
use rustc_index::interval::IntervalSet;
4546
use rustc_session::parse::ParseSess;
46-
use rustc_span::{Ident, Span, Symbol, kw, sym};
47+
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym};
4748
use thin_vec::ThinVec;
4849
use token_type::TokenTypeSet;
4950
pub use token_type::{ExpKeywordPair, ExpTokenPair, TokenType};
@@ -232,6 +233,10 @@ pub struct Parser<'a> {
232233
/// Whether the parser is allowed to do recovery.
233234
/// This is disabled when parsing macro arguments, see #103534
234235
recovery: Recovery = Recovery::Allowed,
236+
/// Whether we're parsing a function body.
237+
in_fn_body: bool = false,
238+
/// Whether we have detected a missing semicolon in function body.
239+
pub fn_body_missing_semi_guar: Option<ErrorGuaranteed> = None,
235240
}
236241

237242
// This type is used a lot, e.g. it's cloned when matching many declarative macro rules with
@@ -1649,6 +1654,65 @@ impl<'a> Parser<'a> {
16491654
_ => self.prev_token.span,
16501655
}
16511656
}
1657+
1658+
fn missing_semi_from_binop(
1659+
&self,
1660+
kind_desc: &str,
1661+
expr: &Expr,
1662+
decl_lo: Option<Span>,
1663+
) -> Option<(Span, ErrorGuaranteed)> {
1664+
if self.token == TokenKind::Semi {
1665+
return None;
1666+
}
1667+
if !self.may_recover() || expr.span.from_expansion() {
1668+
return None;
1669+
}
1670+
let sm = self.psess.source_map();
1671+
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind
1672+
&& sm.is_multiline(lhs.span.shrink_to_hi().until(rhs.span.shrink_to_lo()))
1673+
&& matches!(op.node, BinOpKind::Mul | BinOpKind::BitAnd)
1674+
&& classify::expr_requires_semi_to_be_stmt(rhs)
1675+
{
1676+
let lhs_end_span = lhs.span.shrink_to_hi();
1677+
let token_str = token_descr(&self.token);
1678+
let mut err = self
1679+
.dcx()
1680+
.struct_span_err(lhs_end_span, format!("expected `;`, found {token_str}"));
1681+
err.span_label(self.token.span, "unexpected token");
1682+
1683+
// Use the declaration start if provided, otherwise fall back to lhs_end_span.
1684+
let continuation_start = decl_lo.unwrap_or(lhs_end_span);
1685+
let continuation_span = continuation_start.until(rhs.span.shrink_to_hi());
1686+
err.span_label(
1687+
continuation_span,
1688+
format!(
1689+
"to finish parsing this {kind_desc}, expected this to be followed by a `;`",
1690+
),
1691+
);
1692+
let op_desc = match op.node {
1693+
BinOpKind::BitAnd => "a bit-and",
1694+
BinOpKind::Mul => "a multiplication",
1695+
_ => "a binary",
1696+
};
1697+
let mut note_spans = MultiSpan::new();
1698+
note_spans.push_span_label(lhs.span, "parsed as the left-hand expression");
1699+
note_spans.push_span_label(rhs.span, "parsed as the right-hand expression");
1700+
note_spans.push_span_label(op.span, format!("this was parsed as {op_desc}"));
1701+
err.span_note(
1702+
note_spans,
1703+
format!("the {kind_desc} was parsed as having {op_desc} binary expression"),
1704+
);
1705+
1706+
err.span_suggestion(
1707+
lhs_end_span,
1708+
format!("you may have meant to write a `;` to terminate the {kind_desc} earlier"),
1709+
";",
1710+
Applicability::MaybeIncorrect,
1711+
);
1712+
return Some((lhs.span, err.emit()));
1713+
}
1714+
None
1715+
}
16521716
}
16531717

16541718
// 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)