From b4f15dcead2b53d889b15e77aee1f81cd52bdb84 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 17 Jan 2026 21:27:43 +0800 Subject: [PATCH 1/4] Try to recover from over-parsing in const item when a semicolon is missing --- compiler/rustc_parse/src/errors.rs | 1 - compiler/rustc_parse/src/parser/item.rs | 47 ++++++++++++++++- .../issues/const-recover-semi-issue-151149.rs | 30 +++++++++++ .../const-recover-semi-issue-151149.stderr | 52 +++++++++++++++++++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 tests/ui/issues/const-recover-semi-issue-151149.rs create mode 100644 tests/ui/issues/const-recover-semi-issue-151149.stderr diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 6499f31bb935b..0af0816319f3c 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -1514,7 +1514,6 @@ pub(crate) struct HelpIdentifierStartsWithNumber { pub(crate) struct ExpectedSemi { pub span: Span, pub token: Token, - pub unexpected_token_label: Option, pub sugg: ExpectedSemiSugg, } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 5b81acb0f91f0..3bed4af57b8f0 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -6,7 +6,10 @@ use rustc_ast::ast::*; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::util::case::Case; -use rustc_ast::{self as ast}; +use rustc_ast::util::classify; +use rustc_ast::{ + attr, {self as ast}, +}; use rustc_ast_pretty::pprust; use rustc_errors::codes::*; use rustc_errors::{Applicability, PResult, StashKey, inline_fluent, struct_span_code_err}; @@ -1634,6 +1637,9 @@ impl<'a> Parser<'a> { generics.where_clause = where_clause; + if let Some(recovered_rhs) = self.try_recover_const_missing_semi(&rhs) { + return Ok((ident, generics, ty, Some(ConstItemRhs::Body(recovered_rhs)))); + } self.expect_semi()?; Ok((ident, generics, ty, rhs)) @@ -3473,6 +3479,45 @@ impl<'a> Parser<'a> { Ok(Some(_)) ) } + + /// Try to recover from over-parsing in const item when a semicolon is missing. + /// + /// This detects cases where we parsed too much because a semicolon was missing + /// and the next line started an expression that the parser treated as a continuation + /// (e.g., `foo() \n &bar` was parsed as `foo() & bar`). + /// + /// Returns a corrected expression if recovery is successful. + fn try_recover_const_missing_semi(&mut self, rhs: &Option) -> Option> { + if self.token == TokenKind::Semi { + return None; + } + let Some(ConstItemRhs::Body(rhs)) = rhs else { + return None; + }; + if !self.may_recover() || rhs.span.from_expansion() { + return None; + } + let sm = self.psess.source_map(); + // Check if this is a binary expression that spans multiple lines + // and the RHS looks like it could be an independent expression. + if let ExprKind::Binary(op, lhs, rhs_inner) = &rhs.kind + && sm.is_multiline(lhs.span.shrink_to_hi().until(rhs_inner.span.shrink_to_lo())) + && matches!(op.node, BinOpKind::Mul | BinOpKind::BitAnd) + && classify::expr_requires_semi_to_be_stmt(rhs_inner) + { + let lhs_end_span = lhs.span.shrink_to_hi(); + let guar = self.dcx().emit_err(errors::ExpectedSemi { + span: lhs_end_span, + token: self.token, + unexpected_token_label: Some(self.token.span), + sugg: errors::ExpectedSemiSugg::AddSemi(lhs_end_span), + }); + + Some(self.mk_expr(lhs.span, ExprKind::Err(guar))) + } else { + None + } + } } enum IsMacroRulesItem { diff --git a/tests/ui/issues/const-recover-semi-issue-151149.rs b/tests/ui/issues/const-recover-semi-issue-151149.rs new file mode 100644 index 0000000000000..f5dc27bb3dd86 --- /dev/null +++ b/tests/ui/issues/const-recover-semi-issue-151149.rs @@ -0,0 +1,30 @@ +#![feature(const_trait_impl)] + +const trait ConstDefault { + fn const_default() -> Self; +} + +impl const ConstDefault for u8 { + fn const_default() -> Self { 0 } +} + +const fn val() -> u8 { + 42 +} + +const fn foo() -> &'static u8 { //~ ERROR mismatched types + const C: u8 = u8::const_default() //~ ERROR expected `;` + &C +} + +const fn bar() -> u8 { //~ ERROR mismatched types + const C: u8 = 1 + + 2 //~ ERROR expected `;` +} + +const fn baz() -> u8 { //~ ERROR mismatched types + const C: u8 = 1 + + val() //~ ERROR expected `;` +} + +fn main() {} diff --git a/tests/ui/issues/const-recover-semi-issue-151149.stderr b/tests/ui/issues/const-recover-semi-issue-151149.stderr new file mode 100644 index 0000000000000..96c62c39fb6ad --- /dev/null +++ b/tests/ui/issues/const-recover-semi-issue-151149.stderr @@ -0,0 +1,52 @@ +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:16:38 + | +LL | const C: u8 = u8::const_default() + | ^ help: add `;` here +LL | &C +LL | } + | - unexpected token + +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:22:9 + | +LL | + 2 + | ^ help: add `;` here +LL | } + | - unexpected token + +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:27:13 + | +LL | + val() + | ^ help: add `;` here +LL | } + | - unexpected token + +error[E0308]: mismatched types + --> $DIR/const-recover-semi-issue-151149.rs:15:19 + | +LL | const fn foo() -> &'static u8 { + | --- ^^^^^^^^^^^ expected `&u8`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error[E0308]: mismatched types + --> $DIR/const-recover-semi-issue-151149.rs:20:19 + | +LL | const fn bar() -> u8 { + | --- ^^ expected `u8`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error[E0308]: mismatched types + --> $DIR/const-recover-semi-issue-151149.rs:25:19 + | +LL | const fn baz() -> u8 { + | --- ^^ expected `u8`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0308`. From 6d0e077d138c3c499a51da17b8d7bdc8b1dbdcda Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 19 Jan 2026 15:17:03 +0800 Subject: [PATCH 2/4] also add recover from over parsing for let --- compiler/rustc_parse/src/parser/item.rs | 31 ++--- compiler/rustc_parse/src/parser/mod.rs | 67 ++++++++- compiler/rustc_parse/src/parser/stmt.rs | 130 ++++++++++-------- .../const-recover-semi-issue-151149.stderr | 52 ------- .../const-recover-semi-issue-151149.rs | 12 +- .../const-recover-semi-issue-151149.stderr | 100 ++++++++++++++ 6 files changed, 256 insertions(+), 136 deletions(-) delete mode 100644 tests/ui/issues/const-recover-semi-issue-151149.stderr rename tests/ui/{issues => parser}/const-recover-semi-issue-151149.rs (67%) create mode 100644 tests/ui/parser/const-recover-semi-issue-151149.stderr diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 3bed4af57b8f0..21b061e7d711e 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -6,7 +6,6 @@ use rustc_ast::ast::*; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::util::case::Case; -use rustc_ast::util::classify; use rustc_ast::{ attr, {self as ast}, }; @@ -2722,8 +2721,13 @@ impl<'a> Parser<'a> { *sig_hi = self.prev_token.span; (AttrVec::new(), None) } else if self.check(exp!(OpenBrace)) || self.token.is_metavar_block() { - self.parse_block_common(self.token.span, BlockCheckMode::Default, None) - .map(|(attrs, body)| (attrs, Some(body)))? + let prev_in_fn_body = self.in_fn_body; + self.in_fn_body = true; + let res = self + .parse_block_common(self.token.span, BlockCheckMode::Default, None) + .map(|(attrs, body)| (attrs, Some(body))); + self.in_fn_body = prev_in_fn_body; + res? } else if self.token == token::Eq { // Recover `fn foo() = $expr;`. self.bump(); // `=` @@ -3494,26 +3498,11 @@ impl<'a> Parser<'a> { let Some(ConstItemRhs::Body(rhs)) = rhs else { return None; }; - if !self.may_recover() || rhs.span.from_expansion() { + if !self.in_fn_body || !self.may_recover() || rhs.span.from_expansion() { return None; } - let sm = self.psess.source_map(); - // Check if this is a binary expression that spans multiple lines - // and the RHS looks like it could be an independent expression. - if let ExprKind::Binary(op, lhs, rhs_inner) = &rhs.kind - && sm.is_multiline(lhs.span.shrink_to_hi().until(rhs_inner.span.shrink_to_lo())) - && matches!(op.node, BinOpKind::Mul | BinOpKind::BitAnd) - && classify::expr_requires_semi_to_be_stmt(rhs_inner) - { - let lhs_end_span = lhs.span.shrink_to_hi(); - let guar = self.dcx().emit_err(errors::ExpectedSemi { - span: lhs_end_span, - token: self.token, - unexpected_token_label: Some(self.token.span), - sugg: errors::ExpectedSemiSugg::AddSemi(lhs_end_span), - }); - - Some(self.mk_expr(lhs.span, ExprKind::Err(guar))) + if let Some((span, guar)) = self.missing_semi_from_binop("const", rhs) { + Some(self.mk_expr(span, ExprKind::Err(guar))) } else { None } diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index a6b956b09bc16..d5982e66d99e2 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -32,10 +32,11 @@ use rustc_ast::tokenstream::{ ParserRange, ParserReplacement, Spacing, TokenCursor, TokenStream, TokenTree, TokenTreeCursor, }; use rustc_ast::util::case::Case; +use rustc_ast::util::classify; use rustc_ast::{ - self as ast, AnonConst, AttrArgs, AttrId, ByRef, Const, CoroutineKind, DUMMY_NODE_ID, - DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation, Mutability, - Recovered, Safety, StrLit, Visibility, VisibilityKind, + self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, BlockCheckMode, ByRef, Const, + CoroutineKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, + MgcaDisambiguation, Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::debug_assert_matches; @@ -43,7 +44,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, Diag, FatalError, MultiSpan, PResult}; use rustc_index::interval::IntervalSet; use rustc_session::parse::ParseSess; -use rustc_span::{Ident, Span, Symbol, kw, sym}; +use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym}; use thin_vec::ThinVec; use token_type::TokenTypeSet; pub use token_type::{ExpKeywordPair, ExpTokenPair, TokenType}; @@ -232,6 +233,8 @@ pub struct Parser<'a> { /// Whether the parser is allowed to do recovery. /// This is disabled when parsing macro arguments, see #103534 recovery: Recovery = Recovery::Allowed, + /// Whether we're parsing a function body. + in_fn_body: bool = false, } // This type is used a lot, e.g. it's cloned when matching many declarative macro rules with @@ -1638,6 +1641,62 @@ impl<'a> Parser<'a> { _ => self.prev_token.span, } } + + fn missing_semi_from_binop( + &self, + kind_desc: &str, + expr: &Expr, + ) -> Option<(Span, ErrorGuaranteed)> { + if self.token == TokenKind::Semi { + return None; + } + if !self.may_recover() || expr.span.from_expansion() { + return None; + } + let sm = self.psess.source_map(); + if let ExprKind::Binary(op, lhs, rhs) = &expr.kind + && sm.is_multiline(lhs.span.shrink_to_hi().until(rhs.span.shrink_to_lo())) + && matches!(op.node, BinOpKind::Mul | BinOpKind::BitAnd) + && classify::expr_requires_semi_to_be_stmt(rhs) + { + let lhs_end_span = lhs.span.shrink_to_hi(); + let token_str = token_descr(&self.token); + let mut err = self + .dcx() + .struct_span_err(lhs_end_span, format!("expected `;`, found {token_str}")); + err.span_label(self.token.span, "unexpected token"); + + let continuation_span = lhs_end_span.until(rhs.span.shrink_to_hi()); + err.span_label( + continuation_span, + format!( + "to finish parsing this {kind_desc}, expected this to be followed by a `;`", + ), + ); + let op_desc = match op.node { + BinOpKind::BitAnd => "a bit-and", + BinOpKind::Mul => "a multiplication", + _ => "a binary", + }; + let mut note_spans = MultiSpan::new(); + note_spans.push_span_label(lhs.span, "parsed as the left-hand expression"); + note_spans.push_span_label(rhs.span, "parsed as the right-hand expression"); + note_spans.push_span_label(op.span, format!("this was parsed as {op_desc}")); + err.span_note( + note_spans, + format!("the {kind_desc} was parsed as having {op_desc} binary expression"), + ); + + err.span_suggestion( + lhs_end_span, + format!("you may have meant to write a `;` to terminate the {kind_desc} earlier"), + ";", + Applicability::MaybeIncorrect, + ); + return Some((lhs.span, err.emit())); + } + None + } } // Metavar captures of various kinds. diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 3c6629572ae50..0394760efe292 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -924,6 +924,19 @@ impl<'a> Parser<'a> { } } + fn try_recover_let_missing_semi(&mut self, local: &mut Local) -> Option { + let expr = match &mut local.kind { + LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => expr, + LocalKind::Decl => return None, + }; + if let Some((span, guar)) = self.missing_semi_from_binop("`let` binding", expr) { + *expr = self.mk_expr(span, ExprKind::Err(guar)); + return Some(guar); + } + + None + } + /// Parses a statement, including the trailing semicolon. pub fn parse_full_stmt( &mut self, @@ -1066,71 +1079,74 @@ impl<'a> Parser<'a> { } } StmtKind::Expr(_) | StmtKind::MacCall(_) => {} - StmtKind::Let(local) if let Err(mut e) = self.expect_semi() => { - // We might be at the `,` in `let x = foo;`. Try to recover. - match &mut local.kind { - LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => { - self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err( - |mut e| { - self.recover_missing_dot(&mut e); - self.recover_missing_let_else(&mut e, &local.pat, stmt.span); - e - }, - )?; - // We found `foo`, have we fully recovered? - self.expect_semi()?; - } - LocalKind::Decl => { - if let Some(colon_sp) = local.colon_sp { - e.span_label( - colon_sp, - format!( - "while parsing the type for {}", - local.pat.descr().map_or_else( - || "the binding".to_string(), - |n| format!("`{n}`") - ) - ), - ); - let suggest_eq = if self.token == token::Dot - && let _ = self.bump() - && let mut snapshot = self.create_snapshot_for_diagnostic() - && let Ok(_) = snapshot - .parse_dot_suffix_expr( - colon_sp, - self.mk_expr_err( - colon_sp, - self.dcx() - .delayed_bug("error during `:` -> `=` recovery"), - ), - ) - .map_err(Diag::cancel) - { - true - } else if let Some(op) = self.check_assoc_op() - && op.node.can_continue_expr_unambiguously() - { - true - } else { - false - }; - if suggest_eq { - e.span_suggestion_short( + StmtKind::Let(local) => { + if self.try_recover_let_missing_semi(local).is_some() { + return Ok(Some(stmt)); + } + if let Err(mut e) = self.expect_semi() { + // We might be at the `,` in `let x = foo;`. Try to recover. + match &mut local.kind { + LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => { + self.check_mistyped_turbofish_with_multiple_type_params(e, expr) + .map_err(|mut e| { + self.recover_missing_dot(&mut e); + self.recover_missing_let_else(&mut e, &local.pat, stmt.span); + e + })?; + // We found `foo`, have we fully recovered? + self.expect_semi()?; + } + LocalKind::Decl => { + if let Some(colon_sp) = local.colon_sp { + e.span_label( colon_sp, - "use `=` if you meant to assign", - "=", - Applicability::MaybeIncorrect, + format!( + "while parsing the type for {}", + local.pat.descr().map_or_else( + || "the binding".to_string(), + |n| format!("`{n}`") + ) + ), ); + let suggest_eq = if self.token == token::Dot + && let _ = self.bump() + && let mut snapshot = self.create_snapshot_for_diagnostic() + && let Ok(_) = snapshot + .parse_dot_suffix_expr( + colon_sp, + self.mk_expr_err( + colon_sp, + self.dcx().delayed_bug( + "error during `:` -> `=` recovery", + ), + ), + ) + .map_err(Diag::cancel) + { + true + } else if let Some(op) = self.check_assoc_op() + && op.node.can_continue_expr_unambiguously() + { + true + } else { + false + }; + if suggest_eq { + e.span_suggestion_short( + colon_sp, + "use `=` if you meant to assign", + "=", + Applicability::MaybeIncorrect, + ); + } } + return Err(e); } - return Err(e); } } eat_semi = false; } - StmtKind::Empty | StmtKind::Item(_) | StmtKind::Let(_) | StmtKind::Semi(_) => { - eat_semi = false - } + StmtKind::Empty | StmtKind::Item(_) | StmtKind::Semi(_) => eat_semi = false, } if add_semi_to_stmt || (eat_semi && self.eat(exp!(Semi))) { diff --git a/tests/ui/issues/const-recover-semi-issue-151149.stderr b/tests/ui/issues/const-recover-semi-issue-151149.stderr deleted file mode 100644 index 96c62c39fb6ad..0000000000000 --- a/tests/ui/issues/const-recover-semi-issue-151149.stderr +++ /dev/null @@ -1,52 +0,0 @@ -error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:16:38 - | -LL | const C: u8 = u8::const_default() - | ^ help: add `;` here -LL | &C -LL | } - | - unexpected token - -error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:22:9 - | -LL | + 2 - | ^ help: add `;` here -LL | } - | - unexpected token - -error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:27:13 - | -LL | + val() - | ^ help: add `;` here -LL | } - | - unexpected token - -error[E0308]: mismatched types - --> $DIR/const-recover-semi-issue-151149.rs:15:19 - | -LL | const fn foo() -> &'static u8 { - | --- ^^^^^^^^^^^ expected `&u8`, found `()` - | | - | implicitly returns `()` as its body has no tail or `return` expression - -error[E0308]: mismatched types - --> $DIR/const-recover-semi-issue-151149.rs:20:19 - | -LL | const fn bar() -> u8 { - | --- ^^ expected `u8`, found `()` - | | - | implicitly returns `()` as its body has no tail or `return` expression - -error[E0308]: mismatched types - --> $DIR/const-recover-semi-issue-151149.rs:25:19 - | -LL | const fn baz() -> u8 { - | --- ^^ expected `u8`, found `()` - | | - | implicitly returns `()` as its body has no tail or `return` expression - -error: aborting due to 6 previous errors - -For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/issues/const-recover-semi-issue-151149.rs b/tests/ui/parser/const-recover-semi-issue-151149.rs similarity index 67% rename from tests/ui/issues/const-recover-semi-issue-151149.rs rename to tests/ui/parser/const-recover-semi-issue-151149.rs index f5dc27bb3dd86..b4a45e83613ec 100644 --- a/tests/ui/issues/const-recover-semi-issue-151149.rs +++ b/tests/ui/parser/const-recover-semi-issue-151149.rs @@ -12,6 +12,9 @@ const fn val() -> u8 { 42 } +const C: u8 = u8::const_default() +&1 //~ ERROR expected `;`, found keyword `const` + const fn foo() -> &'static u8 { //~ ERROR mismatched types const C: u8 = u8::const_default() //~ ERROR expected `;` &C @@ -19,12 +22,17 @@ const fn foo() -> &'static u8 { //~ ERROR mismatched types const fn bar() -> u8 { //~ ERROR mismatched types const C: u8 = 1 - + 2 //~ ERROR expected `;` + + 2 //~ ERROR expected `;`, found `}` } const fn baz() -> u8 { //~ ERROR mismatched types const C: u8 = 1 - + val() //~ ERROR expected `;` + + val() //~ ERROR expected `;`, found `}` +} + +fn buzz() { + let r = 1 //~ ERROR expected `;` + &r } fn main() {} diff --git a/tests/ui/parser/const-recover-semi-issue-151149.stderr b/tests/ui/parser/const-recover-semi-issue-151149.stderr new file mode 100644 index 0000000000000..04107ffd2a78d --- /dev/null +++ b/tests/ui/parser/const-recover-semi-issue-151149.stderr @@ -0,0 +1,100 @@ +error: expected `;`, found keyword `const` + --> $DIR/const-recover-semi-issue-151149.rs:16:3 + | +LL | &1 + | ^ help: add `;` here +LL | +LL | const fn foo() -> &'static u8 { + | ----- unexpected token + +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:19:38 + | +LL | const C: u8 = u8::const_default() + | ______________________________________^ +LL | | &C + | |______- to finish parsing this const, expected this to be followed by a `;` +LL | } + | - unexpected token + | +note: the const was parsed as having a bit-and binary expression + --> $DIR/const-recover-semi-issue-151149.rs:19:19 + | +LL | const C: u8 = u8::const_default() + | ------------------- parsed as the left-hand expression +LL | &C + | -- parsed as the right-hand expression + | | + | this was parsed as a bit-and +help: you may have meant to write a `;` to terminate the const earlier + | +LL | const C: u8 = u8::const_default(); + | + + +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:25:9 + | +LL | + 2 + | ^ help: add `;` here +LL | } + | - unexpected token + +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:30:13 + | +LL | + val() + | ^ help: add `;` here +LL | } + | - unexpected token + +error: expected `;`, found `}` + --> $DIR/const-recover-semi-issue-151149.rs:34:14 + | +LL | let r = 1 + | ______________^ +LL | | &r + | |______- to finish parsing this `let` binding, expected this to be followed by a `;` +LL | } + | - unexpected token + | +note: the `let` binding was parsed as having a bit-and binary expression + --> $DIR/const-recover-semi-issue-151149.rs:34:13 + | +LL | let r = 1 + | - parsed as the left-hand expression +LL | &r + | -- parsed as the right-hand expression + | | + | this was parsed as a bit-and +help: you may have meant to write a `;` to terminate the `let` binding earlier + | +LL | let r = 1; + | + + +error[E0308]: mismatched types + --> $DIR/const-recover-semi-issue-151149.rs:18:19 + | +LL | const fn foo() -> &'static u8 { + | --- ^^^^^^^^^^^ expected `&u8`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error[E0308]: mismatched types + --> $DIR/const-recover-semi-issue-151149.rs:23:19 + | +LL | const fn bar() -> u8 { + | --- ^^ expected `u8`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error[E0308]: mismatched types + --> $DIR/const-recover-semi-issue-151149.rs:28:19 + | +LL | const fn baz() -> u8 { + | --- ^^ expected `u8`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error: aborting due to 8 previous errors + +For more information about this error, try `rustc --explain E0308`. From 6f406161488b9656ab8bd8f35fa8ce6f3c2c8b95 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 19 Jan 2026 15:43:03 +0800 Subject: [PATCH 3/4] remove mismatched errors when recovered --- compiler/rustc_parse/src/parser/item.rs | 15 ++++++++++++--- compiler/rustc_parse/src/parser/mod.rs | 2 ++ compiler/rustc_parse/src/parser/stmt.rs | 2 +- .../ui/parser/const-recover-semi-issue-151149.rs | 4 ++-- .../parser/const-recover-semi-issue-151149.stderr | 10 +--------- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 21b061e7d711e..bf9470ee22a2a 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2723,9 +2723,17 @@ impl<'a> Parser<'a> { } else if self.check(exp!(OpenBrace)) || self.token.is_metavar_block() { let prev_in_fn_body = self.in_fn_body; self.in_fn_body = true; - let res = self - .parse_block_common(self.token.span, BlockCheckMode::Default, None) - .map(|(attrs, body)| (attrs, Some(body))); + let res = self.parse_block_common(self.token.span, BlockCheckMode::Default, None).map( + |(attrs, mut body)| { + if let Some(guar) = self.fn_body_missing_semi_guar.take() { + body.stmts.push(self.mk_stmt( + body.span, + StmtKind::Expr(self.mk_expr(body.span, ExprKind::Err(guar))), + )); + } + (attrs, Some(body)) + }, + ); self.in_fn_body = prev_in_fn_body; res? } else if self.token == token::Eq { @@ -3502,6 +3510,7 @@ impl<'a> Parser<'a> { return None; } if let Some((span, guar)) = self.missing_semi_from_binop("const", rhs) { + self.fn_body_missing_semi_guar = Some(guar); Some(self.mk_expr(span, ExprKind::Err(guar))) } else { None diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index d5982e66d99e2..2e51e24532ea6 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -235,6 +235,8 @@ pub struct Parser<'a> { recovery: Recovery = Recovery::Allowed, /// Whether we're parsing a function body. in_fn_body: bool = false, + /// Whether we have detected a missing semicolon in the function body. + pub fn_body_missing_semi_guar: Option = None, } // This type is used a lot, e.g. it's cloned when matching many declarative macro rules with diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 0394760efe292..3d049714c59de 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -930,10 +930,10 @@ impl<'a> Parser<'a> { LocalKind::Decl => return None, }; if let Some((span, guar)) = self.missing_semi_from_binop("`let` binding", expr) { + self.fn_body_missing_semi_guar = Some(guar); *expr = self.mk_expr(span, ExprKind::Err(guar)); return Some(guar); } - None } diff --git a/tests/ui/parser/const-recover-semi-issue-151149.rs b/tests/ui/parser/const-recover-semi-issue-151149.rs index b4a45e83613ec..2b443d7b4609f 100644 --- a/tests/ui/parser/const-recover-semi-issue-151149.rs +++ b/tests/ui/parser/const-recover-semi-issue-151149.rs @@ -15,7 +15,7 @@ const fn val() -> u8 { const C: u8 = u8::const_default() &1 //~ ERROR expected `;`, found keyword `const` -const fn foo() -> &'static u8 { //~ ERROR mismatched types +const fn foo() -> &'static u8 { const C: u8 = u8::const_default() //~ ERROR expected `;` &C } @@ -30,7 +30,7 @@ const fn baz() -> u8 { //~ ERROR mismatched types + val() //~ ERROR expected `;`, found `}` } -fn buzz() { +fn buzz() -> &'static u8 { let r = 1 //~ ERROR expected `;` &r } diff --git a/tests/ui/parser/const-recover-semi-issue-151149.stderr b/tests/ui/parser/const-recover-semi-issue-151149.stderr index 04107ffd2a78d..264306fcba6f3 100644 --- a/tests/ui/parser/const-recover-semi-issue-151149.stderr +++ b/tests/ui/parser/const-recover-semi-issue-151149.stderr @@ -71,14 +71,6 @@ help: you may have meant to write a `;` to terminate the `let` binding earlier LL | let r = 1; | + -error[E0308]: mismatched types - --> $DIR/const-recover-semi-issue-151149.rs:18:19 - | -LL | const fn foo() -> &'static u8 { - | --- ^^^^^^^^^^^ expected `&u8`, found `()` - | | - | implicitly returns `()` as its body has no tail or `return` expression - error[E0308]: mismatched types --> $DIR/const-recover-semi-issue-151149.rs:23:19 | @@ -95,6 +87,6 @@ LL | const fn baz() -> u8 { | | | implicitly returns `()` as its body has no tail or `return` expression -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0308`. From ba0e1c949729c14fa495c49747b9eea54c234eb4 Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 1 Feb 2026 03:45:12 +0000 Subject: [PATCH 4/4] Address review comments: fix span and add run-rustfix - Change error span to start from 'const' keyword instead of binop RHS - Add decl_lo parameter to missing_semi_from_binop() for better spans - Add run-rustfix directive to test file - Simplify test to focus on const item recovery cases --- compiler/rustc_parse/src/parser/item.rs | 25 +++++---- compiler/rustc_parse/src/parser/mod.rs | 13 +++-- compiler/rustc_parse/src/parser/stmt.rs | 4 +- .../const-recover-semi-issue-151149.fixed | 36 ++++++++++++ .../parser/const-recover-semi-issue-151149.rs | 12 ++-- .../const-recover-semi-issue-151149.stderr | 55 +++---------------- 6 files changed, 74 insertions(+), 71 deletions(-) create mode 100644 tests/ui/parser/const-recover-semi-issue-151149.fixed diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index bf9470ee22a2a..499e7db3bdad6 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -6,9 +6,7 @@ use rustc_ast::ast::*; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::util::case::Case; -use rustc_ast::{ - attr, {self as ast}, -}; +use rustc_ast::{self as ast}; use rustc_ast_pretty::pprust; use rustc_errors::codes::*; use rustc_errors::{Applicability, PResult, StashKey, inline_fluent, struct_span_code_err}; @@ -286,7 +284,7 @@ impl<'a> Parser<'a> { // CONST ITEM self.recover_const_mut(const_span); self.recover_missing_kw_before_item()?; - let (ident, generics, ty, rhs_kind) = self.parse_const_item(false)?; + let (ident, generics, ty, rhs_kind) = self.parse_const_item(false, const_span)?; ItemKind::Const(Box::new(ConstItem { defaultness: def_(), ident, @@ -307,7 +305,7 @@ impl<'a> Parser<'a> { // TYPE CONST (mgca) self.recover_const_mut(const_span); self.recover_missing_kw_before_item()?; - let (ident, generics, ty, rhs_kind) = self.parse_const_item(true)?; + let (ident, generics, ty, rhs_kind) = self.parse_const_item(true, const_span)?; // Make sure this is only allowed if the feature gate is enabled. // #![feature(mgca_type_const_syntax)] self.psess.gated_spans.gate(sym::mgca_type_const_syntax, lo.to(const_span)); @@ -1545,6 +1543,7 @@ impl<'a> Parser<'a> { fn parse_const_item( &mut self, const_arg: bool, + const_span: Span, ) -> PResult<'a, (Ident, Generics, Box, ConstItemRhsKind)> { let ident = self.parse_ident_or_underscore()?; @@ -1636,8 +1635,8 @@ impl<'a> Parser<'a> { generics.where_clause = where_clause; - if let Some(recovered_rhs) = self.try_recover_const_missing_semi(&rhs) { - return Ok((ident, generics, ty, Some(ConstItemRhs::Body(recovered_rhs)))); + if let Some(rhs) = self.try_recover_const_missing_semi(&rhs, const_span) { + return Ok((ident, generics, ty, ConstItemRhsKind::Body { rhs: Some(rhs) })); } self.expect_semi()?; @@ -3499,17 +3498,23 @@ impl<'a> Parser<'a> { /// (e.g., `foo() \n &bar` was parsed as `foo() & bar`). /// /// Returns a corrected expression if recovery is successful. - fn try_recover_const_missing_semi(&mut self, rhs: &Option) -> Option> { + fn try_recover_const_missing_semi( + &mut self, + rhs: &ConstItemRhsKind, + const_span: Span, + ) -> Option> { if self.token == TokenKind::Semi { return None; } - let Some(ConstItemRhs::Body(rhs)) = rhs else { + let ConstItemRhsKind::Body { rhs: Some(rhs) } = rhs else { return None; }; if !self.in_fn_body || !self.may_recover() || rhs.span.from_expansion() { return None; } - if let Some((span, guar)) = self.missing_semi_from_binop("const", rhs) { + if let Some((span, guar)) = + self.missing_semi_from_binop("const", rhs, Some(const_span.shrink_to_lo())) + { self.fn_body_missing_semi_guar = Some(guar); Some(self.mk_expr(span, ExprKind::Err(guar))) } else { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 2e51e24532ea6..c0149f6f1000d 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -34,9 +34,9 @@ use rustc_ast::tokenstream::{ use rustc_ast::util::case::Case; use rustc_ast::util::classify; use rustc_ast::{ - self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, BlockCheckMode, ByRef, Const, - CoroutineKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, - MgcaDisambiguation, Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind, + self as ast, AnonConst, AttrArgs, AttrId, BinOpKind, ByRef, Const, CoroutineKind, + DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation, + Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::debug_assert_matches; @@ -235,7 +235,7 @@ pub struct Parser<'a> { recovery: Recovery = Recovery::Allowed, /// Whether we're parsing a function body. in_fn_body: bool = false, - /// Whether we have detected a missing semicolon in the function body. + /// Whether we have detected a missing semicolon in function body. pub fn_body_missing_semi_guar: Option = None, } @@ -1648,6 +1648,7 @@ impl<'a> Parser<'a> { &self, kind_desc: &str, expr: &Expr, + decl_lo: Option, ) -> Option<(Span, ErrorGuaranteed)> { if self.token == TokenKind::Semi { return None; @@ -1668,7 +1669,9 @@ impl<'a> Parser<'a> { .struct_span_err(lhs_end_span, format!("expected `;`, found {token_str}")); err.span_label(self.token.span, "unexpected token"); - let continuation_span = lhs_end_span.until(rhs.span.shrink_to_hi()); + // Use the declaration start if provided, otherwise fall back to lhs_end_span. + let continuation_start = decl_lo.unwrap_or(lhs_end_span); + let continuation_span = continuation_start.until(rhs.span.shrink_to_hi()); err.span_label( continuation_span, format!( diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 3d049714c59de..3b2102484bdf5 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -929,7 +929,9 @@ impl<'a> Parser<'a> { LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => expr, LocalKind::Decl => return None, }; - if let Some((span, guar)) = self.missing_semi_from_binop("`let` binding", expr) { + if let Some((span, guar)) = + self.missing_semi_from_binop("`let` binding", expr, Some(local.span.shrink_to_lo())) + { self.fn_body_missing_semi_guar = Some(guar); *expr = self.mk_expr(span, ExprKind::Err(guar)); return Some(guar); diff --git a/tests/ui/parser/const-recover-semi-issue-151149.fixed b/tests/ui/parser/const-recover-semi-issue-151149.fixed new file mode 100644 index 0000000000000..79a3c7b248db7 --- /dev/null +++ b/tests/ui/parser/const-recover-semi-issue-151149.fixed @@ -0,0 +1,36 @@ +//@ run-rustfix +#![feature(const_trait_impl)] +#![allow(dead_code)] +#![allow(unused)] + +const trait ConstDefault { + fn const_default() -> Self; +} + +impl const ConstDefault for u8 { + fn const_default() -> Self { 0 } +} + +const fn val() -> u8 { + 42 +} + +const C: u8 = u8::const_default() +&1; //~ ERROR expected `;`, found keyword `const` + +const fn foo() -> &'static u8 { + const C: u8 = u8::const_default(); //~ ERROR expected `;` + &C +} + +const fn bar() { + const C: u8 = 1 + + 2; //~ ERROR expected `;`, found `}` +} + +const fn baz() { + const C: u8 = 1 + + val(); //~ ERROR expected `;`, found `}` +} + +fn main() {} diff --git a/tests/ui/parser/const-recover-semi-issue-151149.rs b/tests/ui/parser/const-recover-semi-issue-151149.rs index 2b443d7b4609f..affd81fd0b34c 100644 --- a/tests/ui/parser/const-recover-semi-issue-151149.rs +++ b/tests/ui/parser/const-recover-semi-issue-151149.rs @@ -1,4 +1,7 @@ +//@ run-rustfix #![feature(const_trait_impl)] +#![allow(dead_code)] +#![allow(unused)] const trait ConstDefault { fn const_default() -> Self; @@ -20,19 +23,14 @@ const fn foo() -> &'static u8 { &C } -const fn bar() -> u8 { //~ ERROR mismatched types +const fn bar() { const C: u8 = 1 + 2 //~ ERROR expected `;`, found `}` } -const fn baz() -> u8 { //~ ERROR mismatched types +const fn baz() { const C: u8 = 1 + val() //~ ERROR expected `;`, found `}` } -fn buzz() -> &'static u8 { - let r = 1 //~ ERROR expected `;` - &r -} - fn main() {} diff --git a/tests/ui/parser/const-recover-semi-issue-151149.stderr b/tests/ui/parser/const-recover-semi-issue-151149.stderr index 264306fcba6f3..7c63f9f98bfe1 100644 --- a/tests/ui/parser/const-recover-semi-issue-151149.stderr +++ b/tests/ui/parser/const-recover-semi-issue-151149.stderr @@ -1,5 +1,5 @@ error: expected `;`, found keyword `const` - --> $DIR/const-recover-semi-issue-151149.rs:16:3 + --> $DIR/const-recover-semi-issue-151149.rs:19:3 | LL | &1 | ^ help: add `;` here @@ -8,17 +8,17 @@ LL | const fn foo() -> &'static u8 { | ----- unexpected token error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:19:38 + --> $DIR/const-recover-semi-issue-151149.rs:22:38 | LL | const C: u8 = u8::const_default() - | ______________________________________^ + | _____- ^ LL | | &C | |______- to finish parsing this const, expected this to be followed by a `;` LL | } | - unexpected token | note: the const was parsed as having a bit-and binary expression - --> $DIR/const-recover-semi-issue-151149.rs:19:19 + --> $DIR/const-recover-semi-issue-151149.rs:22:19 | LL | const C: u8 = u8::const_default() | ------------------- parsed as the left-hand expression @@ -32,7 +32,7 @@ LL | const C: u8 = u8::const_default(); | + error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:25:9 + --> $DIR/const-recover-semi-issue-151149.rs:28:9 | LL | + 2 | ^ help: add `;` here @@ -40,53 +40,12 @@ LL | } | - unexpected token error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:30:13 + --> $DIR/const-recover-semi-issue-151149.rs:33:13 | LL | + val() | ^ help: add `;` here LL | } | - unexpected token -error: expected `;`, found `}` - --> $DIR/const-recover-semi-issue-151149.rs:34:14 - | -LL | let r = 1 - | ______________^ -LL | | &r - | |______- to finish parsing this `let` binding, expected this to be followed by a `;` -LL | } - | - unexpected token - | -note: the `let` binding was parsed as having a bit-and binary expression - --> $DIR/const-recover-semi-issue-151149.rs:34:13 - | -LL | let r = 1 - | - parsed as the left-hand expression -LL | &r - | -- parsed as the right-hand expression - | | - | this was parsed as a bit-and -help: you may have meant to write a `;` to terminate the `let` binding earlier - | -LL | let r = 1; - | + - -error[E0308]: mismatched types - --> $DIR/const-recover-semi-issue-151149.rs:23:19 - | -LL | const fn bar() -> u8 { - | --- ^^ expected `u8`, found `()` - | | - | implicitly returns `()` as its body has no tail or `return` expression - -error[E0308]: mismatched types - --> $DIR/const-recover-semi-issue-151149.rs:28:19 - | -LL | const fn baz() -> u8 { - | --- ^^ expected `u8`, found `()` - | | - | implicitly returns `()` as its body has no tail or `return` expression - -error: aborting due to 7 previous errors +error: aborting due to 4 previous errors -For more information about this error, try `rustc --explain E0308`.