about summary refs log tree commit diff
path: root/src/libsyntax/parse/parser
diff options
context:
space:
mode:
authorMazdak Farrokhzad <twingoow@gmail.com>2019-08-29 05:32:48 +0200
committerGitHub <noreply@github.com>2019-08-29 05:32:48 +0200
commit52c3846d51b8efabebb02bd2587a87905e068290 (patch)
treed34b77611677dd3f0e9081a4417262ff3abd1307 /src/libsyntax/parse/parser
parenteb4ac32c5944e5c690f8731a8b25eaae8cbad0a7 (diff)
parent42e895d4d99ec7724f3efd632f52170f3f99a5aa (diff)
downloadrust-52c3846d51b8efabebb02bd2587a87905e068290.tar.gz
rust-52c3846d51b8efabebb02bd2587a87905e068290.zip
Rollup merge of #63945 - Centril:recover-mut-pat, r=estebank
Recover `mut $pat` and other improvements

- Recover on e.g. `mut Foo(x, y)` and suggest `Foo(mut x, mut y)`. Fixes https://github.com/rust-lang/rust/issues/63764.
- Recover on e.g. `let mut mut x;`
- Recover on e.g. `let keyword` and `let keyword(...)`.
- Cleanups in `token.rs` with `fn is_non_raw_ident_where` and friends.
Diffstat (limited to 'src/libsyntax/parse/parser')
-rw-r--r--src/libsyntax/parse/parser/pat.rs155
-rw-r--r--src/libsyntax/parse/parser/path.rs2
2 files changed, 127 insertions, 30 deletions
diff --git a/src/libsyntax/parse/parser/pat.rs b/src/libsyntax/parse/parser/pat.rs
index 4c6b6ce38f6..823f880337d 100644
--- a/src/libsyntax/parse/parser/pat.rs
+++ b/src/libsyntax/parse/parser/pat.rs
@@ -4,6 +4,7 @@ use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
 use crate::ptr::P;
 use crate::ast::{self, Attribute, Pat, PatKind, FieldPat, RangeEnd, RangeSyntax, Mac};
 use crate::ast::{BindingMode, Ident, Mutability, Path, QSelf, Expr, ExprKind};
+use crate::mut_visit::{noop_visit_pat, MutVisitor};
 use crate::parse::token::{self};
 use crate::print::pprust;
 use crate::source_map::{respan, Span, Spanned};
@@ -273,7 +274,7 @@ impl<'a> Parser<'a> {
                 // Parse _
                 PatKind::Wild
             } else if self.eat_keyword(kw::Mut) {
-                self.recover_pat_ident_mut_first()?
+                self.parse_pat_ident_mut()?
             } else if self.eat_keyword(kw::Ref) {
                 // Parse ref ident @ pat / ref mut ident @ pat
                 let mutbl = self.parse_mutability();
@@ -281,13 +282,12 @@ impl<'a> Parser<'a> {
             } else if self.eat_keyword(kw::Box) {
                 // Parse `box pat`
                 PatKind::Box(self.parse_pat_with_range_pat(false, None)?)
-            } else if self.token.is_ident() && !self.token.is_reserved_ident() &&
-                      self.parse_as_ident() {
+            } else if self.can_be_ident_pat() {
                 // Parse `ident @ pat`
                 // This can give false positives and parse nullary enums,
                 // they are dealt with later in resolve.
                 self.parse_pat_ident(BindingMode::ByValue(Mutability::Immutable))?
-            } else if self.token.is_path_start() {
+            } else if self.is_start_of_pat_with_path() {
                 // Parse pattern starting with a path
                 let (qself, path) = if self.eat_lt() {
                     // Parse a qualified path
@@ -384,24 +384,108 @@ impl<'a> Parser<'a> {
         })
     }
 
+    /// Parse a mutable binding with the `mut` token already eaten.
+    fn parse_pat_ident_mut(&mut self) -> PResult<'a, PatKind> {
+        let mut_span = self.prev_span;
+
+        if self.eat_keyword(kw::Ref) {
+            return self.recover_mut_ref_ident(mut_span)
+        }
+
+        self.recover_additional_muts();
+
+        // Make sure we don't allow e.g. `let mut $p;` where `$p:pat`.
+        if let token::Interpolated(ref nt) = self.token.kind {
+             if let token::NtPat(_) = **nt {
+                 self.expected_ident_found().emit();
+             }
+        }
+
+        // Parse the pattern we hope to be an identifier.
+        let mut pat = self.parse_pat(Some("identifier"))?;
+
+        // Add `mut` to any binding in the parsed pattern.
+        let changed_any_binding = Self::make_all_value_bindings_mutable(&mut pat);
+
+        // Unwrap; If we don't have `mut $ident`, error.
+        let pat = pat.into_inner();
+        match &pat.node {
+            PatKind::Ident(..) => {}
+            _ => self.ban_mut_general_pat(mut_span, &pat, changed_any_binding),
+        }
+
+        Ok(pat.node)
+    }
+
     /// Recover on `mut ref? ident @ pat` and suggest
     /// that the order of `mut` and `ref` is incorrect.
-    fn recover_pat_ident_mut_first(&mut self) -> PResult<'a, PatKind> {
-        let mutref_span = self.prev_span.to(self.token.span);
-        let binding_mode = if self.eat_keyword(kw::Ref) {
-            self.struct_span_err(mutref_span, "the order of `mut` and `ref` is incorrect")
-                .span_suggestion(
-                    mutref_span,
-                    "try switching the order",
-                    "ref mut".into(),
-                    Applicability::MachineApplicable
-                )
-                .emit();
-            BindingMode::ByRef(Mutability::Mutable)
+    fn recover_mut_ref_ident(&mut self, lo: Span) -> PResult<'a, PatKind> {
+        let mutref_span = lo.to(self.prev_span);
+        self.struct_span_err(mutref_span, "the order of `mut` and `ref` is incorrect")
+            .span_suggestion(
+                mutref_span,
+                "try switching the order",
+                "ref mut".into(),
+                Applicability::MachineApplicable
+            )
+            .emit();
+
+        self.parse_pat_ident(BindingMode::ByRef(Mutability::Mutable))
+    }
+
+    /// Turn all by-value immutable bindings in a pattern into mutable bindings.
+    /// Returns `true` if any change was made.
+    fn make_all_value_bindings_mutable(pat: &mut P<Pat>) -> bool {
+        struct AddMut(bool);
+        impl MutVisitor for AddMut {
+            fn visit_pat(&mut self, pat: &mut P<Pat>) {
+                if let PatKind::Ident(BindingMode::ByValue(ref mut m @ Mutability::Immutable), ..)
+                    = pat.node
+                {
+                    *m = Mutability::Mutable;
+                    self.0 = true;
+                }
+                noop_visit_pat(pat, self);
+            }
+        }
+
+        let mut add_mut = AddMut(false);
+        add_mut.visit_pat(pat);
+        add_mut.0
+    }
+
+    /// Error on `mut $pat` where `$pat` is not an ident.
+    fn ban_mut_general_pat(&self, lo: Span, pat: &Pat, changed_any_binding: bool) {
+        let span = lo.to(pat.span);
+        let fix = pprust::pat_to_string(&pat);
+        let (problem, suggestion) = if changed_any_binding {
+            ("`mut` must be attached to each individual binding", "add `mut` to each binding")
         } else {
-            BindingMode::ByValue(Mutability::Mutable)
+            ("`mut` must be followed by a named binding", "remove the `mut` prefix")
         };
-        self.parse_pat_ident(binding_mode)
+        self.struct_span_err(span, problem)
+            .span_suggestion(span, suggestion, fix, Applicability::MachineApplicable)
+            .note("`mut` may be followed by `variable` and `variable @ pattern`")
+            .emit()
+    }
+
+    /// Eat any extraneous `mut`s and error + recover if we ate any.
+    fn recover_additional_muts(&mut self) {
+        let lo = self.token.span;
+        while self.eat_keyword(kw::Mut) {}
+        if lo == self.token.span {
+            return;
+        }
+
+        let span = lo.to(self.prev_span);
+        self.struct_span_err(span, "`mut` on a binding may not be repeated")
+            .span_suggestion(
+                span,
+                "remove the additional `mut`s",
+                String::new(),
+                Applicability::MachineApplicable,
+            )
+            .emit();
     }
 
     /// Parse macro invocation
@@ -479,17 +563,6 @@ impl<'a> Parser<'a> {
         Err(err)
     }
 
-    // Helper function to decide whether to parse as ident binding
-    // or to try to do something more complex like range patterns.
-    fn parse_as_ident(&mut self) -> bool {
-        self.look_ahead(1, |t| match t.kind {
-            token::OpenDelim(token::Paren) | token::OpenDelim(token::Brace) |
-            token::DotDotDot | token::DotDotEq | token::DotDot |
-            token::ModSep | token::Not => false,
-            _ => true,
-        })
-    }
-
     /// Is the current token suitable as the start of a range patterns end?
     fn is_pat_range_end_start(&self) -> bool {
         self.token.is_path_start() // e.g. `MY_CONST`;
@@ -563,6 +636,30 @@ impl<'a> Parser<'a> {
         }
     }
 
+    /// Is this the start of a pattern beginning with a path?
+    fn is_start_of_pat_with_path(&mut self) -> bool {
+        self.check_path()
+        // Just for recovery (see `can_be_ident`).
+        || self.token.is_ident() && !self.token.is_bool_lit() && !self.token.is_keyword(kw::In)
+    }
+
+    /// Would `parse_pat_ident` be appropriate here?
+    fn can_be_ident_pat(&mut self) -> bool {
+        self.check_ident()
+        && !self.token.is_bool_lit() // Avoid `true` or `false` as a binding as it is a literal.
+        && !self.token.is_path_segment_keyword() // Avoid e.g. `Self` as it is a path.
+        // Avoid `in`. Due to recovery in the list parser this messes with `for ( $pat in $expr )`.
+        && !self.token.is_keyword(kw::In)
+        && self.look_ahead(1, |t| match t.kind { // Try to do something more complex?
+            token::OpenDelim(token::Paren) // A tuple struct pattern.
+            | token::OpenDelim(token::Brace) // A struct pattern.
+            | token::DotDotDot | token::DotDotEq | token::DotDot // A range pattern.
+            | token::ModSep // A tuple / struct variant pattern.
+            | token::Not => false, // A macro expanding to a pattern.
+            _ => true,
+        })
+    }
+
     /// Parses `ident` or `ident @ pat`.
     /// Used by the copy foo and ref foo patterns to give a good
     /// error message when parsing mistakes like `ref foo(a, b)`.
diff --git a/src/libsyntax/parse/parser/path.rs b/src/libsyntax/parse/parser/path.rs
index 3eb4d45045a..d4b13cc2e01 100644
--- a/src/libsyntax/parse/parser/path.rs
+++ b/src/libsyntax/parse/parser/path.rs
@@ -423,7 +423,7 @@ impl<'a> Parser<'a> {
                     // FIXME(const_generics): to distinguish between idents for types and consts,
                     // we should introduce a GenericArg::Ident in the AST and distinguish when
                     // lowering to the HIR. For now, idents for const args are not permitted.
-                    if self.token.is_keyword(kw::True) || self.token.is_keyword(kw::False) {
+                    if self.token.is_bool_lit() {
                         self.parse_literal_maybe_minus()?
                     } else {
                         return Err(