]> git.lizzy.rs Git - rust.git/blobdiff - compiler/rustc_parse/src/parser/diagnostics.rs
Emit structured suggestions for field accesses too
[rust.git] / compiler / rustc_parse / src / parser / diagnostics.rs
index 21d5bec65f03a8f57d2a19d087eb17f08836d315..a8a1842f3771dc802b07eede0a120644551c2df0 100644 (file)
@@ -5,6 +5,7 @@
     SemiColonMode, SeqSep, TokenExpectType, TokenType,
 };
 
+use crate::lexer::UnmatchedBrace;
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Lit, LitKind, TokenKind};
@@ -21,6 +22,7 @@
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, Ident};
 use rustc_span::{MultiSpan, Span, SpanSnippetError, DUMMY_SP};
+use std::ops::{Deref, DerefMut};
 
 use std::mem::take;
 
@@ -154,6 +156,79 @@ pub fn no(&self) -> bool {
     }
 }
 
+/// Information for emitting suggestions and recovering from
+/// C-style `i++`, `--i`, etc.
+#[derive(Debug, Copy, Clone)]
+struct IncDecRecovery {
+    /// This increment/decrement is not a subexpression.
+    standalone: bool,
+    /// Is this an increment or decrement?
+    op: IncOrDec,
+    /// Is this pre- or postfix?
+    fixity: UnaryFixity,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum IncOrDec {
+    Inc,
+    // FIXME: `i--` recovery isn't implemented yet
+    #[allow(dead_code)]
+    Dec,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum UnaryFixity {
+    Pre,
+    Post,
+}
+
+impl IncOrDec {
+    fn chr(&self) -> char {
+        match self {
+            Self::Inc => '+',
+            Self::Dec => '-',
+        }
+    }
+
+    fn name(&self) -> &'static str {
+        match self {
+            Self::Inc => "increment",
+            Self::Dec => "decrement",
+        }
+    }
+}
+
+impl std::fmt::Display for UnaryFixity {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Pre => write!(f, "prefix"),
+            Self::Post => write!(f, "postfix"),
+        }
+    }
+}
+
+// SnapshotParser is used to create a snapshot of the parser
+// without causing duplicate errors being emitted when the `Parser`
+// is dropped.
+pub(super) struct SnapshotParser<'a> {
+    parser: Parser<'a>,
+    unclosed_delims: Vec<UnmatchedBrace>,
+}
+
+impl<'a> Deref for SnapshotParser<'a> {
+    type Target = Parser<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.parser
+    }
+}
+
+impl<'a> DerefMut for SnapshotParser<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.parser
+    }
+}
+
 impl<'a> Parser<'a> {
     pub(super) fn span_err<S: Into<MultiSpan>>(
         &self,
@@ -179,6 +254,25 @@ pub(super) fn diagnostic(&self) -> &'a Handler {
         &self.sess.span_diagnostic
     }
 
+    /// Relace `self` with `snapshot.parser` and extend `unclosed_delims` with `snapshot.unclosed_delims`.
+    /// This is to avoid losing unclosed delims errors `create_snapshot_for_diagnostic` clears.
+    pub(super) fn restore_snapshot(&mut self, snapshot: SnapshotParser<'a>) {
+        *self = snapshot.parser;
+        self.unclosed_delims.extend(snapshot.unclosed_delims.clone());
+    }
+
+    /// Create a snapshot of the `Parser`.
+    pub(super) fn create_snapshot_for_diagnostic(&self) -> SnapshotParser<'a> {
+        let mut snapshot = self.clone();
+        let unclosed_delims = self.unclosed_delims.clone();
+        // Clear `unclosed_delims` in snapshot to avoid
+        // duplicate errors being emitted when the `Parser`
+        // is dropped (which may or may not happen, depending
+        // if the parsing the snapshot is created for is successful)
+        snapshot.unclosed_delims.clear();
+        SnapshotParser { parser: snapshot, unclosed_delims }
+    }
+
     pub(super) fn span_to_snippet(&self, span: Span) -> Result<String, SpanSnippetError> {
         self.sess.source_map().span_to_snippet(span)
     }
@@ -327,8 +421,8 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
                 expect.clone()
             };
             (
-                format!("expected one of {}, found {}", expect, actual),
-                (self.prev_token.span.shrink_to_hi(), format!("expected one of {}", short_expect)),
+                format!("expected one of {expect}, found {actual}"),
+                (self.prev_token.span.shrink_to_hi(), format!("expected one of {short_expect}")),
             )
         } else if expected.is_empty() {
             (
@@ -337,8 +431,8 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
             )
         } else {
             (
-                format!("expected {}, found {}", expect, actual),
-                (self.prev_token.span.shrink_to_hi(), format!("expected {}", expect)),
+                format!("expected {expect}, found {actual}"),
+                (self.prev_token.span.shrink_to_hi(), format!("expected {expect}")),
             )
         };
         self.last_unexpected_token_span = Some(self.token.span);
@@ -421,7 +515,7 @@ fn check_too_many_raw_str_terminators(&mut self, err: &mut Diagnostic) -> bool {
                     String::new(),
                     Applicability::MachineApplicable,
                 );
-                err.note(&format!("the raw string started with {} `#`s", n_hashes));
+                err.note(&format!("the raw string started with {n_hashes} `#`s"));
                 true
             }
             _ => false,
@@ -438,7 +532,7 @@ pub fn maybe_suggest_struct_literal(
             // fn foo() -> Foo {
             //     field: value,
             // }
-            let mut snapshot = self.clone();
+            let mut snapshot = self.create_snapshot_for_diagnostic();
             let path =
                 Path { segments: vec![], span: self.prev_token.span.shrink_to_lo(), tokens: None };
             let struct_expr = snapshot.parse_struct_expr(None, path, AttrVec::new(), false);
@@ -464,7 +558,7 @@ pub fn maybe_suggest_struct_literal(
                             Applicability::MaybeIncorrect,
                         )
                         .emit();
-                    *self = snapshot;
+                    self.restore_snapshot(snapshot);
                     let mut tail = self.mk_block(
                         vec![self.mk_stmt_err(expr.span)],
                         s,
@@ -678,7 +772,7 @@ pub(super) fn check_trailing_angle_brackets(
     /// angle brackets.
     pub(super) fn check_turbofish_missing_angle_brackets(&mut self, segment: &mut PathSegment) {
         if token::ModSep == self.token.kind && segment.args.is_none() {
-            let snapshot = self.clone();
+            let snapshot = self.create_snapshot_for_diagnostic();
             self.bump();
             let lo = self.token.span;
             match self.parse_angle_args(None) {
@@ -712,14 +806,14 @@ pub(super) fn check_turbofish_missing_angle_brackets(&mut self, segment: &mut Pa
                         .emit();
                     } else {
                         // This doesn't look like an invalid turbofish, can't recover parse state.
-                        *self = snapshot;
+                        self.restore_snapshot(snapshot);
                     }
                 }
                 Err(err) => {
                     // We couldn't parse generic parameters, unlikely to be a turbofish. Rely on
                     // generic parse error instead.
                     err.cancel();
-                    *self = snapshot;
+                    self.restore_snapshot(snapshot);
                 }
             }
         }
@@ -825,7 +919,7 @@ fn attempt_chained_comparison_suggestion(
                 // `x == y < z`
                 (BinOpKind::Eq, AssocOp::Less | AssocOp::LessEqual | AssocOp::Greater | AssocOp::GreaterEqual) => {
                     // Consume `z`/outer-op-rhs.
-                    let snapshot = self.clone();
+                    let snapshot = self.create_snapshot_for_diagnostic();
                     match self.parse_expr() {
                         Ok(r2) => {
                             // We are sure that outer-op-rhs could be consumed, the suggestion is
@@ -835,14 +929,14 @@ fn attempt_chained_comparison_suggestion(
                         }
                         Err(expr_err) => {
                             expr_err.cancel();
-                            *self = snapshot;
+                            self.restore_snapshot(snapshot);
                             false
                         }
                     }
                 }
                 // `x > y == z`
                 (BinOpKind::Lt | BinOpKind::Le | BinOpKind::Gt | BinOpKind::Ge, AssocOp::Equal) => {
-                    let snapshot = self.clone();
+                    let snapshot = self.create_snapshot_for_diagnostic();
                     // At this point it is always valid to enclose the lhs in parentheses, no
                     // further checks are necessary.
                     match self.parse_expr() {
@@ -852,7 +946,7 @@ fn attempt_chained_comparison_suggestion(
                         }
                         Err(expr_err) => {
                             expr_err.cancel();
-                            *self = snapshot;
+                            self.restore_snapshot(snapshot);
                             false
                         }
                     }
@@ -917,7 +1011,7 @@ pub(super) fn check_no_chained_comparison(
                     || outer_op.node == AssocOp::Greater
                 {
                     if outer_op.node == AssocOp::Less {
-                        let snapshot = self.clone();
+                        let snapshot = self.create_snapshot_for_diagnostic();
                         self.bump();
                         // So far we have parsed `foo<bar<`, consume the rest of the type args.
                         let modifiers =
@@ -929,7 +1023,7 @@ pub(super) fn check_no_chained_comparison(
                         {
                             // We don't have `foo< bar >(` or `foo< bar >::`, so we rewind the
                             // parser and bail out.
-                            *self = snapshot.clone();
+                            self.restore_snapshot(snapshot);
                         }
                     }
                     return if token::ModSep == self.token.kind {
@@ -937,7 +1031,7 @@ pub(super) fn check_no_chained_comparison(
                         // `foo< bar >::`
                         suggest(&mut err);
 
-                        let snapshot = self.clone();
+                        let snapshot = self.create_snapshot_for_diagnostic();
                         self.bump(); // `::`
 
                         // Consume the rest of the likely `foo<bar>::new()` or return at `foo<bar>`.
@@ -954,7 +1048,7 @@ pub(super) fn check_no_chained_comparison(
                                 expr_err.cancel();
                                 // Not entirely sure now, but we bubble the error up with the
                                 // suggestion.
-                                *self = snapshot;
+                                self.restore_snapshot(snapshot);
                                 Err(err)
                             }
                         }
@@ -1008,7 +1102,7 @@ pub(super) fn check_no_chained_comparison(
     }
 
     fn consume_fn_args(&mut self) -> Result<(), ()> {
-        let snapshot = self.clone();
+        let snapshot = self.create_snapshot_for_diagnostic();
         self.bump(); // `(`
 
         // Consume the fn call arguments.
@@ -1018,7 +1112,7 @@ fn consume_fn_args(&mut self) -> Result<(), ()> {
 
         if self.token.kind == token::Eof {
             // Not entirely sure that what we consumed were fn arguments, rollback.
-            *self = snapshot;
+            self.restore_snapshot(snapshot);
             Err(())
         } else {
             // 99% certain that the suggestion is correct, continue parsing.
@@ -1124,6 +1218,122 @@ pub(super) fn maybe_recover_from_bad_type_plus(
         Ok(())
     }
 
+    pub(super) fn maybe_recover_from_prefix_increment(
+        &mut self,
+        operand_expr: P<Expr>,
+        op_span: Span,
+        prev_is_semi: bool,
+    ) -> PResult<'a, P<Expr>> {
+        let kind = IncDecRecovery {
+            standalone: prev_is_semi,
+            op: IncOrDec::Inc,
+            fixity: UnaryFixity::Pre,
+        };
+
+        self.recover_from_inc_dec(operand_expr, kind, op_span)
+    }
+
+    pub(super) fn maybe_recover_from_postfix_increment(
+        &mut self,
+        operand_expr: P<Expr>,
+        op_span: Span,
+        prev_is_semi: bool,
+    ) -> PResult<'a, P<Expr>> {
+        let kind = IncDecRecovery {
+            standalone: prev_is_semi,
+            op: IncOrDec::Inc,
+            fixity: UnaryFixity::Post,
+        };
+
+        self.recover_from_inc_dec(operand_expr, kind, op_span)
+    }
+
+    fn recover_from_inc_dec(
+        &mut self,
+        base: P<Expr>,
+        kind: IncDecRecovery,
+        op_span: Span,
+    ) -> PResult<'a, P<Expr>> {
+        let mut err = self.struct_span_err(
+            op_span,
+            &format!("Rust has no {} {} operator", kind.fixity, kind.op.name()),
+        );
+        err.span_label(op_span, &format!("not a valid {} operator", kind.fixity));
+
+        let help_base_case = |mut err: DiagnosticBuilder<'_>, base| {
+            err.help(&format!("use `{}= 1` instead", kind.op.chr()));
+            err.emit();
+            Ok(base)
+        };
+
+        // (pre, post)
+        let spans = match kind.fixity {
+            UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()),
+            UnaryFixity::Post => (base.span.shrink_to_lo(), op_span),
+        };
+
+        if kind.standalone {
+            self.inc_dec_standalone_recovery(err, kind, spans)
+        } else {
+            let Ok(base_src) = self.span_to_snippet(base.span)
+                else { return help_base_case(err, base) };
+            match kind.fixity {
+                UnaryFixity::Pre => self.prefix_inc_dec_suggest(base_src, err, kind, spans),
+                UnaryFixity::Post => self.postfix_inc_dec_suggest(base_src, err, kind, spans),
+            }
+        }
+    }
+
+    fn prefix_inc_dec_suggest(
+        &mut self,
+        base_src: String,
+        mut err: DiagnosticBuilder<'a>,
+        kind: IncDecRecovery,
+        (pre_span, post_span): (Span, Span),
+    ) -> PResult<'a, P<Expr>> {
+        err.multipart_suggestion(
+            &format!("use `{}= 1` instead", kind.op.chr()),
+            vec![
+                (pre_span, "{ ".to_string()),
+                (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)),
+            ],
+            Applicability::MachineApplicable,
+        );
+        Err(err)
+    }
+
+    fn postfix_inc_dec_suggest(
+        &mut self,
+        base_src: String,
+        mut err: DiagnosticBuilder<'a>,
+        kind: IncDecRecovery,
+        (pre_span, post_span): (Span, Span),
+    ) -> PResult<'a, P<Expr>> {
+        err.multipart_suggestion(
+            &format!("use `{}= 1` instead", kind.op.chr()),
+            vec![
+                (pre_span, "{ let tmp = ".to_string()),
+                (post_span, format!("; {} {}= 1; tmp }}", base_src, kind.op.chr())),
+            ],
+            Applicability::MachineApplicable,
+        );
+        Err(err)
+    }
+
+    fn inc_dec_standalone_recovery(
+        &mut self,
+        mut err: DiagnosticBuilder<'a>,
+        kind: IncDecRecovery,
+        (pre_span, post_span): (Span, Span),
+    ) -> PResult<'a, P<Expr>> {
+        err.multipart_suggestion(
+            &format!("use `{}= 1` instead", kind.op.chr()),
+            vec![(pre_span, String::new()), (post_span, format!(" {}= 1", kind.op.chr()))],
+            Applicability::MachineApplicable,
+        );
+        Err(err)
+    }
+
     /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`.
     /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem`
     /// tail, and combines them into a `<Ty>::AssocItem` expression/pattern/type.
@@ -1191,7 +1401,7 @@ pub fn maybe_consume_incorrect_semicolon(&mut self, items: &[P<Item>]) -> bool {
                     _ => None,
                 };
                 if let Some(name) = previous_item_kind_name {
-                    err.help(&format!("{} declarations are not followed by a semicolon", name));
+                    err.help(&format!("{name} declarations are not followed by a semicolon"));
                 }
             }
             err.emit();
@@ -1226,12 +1436,12 @@ pub(super) fn unexpected_try_recover(
             "expected `{}`, found {}",
             token_str,
             match (&self.token.kind, self.subparser_name) {
-                (token::Eof, Some(origin)) => format!("end of {}", origin),
+                (token::Eof, Some(origin)) => format!("end of {origin}"),
                 _ => this_token_str,
             },
         );
         let mut err = self.struct_span_err(sp, &msg);
-        let label_exp = format!("expected `{}`", token_str);
+        let label_exp = format!("expected `{token_str}`");
         match self.recover_closing_delimiter(&[t.clone()], err) {
             Err(e) => err = e,
             Ok(recovered) => {
@@ -1368,7 +1578,7 @@ pub(super) fn try_macro_suggestion(&mut self) -> PResult<'a, P<Expr>> {
                     Applicability::MachineApplicable,
                 );
             }
-            err.span_suggestion(lo.shrink_to_lo(), &format!("{}you can still access the deprecated `try!()` macro using the \"raw identifier\" syntax", prefix), "r#".to_string(), Applicability::MachineApplicable);
+            err.span_suggestion(lo.shrink_to_lo(), &format!("{prefix}you can still access the deprecated `try!()` macro using the \"raw identifier\" syntax"), "r#".to_string(), Applicability::MachineApplicable);
             err.emit();
             Ok(self.mk_expr_err(lo.to(hi)))
         } else {
@@ -1504,7 +1714,7 @@ pub(super) fn recover_closing_delimiter(
                 delim.retain(|c| c != '`');
                 err.span_suggestion_short(
                     self.prev_token.span.shrink_to_hi(),
-                    &format!("`{}` may belong here", delim),
+                    &format!("`{delim}` may belong here"),
                     delim,
                     Applicability::MaybeIncorrect,
                 );
@@ -1698,7 +1908,7 @@ pub(super) fn parameter_without_type(
                                 (
                                     ident,
                                     "self: ".to_string(),
-                                    format!("{}: &{}TypeName", ident, mutab),
+                                    format!("{ident}: &{mutab}TypeName"),
                                     "_: ".to_string(),
                                     pat.span.shrink_to_lo(),
                                     pat.span,
@@ -1826,7 +2036,7 @@ pub(super) fn expected_expression_found(&self) -> DiagnosticBuilder<'a, ErrorGua
         let (span, msg) = match (&self.token.kind, self.subparser_name) {
             (&token::Eof, Some(origin)) => {
                 let sp = self.sess.source_map().next_point(self.prev_token.span);
-                (sp, format!("expected expression, found end of {}", origin))
+                (sp, format!("expected expression, found end of {origin}"))
             }
             _ => (
                 self.token.span,
@@ -1959,12 +2169,12 @@ pub fn handle_unambiguous_unbraced_const_arg(&mut self) -> PResult<'a, P<Expr>>
     }
 
     fn recover_const_param_decl(&mut self, ty_generics: Option<&Generics>) -> Option<GenericArg> {
-        let snapshot = self.clone();
+        let snapshot = self.create_snapshot_for_diagnostic();
         let param = match self.parse_const_param(vec![]) {
             Ok(param) => param,
             Err(err) => {
                 err.cancel();
-                *self = snapshot;
+                self.restore_snapshot(snapshot);
                 return None;
             }
         };
@@ -1975,8 +2185,8 @@ fn recover_const_param_decl(&mut self, ty_generics: Option<&Generics>) -> Option
             (ty_generics, self.sess.source_map().span_to_snippet(param.span()))
         {
             let (span, sugg) = match &generics.params[..] {
-                [] => (generics.span, format!("<{}>", snippet)),
-                [.., generic] => (generic.span().shrink_to_hi(), format!(", {}", snippet)),
+                [] => (generics.span, format!("<{snippet}>")),
+                [.., generic] => (generic.span().shrink_to_hi(), format!(", {snippet}")),
             };
             err.multipart_suggestion(
                 "`const` parameters must be declared for the `impl`",
@@ -2056,7 +2266,7 @@ pub fn recover_const_arg(
             // We perform these checks and early return to avoid taking a snapshot unnecessarily.
             return Err(err);
         }
-        let snapshot = self.clone();
+        let snapshot = self.create_snapshot_for_diagnostic();
         if is_op_or_dot {
             self.bump();
         }
@@ -2101,7 +2311,7 @@ pub fn recover_const_arg(
                 err.cancel();
             }
         }
-        *self = snapshot;
+        self.restore_snapshot(snapshot);
         Err(err)
     }
 
@@ -2161,7 +2371,7 @@ pub(super) fn incorrect_move_async_order_found(
         let span = self.token.span;
         // We only emit "unexpected `:`" error here if we can successfully parse the
         // whole pattern correctly in that case.
-        let snapshot = self.clone();
+        let snapshot = self.create_snapshot_for_diagnostic();
 
         // Create error for "unexpected `:`".
         match self.expected_one_of_not_found(&[], &[]) {
@@ -2173,7 +2383,7 @@ pub(super) fn incorrect_move_async_order_found(
                         // reasonable error.
                         inner_err.cancel();
                         err.cancel();
-                        *self = snapshot;
+                        self.restore_snapshot(snapshot);
                     }
                     Ok(mut pat) => {
                         // We've parsed the rest of the pattern.
@@ -2252,7 +2462,7 @@ pub(super) fn incorrect_move_async_order_found(
             }
             _ => {
                 // Carry on as if we had not done anything. This should be unreachable.
-                *self = snapshot;
+                self.restore_snapshot(snapshot);
             }
         };
         first_pat