also add recover from over parsing for let

This commit is contained in:
yukang
2026-01-19 15:17:03 +08:00
parent b4f15dcead
commit 6d0e077d13
6 changed files with 259 additions and 139 deletions
+10 -21
View File
@@ -6,7 +6,6 @@
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 @@ fn parse_fn_body(
*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 @@ fn try_recover_const_missing_semi(&mut self, rhs: &Option<ConstItemRhs>) -> Opti
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
}
+63 -4
View File
@@ -32,10 +32,11 @@
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_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 @@ pub fn prev_token_uninterpolated_span(&self) -> Span {
_ => 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.
+76 -60
View File
@@ -924,6 +924,19 @@ fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
}
}
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) {
*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 @@ pub fn parse_full_stmt(
}
}
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(
colon_sp,
"use `=` if you meant to assign",
"=",
Applicability::MaybeIncorrect,
);
}
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,
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))) {
@@ -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`.
@@ -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() {}
@@ -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`.