Skip to main content

rustc_parse/parser/expr/
diagnostics.rs

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};
6
7use crate::parser::Parser;
8use crate::{diagnostics, exp};
9
10impl<'a> Parser<'a> {
11    /// Recover from a binary operator after complete statement expression as in `{ 4 } / 2`.
12    pub(super) fn recover_from_bin_op_after_complete_stmt_expr(&self, lhs: &Expr) -> bool {
13        use BinOpKind::*;
14
15        // Starting point: We've just parsed a *complete* stmt expr (e.g., a block like `{ 4 }`).
16
17        let Some(op) = AssocOp::from_token(&self.token) else { return false };
18
19        // 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)`).
23
24        if 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.
28
29            self.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            });
34            return true;
35        }
36
37        // 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.
45        if let AssocOp::Binary(Add | And | BitOr | Mul | Or | Sub) = op {
46            let sp = self.psess.source_map().start_point(self.token.span);
47            self.psess
48                .complete_stmt_exprs_before_bin_op_lookalike
49                .borrow_mut()
50                .insert(sp, lhs.span);
51        }
52
53        false
54    }
55
56    /// Recover from alphabetic logic operators `and` and `or` as found in e.g., Python and PHP.
57    pub(super) fn recover_from_alpha_logic_op(&self) -> Option<Spanned<AssocOp>> {
58        if self.may_recover()
59            && let Some(ident) = self.token.non_raw_ident()
60        {
61            let (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            };
66
67            self.dcx().emit_err(diagnostics::InvalidLogicalOperator {
68                span: self.token.span,
69                incorrect: ident.name,
70                sub: sub(self.token.span),
71            });
72
73            Some(respan(self.token.span, AssocOp::Binary(op)))
74        } else {
75            None
76        }
77    }
78
79    /// Reject `...` being used as an expression operator.
80    pub(super) fn reject_dotdotdot_expr_op(&self) {
81        if self.token == token::DotDotDot {
82            self.dcx().emit_err(diagnostics::DotDotDotExprOp { span: self.token.span });
83        }
84    }
85
86    /// Reject `<-` being used as an expression operator.
87    pub(super) fn reject_larrow_expr_op(&self) {
88        if self.token == token::LArrow {
89            self.dcx().emit_err(diagnostics::LArrowExprOp { span: self.token.span });
90        }
91    }
92
93    /// Recover from strict equality operators `===` and `!==` as found in e.g., JS and PHP.
94    pub(super) fn recover_from_strict_eq_op(&mut self, op: Spanned<AssocOp>) {
95        if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node
96            && self.token == token::Eq
97            && self.prev_token.span.hi() == self.token.span.lo()
98        {
99            let sp = op.span.to(self.token.span);
100            let sugg = bop.as_str().into();
101            let invalid = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}=", sugg))
    })format!("{sugg}=");
102            self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
103                span: sp,
104                invalid: invalid.clone(),
105                sub: diagnostics::InvalidComparisonOperatorSub::Correctable {
106                    span: sp,
107                    invalid,
108                    correct: sugg,
109                },
110            });
111            self.bump();
112        }
113    }
114
115    /// Recover from inequality operator `<>` ("diamond") as found in e.g., PHP.
116    pub(super) fn recover_from_diamond_ne_op(&mut self) {
117        if let (token::Lt, token::Gt) = (self.prev_token.kind, self.token.kind)
118            && self.prev_token.span.hi() == self.token.span.lo()
119        {
120            let sp = self.prev_token.span.to(self.token.span);
121            self.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            });
130            self.bump();
131        }
132    }
133
134    /// Recover from comparison operator `<=>` ("spaceship") as found in e.g., C++.
135    pub(super) fn recover_from_spaceship_cmp_op(&mut self) {
136        if let (token::Le, token::Gt) = (self.prev_token.kind, self.token.kind)
137            && self.prev_token.span.hi() == self.token.span.lo()
138        {
139            let sp = self.prev_token.span.to(self.token.span);
140            self.dcx().emit_err(diagnostics::InvalidComparisonOperator {
141                span: sp,
142                invalid: "<=>".into(),
143                sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp),
144            });
145            self.bump();
146        }
147    }
148
149    /// Recover from postfix increment operator `++` as found in many C-style languages.
150    pub(super) fn recover_from_postfix_inc_op(
151        &mut self,
152        lhs: &Expr,
153        starts_stmt: bool,
154    ) -> PResult<'a, ()> {
155        if let (token::Plus, token::Plus) = (self.prev_token.kind, self.token.kind)
156            && self.prev_token.span.hi() == self.token.span.lo()
157        {
158            let op_span = self.prev_token.span.to(self.token.span);
159            self.bump(); // eat the second `+`
160            Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Inc, UnaryFixity::Post, op_span))
161        } else {
162            Ok(())
163        }
164    }
165
166    /// Recover from postfix decrement operator `--` as found in many C-style languages.
167    pub(super) fn recover_from_postfix_dec_op(
168        &mut self,
169        lhs: &Expr,
170        starts_stmt: bool,
171    ) -> PResult<'a, ()> {
172        if 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        {
176            let op_span = self.prev_token.span.to(self.token.span);
177            self.bump(); // eat the second `-`
178            Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Dec, UnaryFixity::Post, op_span))
179        } else {
180            Ok(())
181        }
182    }
183
184    /// Report increment operator `++` & decrement operator `--` as found in many C-style languages.
185    pub(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.)
198
199        let mut err = {
200            let fixity = match fixity {
201                UnaryFixity::Pre => "prefix",
202                UnaryFixity::Post => "postfix",
203            };
204            let op = match op {
205                IncOrDec::Inc => "increment",
206                IncOrDec::Dec => "decrement",
207            };
208            self.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        };
212
213        let op = match op {
214            IncOrDec::Inc => "+= 1",
215            IncOrDec::Dec => "-= 1",
216        };
217        let (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        };
221
222        if starts_stmt {
223            let mut patches = Vec::new();
224            if !pre_span.is_empty() {
225                patches.push((pre_span, String::new()));
226            }
227            patches.push((post_span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" {0}", op))
    })format!(" {op}")));
228            err.multipart_suggestion(
229                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("use `{0}` instead", op))
    })format!("use `{op}` instead"),
230                patches,
231                Applicability::MachineApplicable,
232            );
233        } else {
234            let Ok(base_src) = self.span_to_snippet(base.span) else {
235                err.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("use `{0}` instead", op))
    })format!("use `{op}` instead"));
236                return err;
237            };
238            match fixity {
239                UnaryFixity::Pre => {
240                    err.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
249                    if !#[allow(non_exhaustive_omitted_patterns)] match base.kind {
    ExprKind::Binary(..) => true,
    _ => false,
}matches!(base.kind, ExprKind::Binary(..)) {
250                        let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" };
251                        err.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        }
263        err
264    }
265
266    /// Recover from array expressions as found in C like `{0, 1, 2, 3}`.
267    pub(super) fn recover_from_c_array(&mut self, lo: Span) -> Option<Box<Expr>> {
268        if !self.may_recover()
269            || self.token.kind != token::OpenBrace
270            || 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        {
274            return None;
275        }
276
277        let mut snapshot = self.create_snapshot_for_diagnostic();
278        match 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)) {
279            Ok(arr) => {
280                let 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                });
287
288                self.restore_snapshot(snapshot);
289                Some(self.mk_expr_err(arr.span, guar))
290            }
291            Err(e) => {
292                e.cancel();
293                None
294            }
295        }
296    }
297}
298
299/// Whether this operator could be used to follow a complete statement expression unambiguously
300/// during parse error recovery.
301pub(in crate::parser) fn op_can_continue_stmt_expr_unambiguously(op: AssocOp) -> bool {
302    // NOTE: At the time of writing, it's only safe to return `true` for tokens that don't share a
303    //       prefix with statements or patterns. For context, statement lists and match arm bodies
304    //       are (the only two) places that use `Restriction::STMT_EXPR` (in the happy path).
305
306    use BinOpKind::*;
307    match op {
308        AssocOp::Assign | AssocOp::AssignOp(_) | AssocOp::Cast => true,
309        AssocOp::Binary(bin_op) => match bin_op {
310            | BitXor | Div | Ge | Gt | Le | Rem | Shr => true,
311            | Add // unambiguous but would inhibit our recovery from unary plus
312            | And // `&&` starts repeated borrow (e.g., `&&x`)
313            | BitAnd // `&` starts borrow (e.g., `&x`)
314            | BitOr // `|` starts closure (e.g., `|_| ()`) or leading vert (e.g., `| Some(_)`)
315            | Eq | Ne // unambiguous but would inhibit our recovery from bad struct literals
316            | Lt // `<` starts qualified path (e.g., `<T as X>::P`)
317            | Mul // `*` starts unary deref (e.g., `*x`, `*x = 0`)
318            | Or // `||` starts parameterless closure (e.g. `|| 0`)
319            | Shl // `<<` starts repeated qualified path (e.g., `<<T>::Q as X>::P`)
320            | Sub => false, // `-` starts unary negation (e.g, `-x`)
321        },
322        AssocOp::Range(_) => false, // `..`/`..=` start ranges w/o lower bound (e.g., `..`, `..=1`)
323    }
324}
325
326#[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)]
327pub(super) enum IncOrDec {
328    Inc,
329    Dec,
330}
331
332#[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)]
333pub(super) enum UnaryFixity {
334    Pre,
335    Post,
336}