From d6e34ad108dc998e9f9d872e807e221c0df4f24b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Thu, 15 Jul 2021 08:36:19 -0700
Subject: [PATCH] When recovering from a `:` in a pattern, use adequate AST
 pattern

---
 compiler/rustc_parse/src/parser/pat.rs        | 103 +++++++++++++++---
 .../issues/issue-87086-colon-path-sep.rs      |  31 ++++--
 .../issues/issue-87086-colon-path-sep.stderr  |  60 ++++++----
 3 files changed, 150 insertions(+), 44 deletions(-)

diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index bb3947bb47a25..c1f5c569cf09f 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -3,13 +3,17 @@ use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
 use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
 use rustc_ast::ptr::P;
 use rustc_ast::token;
-use rustc_ast::{self as ast, AttrVec, Attribute, MacCall, Pat, PatField, PatKind, RangeEnd};
-use rustc_ast::{BindingMode, Expr, ExprKind, Mutability, Path, QSelf, RangeSyntax};
+use rustc_ast::{
+    self as ast, AttrVec, Attribute, BindingMode, Expr, ExprKind, MacCall, Mutability, Pat,
+    PatField, PatKind, Path, PathSegment, QSelf, RangeEnd, RangeSyntax,
+};
 use rustc_ast_pretty::pprust;
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, PResult};
 use rustc_span::source_map::{respan, Span, Spanned};
 use rustc_span::symbol::{kw, sym, Ident};
 
+use std::mem::take;
+
 type Expected = Option<&'static str>;
 
 /// `Expected` for function and lambda parameter patterns.
@@ -101,11 +105,8 @@ impl<'a> Parser<'a> {
             let mut first_pat = first_pat;
 
             if let (RecoverColon::Yes, token::Colon) = (ra, &self.token.kind) {
-                if matches!(
-                    first_pat.kind,
-                    PatKind::Ident(BindingMode::ByValue(Mutability::Not), _, None)
-                        | PatKind::Path(..)
-                ) && self.look_ahead(1, |token| token.is_ident() && !token.is_reserved_ident())
+                if matches!(first_pat.kind, PatKind::Ident(_, _, None) | PatKind::Path(..))
+                    && self.look_ahead(1, |token| token.is_ident() && !token.is_reserved_ident())
                 {
                     // The pattern looks like it might be a path with a `::` -> `:` typo:
                     // `match foo { bar:baz => {} }`
@@ -126,17 +127,87 @@ impl<'a> Parser<'a> {
                                     err.cancel();
                                     *self = snapshot;
                                 }
-                                Ok(pat) => {
+                                Ok(mut pat) => {
                                     // We've parsed the rest of the pattern.
-                                    err.span_suggestion(
-                                        span,
-                                        "maybe write a path separator here",
-                                        "::".to_string(),
-                                        Applicability::MachineApplicable,
-                                    );
+                                    let new_span = first_pat.span.to(pat.span);
+                                    let mut show_sugg = false;
+                                    match &mut pat.kind {
+                                        PatKind::Struct(qself @ None, path, ..)
+                                        | PatKind::TupleStruct(qself @ None, path, _)
+                                        | PatKind::Path(qself @ None, path) => {
+                                            match &first_pat.kind {
+                                                PatKind::Ident(_, ident, _) => {
+                                                    path.segments.insert(
+                                                        0,
+                                                        PathSegment::from_ident(ident.clone()),
+                                                    );
+                                                    path.span = new_span;
+                                                    show_sugg = true;
+                                                    first_pat = pat;
+                                                }
+                                                PatKind::Path(old_qself, old_path) => {
+                                                    path.segments = old_path
+                                                        .segments
+                                                        .iter()
+                                                        .cloned()
+                                                        .chain(take(&mut path.segments))
+                                                        .collect();
+                                                    path.span = new_span;
+                                                    *qself = old_qself.clone();
+                                                    first_pat = pat;
+                                                    show_sugg = true;
+                                                }
+                                                _ => {}
+                                            }
+                                        }
+                                        PatKind::Ident(
+                                            BindingMode::ByValue(Mutability::Not),
+                                            ident,
+                                            None,
+                                        ) => match &first_pat.kind {
+                                            PatKind::Ident(_, old_ident, _) => {
+                                                let path = PatKind::Path(
+                                                    None,
+                                                    Path {
+                                                        span: new_span,
+                                                        segments: vec![
+                                                            PathSegment::from_ident(
+                                                                old_ident.clone(),
+                                                            ),
+                                                            PathSegment::from_ident(ident.clone()),
+                                                        ],
+                                                        tokens: None,
+                                                    },
+                                                );
+                                                first_pat = self.mk_pat(new_span, path);
+                                                show_sugg = true;
+                                            }
+                                            PatKind::Path(old_qself, old_path) => {
+                                                let mut segments = old_path.segments.clone();
+                                                segments
+                                                    .push(PathSegment::from_ident(ident.clone()));
+                                                let path = PatKind::Path(
+                                                    old_qself.clone(),
+                                                    Path { span: new_span, segments, tokens: None },
+                                                );
+                                                first_pat = self.mk_pat(new_span, path);
+                                                show_sugg = true;
+                                            }
+                                            _ => {}
+                                        },
+                                        _ => {}
+                                    }
+                                    if show_sugg {
+                                        err.span_suggestion(
+                                            span,
+                                            "maybe write a path separator here",
+                                            "::".to_string(),
+                                            Applicability::MachineApplicable,
+                                        );
+                                    } else {
+                                        first_pat = self.mk_pat(new_span, PatKind::Wild);
+                                    }
                                     err.emit();
-                                    first_pat =
-                                        self.mk_pat(first_pat.span.to(pat.span), PatKind::Wild);
                                 }
                             }
                         }
diff --git a/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs b/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs
index 4ee0b2054ff77..0b7b67496d6f3 100644
--- a/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs
+++ b/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs
@@ -1,11 +1,15 @@
 // Tests that a suggestion is issued if the user wrote a colon instead of
 // a path separator in a match arm.
 
-enum Foo {
-    Bar,
-    Baz,
+mod qux {
+    pub enum Foo {
+        Bar,
+        Baz,
+    }
 }
 
+use qux::Foo;
+
 fn f() -> Foo { Foo::Bar }
 
 fn g1() {
@@ -16,24 +20,24 @@ fn g1() {
         _ => {}
     }
     match f() {
-        Foo::Bar:Baz => {}
+        qux::Foo:Bar => {}
         //~^ ERROR: expected one of
         //~| HELP: maybe write a path separator here
         _ => {}
     }
     match f() {
-        Foo:Bar::Baz => {}
+        qux:Foo::Baz => {}
         //~^ ERROR: expected one of
         //~| HELP: maybe write a path separator here
         _ => {}
     }
     match f() {
-        Foo: Bar::Baz if true => {}
+        qux: Foo::Baz if true => {}
         //~^ ERROR: expected one of
         //~| HELP: maybe write a path separator here
         _ => {}
     }
-    if let Bar:Baz = f() {
+    if let Foo:Bar = f() {
     //~^ ERROR: expected one of
     //~| HELP: maybe write a path separator here
     }
@@ -41,16 +45,18 @@ fn g1() {
 
 fn g1_neg() {
     match f() {
-        ref Foo: Bar::Baz => {}
+        ref qux: Foo::Baz => {}
         //~^ ERROR: expected one of
+        //~| HELP: maybe write a path separator here
         _ => {}
     }
 }
 
 fn g2_neg() {
     match f() {
-        mut Foo: Bar::Baz => {}
+        mut qux: Foo::Baz => {}
         //~^ ERROR: expected one of
+        //~| HELP: maybe write a path separator here
         _ => {}
     }
 }
@@ -62,5 +68,12 @@ fn main() {
         Foo:Bar::Baz => {}
         //~^ ERROR: expected one of
         //~| HELP: maybe write a path separator here
+        //~| ERROR: failed to resolve: `Bar` is a variant, not a module
+    }
+    match myfoo {
+        Foo::Bar => {}
+        Foo:Bar => {}
+        //~^ ERROR: expected one of
+        //~| HELP: maybe write a path separator here
     }
 }
diff --git a/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr b/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr
index 8f93661a62646..2050a16beb349 100644
--- a/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr
+++ b/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr
@@ -1,5 +1,5 @@
 error: expected one of `@` or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:13:12
+  --> $DIR/issue-87086-colon-path-sep.rs:17:12
    |
 LL |         Foo:Bar => {}
    |            ^
@@ -8,55 +8,61 @@ LL |         Foo:Bar => {}
    |            help: maybe write a path separator here: `::`
 
 error: expected one of `!`, `(`, `...`, `..=`, `..`, `::`, `{`, or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:19:17
+  --> $DIR/issue-87086-colon-path-sep.rs:23:17
    |
-LL |         Foo::Bar:Baz => {}
+LL |         qux::Foo:Bar => {}
    |                 ^
    |                 |
    |                 expected one of 8 possible tokens
    |                 help: maybe write a path separator here: `::`
 
 error: expected one of `@` or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:25:12
+  --> $DIR/issue-87086-colon-path-sep.rs:29:12
    |
-LL |         Foo:Bar::Baz => {}
+LL |         qux:Foo::Baz => {}
    |            ^
    |            |
    |            expected one of `@` or `|`
    |            help: maybe write a path separator here: `::`
 
 error: expected one of `@` or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:31:12
+  --> $DIR/issue-87086-colon-path-sep.rs:35:12
    |
-LL |         Foo: Bar::Baz if true => {}
+LL |         qux: Foo::Baz if true => {}
    |            ^
    |            |
    |            expected one of `@` or `|`
    |            help: maybe write a path separator here: `::`
 
 error: expected one of `@` or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:36:15
+  --> $DIR/issue-87086-colon-path-sep.rs:40:15
    |
-LL |     if let Bar:Baz = f() {
+LL |     if let Foo:Bar = f() {
    |               ^
    |               |
    |               expected one of `@` or `|`
    |               help: maybe write a path separator here: `::`
 
-error: expected one of `=>`, `@`, `if`, or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:44:16
+error: expected one of `@` or `|`, found `:`
+  --> $DIR/issue-87086-colon-path-sep.rs:48:16
    |
-LL |         ref Foo: Bar::Baz => {}
-   |                ^ expected one of `=>`, `@`, `if`, or `|`
+LL |         ref qux: Foo::Baz => {}
+   |                ^
+   |                |
+   |                expected one of `@` or `|`
+   |                help: maybe write a path separator here: `::`
 
-error: expected one of `=>`, `@`, `if`, or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:52:16
+error: expected one of `@` or `|`, found `:`
+  --> $DIR/issue-87086-colon-path-sep.rs:57:16
    |
-LL |         mut Foo: Bar::Baz => {}
-   |                ^ expected one of `=>`, `@`, `if`, or `|`
+LL |         mut qux: Foo::Baz => {}
+   |                ^
+   |                |
+   |                expected one of `@` or `|`
+   |                help: maybe write a path separator here: `::`
 
 error: expected one of `@` or `|`, found `:`
-  --> $DIR/issue-87086-colon-path-sep.rs:62:12
+  --> $DIR/issue-87086-colon-path-sep.rs:68:12
    |
 LL |         Foo:Bar::Baz => {}
    |            ^
@@ -64,5 +70,21 @@ LL |         Foo:Bar::Baz => {}
    |            expected one of `@` or `|`
    |            help: maybe write a path separator here: `::`
 
-error: aborting due to 8 previous errors
+error: expected one of `@` or `|`, found `:`
+  --> $DIR/issue-87086-colon-path-sep.rs:75:12
+   |
+LL |         Foo:Bar => {}
+   |            ^
+   |            |
+   |            expected one of `@` or `|`
+   |            help: maybe write a path separator here: `::`
+
+error[E0433]: failed to resolve: `Bar` is a variant, not a module
+  --> $DIR/issue-87086-colon-path-sep.rs:68:13
+   |
+LL |         Foo:Bar::Baz => {}
+   |             ^^^ `Bar` is a variant, not a module
+
+error: aborting due to 10 previous errors
 
+For more information about this error, try `rustc --explain E0433`.