-use super::pat::{RecoverColon, RecoverComma, PARAM_EXPECTED};
+use super::diagnostics::SnapshotParser;
+use super::pat::{CommaRecoveryMode, RecoverColon, RecoverComma, PARAM_EXPECTED};
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
-use super::{AttrWrapper, BlockMode, ForceCollect, Parser, PathStyle, Restrictions, TokenType};
-use super::{SemiColonMode, SeqSep, TokenExpectType, TrailingToken};
+use super::{
+ AttrWrapper, BlockMode, ClosureSpans, ForceCollect, Parser, PathStyle, Restrictions,
+ SemiColonMode, SeqSep, TokenExpectType, TokenType, TrailingToken,
+};
use crate::maybe_recover_from_interpolated_ty_qpath;
+use ast::token::DelimToken;
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Token, TokenKind};
use rustc_ast::tokenstream::Spacing;
use rustc_ast::{AnonConst, BinOp, BinOpKind, FnDecl, FnRetTy, MacCall, Param, Ty, TyKind};
use rustc_ast::{Arm, Async, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits};
use rustc_ast_pretty::pprust;
-use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, PResult};
use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
use rustc_session::lint::BuiltinLintDiagnostics;
-use rustc_span::edition::LATEST_STABLE_EDITION;
use rustc_span::source_map::{self, Span, Spanned};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{BytePos, Pos};
/// Parses an expression.
#[inline]
pub fn parse_expr(&mut self) -> PResult<'a, P<Expr>> {
+ self.current_closure.take();
+
self.parse_expr_res(Restrictions::empty(), None)
}
}
}
+ // Look for JS' `===` and `!==` and recover
if (op.node == AssocOp::Equal || op.node == AssocOp::NotEqual)
&& self.token.kind == token::Eq
&& self.prev_token.span.hi() == self.token.span.lo()
{
- // Look for JS' `===` and `!==` and recover 😇
let sp = op.span.to(self.token.span);
let sugg = match op.node {
AssocOp::Equal => "==",
AssocOp::NotEqual => "!=",
_ => unreachable!(),
};
- self.struct_span_err(sp, &format!("invalid comparison operator `{}=`", sugg))
+ self.struct_span_err(sp, &format!("invalid comparison operator `{sugg}=`"))
.span_suggestion_short(
sp,
&format!("`{s}=` is not a valid comparison operator, use `{s}`", s = sugg),
self.bump();
}
+ // Look for PHP's `<>` and recover
+ if op.node == AssocOp::Less
+ && self.token.kind == token::Gt
+ && self.prev_token.span.hi() == self.token.span.lo()
+ {
+ let sp = op.span.to(self.token.span);
+ self.struct_span_err(sp, "invalid comparison operator `<>`")
+ .span_suggestion_short(
+ sp,
+ "`<>` is not a valid comparison operator, use `!=`",
+ "!=".to_string(),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ self.bump();
+ }
+
+ // Look for C++'s `<=>` and recover
+ if op.node == AssocOp::LessEqual
+ && self.token.kind == token::Gt
+ && self.prev_token.span.hi() == self.token.span.lo()
+ {
+ let sp = op.span.to(self.token.span);
+ self.struct_span_err(sp, "invalid comparison operator `<=>`")
+ .span_label(
+ sp,
+ "`<=>` is not a valid comparison operator, use `std::cmp::Ordering`",
+ )
+ .emit();
+ self.bump();
+ }
+
+ if self.prev_token == token::BinOp(token::Plus)
+ && self.token == token::BinOp(token::Plus)
+ && self.prev_token.span.between(self.token.span).is_empty()
+ {
+ let op_span = self.prev_token.span.to(self.token.span);
+ // Eat the second `+`
+ self.bump();
+ lhs = self.recover_from_postfix_increment(lhs, op_span)?;
+ continue;
+ }
+
let op = op.node;
// Special cases:
if op == AssocOp::As {
lhs = self.parse_assoc_op_ascribe(lhs, lhs_span)?;
continue;
} else if op == AssocOp::DotDot || op == AssocOp::DotDotEq {
- // If we didn’t have to handle `x..`/`x..=`, it would be pretty easy to
+ // If we didn't have to handle `x..`/`x..=`, it would be pretty easy to
// generalise it to the Fixity::None code.
lhs = self.parse_range_expr(prec, lhs, op, cur_op_span)?;
break;
self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
false
}
- (true, Some(AssocOp::LAnd)) => {
+ (true, Some(AssocOp::LAnd)) |
+ (true, Some(AssocOp::LOr)) |
+ (true, Some(AssocOp::BitOr)) => {
// `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`. Separated from the
// above due to #74233.
// These cases are ambiguous and can't be identified in the parser alone.
+ //
+ // Bitwise AND is left out because guessing intent is hard. We can make
+ // suggestions based on the assumption that double-refs are rarely intentional,
+ // and closures are distinct enough that they don't get mixed up with their
+ // return value.
let sp = self.sess.source_map().start_point(self.token.span);
self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
false
/// Error on `and` and `or` suggesting `&&` and `||` respectively.
fn error_bad_logical_op(&self, bad: &str, good: &str, english: &str) {
- self.struct_span_err(self.token.span, &format!("`{}` is not a logical operator", bad))
+ self.struct_span_err(self.token.span, &format!("`{bad}` is not a logical operator"))
.span_suggestion_short(
self.token.span,
- &format!("use `{}` to perform logical {}", good, english),
+ &format!("use `{good}` to perform logical {english}"),
good.to_string(),
Applicability::MachineApplicable,
)
token::BinOp(token::And) | token::AndAnd => {
make_it!(this, attrs, |this, _| this.parse_borrow_expr(lo))
}
+ token::BinOp(token::Plus) if this.look_ahead(1, |tok| tok.is_numeric_lit()) => {
+ let mut err = this.struct_span_err(lo, "leading `+` is not supported");
+ err.span_label(lo, "unexpected `+`");
+
+ // a block on the LHS might have been intended to be an expression instead
+ if let Some(sp) = this.sess.ambiguous_block_expr_parse.borrow().get(&lo) {
+ this.sess.expr_parentheses_needed(&mut err, *sp);
+ } else {
+ err.span_suggestion_verbose(
+ lo,
+ "try removing the `+`",
+ "".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ err.emit();
+
+ this.bump();
+ this.parse_prefix_expr(None)
+ } // `+expr`
+ // Recover from `++x`:
+ token::BinOp(token::Plus)
+ if this.look_ahead(1, |t| *t == token::BinOp(token::Plus)) =>
+ {
+ let prev_is_semi = this.prev_token == token::Semi;
+ let pre_span = this.token.span.to(this.look_ahead(1, |t| t.span));
+ // Eat both `+`s.
+ this.bump();
+ this.bump();
+
+ let operand_expr = this.parse_dot_or_call_expr(Default::default())?;
+ this.recover_from_prefix_increment(operand_expr, pre_span, prev_is_semi)
+ }
token::Ident(..) if this.token.is_keyword(kw::Box) => {
make_it!(this, attrs, |this, _| this.parse_box_expr(lo))
}
// Save the state of the parser before parsing type normally, in case there is a
// LessThan comparison after this cast.
let parser_snapshot_before_type = self.clone();
- let cast_expr = match self.parse_ty_no_plus() {
+ let cast_expr = match self.parse_as_cast_ty() {
Ok(rhs) => mk_expr(self, lhs, rhs),
- Err(mut type_err) => {
+ Err(type_err) => {
// Rewind to before attempting to parse the type with generics, to recover
// from situations like `x as usize < y` in which we first tried to parse
// `usize < y` as a type with generic arguments.
ExprKind::Path(None, ast::Path { segments, .. }),
TokenKind::Ident(kw::For | kw::Loop | kw::While, false),
) if segments.len() == 1 => {
- let snapshot = self.clone();
+ let snapshot = self.create_snapshot_for_diagnostic();
let label = Label {
ident: Ident::from_str_and_span(
&format!("'{}", segments[0].ident),
.emit();
return Ok(expr);
}
- Err(mut err) => {
+ Err(err) => {
err.cancel();
- *self = snapshot;
+ self.restore_snapshot(snapshot);
}
}
}
self.look_ahead(1, |t| t.span).to(span_after_type),
"interpreted as generic arguments",
)
- .span_label(self.token.span, format!("not interpreted as {}", op_noun))
+ .span_label(self.token.span, format!("not interpreted as {op_noun}"))
.multipart_suggestion(
- &format!("try {} the cast value", op_verb),
+ &format!("try {op_verb} the cast value"),
vec![
(expr.span.shrink_to_lo(), "(".to_string()),
(expr.span.shrink_to_hi(), ")".to_string()),
expr
}
- Err(mut path_err) => {
+ Err(path_err) => {
// Couldn't parse as a path, return original error and parser state.
path_err.cancel();
*self = parser_snapshot_after_type;
&mut self,
cast_expr: P<Expr>,
) -> PResult<'a, P<Expr>> {
+ let span = cast_expr.span;
+ let maybe_ascription_span = if let ExprKind::Type(ascripted_expr, _) = &cast_expr.kind {
+ Some(ascripted_expr.span.shrink_to_hi().with_hi(span.hi()))
+ } else {
+ None
+ };
+
// Save the memory location of expr before parsing any following postfix operators.
// This will be compared with the memory location of the output expression.
// If they different we can assume we parsed another expression because the existing expression is not reallocated.
let addr_before = &*cast_expr as *const _ as usize;
- let span = cast_expr.span;
let with_postfix = self.parse_dot_or_call_expr_with_(cast_expr, span)?;
let changed = addr_before != &*with_postfix as *const _ as usize;
"casts cannot be followed by {}",
match with_postfix.kind {
ExprKind::Index(_, _) => "indexing",
- ExprKind::Try(_) => "?",
+ ExprKind::Try(_) => "`?`",
ExprKind::Field(_, _) => "a field access",
ExprKind::MethodCall(_, _, _) => "a method call",
ExprKind::Call(_, _) => "a function call",
}
);
let mut err = self.struct_span_err(span, &msg);
- // If type ascription is "likely an error", the user will already be getting a useful
- // help message, and doesn't need a second.
- if self.last_type_ascription.map_or(false, |last_ascription| last_ascription.1) {
- self.maybe_annotate_with_ascription(&mut err, false);
- } else {
+
+ let suggest_parens = |err: &mut DiagnosticBuilder<'_, _>| {
let suggestions = vec![
(span.shrink_to_lo(), "(".to_string()),
(span.shrink_to_hi(), ")".to_string()),
suggestions,
Applicability::MachineApplicable,
);
+ };
+
+ // If type ascription is "likely an error", the user will already be getting a useful
+ // help message, and doesn't need a second.
+ if self.last_type_ascription.map_or(false, |last_ascription| last_ascription.1) {
+ self.maybe_annotate_with_ascription(&mut err, false);
+ } else if let Some(ascription_span) = maybe_ascription_span {
+ let is_nightly = self.sess.unstable_features.is_nightly_build();
+ if is_nightly {
+ suggest_parens(&mut err);
+ }
+ err.span_suggestion(
+ ascription_span,
+ &format!(
+ "{}remove the type ascription",
+ if is_nightly { "alternatively, " } else { "" }
+ ),
+ String::new(),
+ if is_nightly {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ },
+ );
+ } else {
+ suggest_parens(&mut err);
}
err.emit();
};
}
}
+ fn look_ahead_type_ascription_as_field(&mut self) -> bool {
+ self.look_ahead(1, |t| t.is_ident())
+ && self.look_ahead(2, |t| t == &token::Colon)
+ && self.look_ahead(3, |t| t.can_begin_expr())
+ }
+
fn parse_dot_suffix_expr(&mut self, lo: Span, base: P<Expr>) -> PResult<'a, P<Expr>> {
match self.token.uninterpolate().kind {
token::Ident(..) => self.parse_dot_suffix(base, lo),
fn error_unexpected_after_dot(&self) {
// FIXME Could factor this out into non_fatal_unexpected or something.
let actual = pprust::token_to_string(&self.token);
- self.struct_span_err(self.token.span, &format!("unexpected token: `{}`", actual)).emit();
+ self.struct_span_err(self.token.span, &format!("unexpected token: `{actual}`")).emit();
}
// We need an identifier or integer, but the next token is a float.
[IdentLike(_), Punct('+' | '-')] |
// 1e+2 | 1e-2
[IdentLike(_), Punct('+' | '-'), IdentLike(_)] |
+ // 1.2e+ | 1.2e-
+ [IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-')] |
// 1.2e+3 | 1.2e-3
[IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-'), IdentLike(_)] => {
// See the FIXME about `TokenCursor` above.
/// Parse a function call expression, `expr(...)`.
fn parse_fn_call_expr(&mut self, lo: Span, fun: P<Expr>) -> P<Expr> {
- let seq = self.parse_paren_expr_seq().map(|args| {
+ let snapshot = if self.token.kind == token::OpenDelim(token::Paren)
+ && self.look_ahead_type_ascription_as_field()
+ {
+ Some((self.create_snapshot_for_diagnostic(), fun.kind.clone()))
+ } else {
+ None
+ };
+ let open_paren = self.token.span;
+
+ let mut seq = self.parse_paren_expr_seq().map(|args| {
self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args), AttrVec::new())
});
+ if let Some(expr) =
+ self.maybe_recover_struct_lit_bad_delims(lo, open_paren, &mut seq, snapshot)
+ {
+ return expr;
+ }
self.recover_seq_parse_error(token::Paren, lo, seq)
}
+ /// If we encounter a parser state that looks like the user has written a `struct` literal with
+ /// parentheses instead of braces, recover the parser state and provide suggestions.
+ #[instrument(skip(self, seq, snapshot), level = "trace")]
+ fn maybe_recover_struct_lit_bad_delims(
+ &mut self,
+ lo: Span,
+ open_paren: Span,
+ seq: &mut PResult<'a, P<Expr>>,
+ snapshot: Option<(SnapshotParser<'a>, ExprKind)>,
+ ) -> Option<P<Expr>> {
+ match (seq.as_mut(), snapshot) {
+ (Err(err), Some((mut snapshot, ExprKind::Path(None, path)))) => {
+ let name = pprust::path_to_string(&path);
+ snapshot.bump(); // `(`
+ match snapshot.parse_struct_fields(path, false, token::Paren) {
+ Ok((fields, ..)) if snapshot.eat(&token::CloseDelim(token::Paren)) => {
+ // We are certain we have `Enum::Foo(a: 3, b: 4)`, suggest
+ // `Enum::Foo { a: 3, b: 4 }` or `Enum::Foo(3, 4)`.
+ self.restore_snapshot(snapshot);
+ let close_paren = self.prev_token.span;
+ let span = lo.to(self.prev_token.span);
+ if !fields.is_empty() {
+ let replacement_err = self.struct_span_err(
+ span,
+ "invalid `struct` delimiters or `fn` call arguments",
+ );
+ mem::replace(err, replacement_err).cancel();
+
+ err.multipart_suggestion(
+ &format!("if `{name}` is a struct, use braces as delimiters"),
+ vec![
+ (open_paren, " { ".to_string()),
+ (close_paren, " }".to_string()),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ err.multipart_suggestion(
+ &format!("if `{name}` is a function, use the arguments directly"),
+ fields
+ .into_iter()
+ .map(|field| (field.span.until(field.expr.span), String::new()))
+ .collect(),
+ Applicability::MaybeIncorrect,
+ );
+ err.emit();
+ } else {
+ err.emit();
+ }
+ return Some(self.mk_expr_err(span));
+ }
+ Ok(_) => {}
+ Err(mut err) => {
+ err.emit();
+ }
+ }
+ }
+ _ => {}
+ }
+ None
+ }
+
/// Parse an indexing expression `expr[...]`.
fn parse_index_expr(&mut self, lo: Span, base: P<Expr>) -> PResult<'a, P<Expr>> {
self.bump(); // `[`
}
let fn_span_lo = self.token.span;
- let mut segment = self.parse_path_segment(PathStyle::Expr)?;
+ let mut segment = self.parse_path_segment(PathStyle::Expr, None)?;
self.check_trailing_angle_brackets(&segment, &[&token::OpenDelim(token::Paren)]);
self.check_turbofish_missing_angle_brackets(&mut segment);
} else if self.check(&token::OpenDelim(token::Brace)) {
self.parse_block_expr(None, lo, BlockCheckMode::Default, attrs)
} else if self.check(&token::BinOp(token::Or)) || self.check(&token::OrOr) {
- self.parse_closure_expr(attrs)
+ self.parse_closure_expr(attrs).map_err(|mut err| {
+ // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }`
+ // then suggest parens around the lhs.
+ if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&lo) {
+ self.sess.expr_parentheses_needed(&mut err, *sp);
+ }
+ err
+ })
} else if self.check(&token::OpenDelim(token::Bracket)) {
- self.parse_array_or_repeat_expr(attrs)
+ self.parse_array_or_repeat_expr(attrs, token::Bracket)
} else if self.check_path() {
self.parse_path_start_expr(attrs)
} else if self.check_keyword(kw::Move) || self.check_keyword(kw::Static) {
} else if let Some(label) = self.eat_label() {
self.parse_labeled_expr(label, attrs, true)
} else if self.eat_keyword(kw::Loop) {
- self.parse_loop_expr(None, self.prev_token.span, attrs)
+ let sp = self.prev_token.span;
+ self.parse_loop_expr(None, self.prev_token.span, attrs).map_err(|mut err| {
+ err.span_label(sp, "while parsing this `loop` expression");
+ err
+ })
} else if self.eat_keyword(kw::Continue) {
let kind = ExprKind::Continue(self.eat_label());
Ok(self.mk_expr(lo.to(self.prev_token.span), kind, attrs))
} else if self.eat_keyword(kw::Match) {
let match_sp = self.prev_token.span;
self.parse_match_expr(attrs).map_err(|mut err| {
- err.span_label(match_sp, "while parsing this match expression");
+ err.span_label(match_sp, "while parsing this `match` expression");
err
})
} else if self.eat_keyword(kw::Unsafe) {
+ let sp = self.prev_token.span;
self.parse_block_expr(None, lo, BlockCheckMode::Unsafe(ast::UserProvided), attrs)
+ .map_err(|mut err| {
+ err.span_label(sp, "while parsing this `unsafe` expression");
+ err
+ })
} else if self.check_inline_const(0) {
- self.parse_const_block(lo.to(self.token.span))
+ self.parse_const_block(lo.to(self.token.span), false)
} else if self.is_do_catch_block() {
self.recover_do_catch(attrs)
} else if self.is_try_block() {
} else if self.eat_keyword(kw::Let) {
self.parse_let_expr(attrs)
} else if self.eat_keyword(kw::Underscore) {
- self.sess.gated_spans.gate(sym::destructuring_assignment, self.prev_token.span);
Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore, attrs))
} else if !self.unclosed_delims.is_empty() && self.check(&token::Semi) {
// Don't complain about bare semicolons after unclosed braces
self.maybe_recover_from_bad_qpath(expr, true)
}
- fn parse_array_or_repeat_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+ fn parse_array_or_repeat_expr(
+ &mut self,
+ attrs: AttrVec,
+ close_delim: token::DelimToken,
+ ) -> PResult<'a, P<Expr>> {
let lo = self.token.span;
- self.bump(); // `[`
+ self.bump(); // `[` or other open delim
- let close = &token::CloseDelim(token::Bracket);
+ let close = &token::CloseDelim(close_delim);
let kind = if self.eat(close) {
// Empty vector
ExprKind::Array(Vec::new())
&mut self,
label: Label,
attrs: AttrVec,
- consume_colon: bool,
+ mut consume_colon: bool,
) -> PResult<'a, P<Expr>> {
let lo = label.ident.span;
let label = Some(label);
self.parse_loop_expr(label, lo, attrs)
} else if self.check(&token::OpenDelim(token::Brace)) || self.token.is_whole_block() {
self.parse_block_expr(label, lo, BlockCheckMode::Default, attrs)
+ } else if !ate_colon && (self.check(&TokenKind::Comma) || self.check(&TokenKind::Gt)) {
+ // We're probably inside of a `Path<'a>` that needs a turbofish
+ let msg = "expected `while`, `for`, `loop` or `{` after a label";
+ self.struct_span_err(self.token.span, msg).span_label(self.token.span, msg).emit();
+ consume_colon = false;
+ Ok(self.mk_expr_err(lo))
} else {
let msg = "expected `while`, `for`, `loop` or `{` after a label";
self.struct_span_err(self.token.span, msg).span_label(self.token.span, msg).emit();
pub(super) fn parse_lit(&mut self) -> PResult<'a, Lit> {
self.parse_opt_lit().ok_or_else(|| {
+ if let token::Interpolated(inner) = &self.token.kind {
+ let expr = match inner.as_ref() {
+ token::NtExpr(expr) => Some(expr),
+ token::NtLiteral(expr) => Some(expr),
+ _ => None,
+ };
+ if let Some(expr) = expr {
+ if matches!(expr.kind, ExprKind::Err) {
+ let mut err = self
+ .diagnostic()
+ .struct_span_err(self.token.span, &"invalid interpolated expression");
+ err.downgrade_to_delayed_bug();
+ return err;
+ }
+ }
+ }
let msg = format!("unexpected token: {}", super::token_descr(&self.token));
self.struct_span_err(self.token.span, &msg)
})
next_token.kind
{
if self.token.span.hi() == next_token.span.lo() {
- let s = String::from("0.") + &symbol.as_str();
+ let s = String::from("0.") + symbol.as_str();
let kind = TokenKind::lit(token::Float, Symbol::intern(&s), suffix);
return Some(Token::new(kind, self.token.span.to(next_token.span)));
}
Err(LitError::NotLiteral) => None,
Err(err) => {
let span = token.span;
- let lit = match token.kind {
- token::Literal(lit) => lit,
- _ => unreachable!(),
+ let token::Literal(lit) = token.kind else {
+ unreachable!();
};
self.bump();
self.report_lit_error(err, lit, span);
s.len() > 1 && s.starts_with(first_chars) && s[1..].chars().all(|c| c.is_ascii_digit())
}
+ // Try to lowercase the prefix if it's a valid base prefix.
+ fn fix_base_capitalisation(s: &str) -> Option<String> {
+ if let Some(stripped) = s.strip_prefix('B') {
+ Some(format!("0b{stripped}"))
+ } else if let Some(stripped) = s.strip_prefix('O') {
+ Some(format!("0o{stripped}"))
+ } else if let Some(stripped) = s.strip_prefix('X') {
+ Some(format!("0x{stripped}"))
+ } else {
+ None
+ }
+ }
+
let token::Lit { kind, suffix, .. } = lit;
match err {
// `NotLiteral` is not an error by itself, so we don't report
);
}
LitError::InvalidIntSuffix => {
- let suf = suffix.expect("suffix error with no suffix").as_str();
+ let suf = suffix.expect("suffix error with no suffix");
+ let suf = suf.as_str();
if looks_like_width_suffix(&['i', 'u'], &suf) {
// If it looks like a width, try to be helpful.
let msg = format!("invalid width `{}` for integer literal", &suf[1..]);
self.struct_span_err(span, &msg)
.help("valid widths are 8, 16, 32, 64 and 128")
.emit();
+ } else if let Some(fixed) = fix_base_capitalisation(suf) {
+ let msg = "invalid base prefix for number literal";
+
+ self.struct_span_err(span, &msg)
+ .note("base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase")
+ .span_suggestion(
+ span,
+ "try making the prefix lowercase",
+ fixed,
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
} else {
- let msg = format!("invalid suffix `{}` for number literal", suf);
+ let msg = format!("invalid suffix `{suf}` for number literal");
self.struct_span_err(span, &msg)
- .span_label(span, format!("invalid suffix `{}`", suf))
+ .span_label(span, format!("invalid suffix `{suf}`"))
.help("the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.)")
.emit();
}
}
LitError::InvalidFloatSuffix => {
- let suf = suffix.expect("suffix error with no suffix").as_str();
- if looks_like_width_suffix(&['f'], &suf) {
+ let suf = suffix.expect("suffix error with no suffix");
+ let suf = suf.as_str();
+ if looks_like_width_suffix(&['f'], suf) {
// If it looks like a width, try to be helpful.
let msg = format!("invalid width `{}` for float literal", &suf[1..]);
self.struct_span_err(span, &msg).help("valid widths are 32 and 64").emit();
} else {
- let msg = format!("invalid suffix `{}` for float literal", suf);
+ let msg = format!("invalid suffix `{suf}` for float literal");
self.struct_span_err(span, &msg)
- .span_label(span, format!("invalid suffix `{}`", suf))
+ .span_label(span, format!("invalid suffix `{suf}`"))
.help("valid suffixes are `f32` and `f64`")
.emit();
}
2 => "binary",
_ => unreachable!(),
};
- self.struct_span_err(span, &format!("{} float literal is not supported", descr))
+ self.struct_span_err(span, &format!("{descr} float literal is not supported"))
.span_label(span, "not supported")
.emit();
}
let mut err = self
.sess
.span_diagnostic
- .struct_span_warn(sp, &format!("suffixes on {} are invalid", kind));
+ .struct_span_warn(sp, &format!("suffixes on {kind} are invalid"));
err.note(&format!(
"`{}` is *temporarily* accepted on tuple index fields as it was \
incorrectly accepted on stable for a few releases",
);
err
} else {
- self.struct_span_err(sp, &format!("suffixes on {} are invalid", kind))
+ self.struct_span_err(sp, &format!("suffixes on {kind} are invalid"))
+ .forget_guarantee()
};
- err.span_label(sp, format!("invalid suffix `{}`", suf));
+ err.span_label(sp, format!("invalid suffix `{suf}`"));
err.emit();
}
}
}
}
+ fn is_array_like_block(&mut self) -> bool {
+ self.look_ahead(1, |t| matches!(t.kind, TokenKind::Ident(..) | TokenKind::Literal(_)))
+ && self.look_ahead(2, |t| t == &token::Comma)
+ && self.look_ahead(3, |t| t.can_begin_expr())
+ }
+
+ /// Emits a suggestion if it looks like the user meant an array but
+ /// accidentally used braces, causing the code to be interpreted as a block
+ /// expression.
+ fn maybe_suggest_brackets_instead_of_braces(
+ &mut self,
+ lo: Span,
+ attrs: AttrVec,
+ ) -> Option<P<Expr>> {
+ let mut snapshot = self.create_snapshot_for_diagnostic();
+ match snapshot.parse_array_or_repeat_expr(attrs, token::Brace) {
+ Ok(arr) => {
+ let hi = snapshot.prev_token.span;
+ self.struct_span_err(arr.span, "this is a block expression, not an array")
+ .multipart_suggestion(
+ "to make an array, use square brackets instead of curly braces",
+ vec![(lo, "[".to_owned()), (hi, "]".to_owned())],
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+
+ self.restore_snapshot(snapshot);
+ Some(self.mk_expr_err(arr.span))
+ }
+ Err(e) => {
+ e.cancel();
+ None
+ }
+ }
+ }
+
/// Parses a block or unsafe block.
pub(super) fn parse_block_expr(
&mut self,
blk_mode: BlockCheckMode,
mut attrs: AttrVec,
) -> PResult<'a, P<Expr>> {
+ if self.is_array_like_block() {
+ if let Some(arr) = self.maybe_suggest_brackets_instead_of_braces(lo, attrs.clone()) {
+ return Ok(arr);
+ }
+ }
+
if let Some(label) = opt_label {
self.sess.gated_spans.gate(sym::label_break_value, label.ident.span);
}
let capture_clause = self.parse_capture_clause()?;
let decl = self.parse_fn_block_decl()?;
let decl_hi = self.prev_token.span;
- let body = match decl.output {
+ let mut body = match decl.output {
FnRetTy::Default(_) => {
let restrictions = self.restrictions - Restrictions::STMT_EXPR;
self.parse_expr_res(restrictions, None)?
self.sess.gated_spans.gate(sym::async_closure, span);
}
- Ok(self.mk_expr(
+ if self.token.kind == TokenKind::Semi && self.token_cursor.frame.delim == DelimToken::Paren
+ {
+ // It is likely that the closure body is a block but where the
+ // braces have been removed. We will recover and eat the next
+ // statements later in the parsing process.
+ body = self.mk_expr_err(body.span);
+ }
+
+ let body_span = body.span;
+
+ let closure = self.mk_expr(
lo.to(body.span),
ExprKind::Closure(capture_clause, asyncness, movability, decl, body, lo.to(decl_hi)),
attrs,
- ))
+ );
+
+ // Disable recovery for closure body
+ let spans =
+ ClosureSpans { whole_closure: closure.span, closing_pipe: decl_hi, body: body_span };
+ self.current_closure = Some(spans);
+
+ Ok(closure)
}
/// Parses an optional `move` prefix to a closure-like construct.
let lo = self.prev_token.span;
let cond = self.parse_cond_expr()?;
+ let missing_then_block_binop_span = || {
+ match cond.kind {
+ ExprKind::Binary(Spanned { span: binop_span, .. }, _, ref right)
+ if let ExprKind::Block(..) = right.kind => Some(binop_span),
+ _ => None
+ }
+ };
+
// Verify that the parsed `if` condition makes sense as a condition. If it is a block, then
// verify that the last statement is either an implicit return (no `;`) or an explicit
// return. This won't catch blocks with an explicit `return`, but that would be caught by
// the dead code lint.
- let thn = if self.eat_keyword(kw::Else) || !cond.returns() {
- self.error_missing_if_cond(lo, cond.span)
+ let thn = if self.token.is_keyword(kw::Else) || !cond.returns() {
+ if let Some(binop_span) = missing_then_block_binop_span() {
+ self.error_missing_if_then_block(lo, None, Some(binop_span)).emit();
+ self.mk_block_err(cond.span)
+ } else {
+ self.error_missing_if_cond(lo, cond.span)
+ }
} else {
let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery.
let not_block = self.token != token::OpenDelim(token::Brace);
- let block = self.parse_block().map_err(|mut err| {
+ let block = self.parse_block().map_err(|err| {
if not_block {
- err.span_label(lo, "this `if` expression has a condition, but no block");
- if let ExprKind::Binary(_, _, ref right) = cond.kind {
- if let ExprKind::Block(_, _) = right.kind {
- err.help("maybe you forgot the right operand of the condition?");
- }
- }
+ self.error_missing_if_then_block(lo, Some(err), missing_then_block_binop_span())
+ } else {
+ err
}
- err
})?;
self.error_on_if_block_attrs(lo, false, block.span, &attrs);
block
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els), attrs))
}
+ fn error_missing_if_then_block(
+ &self,
+ if_span: Span,
+ err: Option<DiagnosticBuilder<'a, ErrorGuaranteed>>,
+ binop_span: Option<Span>,
+ ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
+ let msg = "this `if` expression has a condition, but no block";
+
+ let mut err = if let Some(mut err) = err {
+ err.span_label(if_span, msg);
+ err
+ } else {
+ self.struct_span_err(if_span, msg)
+ };
+
+ if let Some(binop_span) = binop_span {
+ err.span_help(binop_span, "maybe you forgot the right operand of the condition?");
+ }
+
+ err
+ }
+
fn error_missing_if_cond(&self, lo: Span, span: Span) -> P<ast::Block> {
let sp = self.sess.source_map().next_point(lo);
self.struct_span_err(sp, "missing condition for `if` expression")
/// The `let` token has already been eaten.
fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
let lo = self.prev_token.span;
- let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
+ let pat = self.parse_pat_allow_top_alt(
+ None,
+ RecoverComma::Yes,
+ RecoverColon::Yes,
+ CommaRecoveryMode::LikelyTuple,
+ )?;
self.expect(&token::Eq)?;
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
let ctx = if is_ctx_else { "else" } else { "if" };
self.struct_span_err(last, "outer attributes are not allowed on `if` and `else` branches")
.span_label(branch_span, "the attributes are attached to this branch")
- .span_label(ctx_span, format!("the branch belongs to this `{}`", ctx))
+ .span_label(ctx_span, format!("the branch belongs to this `{ctx}`"))
.span_suggestion(
span,
"remove the attributes",
_ => None,
};
- let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
+ let pat = self.parse_pat_allow_top_alt(
+ None,
+ RecoverComma::Yes,
+ RecoverColon::Yes,
+ CommaRecoveryMode::LikelyTuple,
+ )?;
if !self.eat_keyword(kw::In) {
self.error_missing_in_for_loop();
}
self.check_for_for_in_in_typo(self.prev_token.span);
let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
- let pat = self.recover_parens_around_for_head(pat, &expr, begin_paren);
+ let pat = self.recover_parens_around_for_head(pat, begin_paren);
let (iattrs, loop_block) = self.parse_inner_attrs_and_block()?;
attrs.extend(iattrs);
lo: Span,
mut attrs: AttrVec,
) -> PResult<'a, P<Expr>> {
- let cond = self.parse_cond_expr()?;
- let (iattrs, body) = self.parse_inner_attrs_and_block()?;
+ let cond = self.parse_cond_expr().map_err(|mut err| {
+ err.span_label(lo, "while parsing the condition of this `while` expression");
+ err
+ })?;
+ let (iattrs, body) = self.parse_inner_attrs_and_block().map_err(|mut err| {
+ err.span_label(lo, "while parsing the body of this `while` expression");
+ err.span_label(cond.span, "this `while` condition successfully parsed");
+ err
+ })?;
attrs.extend(iattrs);
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::While(cond, body, opt_label), attrs))
}
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::Loop(body, opt_label), attrs))
}
- fn eat_label(&mut self) -> Option<Label> {
+ crate fn eat_label(&mut self) -> Option<Label> {
self.token.lifetime().map(|ident| {
self.bump();
Label { ident }
Applicability::MaybeIncorrect, // speculative
);
}
- return Err(e);
+ if self.maybe_recover_unexpected_block_label() {
+ e.cancel();
+ self.bump();
+ } else {
+ return Err(e);
+ }
}
attrs.extend(self.parse_inner_attributes()?);
if self.token.kind != token::Semi {
return None;
}
- let start_snapshot = self.clone();
+ let start_snapshot = self.create_snapshot_for_diagnostic();
let semi_sp = self.token.span;
self.bump(); // `;`
let mut stmts =
err.span_label(arrow_span, "while parsing the `match` arm starting here");
if stmts.len() > 1 {
err.multipart_suggestion(
- &format!("surround the statement{} with a body", s),
+ &format!("surround the statement{s} with a body"),
vec![
(span.shrink_to_lo(), "{ ".to_string()),
(span.shrink_to_hi(), " }".to_string()),
return Some(err(self, stmts));
}
if self.token.kind == token::Comma {
- *self = start_snapshot;
+ self.restore_snapshot(start_snapshot);
return None;
}
- let pre_pat_snapshot = self.clone();
+ let pre_pat_snapshot = self.create_snapshot_for_diagnostic();
match self.parse_pat_no_top_alt(None) {
Ok(_pat) => {
if self.token.kind == token::FatArrow {
// Reached arm end.
- *self = pre_pat_snapshot;
+ self.restore_snapshot(pre_pat_snapshot);
return Some(err(self, stmts));
}
}
- Err(mut err) => {
+ Err(err) => {
err.cancel();
}
}
- *self = pre_pat_snapshot;
+ self.restore_snapshot(pre_pat_snapshot);
match self.parse_stmt_without_recovery(true, ForceCollect::No) {
// Consume statements for as long as possible.
Ok(Some(stmt)) => {
stmts.push(stmt);
}
Ok(None) => {
- *self = start_snapshot;
+ self.restore_snapshot(start_snapshot);
break;
}
// We couldn't parse either yet another statement missing it's
// enclosing block nor the next arm's pattern or closing brace.
- Err(mut stmt_err) => {
+ Err(stmt_err) => {
stmt_err.cancel();
- *self = start_snapshot;
+ self.restore_snapshot(start_snapshot);
break;
}
}
}
pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
+ fn check_let_expr(expr: &Expr) -> (bool, bool) {
+ match expr.kind {
+ ExprKind::Binary(_, ref lhs, ref rhs) => {
+ let lhs_rslt = check_let_expr(lhs);
+ let rhs_rslt = check_let_expr(rhs);
+ (lhs_rslt.0 || rhs_rslt.0, false)
+ }
+ ExprKind::Let(..) => (true, true),
+ _ => (false, true),
+ }
+ }
let attrs = self.parse_outer_attributes()?;
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
let lo = this.token.span;
- let pat = this.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
+ let pat = this.parse_pat_allow_top_alt(
+ None,
+ RecoverComma::Yes,
+ RecoverColon::Yes,
+ CommaRecoveryMode::EitherTupleOrPipe,
+ )?;
let guard = if this.eat_keyword(kw::If) {
let if_span = this.prev_token.span;
let cond = this.parse_expr()?;
- if let ExprKind::Let(..) = cond.kind {
- // Remove the last feature gating of a `let` expression since it's stable.
- this.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
+ let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
+ if has_let_expr {
+ if does_not_have_bin_op {
+ // Remove the last feature gating of a `let` expression since it's stable.
+ this.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
+ }
let span = if_span.to(cond.span);
this.sess.gated_spans.gate(sym::if_let_guard, span);
}
None
};
let arrow_span = this.token.span;
- this.expect(&token::FatArrow)?;
+ if let Err(mut err) = this.expect(&token::FatArrow) {
+ // We might have a `=>` -> `=` or `->` typo (issue #89396).
+ if TokenKind::FatArrow
+ .similar_tokens()
+ .map_or(false, |similar_tokens| similar_tokens.contains(&this.token.kind))
+ {
+ err.span_suggestion(
+ this.token.span,
+ "try using a fat arrow here",
+ "=>".to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ err.emit();
+ this.bump();
+ } else {
+ return Err(err);
+ }
+ }
let arm_start_span = this.token.span;
let expr = this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| {
.emit();
}
- /// Precondition: already parsed the '{'.
- pub(super) fn parse_struct_expr(
+ pub(super) fn parse_struct_fields(
&mut self,
- qself: Option<ast::QSelf>,
pth: ast::Path,
- attrs: AttrVec,
recover: bool,
- ) -> PResult<'a, P<Expr>> {
+ close_delim: token::DelimToken,
+ ) -> PResult<'a, (Vec<ExprField>, ast::StructRest, bool)> {
let mut fields = Vec::new();
let mut base = ast::StructRest::None;
let mut recover_async = false;
- let mut async_block_err = |e: &mut DiagnosticBuilder<'_>, span: Span| {
+ let mut async_block_err = |e: &mut Diagnostic, span: Span| {
recover_async = true;
e.span_label(span, "`async` blocks are only allowed in Rust 2018 or later");
- e.help(&format!("set `edition = \"{}\"` in `Cargo.toml`", LATEST_STABLE_EDITION));
- e.note("for more on editions, read https://doc.rust-lang.org/edition-guide");
+ e.help_use_latest_edition();
};
- while self.token != token::CloseDelim(token::Brace) {
+ while self.token != token::CloseDelim(close_delim) {
if self.eat(&token::DotDot) {
let exp_span = self.prev_token.span;
// We permit `.. }` on the left-hand side of a destructuring assignment.
- if self.check(&token::CloseDelim(token::Brace)) {
- self.sess.gated_spans.gate(sym::destructuring_assignment, self.prev_token.span);
+ if self.check(&token::CloseDelim(close_delim)) {
base = ast::StructRest::Rest(self.prev_token.span.shrink_to_hi());
break;
}
}
};
- match self.expect_one_of(&[token::Comma], &[token::CloseDelim(token::Brace)]) {
+ match self.expect_one_of(&[token::Comma], &[token::CloseDelim(close_delim)]) {
Ok(_) => {
if let Some(f) = parsed_field.or(recovery_field) {
// Only include the field if there's no parse error for the field name.
}
}
}
+ Ok((fields, base, recover_async))
+ }
- let span = pth.span.to(self.token.span);
+ /// Precondition: already parsed the '{'.
+ pub(super) fn parse_struct_expr(
+ &mut self,
+ qself: Option<ast::QSelf>,
+ pth: ast::Path,
+ attrs: AttrVec,
+ recover: bool,
+ ) -> PResult<'a, P<Expr>> {
+ let lo = pth.span;
+ let (fields, base, recover_async) =
+ self.parse_struct_fields(pth.clone(), recover, token::Brace)?;
+ let span = lo.to(self.token.span);
self.expect(&token::CloseDelim(token::Brace))?;
let expr = if recover_async {
ExprKind::Err