Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,6 @@ pub(crate) struct HelpIdentifierStartsWithNumber {
pub(crate) struct ExpectedSemi {
pub span: Span,
pub token: Token,

pub unexpected_token_label: Option<Span>,
pub sugg: ExpectedSemiSugg,
}
Expand Down
45 changes: 43 additions & 2 deletions compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,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))
Expand Down Expand Up @@ -2650,8 +2653,21 @@ 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, 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 {
// Recover `fn foo() = $expr;`.
self.bump(); // `=`
Expand Down Expand Up @@ -3407,6 +3423,31 @@ 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<ConstItemRhs>) -> Option<Box<Expr>> {
if self.token == TokenKind::Semi {
return None;
}
let Some(ConstItemRhs::Body(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) {
self.fn_body_missing_semi_guar = Some(guar);
Some(self.mk_expr(span, ExprKind::Err(guar)))
} else {
None
}
}
}

enum IsMacroRulesItem {
Expand Down
71 changes: 67 additions & 4 deletions compiler/rustc_parse/src/parser/mod.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to re-read this file.

Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,18 @@ 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, 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, 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::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};
Expand Down Expand Up @@ -223,6 +224,10 @@ pub struct Parser<'a> {
/// Whether the parser is allowed to do recovery.
/// This is disabled when parsing macro arguments, see #103534
recovery: Recovery,
/// Whether we're parsing a function body.
in_fn_body: bool,
/// Whether we have detected a missing semicolon in the function body.
pub fn_body_missing_semi_guar: Option<ErrorGuaranteed>,
}

// This type is used a lot, e.g. it's cloned when matching many declarative macro rules with
Expand Down Expand Up @@ -372,6 +377,8 @@ impl<'a> Parser<'a> {
},
current_closure: None,
recovery: Recovery::Allowed,
in_fn_body: false,
fn_body_missing_semi_guar: None,
};

// Make parser point to the first token.
Expand Down Expand Up @@ -1683,6 +1690,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.
Expand Down
130 changes: 73 additions & 57 deletions compiler/rustc_parse/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,19 @@ impl<'a> Parser<'a> {
}
}

fn try_recover_let_missing_semi(&mut self, local: &mut Local) -> Option<ErrorGuaranteed> {
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) {
self.fn_body_missing_semi_guar = Some(guar);
*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,
Expand Down Expand Up @@ -1065,71 +1078,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<bar, baz>;`. 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<bar, baz>`, 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<bar, baz>;`. 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<bar, baz>`, 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))) {
Expand Down
38 changes: 38 additions & 0 deletions tests/ui/parser/const-recover-semi-issue-151149.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#![feature(const_trait_impl)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this //@ run-rustfix?


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() -> u8 { //~ ERROR mismatched types
const C: u8 = 1
+ 2 //~ ERROR expected `;`, found `}`
}

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

fn buzz() -> &'static u8 {
let r = 1 //~ ERROR expected `;`
&r
}

fn main() {}
Loading
Loading