blob: 60b080175552071f896b14a7bf963785609b86fe [file]
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{BinOpKind, Expr, ExprKind, token};
use rustc_ast_pretty::pprust;
use rustc_errors::{Applicability, Diag, PResult};
use rustc_span::{Span, Spanned, respan, sym};
use crate::parser::Parser;
use crate::{diagnostics, exp};
impl<'a> Parser<'a> {
/// Recover from a binary operator after complete statement expression as in `{ 4 } / 2`.
pub(super) fn recover_from_bin_op_after_complete_stmt_expr(&self, lhs: &Expr) -> bool {
use BinOpKind::*;
// Starting point: We've just parsed a *complete* stmt expr (e.g., a block like `{ 4 }`).
let Some(op) = AssocOp::from_token(&self.token) else { return false };
// We've now encountered a token that could(!) be interpreted as a binary operator. This
// could mean that the user intended to write a binary operation where the left operand is
// block-like. In that case they would need to parenthesize the LHS or the entire operation
// (e.g., `{ 4 } / 2` -> `({ 4 }) / 2` or `({ 4 } / 2)`).
if op_can_continue_stmt_expr_unambiguously(op) {
// We know that the token (e.g., `/`) can't possibly begin a new statement or pattern.
// Instead of letting the stmt/pat parser emit a generic & rather confusing diagnostic,
// let's emit a more targeted one, and continue parsing the expression.
self.dcx().emit_err(diagnostics::FoundExprWouldBeStmt {
span: self.token.span,
token: pprust::token_to_string(&self.token),
suggestion: diagnostics::ExprParenthesesNeeded::surrounding(lhs.span),
});
return true;
}
// The token can begin a new statement or pattern; this means we're on the happy path!
// Still, the user might've meant to write a bin op here but we can't tell at this stage of
// compilation. So if it's a "common lookalike" let's proactively register it somewhere for
// later stages of compilation to potentially retrieve (e.g., in typeck).
//
// We've left out `BitAnd` because guessing its 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.
if let AssocOp::Binary(Add | And | BitOr | Mul | Or | Sub) = op {
let sp = self.psess.source_map().start_point(self.token.span);
self.psess
.complete_stmt_exprs_before_bin_op_lookalike
.borrow_mut()
.insert(sp, lhs.span);
}
false
}
/// Recover from alphabetic logic operators `and` and `or` as found in e.g., Python and PHP.
pub(super) fn recover_from_alpha_logic_op(&self) -> Option<Spanned<AssocOp>> {
if self.may_recover()
&& let Some(ident) = self.token.non_raw_ident()
{
let (op, sub): (_, fn(_) -> _) = match ident.name {
sym::and => (BinOpKind::And, diagnostics::InvalidLogicalOperatorSub::Conjunction),
sym::or => (BinOpKind::Or, diagnostics::InvalidLogicalOperatorSub::Disjunction),
_ => return None,
};
self.dcx().emit_err(diagnostics::InvalidLogicalOperator {
span: self.token.span,
incorrect: ident.name,
sub: sub(self.token.span),
});
Some(respan(self.token.span, AssocOp::Binary(op)))
} else {
None
}
}
/// Reject `...` being used as an expression operator.
pub(super) fn reject_dotdotdot_expr_op(&self) {
if self.token == token::DotDotDot {
self.dcx().emit_err(diagnostics::DotDotDotExprOp { span: self.token.span });
}
}
/// Reject `<-` being used as an expression operator.
pub(super) fn reject_larrow_expr_op(&self) {
if self.token == token::LArrow {
self.dcx().emit_err(diagnostics::LArrowExprOp { span: self.token.span });
}
}
/// Recover from strict equality operators `===` and `!==` as found in e.g., JS and PHP.
pub(super) fn recover_from_strict_eq_op(&mut self, op: Spanned<AssocOp>) {
if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node
&& self.token == token::Eq
&& self.prev_token.span.hi() == self.token.span.lo()
{
let sp = op.span.to(self.token.span);
let sugg = bop.as_str().into();
let invalid = format!("{sugg}=");
self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
span: sp,
invalid: invalid.clone(),
sub: diagnostics::InvalidComparisonOperatorSub::Correctable {
span: sp,
invalid,
correct: sugg,
},
});
self.bump();
}
}
/// Recover from inequality operator `<>` ("diamond") as found in e.g., PHP.
pub(super) fn recover_from_diamond_ne_op(&mut self) {
if let (token::Lt, token::Gt) = (self.prev_token.kind, self.token.kind)
&& self.prev_token.span.hi() == self.token.span.lo()
{
let sp = self.prev_token.span.to(self.token.span);
self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
span: sp,
invalid: "<>".into(),
sub: diagnostics::InvalidComparisonOperatorSub::Correctable {
span: sp,
invalid: "<>".into(),
correct: "!=".into(),
},
});
self.bump();
}
}
/// Recover from comparison operator `<=>` ("spaceship") as found in e.g., C++.
pub(super) fn recover_from_spaceship_cmp_op(&mut self) {
if let (token::Le, token::Gt) = (self.prev_token.kind, self.token.kind)
&& self.prev_token.span.hi() == self.token.span.lo()
{
let sp = self.prev_token.span.to(self.token.span);
self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
span: sp,
invalid: "<=>".into(),
sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp),
});
self.bump();
}
}
/// Recover from postfix increment operator `++` as found in many C-style languages.
pub(super) fn recover_from_postfix_inc_op(
&mut self,
lhs: &Expr,
starts_stmt: bool,
) -> PResult<'a, ()> {
if let (token::Plus, token::Plus) = (self.prev_token.kind, self.token.kind)
&& self.prev_token.span.hi() == self.token.span.lo()
{
let op_span = self.prev_token.span.to(self.token.span);
self.bump(); // eat the second `+`
Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Inc, UnaryFixity::Post, op_span))
} else {
Ok(())
}
}
/// Recover from postfix decrement operator `--` as found in many C-style languages.
pub(super) fn recover_from_postfix_dec_op(
&mut self,
lhs: &Expr,
starts_stmt: bool,
) -> PResult<'a, ()> {
if let (token::Minus, token::Minus) = (self.prev_token.kind, self.token.kind)
&& self.prev_token.span.hi() == self.token.span.lo()
&& !self.look_ahead(1, |tok| tok.can_begin_expr())
{
let op_span = self.prev_token.span.to(self.token.span);
self.bump(); // eat the second `-`
Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Dec, UnaryFixity::Post, op_span))
} else {
Ok(())
}
}
/// Report increment operator `++` & decrement operator `--` as found in many C-style languages.
pub(super) fn report_inc_dec_op(
&mut self,
base: &Expr,
starts_stmt: bool,
op: IncOrDec,
fixity: UnaryFixity,
op_span: Span,
) -> Diag<'a> {
// FIXME: Don't return an error diag, emit the diag here *and* return a new expr of the form
// `$base += 1` / `$base -= 1` (taking `base: Expr` by value) for *proper* recovery.
// (Just emitting the diag would be insufficient since callers would most likely just
// use `$base` as the recovered AST node which would lead to annoying follow-up diags
// like "variable doesn't need to be mutable" getting emitted in some cases.)
let mut err = {
let fixity = match fixity {
UnaryFixity::Pre => "prefix",
UnaryFixity::Post => "postfix",
};
let op = match op {
IncOrDec::Inc => "increment",
IncOrDec::Dec => "decrement",
};
self.dcx()
.struct_span_err(op_span, format!("Rust has no {fixity} {op} operator"))
.with_span_label(op_span, format!("not a valid {fixity} operator"))
};
let op = match op {
IncOrDec::Inc => "+= 1",
IncOrDec::Dec => "-= 1",
};
let (pre_span, post_span) = match fixity {
UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()),
UnaryFixity::Post => (base.span.shrink_to_lo(), op_span),
};
if starts_stmt {
let mut patches = Vec::new();
if !pre_span.is_empty() {
patches.push((pre_span, String::new()));
}
patches.push((post_span, format!(" {op}")));
err.multipart_suggestion(
format!("use `{op}` instead"),
patches,
Applicability::MachineApplicable,
);
} else {
let Ok(base_src) = self.span_to_snippet(base.span) else {
err.help(format!("use `{op}` instead"));
return err;
};
match fixity {
UnaryFixity::Pre => {
err.multipart_suggestion(
format!("use `{op}` instead"),
vec![(pre_span, "{ ".into()), (post_span, format!(" {op}; {base_src} }}"))],
Applicability::MachineApplicable,
);
}
UnaryFixity::Post => {
// won't suggest since we can not handle the precedences
// for example: `a + b++` has been parsed (a + b)++ and we can not suggest here
if !matches!(base.kind, ExprKind::Binary(..)) {
let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" };
err.multipart_suggestion(
format!("use `{op}` instead"),
vec![
(pre_span, format!("{{ let {tmp_var} = ")),
(post_span, format!("; {base_src} {op}; {tmp_var} }}")),
],
Applicability::HasPlaceholders,
);
}
}
}
}
err
}
/// Recover from array expressions as found in C like `{0, 1, 2, 3}`.
pub(super) fn recover_from_c_array(&mut self, lo: Span) -> Option<Box<Expr>> {
if !self.may_recover()
|| self.token.kind != token::OpenBrace
|| self.look_ahead(1, |t| !matches!(t.kind, token::Literal(_)))
|| self.look_ahead(2, |t| t != &token::Comma)
|| self.look_ahead(3, |t| !t.can_begin_expr())
{
return None;
}
let mut snapshot = self.create_snapshot_for_diagnostic();
match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) {
Ok(arr) => {
let guar = self.dcx().emit_err(diagnostics::ArrayBracketsInsteadOfBraces {
span: arr.span,
sub: diagnostics::ArrayBracketsInsteadOfBracesSugg {
left: lo,
right: snapshot.prev_token.span,
},
});
self.restore_snapshot(snapshot);
Some(self.mk_expr_err(arr.span, guar))
}
Err(e) => {
e.cancel();
None
}
}
}
}
/// Whether this operator could be used to follow a complete statement expression unambiguously
/// during parse error recovery.
pub(in crate::parser) fn op_can_continue_stmt_expr_unambiguously(op: AssocOp) -> bool {
// NOTE: At the time of writing, it's only safe to return `true` for tokens that don't share a
// prefix with statements or patterns. For context, statement lists and match arm bodies
// are (the only two) places that use `Restriction::STMT_EXPR` (in the happy path).
use BinOpKind::*;
match op {
AssocOp::Assign | AssocOp::AssignOp(_) | AssocOp::Cast => true,
AssocOp::Binary(bin_op) => match bin_op {
| BitXor | Div | Ge | Gt | Le | Rem | Shr => true,
| Add // unambiguous but would inhibit our recovery from unary plus
| And // `&&` starts repeated borrow (e.g., `&&x`)
| BitAnd // `&` starts borrow (e.g., `&x`)
| BitOr // `|` starts closure (e.g., `|_| ()`) or leading vert (e.g., `| Some(_)`)
| Eq | Ne // unambiguous but would inhibit our recovery from bad struct literals
| Lt // `<` starts qualified path (e.g., `<T as X>::P`)
| Mul // `*` starts unary deref (e.g., `*x`, `*x = 0`)
| Or // `||` starts parameterless closure (e.g. `|| 0`)
| Shl // `<<` starts repeated qualified path (e.g., `<<T>::Q as X>::P`)
| Sub => false, // `-` starts unary negation (e.g, `-x`)
},
AssocOp::Range(_) => false, // `..`/`..=` start ranges w/o lower bound (e.g., `..`, `..=1`)
}
}
#[derive(Copy, Clone)]
pub(super) enum IncOrDec {
Inc,
Dec,
}
#[derive(Copy, Clone)]
pub(super) enum UnaryFixity {
Pre,
Post,
}