1use rustc_ast::util::parser::AssocOp;
2use rustc_ast::{BinOpKind, Expr, ExprKind, token};
3use rustc_ast_pretty::pprust;
4use rustc_errors::{Applicability, Diag, PResult};
5use rustc_span::{Span, Spanned, respan, sym};
67use crate::parser::{Parser, SeqSep};
8use crate::{diagnostics, exp};
910impl<'a> Parser<'a> {
11/// Recover from a binary operator after complete statement expression as in `{ 4 } / 2`.
12pub(super) fn recover_from_bin_op_after_complete_stmt_expr(&self, lhs: &Expr) -> bool {
13use BinOpKind::*;
1415// Starting point: We've just parsed a *complete* stmt expr (e.g., a block like `{ 4 }`).
1617let Some(op) = AssocOp::from_token(&self.token) else { return false };
1819// We've now encountered a token that could(!) be interpreted as a binary operator. This
20 // could mean that the user intended to write a binary operation where the left operand is
21 // block-like. In that case they would need to parenthesize the LHS or the entire operation
22 // (e.g., `{ 4 } / 2` -> `({ 4 }) / 2` or `({ 4 } / 2)`).
2324if op_can_continue_stmt_expr_unambiguously(op) {
25// We know that the token (e.g., `/`) can't possibly begin a new statement or pattern.
26 // Instead of letting the stmt/pat parser emit a generic & rather confusing diagnostic,
27 // let's emit a more targeted one, and continue parsing the expression.
2829self.dcx().emit_err(diagnostics::FoundExprWouldBeStmt {
30 span: self.token.span,
31 token: pprust::token_to_string(&self.token),
32 suggestion: diagnostics::ExprParenthesesNeeded::surrounding(lhs.span),
33 });
34return true;
35 }
3637// The token can begin a new statement or pattern; this means we're on the happy path!
38 // Still, the user might've meant to write a bin op here but we can't tell at this stage of
39 // compilation. So if it's a "common lookalike" let's proactively register it somewhere for
40 // later stages of compilation to potentially retrieve (e.g., in typeck).
41 //
42 // We've left out `BitAnd` because guessing its intent is hard. We can make suggestions
43 // based on the assumption that double-refs are rarely intentional, and closures are
44 // distinct enough that they don't get mixed up with their return value.
45if let AssocOp::Binary(Add | And | BitOr | Mul | Or | Sub) = op {
46let sp = self.psess.source_map().start_point(self.token.span);
47self.psess
48 .complete_stmt_exprs_before_bin_op_lookalike
49 .borrow_mut()
50 .insert(sp, lhs.span);
51 }
5253false
54}
5556/// Recover from alphabetic logic operators `and` and `or` as found in e.g., Python and PHP.
57pub(super) fn recover_from_alpha_logic_op(&self) -> Option<Spanned<AssocOp>> {
58if self.may_recover()
59 && let Some(ident) = self.token.non_raw_ident()
60 {
61let (op, sub): (_, fn(_) -> _) = match ident.name {
62 sym::and => (BinOpKind::And, diagnostics::InvalidLogicalOperatorSub::Conjunction),
63 sym::or => (BinOpKind::Or, diagnostics::InvalidLogicalOperatorSub::Disjunction),
64_ => return None,
65 };
6667self.dcx().emit_err(diagnostics::InvalidLogicalOperator {
68 span: self.token.span,
69 incorrect: ident.name,
70 sub: sub(self.token.span),
71 });
7273Some(respan(self.token.span, AssocOp::Binary(op)))
74 } else {
75None76 }
77 }
7879/// Reject `...` being used as an expression operator.
80pub(super) fn reject_dotdotdot_expr_op(&self) {
81if self.token == token::DotDotDot {
82self.dcx().emit_err(diagnostics::DotDotDotExprOp { span: self.token.span });
83 }
84 }
8586/// Reject `<-` being used as an expression operator.
87pub(super) fn reject_larrow_expr_op(&self) {
88if self.token == token::LArrow {
89self.dcx().emit_err(diagnostics::LArrowExprOp { span: self.token.span });
90 }
91 }
9293/// Recover from strict equality operators `===` and `!==` as found in e.g., JS and PHP.
94pub(super) fn recover_from_strict_eq_op(&mut self, op: Spanned<AssocOp>) {
95if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node
96 && self.token == token::Eq97 && self.prev_token.span.hi() == self.token.span.lo()
98 {
99let sp = op.span.to(self.token.span);
100let sugg = bop.as_str().into();
101let invalid = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}=", sugg))
})format!("{sugg}=");
102self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
103 span: sp,
104 invalid: invalid.clone(),
105 sub: diagnostics::InvalidComparisonOperatorSub::Correctable {
106 span: sp,
107invalid,
108 correct: sugg,
109 },
110 });
111self.bump();
112 }
113 }
114115/// Recover from inequality operator `<>` ("diamond") as found in e.g., PHP.
116pub(super) fn recover_from_diamond_ne_op(&mut self) {
117if let (token::Lt, token::Gt) = (self.prev_token.kind, self.token.kind)
118 && self.prev_token.span.hi() == self.token.span.lo()
119 {
120let sp = self.prev_token.span.to(self.token.span);
121self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
122 span: sp,
123 invalid: "<>".into(),
124 sub: diagnostics::InvalidComparisonOperatorSub::Correctable {
125 span: sp,
126 invalid: "<>".into(),
127 correct: "!=".into(),
128 },
129 });
130self.bump();
131 }
132 }
133134/// Recover from comparison operator `<=>` ("spaceship") as found in e.g., C++.
135pub(super) fn recover_from_spaceship_cmp_op(&mut self) {
136if let (token::Le, token::Gt) = (self.prev_token.kind, self.token.kind)
137 && self.prev_token.span.hi() == self.token.span.lo()
138 {
139let sp = self.prev_token.span.to(self.token.span);
140self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
141 span: sp,
142 invalid: "<=>".into(),
143 sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp),
144 });
145self.bump();
146 }
147 }
148149/// Recover from postfix increment operator `++` as found in many C-style languages.
150pub(super) fn recover_from_postfix_inc_op(
151&mut self,
152 lhs: &Expr,
153 starts_stmt: bool,
154 ) -> PResult<'a, ()> {
155if let (token::Plus, token::Plus) = (self.prev_token.kind, self.token.kind)
156 && self.prev_token.span.hi() == self.token.span.lo()
157 {
158let op_span = self.prev_token.span.to(self.token.span);
159self.bump(); // eat the second `+`
160Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Inc, UnaryFixity::Post, op_span))
161 } else {
162Ok(())
163 }
164 }
165166/// Recover from postfix decrement operator `--` as found in many C-style languages.
167pub(super) fn recover_from_postfix_dec_op(
168&mut self,
169 lhs: &Expr,
170 starts_stmt: bool,
171 ) -> PResult<'a, ()> {
172if let (token::Minus, token::Minus) = (self.prev_token.kind, self.token.kind)
173 && self.prev_token.span.hi() == self.token.span.lo()
174 && !self.look_ahead(1, |tok| tok.can_begin_expr())
175 {
176let op_span = self.prev_token.span.to(self.token.span);
177self.bump(); // eat the second `-`
178Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Dec, UnaryFixity::Post, op_span))
179 } else {
180Ok(())
181 }
182 }
183184/// Report increment operator `++` & decrement operator `--` as found in many C-style languages.
185pub(super) fn report_inc_dec_op(
186&mut self,
187 base: &Expr,
188 starts_stmt: bool,
189 op: IncOrDec,
190 fixity: UnaryFixity,
191 op_span: Span,
192 ) -> Diag<'a> {
193// FIXME: Don't return an error diag, emit the diag here *and* return a new expr of the form
194 // `$base += 1` / `$base -= 1` (taking `base: Expr` by value) for *proper* recovery.
195 // (Just emitting the diag would be insufficient since callers would most likely just
196 // use `$base` as the recovered AST node which would lead to annoying follow-up diags
197 // like "variable doesn't need to be mutable" getting emitted in some cases.)
198199let mut err = {
200let fixity = match fixity {
201 UnaryFixity::Pre => "prefix",
202 UnaryFixity::Post => "postfix",
203 };
204let op = match op {
205 IncOrDec::Inc => "increment",
206 IncOrDec::Dec => "decrement",
207 };
208self.dcx()
209 .struct_span_err(op_span, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Rust has no {0} {1} operator",
fixity, op))
})format!("Rust has no {fixity} {op} operator"))
210 .with_span_label(op_span, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("not a valid {0} operator", fixity))
})format!("not a valid {fixity} operator"))
211 };
212213let op = match op {
214 IncOrDec::Inc => "+= 1",
215 IncOrDec::Dec => "-= 1",
216 };
217let (pre_span, post_span) = match fixity {
218 UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()),
219 UnaryFixity::Post => (base.span.shrink_to_lo(), op_span),
220 };
221222if starts_stmt {
223let mut patches = Vec::new();
224if !pre_span.is_empty() {
225patches.push((pre_span, String::new()));
226 }
227patches.push((post_span, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!(" {0}", op))
})format!(" {op}")));
228err.multipart_suggestion(
229::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use `{0}` instead", op))
})format!("use `{op}` instead"),
230patches,
231 Applicability::MachineApplicable,
232 );
233 } else {
234let Ok(base_src) = self.span_to_snippet(base.span) else {
235err.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use `{0}` instead", op))
})format!("use `{op}` instead"));
236return err;
237 };
238match fixity {
239 UnaryFixity::Pre => {
240err.multipart_suggestion(
241::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use `{0}` instead", op))
})format!("use `{op}` instead"),
242::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(pre_span, "{ ".into()),
(post_span,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!(" {0}; {1} }}", op,
base_src))
}))]))vec![(pre_span, "{ ".into()), (post_span, format!(" {op}; {base_src} }}"))],
243 Applicability::MachineApplicable,
244 );
245 }
246 UnaryFixity::Post => {
247// won't suggest since we can not handle the precedences
248 // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here
249if !#[allow(non_exhaustive_omitted_patterns)] match base.kind {
ExprKind::Binary(..) => true,
_ => false,
}matches!(base.kind, ExprKind::Binary(..)) {
250let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" };
251err.multipart_suggestion(
252::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use `{0}` instead", op))
})format!("use `{op}` instead"),
253::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(pre_span,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{{ let {0} = ", tmp_var))
})),
(post_span,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("; {0} {1}; {2} }}",
base_src, op, tmp_var))
}))]))vec![
254 (pre_span, format!("{{ let {tmp_var} = ")),
255 (post_span, format!("; {base_src} {op}; {tmp_var} }}")),
256 ],
257 Applicability::HasPlaceholders,
258 );
259 }
260 }
261 }
262 }
263err264 }
265266/// Recover from array expressions as found in C like `{0, 1, 2, 3}`.
267pub(super) fn recover_from_c_array(&mut self, lo: Span) -> Option<Box<Expr>> {
268if !self.may_recover()
269 || self.token.kind != token::OpenBrace270 || self.look_ahead(1, |t| !#[allow(non_exhaustive_omitted_patterns)] match t.kind {
token::Literal(_) => true,
_ => false,
}matches!(t.kind, token::Literal(_)))
271 || self.look_ahead(2, |t| t != &token::Comma)
272 || self.look_ahead(3, |t| !t.can_begin_expr())
273 {
274return None;
275 }
276277let mut snapshot = self.create_snapshot_for_diagnostic();
278match snapshot.parse_expr_array_or_repeat(crate::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::CloseBrace,
token_type: crate::parser::token_type::TokenType::CloseBrace,
}exp!(CloseBrace)) {
279Ok(arr) => {
280let guar = self.dcx().emit_err(diagnostics::ArrayBracketsInsteadOfBraces {
281 span: arr.span,
282 sub: diagnostics::ArrayBracketsInsteadOfBracesSugg {
283 left: lo,
284 right: snapshot.prev_token.span,
285 },
286 });
287288self.restore_snapshot(snapshot);
289Some(self.mk_expr_err(arr.span, guar))
290 }
291Err(e) => {
292e.cancel();
293None294 }
295 }
296 }
297298pub(super) fn recover_from_missing_semicolon_before_array(
299&self,
300 prev_span: Span,
301 open_delim_span: Span,
302 ) -> PResult<'a, ()> {
303if !self.may_recover()
304 || self.token != token::Comma305// Only make this suggestion if the `[` is not on the same line as the last token of
306 // the expression preceding it, which is a sign that it is not intended as an
307 // index expression.
308|| !self.psess.source_map().is_multiline(prev_span.until(open_delim_span))
309 {
310return Ok(());
311 }
312313let mut snapshot = self.create_snapshot_for_diagnostic();
314snapshot.bump();
315match snapshot.parse_seq_to_before_end(
316crate::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::CloseBracket,
token_type: crate::parser::token_type::TokenType::CloseBracket,
}exp!(CloseBracket),
317SeqSep::trailing_allowed(crate::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::Comma,
token_type: crate::parser::token_type::TokenType::Comma,
}exp!(Comma)),
318 |p| p.parse_expr(),
319 ) {
320Ok(_)
321// When the close delim is `)`, `token.kind` is expected to be `token::CloseParen`,
322 // but the actual `token.kind` is `token::CloseBracket`. This is because the
323 // `token.kind` of the close delim is treated as the same as that of the open delim
324 // in `TokenTreesReader::parse_token_tree`, even if the delimiters of them are
325 // different. Therefore, `token.kind` should not be compared here.
326if snapshot327 .span_to_snippet(snapshot.token.span)
328 .is_ok_and(|snippet| snippet == "]") =>
329 {
330// FIXME: This is way to assertive for a diagnostic with so many false positives!
331 // Just `expect(Comma)` and modify the diagnostic with a note that says sth.
332 // along the lines of "consider adding a `;` here if you meant the `[...]` to
333 // denote an array instead of an index".
334 // FIXME: In most cases there certainly isn't a "missing `;`". E.g., if we're nested
335 // in an expression like `(x[y, z])` or `match x[y, z] {}`. Requiring that
336 // we're not under `Restriction::STMT_EXPR` isn't sufficient by the way since
337 // we could still be part of a match arm body *and* it would prevent us from
338 // suggesting a `;` in cases like `let x = y[z, w];`.
339Err(self.dcx().create_err(crate::diagnostics::MissingSemicolonBeforeArray {
340 open_delim: open_delim_span,
341 semicolon: prev_span.shrink_to_hi(),
342 }))
343 }
344Ok(_) => Ok(()),
345Err(err) => {
346err.cancel();
347Ok(())
348 }
349 }
350 }
351}
352353/// Whether this operator could be used to follow a complete statement expression unambiguously
354/// during parse error recovery.
355pub(in crate::parser) fn op_can_continue_stmt_expr_unambiguously(op: AssocOp) -> bool {
356// NOTE: At the time of writing, it's only safe to return `true` for tokens that don't share a
357 // prefix with statements or patterns. For context, statement lists and match arm bodies
358 // are (the only two) places that use `Restriction::STMT_EXPR` (in the happy path).
359360use BinOpKind::*;
361match op {
362 AssocOp::Assign | AssocOp::AssignOp(_) | AssocOp::Cast => true,
363 AssocOp::Binary(bin_op) => match bin_op {
364 | BitXor | Div | Ge | Gt | Le | Rem | Shr => true,
365 | Add// unambiguous but would inhibit our recovery from unary plus
366| And// `&&` starts repeated borrow (e.g., `&&x`)
367| BitAnd// `&` starts borrow (e.g., `&x`)
368| BitOr// `|` starts closure (e.g., `|_| ()`) or leading vert (e.g., `| Some(_)`)
369| Eq | Ne// unambiguous but would inhibit our recovery from bad struct literals
370| Lt// `<` starts qualified path (e.g., `<T as X>::P`)
371| Mul// `*` starts unary deref (e.g., `*x`, `*x = 0`)
372| Or// `||` starts parameterless closure (e.g. `|| 0`)
373| Shl// `<<` starts repeated qualified path (e.g., `<<T>::Q as X>::P`)
374| Sub => false, // `-` starts unary negation (e.g, `-x`)
375},
376 AssocOp::Range(_) => false, // `..`/`..=` start ranges w/o lower bound (e.g., `..`, `..=1`)
377}
378}
379380#[derive(#[automatically_derived]impl ::core::marker::Copy for IncOrDec { }Copy, #[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
for IncOrDec {
}
#[automatically_derived]impl ::core::clone::Clone for IncOrDec {
#[inline]
fn clone(&self) -> Self { *self }
}Clone)]
381pub(super) enum IncOrDec {
382 Inc,
383 Dec,
384}
385386#[derive(#[automatically_derived]impl ::core::marker::Copy for UnaryFixity { }Copy, #[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
for UnaryFixity {
}
#[automatically_derived]impl ::core::clone::Clone for UnaryFixity {
#[inline]
fn clone(&self) -> Self { *self }
}Clone)]
387pub(super) enum UnaryFixity {
388 Pre,
389 Post,
390}