mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-21 20:17:55 +00:00

Go over all structured parser suggestions and make them verbose style. When suggesting to add or remove delimiters, turn them into multiple suggestion parts.
857 lines
36 KiB
Rust
857 lines
36 KiB
Rust
use super::attr::InnerAttrForbiddenReason;
|
|
use super::diagnostics::AttemptLocalParseRecovery;
|
|
use super::expr::LhsExpr;
|
|
use super::pat::{PatternLocation, RecoverComma};
|
|
use super::path::PathStyle;
|
|
use super::TrailingToken;
|
|
use super::{
|
|
AttrWrapper, BlockMode, FnParseMode, ForceCollect, Parser, Restrictions, SemiColonMode,
|
|
};
|
|
use crate::errors;
|
|
use crate::maybe_whole;
|
|
|
|
use crate::errors::MalformedLoopLabel;
|
|
use ast::Label;
|
|
use rustc_ast as ast;
|
|
use rustc_ast::ptr::P;
|
|
use rustc_ast::token::{self, Delimiter, TokenKind};
|
|
use rustc_ast::util::classify::{self, TrailingBrace};
|
|
use rustc_ast::{AttrStyle, AttrVec, LocalKind, MacCall, MacCallStmt, MacStmtStyle};
|
|
use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, HasAttrs, Local, Recovered, Stmt};
|
|
use rustc_ast::{StmtKind, DUMMY_NODE_ID};
|
|
use rustc_errors::{Applicability, Diag, PResult};
|
|
use rustc_span::symbol::{kw, sym, Ident};
|
|
use rustc_span::{BytePos, ErrorGuaranteed, Span};
|
|
|
|
use std::borrow::Cow;
|
|
use std::mem;
|
|
use thin_vec::{thin_vec, ThinVec};
|
|
|
|
impl<'a> Parser<'a> {
|
|
/// Parses a statement. This stops just before trailing semicolons on everything but items.
|
|
/// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed.
|
|
// Public for rustfmt usage.
|
|
pub(super) fn parse_stmt(&mut self, force_collect: ForceCollect) -> PResult<'a, Option<Stmt>> {
|
|
Ok(self.parse_stmt_without_recovery(false, force_collect).unwrap_or_else(|e| {
|
|
e.emit();
|
|
self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
|
|
None
|
|
}))
|
|
}
|
|
|
|
/// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of
|
|
/// whether or not we have attributes.
|
|
// Public for `cfg_eval` macro expansion.
|
|
pub fn parse_stmt_without_recovery(
|
|
&mut self,
|
|
capture_semi: bool,
|
|
force_collect: ForceCollect,
|
|
) -> PResult<'a, Option<Stmt>> {
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let lo = self.token.span;
|
|
|
|
maybe_whole!(self, NtStmt, |stmt| {
|
|
stmt.visit_attrs(|stmt_attrs| {
|
|
attrs.prepend_to_nt_inner(stmt_attrs);
|
|
});
|
|
Some(stmt.into_inner())
|
|
});
|
|
|
|
if self.token.is_keyword(kw::Mut) && self.is_keyword_ahead(1, &[kw::Let]) {
|
|
self.bump();
|
|
let mut_let_span = lo.to(self.token.span);
|
|
self.dcx().emit_err(errors::InvalidVariableDeclaration {
|
|
span: mut_let_span,
|
|
sub: errors::InvalidVariableDeclarationSub::SwitchMutLetOrder(mut_let_span),
|
|
});
|
|
}
|
|
|
|
Ok(Some(if self.token.is_keyword(kw::Let) {
|
|
self.parse_local_mk(lo, attrs, capture_semi, force_collect)?
|
|
} else if self.is_kw_followed_by_ident(kw::Mut) && self.may_recover() {
|
|
self.recover_stmt_local_after_let(
|
|
lo,
|
|
attrs,
|
|
errors::InvalidVariableDeclarationSub::MissingLet,
|
|
)?
|
|
} else if self.is_kw_followed_by_ident(kw::Auto) && self.may_recover() {
|
|
self.bump(); // `auto`
|
|
self.recover_stmt_local_after_let(
|
|
lo,
|
|
attrs,
|
|
errors::InvalidVariableDeclarationSub::UseLetNotAuto,
|
|
)?
|
|
} else if self.is_kw_followed_by_ident(sym::var) && self.may_recover() {
|
|
self.bump(); // `var`
|
|
self.recover_stmt_local_after_let(
|
|
lo,
|
|
attrs,
|
|
errors::InvalidVariableDeclarationSub::UseLetNotVar,
|
|
)?
|
|
} else if self.check_path()
|
|
&& !self.token.is_qpath_start()
|
|
&& !self.is_path_start_item()
|
|
&& !self.is_builtin()
|
|
{
|
|
// We have avoided contextual keywords like `union`, items with `crate` visibility,
|
|
// or `auto trait` items. We aim to parse an arbitrary path `a::b` but not something
|
|
// that starts like a path (1 token), but it fact not a path.
|
|
// Also, we avoid stealing syntax from `parse_item_`.
|
|
match force_collect {
|
|
ForceCollect::Yes => {
|
|
self.collect_tokens_no_attrs(|this| this.parse_stmt_path_start(lo, attrs))?
|
|
}
|
|
ForceCollect::No => match self.parse_stmt_path_start(lo, attrs) {
|
|
Ok(stmt) => stmt,
|
|
Err(mut err) => {
|
|
self.suggest_add_missing_let_for_stmt(&mut err);
|
|
return Err(err);
|
|
}
|
|
},
|
|
}
|
|
} else if let Some(item) = self.parse_item_common(
|
|
attrs.clone(),
|
|
false,
|
|
true,
|
|
FnParseMode { req_name: |_| true, req_body: true },
|
|
force_collect,
|
|
)? {
|
|
// FIXME: Bad copy of attrs
|
|
self.mk_stmt(lo.to(item.span), StmtKind::Item(P(item)))
|
|
} else if self.eat(&token::Semi) {
|
|
// Do not attempt to parse an expression if we're done here.
|
|
self.error_outer_attrs(attrs);
|
|
self.mk_stmt(lo, StmtKind::Empty)
|
|
} else if self.token != token::CloseDelim(Delimiter::Brace) {
|
|
// Remainder are line-expr stmts.
|
|
let e = match force_collect {
|
|
ForceCollect::Yes => self.collect_tokens_no_attrs(|this| {
|
|
this.parse_expr_res(Restrictions::STMT_EXPR, attrs)
|
|
})?,
|
|
ForceCollect::No => self.parse_expr_res(Restrictions::STMT_EXPR, attrs)?,
|
|
};
|
|
if matches!(e.kind, ExprKind::Assign(..)) && self.eat_keyword(kw::Else) {
|
|
let bl = self.parse_block()?;
|
|
// Destructuring assignment ... else.
|
|
// This is not allowed, but point it out in a nice way.
|
|
self.dcx().emit_err(errors::AssignmentElseNotAllowed { span: e.span.to(bl.span) });
|
|
}
|
|
self.mk_stmt(lo.to(e.span), StmtKind::Expr(e))
|
|
} else {
|
|
self.error_outer_attrs(attrs);
|
|
return Ok(None);
|
|
}))
|
|
}
|
|
|
|
fn parse_stmt_path_start(&mut self, lo: Span, attrs: AttrWrapper) -> PResult<'a, Stmt> {
|
|
let stmt = self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
|
|
let path = this.parse_path(PathStyle::Expr)?;
|
|
|
|
if this.eat(&token::Not) {
|
|
let stmt_mac = this.parse_stmt_mac(lo, attrs, path)?;
|
|
if this.token == token::Semi {
|
|
return Ok((stmt_mac, TrailingToken::Semi));
|
|
} else {
|
|
return Ok((stmt_mac, TrailingToken::None));
|
|
}
|
|
}
|
|
|
|
let expr = if this.eat(&token::OpenDelim(Delimiter::Brace)) {
|
|
this.parse_expr_struct(None, path, true)?
|
|
} else {
|
|
let hi = this.prev_token.span;
|
|
this.mk_expr(lo.to(hi), ExprKind::Path(None, path))
|
|
};
|
|
|
|
let expr = this.with_res(Restrictions::STMT_EXPR, |this| {
|
|
this.parse_expr_dot_or_call_with(expr, lo, attrs)
|
|
})?;
|
|
// `DUMMY_SP` will get overwritten later in this function
|
|
Ok((this.mk_stmt(rustc_span::DUMMY_SP, StmtKind::Expr(expr)), TrailingToken::None))
|
|
})?;
|
|
|
|
if let StmtKind::Expr(expr) = stmt.kind {
|
|
// Perform this outside of the `collect_tokens_trailing_token` closure,
|
|
// since our outer attributes do not apply to this part of the expression
|
|
let expr = self.with_res(Restrictions::STMT_EXPR, |this| {
|
|
this.parse_expr_assoc_with(0, LhsExpr::Parsed { expr, starts_statement: true })
|
|
})?;
|
|
Ok(self.mk_stmt(lo.to(self.prev_token.span), StmtKind::Expr(expr)))
|
|
} else {
|
|
Ok(stmt)
|
|
}
|
|
}
|
|
|
|
/// Parses a statement macro `mac!(args)` provided a `path` representing `mac`.
|
|
/// At this point, the `!` token after the path has already been eaten.
|
|
fn parse_stmt_mac(&mut self, lo: Span, attrs: AttrVec, path: ast::Path) -> PResult<'a, Stmt> {
|
|
let args = self.parse_delim_args()?;
|
|
let hi = self.prev_token.span;
|
|
|
|
let style = match args.delim {
|
|
Delimiter::Brace => MacStmtStyle::Braces,
|
|
_ => MacStmtStyle::NoBraces,
|
|
};
|
|
|
|
let mac = P(MacCall { path, args });
|
|
|
|
let kind = if (style == MacStmtStyle::Braces
|
|
&& self.token != token::Dot
|
|
&& self.token != token::Question)
|
|
|| self.token == token::Semi
|
|
|| self.token == token::Eof
|
|
{
|
|
StmtKind::MacCall(P(MacCallStmt { mac, style, attrs, tokens: None }))
|
|
} else {
|
|
// Since none of the above applied, this is an expression statement macro.
|
|
let e = self.mk_expr(lo.to(hi), ExprKind::MacCall(mac));
|
|
let e = self.maybe_recover_from_bad_qpath(e)?;
|
|
let e = self.parse_expr_dot_or_call_with(e, lo, attrs)?;
|
|
let e = self
|
|
.parse_expr_assoc_with(0, LhsExpr::Parsed { expr: e, starts_statement: false })?;
|
|
StmtKind::Expr(e)
|
|
};
|
|
Ok(self.mk_stmt(lo.to(hi), kind))
|
|
}
|
|
|
|
/// Error on outer attributes in this context.
|
|
/// Also error if the previous token was a doc comment.
|
|
fn error_outer_attrs(&self, attrs: AttrWrapper) {
|
|
if !attrs.is_empty()
|
|
&& let attrs @ [.., last] = &*attrs.take_for_recovery(self.psess)
|
|
{
|
|
if last.is_doc_comment() {
|
|
self.dcx().emit_err(errors::DocCommentDoesNotDocumentAnything {
|
|
span: last.span,
|
|
missing_comma: None,
|
|
});
|
|
} else if attrs.iter().any(|a| a.style == AttrStyle::Outer) {
|
|
self.dcx().emit_err(errors::ExpectedStatementAfterOuterAttr { span: last.span });
|
|
}
|
|
}
|
|
}
|
|
|
|
fn recover_stmt_local_after_let(
|
|
&mut self,
|
|
lo: Span,
|
|
attrs: AttrWrapper,
|
|
subdiagnostic: fn(Span) -> errors::InvalidVariableDeclarationSub,
|
|
) -> PResult<'a, Stmt> {
|
|
let stmt =
|
|
self.collect_tokens_trailing_token(attrs, ForceCollect::Yes, |this, attrs| {
|
|
let local = this.parse_local(attrs)?;
|
|
// FIXME - maybe capture semicolon in recovery?
|
|
Ok((
|
|
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
|
|
TrailingToken::None,
|
|
))
|
|
})?;
|
|
self.dcx()
|
|
.emit_err(errors::InvalidVariableDeclaration { span: lo, sub: subdiagnostic(lo) });
|
|
Ok(stmt)
|
|
}
|
|
|
|
fn parse_local_mk(
|
|
&mut self,
|
|
lo: Span,
|
|
attrs: AttrWrapper,
|
|
capture_semi: bool,
|
|
force_collect: ForceCollect,
|
|
) -> PResult<'a, Stmt> {
|
|
self.collect_tokens_trailing_token(attrs, force_collect, |this, attrs| {
|
|
this.expect_keyword(kw::Let)?;
|
|
let local = this.parse_local(attrs)?;
|
|
let trailing = if capture_semi && this.token.kind == token::Semi {
|
|
TrailingToken::Semi
|
|
} else {
|
|
TrailingToken::None
|
|
};
|
|
Ok((this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)), trailing))
|
|
})
|
|
}
|
|
|
|
/// Parses a local variable declaration.
|
|
fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
|
|
let lo = self.prev_token.span;
|
|
|
|
if self.token.is_keyword(kw::Const) && self.look_ahead(1, |t| t.is_ident()) {
|
|
self.dcx().emit_err(errors::ConstLetMutuallyExclusive { span: lo.to(self.token.span) });
|
|
self.bump();
|
|
}
|
|
|
|
let (pat, colon) =
|
|
self.parse_pat_before_ty(None, RecoverComma::Yes, PatternLocation::LetBinding)?;
|
|
|
|
let (err, ty, colon_sp) = if colon {
|
|
// Save the state of the parser before parsing type normally, in case there is a `:`
|
|
// instead of an `=` typo.
|
|
let parser_snapshot_before_type = self.clone();
|
|
let colon_sp = self.prev_token.span;
|
|
match self.parse_ty() {
|
|
Ok(ty) => (None, Some(ty), Some(colon_sp)),
|
|
Err(mut err) => {
|
|
err.span_label(
|
|
colon_sp,
|
|
format!(
|
|
"while parsing the type for {}",
|
|
pat.descr()
|
|
.map_or_else(|| "the binding".to_string(), |n| format!("`{n}`"))
|
|
),
|
|
);
|
|
// we use noexpect here because we don't actually expect Eq to be here
|
|
// but we are still checking for it in order to be able to handle it if
|
|
// it is there
|
|
let err = if self.check_noexpect(&token::Eq) {
|
|
err.emit();
|
|
None
|
|
} else {
|
|
// Rewind to before attempting to parse the type and continue parsing.
|
|
let parser_snapshot_after_type =
|
|
mem::replace(self, parser_snapshot_before_type);
|
|
Some((parser_snapshot_after_type, colon_sp, err))
|
|
};
|
|
(err, None, Some(colon_sp))
|
|
}
|
|
}
|
|
} else {
|
|
(None, None, None)
|
|
};
|
|
let init = match (self.parse_initializer(err.is_some()), err) {
|
|
(Ok(init), None) => {
|
|
// init parsed, ty parsed
|
|
init
|
|
}
|
|
(Ok(init), Some((_, colon_sp, mut err))) => {
|
|
// init parsed, ty error
|
|
// Could parse the type as if it were the initializer, it is likely there was a
|
|
// typo in the code: `:` instead of `=`. Add suggestion and emit the error.
|
|
err.span_suggestion_short(
|
|
colon_sp,
|
|
"use `=` if you meant to assign",
|
|
" =",
|
|
Applicability::MachineApplicable,
|
|
);
|
|
err.emit();
|
|
// As this was parsed successfully, continue as if the code has been fixed for the
|
|
// rest of the file. It will still fail due to the emitted error, but we avoid
|
|
// extra noise.
|
|
init
|
|
}
|
|
(Err(init_err), Some((snapshot, _, ty_err))) => {
|
|
// init error, ty error
|
|
init_err.cancel();
|
|
// Couldn't parse the type nor the initializer, only raise the type error and
|
|
// return to the parser state before parsing the type as the initializer.
|
|
// let x: <parse_error>;
|
|
*self = snapshot;
|
|
return Err(ty_err);
|
|
}
|
|
(Err(err), None) => {
|
|
// init error, ty parsed
|
|
// Couldn't parse the initializer and we're not attempting to recover a failed
|
|
// parse of the type, return the error.
|
|
return Err(err);
|
|
}
|
|
};
|
|
let kind = match init {
|
|
None => LocalKind::Decl,
|
|
Some(init) => {
|
|
if self.eat_keyword(kw::Else) {
|
|
if self.token.is_keyword(kw::If) {
|
|
// `let...else if`. Emit the same error that `parse_block()` would,
|
|
// but explicitly point out that this pattern is not allowed.
|
|
let msg = "conditional `else if` is not supported for `let...else`";
|
|
return Err(self.error_block_no_opening_brace_msg(Cow::from(msg)));
|
|
}
|
|
let els = self.parse_block()?;
|
|
self.check_let_else_init_bool_expr(&init);
|
|
self.check_let_else_init_trailing_brace(&init);
|
|
LocalKind::InitElse(init, els)
|
|
} else {
|
|
LocalKind::Init(init)
|
|
}
|
|
}
|
|
};
|
|
let hi = if self.token == token::Semi { self.token.span } else { self.prev_token.span };
|
|
Ok(P(ast::Local {
|
|
ty,
|
|
pat,
|
|
kind,
|
|
id: DUMMY_NODE_ID,
|
|
span: lo.to(hi),
|
|
colon_sp,
|
|
attrs,
|
|
tokens: None,
|
|
}))
|
|
}
|
|
|
|
fn check_let_else_init_bool_expr(&self, init: &ast::Expr) {
|
|
if let ast::ExprKind::Binary(op, ..) = init.kind {
|
|
if op.node.is_lazy() {
|
|
self.dcx().emit_err(errors::InvalidExpressionInLetElse {
|
|
span: init.span,
|
|
operator: op.node.as_str(),
|
|
sugg: errors::WrapInParentheses::Expression {
|
|
left: init.span.shrink_to_lo(),
|
|
right: init.span.shrink_to_hi(),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_let_else_init_trailing_brace(&self, init: &ast::Expr) {
|
|
if let Some(trailing) = classify::expr_trailing_brace(init) {
|
|
let (span, sugg) = match trailing {
|
|
TrailingBrace::MacCall(mac) => (
|
|
mac.span(),
|
|
errors::WrapInParentheses::MacroArgs {
|
|
left: mac.args.dspan.open,
|
|
right: mac.args.dspan.close,
|
|
},
|
|
),
|
|
TrailingBrace::Expr(expr) => (
|
|
expr.span,
|
|
errors::WrapInParentheses::Expression {
|
|
left: expr.span.shrink_to_lo(),
|
|
right: expr.span.shrink_to_hi(),
|
|
},
|
|
),
|
|
};
|
|
self.dcx().emit_err(errors::InvalidCurlyInLetElse {
|
|
span: span.with_lo(span.hi() - BytePos(1)),
|
|
sugg,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Parses the RHS of a local variable declaration (e.g., `= 14;`).
|
|
fn parse_initializer(&mut self, eq_optional: bool) -> PResult<'a, Option<P<Expr>>> {
|
|
let eq_consumed = match self.token.kind {
|
|
token::BinOpEq(..) => {
|
|
// Recover `let x <op>= 1` as `let x = 1`
|
|
self.dcx().emit_err(errors::CompoundAssignmentExpressionInLet {
|
|
span: self.token.span,
|
|
suggestion: self.token.span.with_hi(self.token.span.lo() + BytePos(1)),
|
|
});
|
|
self.bump();
|
|
true
|
|
}
|
|
_ => self.eat(&token::Eq),
|
|
};
|
|
|
|
Ok(if eq_consumed || eq_optional { Some(self.parse_expr()?) } else { None })
|
|
}
|
|
|
|
/// Parses a block. No inner attributes are allowed.
|
|
pub fn parse_block(&mut self) -> PResult<'a, P<Block>> {
|
|
let (attrs, block) = self.parse_inner_attrs_and_block()?;
|
|
if let [.., last] = &*attrs {
|
|
self.error_on_forbidden_inner_attr(
|
|
last.span,
|
|
super::attr::InnerAttrPolicy::Forbidden(Some(
|
|
InnerAttrForbiddenReason::InCodeBlock,
|
|
)),
|
|
);
|
|
}
|
|
Ok(block)
|
|
}
|
|
|
|
fn error_block_no_opening_brace_msg(&mut self, msg: Cow<'static, str>) -> Diag<'a> {
|
|
let sp = self.token.span;
|
|
let mut e = self.dcx().struct_span_err(sp, msg);
|
|
let do_not_suggest_help = self.token.is_keyword(kw::In) || self.token == token::Colon;
|
|
|
|
// Check to see if the user has written something like
|
|
//
|
|
// if (cond)
|
|
// bar;
|
|
//
|
|
// which is valid in other languages, but not Rust.
|
|
match self.parse_stmt_without_recovery(false, ForceCollect::No) {
|
|
// If the next token is an open brace, e.g., we have:
|
|
//
|
|
// if expr other_expr {
|
|
// ^ ^ ^- lookahead(1) is a brace
|
|
// | |- current token is not "else"
|
|
// |- (statement we just parsed)
|
|
//
|
|
// the place-inside-a-block suggestion would be more likely wrong than right.
|
|
//
|
|
// FIXME(compiler-errors): this should probably parse an arbitrary expr and not
|
|
// just lookahead one token, so we can see if there's a brace after _that_,
|
|
// since we want to protect against:
|
|
// `if 1 1 + 1 {` being suggested as `if { 1 } 1 + 1 {`
|
|
// + +
|
|
Ok(Some(_))
|
|
if (!self.token.is_keyword(kw::Else)
|
|
&& self.look_ahead(1, |t| t == &token::OpenDelim(Delimiter::Brace)))
|
|
|| do_not_suggest_help => {}
|
|
// Do not suggest `if foo println!("") {;}` (as would be seen in test for #46836).
|
|
Ok(Some(Stmt { kind: StmtKind::Empty, .. })) => {}
|
|
Ok(Some(stmt)) => {
|
|
let stmt_own_line = self.psess.source_map().is_line_before_span_empty(sp);
|
|
let stmt_span = if stmt_own_line && self.eat(&token::Semi) {
|
|
// Expand the span to include the semicolon.
|
|
stmt.span.with_hi(self.prev_token.span.hi())
|
|
} else {
|
|
stmt.span
|
|
};
|
|
e.multipart_suggestion(
|
|
"try placing this code inside a block",
|
|
vec![
|
|
(stmt_span.shrink_to_lo(), "{ ".to_string()),
|
|
(stmt_span.shrink_to_hi(), " }".to_string()),
|
|
],
|
|
// Speculative; has been misleading in the past (#46836).
|
|
Applicability::MaybeIncorrect,
|
|
);
|
|
}
|
|
Err(e) => {
|
|
self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
|
|
e.cancel();
|
|
}
|
|
_ => {}
|
|
}
|
|
e.span_label(sp, "expected `{`");
|
|
e
|
|
}
|
|
|
|
fn error_block_no_opening_brace<T>(&mut self) -> PResult<'a, T> {
|
|
let tok = super::token_descr(&self.token);
|
|
let msg = format!("expected `{{`, found {tok}");
|
|
Err(self.error_block_no_opening_brace_msg(Cow::from(msg)))
|
|
}
|
|
|
|
/// Parses a block. Inner attributes are allowed.
|
|
pub(super) fn parse_inner_attrs_and_block(&mut self) -> PResult<'a, (AttrVec, P<Block>)> {
|
|
self.parse_block_common(self.token.span, BlockCheckMode::Default, true)
|
|
}
|
|
|
|
/// Parses a block. Inner attributes are allowed.
|
|
pub(super) fn parse_block_common(
|
|
&mut self,
|
|
lo: Span,
|
|
blk_mode: BlockCheckMode,
|
|
can_be_struct_literal: bool,
|
|
) -> PResult<'a, (AttrVec, P<Block>)> {
|
|
maybe_whole!(self, NtBlock, |block| (AttrVec::new(), block));
|
|
|
|
let maybe_ident = self.prev_token.clone();
|
|
self.maybe_recover_unexpected_block_label();
|
|
if !self.eat(&token::OpenDelim(Delimiter::Brace)) {
|
|
return self.error_block_no_opening_brace();
|
|
}
|
|
|
|
let attrs = self.parse_inner_attributes()?;
|
|
let tail = match self.maybe_suggest_struct_literal(
|
|
lo,
|
|
blk_mode,
|
|
maybe_ident,
|
|
can_be_struct_literal,
|
|
) {
|
|
Some(tail) => tail?,
|
|
None => self.parse_block_tail(lo, blk_mode, AttemptLocalParseRecovery::Yes)?,
|
|
};
|
|
Ok((attrs, tail))
|
|
}
|
|
|
|
/// Parses the rest of a block expression or function body.
|
|
/// Precondition: already parsed the '{'.
|
|
pub(crate) fn parse_block_tail(
|
|
&mut self,
|
|
lo: Span,
|
|
s: BlockCheckMode,
|
|
recover: AttemptLocalParseRecovery,
|
|
) -> PResult<'a, P<Block>> {
|
|
let mut stmts = ThinVec::new();
|
|
let mut snapshot = None;
|
|
while !self.eat(&token::CloseDelim(Delimiter::Brace)) {
|
|
if self.token == token::Eof {
|
|
break;
|
|
}
|
|
if self.is_vcs_conflict_marker(&TokenKind::BinOp(token::Shl), &TokenKind::Lt) {
|
|
// Account for `<<<<<<<` diff markers. We can't proactively error here because
|
|
// that can be a valid path start, so we snapshot and reparse only we've
|
|
// encountered another parse error.
|
|
snapshot = Some(self.create_snapshot_for_diagnostic());
|
|
}
|
|
let stmt = match self.parse_full_stmt(recover) {
|
|
Err(mut err) if recover.yes() => {
|
|
if let Some(ref mut snapshot) = snapshot {
|
|
snapshot.recover_vcs_conflict_marker();
|
|
}
|
|
if self.token == token::Colon {
|
|
// if a previous and next token of the current one is
|
|
// integer literal (e.g. `1:42`), it's likely a range
|
|
// expression for Pythonistas and we can suggest so.
|
|
if self.prev_token.is_integer_lit()
|
|
&& self.may_recover()
|
|
&& self.look_ahead(1, |token| token.is_integer_lit())
|
|
{
|
|
// FIXME(hkmatsumoto): Might be better to trigger
|
|
// this only when parsing an index expression.
|
|
err.span_suggestion_verbose(
|
|
self.token.span,
|
|
"you might have meant a range expression",
|
|
"..",
|
|
Applicability::MaybeIncorrect,
|
|
);
|
|
} else {
|
|
// if next token is following a colon, it's likely a path
|
|
// and we can suggest a path separator
|
|
self.bump();
|
|
if self.token.span.lo() == self.prev_token.span.hi() {
|
|
err.span_suggestion_verbose(
|
|
self.prev_token.span,
|
|
"maybe write a path separator here",
|
|
"::",
|
|
Applicability::MaybeIncorrect,
|
|
);
|
|
}
|
|
if self.psess.unstable_features.is_nightly_build() {
|
|
// FIXME(Nilstrieb): Remove this again after a few months.
|
|
err.note("type ascription syntax has been removed, see issue #101728 <https://github.com/rust-lang/rust/issues/101728>");
|
|
}
|
|
}
|
|
}
|
|
|
|
let guar = err.emit();
|
|
self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
|
|
Some(self.mk_stmt_err(self.token.span, guar))
|
|
}
|
|
Ok(stmt) => stmt,
|
|
Err(err) => return Err(err),
|
|
};
|
|
if let Some(stmt) = stmt {
|
|
stmts.push(stmt);
|
|
} else {
|
|
// Found only `;` or `}`.
|
|
continue;
|
|
};
|
|
}
|
|
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
|
|
}
|
|
|
|
/// Parses a statement, including the trailing semicolon.
|
|
pub fn parse_full_stmt(
|
|
&mut self,
|
|
recover: AttemptLocalParseRecovery,
|
|
) -> PResult<'a, Option<Stmt>> {
|
|
// Skip looking for a trailing semicolon when we have an interpolated statement.
|
|
maybe_whole!(self, NtStmt, |stmt| Some(stmt.into_inner()));
|
|
|
|
let Some(mut stmt) = self.parse_stmt_without_recovery(true, ForceCollect::No)? else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let mut eat_semi = true;
|
|
let mut add_semi_to_stmt = false;
|
|
|
|
match &mut stmt.kind {
|
|
// Expression without semicolon.
|
|
StmtKind::Expr(expr)
|
|
if classify::expr_requires_semi_to_be_stmt(expr)
|
|
&& !expr.attrs.is_empty()
|
|
&& ![token::Eof, token::Semi, token::CloseDelim(Delimiter::Brace)]
|
|
.contains(&self.token.kind) =>
|
|
{
|
|
// The user has written `#[attr] expr` which is unsupported. (#106020)
|
|
let guar = self.attr_on_non_tail_expr(&expr);
|
|
// We already emitted an error, so don't emit another type error
|
|
let sp = expr.span.to(self.prev_token.span);
|
|
*expr = self.mk_expr_err(sp, guar);
|
|
}
|
|
|
|
// Expression without semicolon.
|
|
StmtKind::Expr(expr)
|
|
if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) =>
|
|
{
|
|
// Just check for errors and recover; do not eat semicolon yet.
|
|
|
|
let expect_result =
|
|
self.expect_one_of(&[], &[token::Semi, token::CloseDelim(Delimiter::Brace)]);
|
|
|
|
// Try to both emit a better diagnostic, and avoid further errors by replacing
|
|
// the `expr` with `ExprKind::Err`.
|
|
let replace_with_err = 'break_recover: {
|
|
match expect_result {
|
|
Ok(Recovered::No) => None,
|
|
Ok(Recovered::Yes(guar)) => {
|
|
// Skip type error to avoid extra errors.
|
|
Some(guar)
|
|
}
|
|
Err(e) => {
|
|
if self.recover_colon_as_semi() {
|
|
// recover_colon_as_semi has already emitted a nicer error.
|
|
e.delay_as_bug();
|
|
add_semi_to_stmt = true;
|
|
eat_semi = false;
|
|
|
|
break 'break_recover None;
|
|
}
|
|
|
|
match &expr.kind {
|
|
ExprKind::Path(None, ast::Path { segments, .. })
|
|
if segments.len() == 1 =>
|
|
{
|
|
if self.token == token::Colon
|
|
&& self.look_ahead(1, |token| {
|
|
token.is_whole_block()
|
|
|| matches!(
|
|
token.kind,
|
|
token::Ident(
|
|
kw::For | kw::Loop | kw::While,
|
|
token::IdentIsRaw::No
|
|
) | token::OpenDelim(Delimiter::Brace)
|
|
)
|
|
})
|
|
{
|
|
let snapshot = self.create_snapshot_for_diagnostic();
|
|
let label = Label {
|
|
ident: Ident::from_str_and_span(
|
|
&format!("'{}", segments[0].ident),
|
|
segments[0].ident.span,
|
|
),
|
|
};
|
|
match self.parse_expr_labeled(label, false) {
|
|
Ok(labeled_expr) => {
|
|
e.cancel();
|
|
self.dcx().emit_err(MalformedLoopLabel {
|
|
span: label.ident.span,
|
|
suggestion: label.ident.span.shrink_to_lo(),
|
|
});
|
|
*expr = labeled_expr;
|
|
break 'break_recover None;
|
|
}
|
|
Err(err) => {
|
|
err.cancel();
|
|
self.restore_snapshot(snapshot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let res =
|
|
self.check_mistyped_turbofish_with_multiple_type_params(e, expr);
|
|
|
|
Some(if recover.no() {
|
|
res?
|
|
} else {
|
|
res.unwrap_or_else(|e| {
|
|
let guar = e.emit();
|
|
self.recover_stmt();
|
|
guar
|
|
})
|
|
})
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Some(guar) = replace_with_err {
|
|
// We already emitted an error, so don't emit another type error
|
|
let sp = expr.span.to(self.prev_token.span);
|
|
*expr = self.mk_expr_err(sp, guar);
|
|
}
|
|
}
|
|
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)?;
|
|
// 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.kind == 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);
|
|
}
|
|
}
|
|
eat_semi = false;
|
|
}
|
|
StmtKind::Empty | StmtKind::Item(_) | StmtKind::Let(_) | StmtKind::Semi(_) => {
|
|
eat_semi = false
|
|
}
|
|
}
|
|
|
|
if add_semi_to_stmt || (eat_semi && self.eat(&token::Semi)) {
|
|
stmt = stmt.add_trailing_semicolon();
|
|
}
|
|
|
|
stmt.span = stmt.span.to(self.prev_token.span);
|
|
Ok(Some(stmt))
|
|
}
|
|
|
|
pub(super) fn mk_block(
|
|
&self,
|
|
stmts: ThinVec<Stmt>,
|
|
rules: BlockCheckMode,
|
|
span: Span,
|
|
) -> P<Block> {
|
|
P(Block {
|
|
stmts,
|
|
id: DUMMY_NODE_ID,
|
|
rules,
|
|
span,
|
|
tokens: None,
|
|
could_be_bare_literal: false,
|
|
})
|
|
}
|
|
|
|
pub(super) fn mk_stmt(&self, span: Span, kind: StmtKind) -> Stmt {
|
|
Stmt { id: DUMMY_NODE_ID, kind, span }
|
|
}
|
|
|
|
pub(super) fn mk_stmt_err(&self, span: Span, guar: ErrorGuaranteed) -> Stmt {
|
|
self.mk_stmt(span, StmtKind::Expr(self.mk_expr_err(span, guar)))
|
|
}
|
|
|
|
pub(super) fn mk_block_err(&self, span: Span, guar: ErrorGuaranteed) -> P<Block> {
|
|
self.mk_block(thin_vec![self.mk_stmt_err(span, guar)], BlockCheckMode::Default, span)
|
|
}
|
|
}
|