about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorTyler Mandry <tmandry@gmail.com>2019-10-14 17:52:42 -0700
committerGitHub <noreply@github.com>2019-10-14 17:52:42 -0700
commit1b182371e1502a1a3a32c8335be40835f50db93a (patch)
tree42d44aa7c0df30c59861eca54526a60737862c24 /src
parenta14e35f382825549a506eb5c187fa5d58622bb1c (diff)
parent16266a54058a71c943d064054bfe3a1b5704a444 (diff)
downloadrust-1b182371e1502a1a3a32c8335be40835f50db93a.tar.gz
rust-1b182371e1502a1a3a32c8335be40835f50db93a.zip
Rollup merge of #65410 - Centril:intersection-pat-recover, r=davidtwco,varkor
syntax: add parser recovery for intersection- / and-patterns `p1 @ p2`

Fixes https://github.com/rust-lang/rust/issues/65400.

The recovery comes in two flavors:

1. We know that `p2` is a binding so we can invert as `p2 @ p1`:

```rust
error: pattern on wrong side of `@`
  --> $DIR/intersection-patterns.rs:13:9
   |
LL |         Some(x) @ y => {}
   |         -------^^^-
   |         |         |
   |         |         binding on the right, should be to the left
   |         pattern on the left, should be to the right
   |         help: switch the order: `y @ Some(x)`
```

2. Otherwise we emit a generic diagnostic for the lack of support for intersection patterns:

```rust
error: left-hand side of `@` must be a binding
  --> $DIR/intersection-patterns.rs:23:9
   |
LL |         Some(x) @ Some(y) => {}
   |         -------^^^-------
   |         |         |
   |         |         also a pattern
   |         interpreted as a pattern, not a binding
   |
   = note: bindings are `x`, `mut x`, `ref x`, and `ref mut x`
```

For more on and-patterns, see e.g. https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching#and-pattern.

r? @davidtwco
cc @varkor @lzutao
Diffstat (limited to 'src')
-rw-r--r--src/libsyntax/parse/parser/pat.rs60
-rw-r--r--src/libsyntax/print/pprust.rs3
-rw-r--r--src/test/ui/parser/intersection-patterns.rs40
-rw-r--r--src/test/ui/parser/intersection-patterns.stderr33
4 files changed, 135 insertions, 1 deletions
diff --git a/src/libsyntax/parse/parser/pat.rs b/src/libsyntax/parse/parser/pat.rs
index 48f9e301610..e288346a329 100644
--- a/src/libsyntax/parse/parser/pat.rs
+++ b/src/libsyntax/parse/parser/pat.rs
@@ -367,6 +367,7 @@ impl<'a> Parser<'a> {
 
         let pat = self.mk_pat(lo.to(self.prev_span), pat);
         let pat = self.maybe_recover_from_bad_qpath(pat, true)?;
+        let pat = self.recover_intersection_pat(pat)?;
 
         if !allow_range_pat {
             self.ban_pat_range_if_ambiguous(&pat)?
@@ -375,6 +376,65 @@ impl<'a> Parser<'a> {
         Ok(pat)
     }
 
+    /// Try to recover the more general form `intersect ::= $pat_lhs @ $pat_rhs`.
+    ///
+    /// Allowed binding patterns generated by `binding ::= ref? mut? $ident @ $pat_rhs`
+    /// should already have been parsed by now  at this point,
+    /// if the next token is `@` then we can try to parse the more general form.
+    ///
+    /// Consult `parse_pat_ident` for the `binding` grammar.
+    ///
+    /// The notion of intersection patterns are found in
+    /// e.g. [F#][and] where they are called AND-patterns.
+    ///
+    /// [and]: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching
+    fn recover_intersection_pat(&mut self, lhs: P<Pat>) -> PResult<'a, P<Pat>> {
+        if self.token.kind != token::At {
+            // Next token is not `@` so it's not going to be an intersection pattern.
+            return Ok(lhs);
+        }
+
+        // At this point we attempt to parse `@ $pat_rhs` and emit an error.
+        self.bump(); // `@`
+        let mut rhs = self.parse_pat(None)?;
+        let sp = lhs.span.to(rhs.span);
+
+        if let PatKind::Ident(_, _, ref mut sub @ None) = rhs.kind {
+            // The user inverted the order, so help them fix that.
+            let mut applicability = Applicability::MachineApplicable;
+            lhs.walk(&mut |p| match p.kind {
+                // `check_match` is unhappy if the subpattern has a binding anywhere.
+                PatKind::Ident(..) => {
+                    applicability = Applicability::MaybeIncorrect;
+                    false // Short-circuit.
+                },
+                _ => true,
+            });
+
+            let lhs_span = lhs.span;
+            // Move the LHS into the RHS as a subpattern.
+            // The RHS is now the full pattern.
+            *sub = Some(lhs);
+
+            self.struct_span_err(sp, "pattern on wrong side of `@`")
+                .span_label(lhs_span, "pattern on the left, should be on the right")
+                .span_label(rhs.span, "binding on the right, should be on the left")
+                .span_suggestion(sp, "switch the order", pprust::pat_to_string(&rhs), applicability)
+                .emit();
+        } else {
+            // The special case above doesn't apply so we may have e.g. `A(x) @ B(y)`.
+            rhs.kind = PatKind::Wild;
+            self.struct_span_err(sp, "left-hand side of `@` must be a binding")
+                .span_label(lhs.span, "interpreted as a pattern, not a binding")
+                .span_label(rhs.span, "also a pattern")
+                .note("bindings are `x`, `mut x`, `ref x`, and `ref mut x`")
+                .emit();
+        }
+
+        rhs.span = sp;
+        Ok(rhs)
+    }
+
     /// Ban a range pattern if it has an ambiguous interpretation.
     fn ban_pat_range_if_ambiguous(&self, pat: &Pat) -> PResult<'a, ()> {
         match pat.kind {
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 7d4ffe493d7..68dd90b54ab 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -2381,7 +2381,8 @@ impl<'a> State<'a> {
                 }
                 self.print_ident(ident);
                 if let Some(ref p) = *sub {
-                    self.s.word("@");
+                    self.s.space();
+                    self.s.word_space("@");
                     self.print_pat(p);
                 }
             }
diff --git a/src/test/ui/parser/intersection-patterns.rs b/src/test/ui/parser/intersection-patterns.rs
new file mode 100644
index 00000000000..adb607cf6b9
--- /dev/null
+++ b/src/test/ui/parser/intersection-patterns.rs
@@ -0,0 +1,40 @@
+// This tests the parser recovery in `recover_intersection_pat`
+// and serves as a regression test for the diagnostics issue #65400.
+//
+// The general idea is that for `$pat_lhs @ $pat_rhs` where
+// `$pat_lhs` is not generated by `ref? mut? $ident` we want
+// to suggest either switching the order or note that intersection
+// patterns are not allowed.
+
+fn main() {
+    let s: Option<u8> = None;
+
+    match s {
+        Some(x) @ y => {}
+        //~^ ERROR pattern on wrong side of `@`
+        //~| pattern on the left, should be on the right
+        //~| binding on the right, should be on the left
+        //~| HELP switch the order
+        //~| SUGGESTION y @ Some(x)
+        _ => {}
+    }
+
+    match s {
+        Some(x) @ Some(y) => {}
+        //~^ ERROR left-hand side of `@` must be a binding
+        //~| interpreted as a pattern, not a binding
+        //~| also a pattern
+        //~| NOTE bindings are `x`, `mut x`, `ref x`, and `ref mut x`
+        _ => {}
+    }
+
+    match 2 {
+        1 ..= 5 @ e => {}
+        //~^ ERROR pattern on wrong side of `@`
+        //~| pattern on the left, should be on the right
+        //~| binding on the right, should be on the left
+        //~| HELP switch the order
+        //~| SUGGESTION e @ 1 ..=5
+        _ => {}
+    }
+}
diff --git a/src/test/ui/parser/intersection-patterns.stderr b/src/test/ui/parser/intersection-patterns.stderr
new file mode 100644
index 00000000000..f5bfee5bbd6
--- /dev/null
+++ b/src/test/ui/parser/intersection-patterns.stderr
@@ -0,0 +1,33 @@
+error: pattern on wrong side of `@`
+  --> $DIR/intersection-patterns.rs:13:9
+   |
+LL |         Some(x) @ y => {}
+   |         -------^^^-
+   |         |         |
+   |         |         binding on the right, should be on the left
+   |         pattern on the left, should be on the right
+   |         help: switch the order: `y @ Some(x)`
+
+error: left-hand side of `@` must be a binding
+  --> $DIR/intersection-patterns.rs:23:9
+   |
+LL |         Some(x) @ Some(y) => {}
+   |         -------^^^-------
+   |         |         |
+   |         |         also a pattern
+   |         interpreted as a pattern, not a binding
+   |
+   = note: bindings are `x`, `mut x`, `ref x`, and `ref mut x`
+
+error: pattern on wrong side of `@`
+  --> $DIR/intersection-patterns.rs:32:9
+   |
+LL |         1 ..= 5 @ e => {}
+   |         -------^^^-
+   |         |         |
+   |         |         binding on the right, should be on the left
+   |         pattern on the left, should be on the right
+   |         help: switch the order: `e @ 1 ..=5`
+
+error: aborting due to 3 previous errors
+