Repository: rust-lang/rust-analyzer (rust)

Issue:
Syntax error when using const + exclusive range pattern syntax
<!--
Troubleshooting guide: https://rust-analyzer.github.io/manual.html#troubleshooting
Forum for questions: https://users.rust-lang.org/c/ide/14

Before submitting, please make sure that you're not running into one of these known issues:

 1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file): #3107

Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
-->

**rust-analyzer version**: `rust-analyzer 1 (643bc02de 2023-01-01)`

**rustc version**: `rustc 1.68.0-nightly (0b90256ad 2023-01-13)`

**relevant settings**: installed via rustup

Take this snippet: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a2f288d06c1dfcaf8e916a6eca9594cd

```rust
#![feature(inline_const_pat)]
#![feature(exclusive_range_pattern)]
pub fn ohno(val: i128) -> Option<i64> {
    match val {
        const { i64::MAX as i128 + 1 }.. => None,
        ..const { i64::MIN as i128 } => None,
        _ => Some(val as i64),
    }
}
```

This compiles fine on the latest version of rustc, but I'm getting a syntax error in `rust-analyzer` on this line:

```rust
        ..const { i64::MIN as i128 } => None,
```

The span of the error appears to be the arrow. I'm not sure if there's a succinct command I can run to get the exact error output, but here's what it looks like via `kak-lsp`:

![Syntax errors displayed](https://user-images.githubusercontent.com/15850505/212497767-565f4e4b-8eea-4bfc-9899-43946c90baa2.png)

(note: the features are applied to the crate, this is just running inside a file in that crate)

Pull request:
Parse more exclusive range patterns and inline const patterns
Closes #13955

This PR
- implements exclusive range pattern without start bound (tracking issue: rust-lang/rust#37854)
  - additionally moves rest pattern handling into the same place since they only differ in whether another pattern follows; this actually solves some FIXMEs
- updates `PATTERN_FIRST` token set to include `const` token so we can parse inline const pattern in nested patterns

Patch:
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index 5f4977886f..4801732101 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -5,6 +5,7 @@ pub(super) const PATTERN_FIRST: TokenSet =
         T![box],
         T![ref],
         T![mut],
+        T![const],
         T!['('],
         T!['['],
         T![&],
@@ -15,6 +16,10 @@ pub(super) const PATTERN_FIRST: TokenSet =
 
 const PAT_TOP_FIRST: TokenSet = PATTERN_FIRST.union(TokenSet::new(&[T![|]]));
 
+/// Set of possible tokens at the start of a range pattern's end bound.
+const RANGE_PAT_END_FIRST: TokenSet =
+    expressions::LITERAL_FIRST.union(paths::PATH_FIRST).union(TokenSet::new(&[T![-], T![const]]));
+
 pub(crate) fn pattern(p: &mut Parser<'_>) {
     pattern_r(p, PAT_RECOVERY_SET);
 }
@@ -105,6 +110,52 @@ fn pattern_single_r(p: &mut Parser<'_>, recovery_set: TokenSet) {
         return;
     }
 
+    // test exclusive_range_pat
+    // fn main() {
+    //     match 42 {
+    //         ..0 => {}
+    //         1..2 => {}
+    //     }
+    // }
+
+    // test dot_dot_pat
+    // fn main() {
+    //     let .. = ();
+    //     //
+    //     // Tuples
+    //     //
+    //     let (a, ..) = ();
+    //     let (a, ..,) = ();
+    //     let Tuple(a, ..) = ();
+    //     let Tuple(a, ..,) = ();
+    //     let (.., ..) = ();
+    //     let Tuple(.., ..) = ();
+    //     let (.., a, ..) = ();
+    //     let Tuple(.., a, ..) = ();
+    //     //
+    //     // Slices
+    //     //
+    //     let [..] = ();
+    //     let [head, ..] = ();
+    //     let [head, tail @ ..] = ();
+    //     let [head, .., cons] = ();
+    //     let [head, mid @ .., cons] = ();
+    //     let [head, .., .., cons] = ();
+    //     let [head, .., mid, tail @ ..] = ();
+    //     let [head, .., mid, .., cons] = ();
+    // }
+    if p.at(T![..]) {
+        let m = p.start();
+        p.bump(T![..]);
+        if p.at_ts(RANGE_PAT_END_FIRST) {
+            atom_pat(p, recovery_set);
+            m.complete(p, RANGE_PAT);
+        } else {
+            m.complete(p, REST_PAT);
+        }
+        return;
+    }
+
     if let Some(lhs) = atom_pat(p, recovery_set) {
         for range_op in [T![...], T![..=], T![..]] {
             if p.at(range_op) {
@@ -173,7 +224,6 @@ fn atom_pat(p: &mut Parser<'_>, recovery_set: TokenSet) -> Option<CompletedMarke
         _ if paths::is_path_start(p) => path_or_macro_pat(p),
         _ if is_literal_pat_start(p) => literal_pat(p),
 
-        T![.] if p.at(T![..]) => rest_pat(p),
         T![_] => wildcard_pat(p),
         T![&] => ref_pat(p),
         T!['('] => tuple_pat(p),
@@ -334,39 +384,6 @@ fn wildcard_pat(p: &mut Parser<'_>) -> CompletedMarker {
     m.complete(p, WILDCARD_PAT)
 }
 
-// test dot_dot_pat
-// fn main() {
-//     let .. = ();
-//     //
-//     // Tuples
-//     //
-//     let (a, ..) = ();
-//     let (a, ..,) = ();
-//     let Tuple(a, ..) = ();
-//     let Tuple(a, ..,) = ();
-//     let (.., ..) = ();
-//     let Tuple(.., ..) = ();
-//     let (.., a, ..) = ();
-//     let Tuple(.., a, ..) = ();
-//     //
-//     // Slices
-//     //
-//     let [..] = ();
-//     let [head, ..] = ();
-//     let [head, tail @ ..] = ();
-//     let [head, .., cons] = ();
-//     let [head, mid @ .., cons] = ();
-//     let [head, .., .., cons] = ();
-//     let [head, .., mid, tail @ ..] = ();
-//     let [head, .., mid, .., cons] = ();
-// }
-fn rest_pat(p: &mut Parser<'_>) -> CompletedMarker {
-    assert!(p.at(T![..]));
-    let m = p.start();
-    p.bump(T![..]);
-    m.complete(p, REST_PAT)
-}
-
 // test ref_pat
 // fn main() {
 //     let &a = ();
@@ -483,6 +500,14 @@ fn box_pat(p: &mut Parser<'_>) -> CompletedMarker {
 // fn main() {
 //     let const { 15 } = ();
 //     let const { foo(); bar() } = ();
+//
+//     match 42 {
+//         const { 0 } .. const { 1 } => (),
+//         .. const { 0 } => (),
+//         const { 2 } .. => (),
+//     }
+//
+//     let (const { () },) = ();
 // }
 fn const_block_pat(p: &mut Parser<'_>) -> CompletedMarker {
     assert!(p.at(T![const]));


Test patch:
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs
index 26f16542cb..e5cb9b9567 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs
@@ -106,7 +106,6 @@ stringify!(;
 
 #[test]
 fn range_patterns() {
-    // FIXME: rustc thinks there are three patterns here, not one.
     check(
         r#"
 macro_rules! m {
@@ -118,7 +117,7 @@ m!(.. .. ..);
 macro_rules! m {
     ($($p:pat)*) => (stringify!($($p |)*);)
 }
-stringify!(.. .. .. |);
+stringify!(.. | .. | .. |);
 "#]],
     );
 }
diff --git a/crates/parser/src/tests/prefix_entries.rs b/crates/parser/src/tests/prefix_entries.rs
index 40f92e5880..11f9c34abd 100644
--- a/crates/parser/src/tests/prefix_entries.rs
+++ b/crates/parser/src/tests/prefix_entries.rs
@@ -33,8 +33,7 @@ fn stmt() {
 fn pat() {
     check(PrefixEntryPoint::Pat, "x y", "x");
     check(PrefixEntryPoint::Pat, "fn f() {}", "fn");
-    // FIXME: This one is wrong, we should consume only one pattern.
-    check(PrefixEntryPoint::Pat, ".. ..", ".. ..");
+    check(PrefixEntryPoint::Pat, ".. ..", "..");
 }
 
 #[test]
diff --git a/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rast b/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rast
index 59de2b9f16..593867a7b1 100644
--- a/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rast
+++ b/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rast
@@ -74,6 +74,126 @@ SOURCE_FILE
             L_PAREN "("
             R_PAREN ")"
           SEMICOLON ";"
+        WHITESPACE "\n\n    "
+        EXPR_STMT
+          MATCH_EXPR
+            MATCH_KW "match"
+            WHITESPACE " "
+            LITERAL
+              INT_NUMBER "42"
+            WHITESPACE " "
+            MATCH_ARM_LIST
+              L_CURLY "{"
+              WHITESPACE "\n        "
+              MATCH_ARM
+                RANGE_PAT
+                  CONST_BLOCK_PAT
+                    CONST_KW "const"
+                    WHITESPACE " "
+                    BLOCK_EXPR
+                      STMT_LIST
+                        L_CURLY "{"
+                        WHITESPACE " "
+                        LITERAL
+                          INT_NUMBER "0"
+                        WHITESPACE " "
+                        R_CURLY "}"
+                  WHITESPACE " "
+                  DOT2 ".."
+                  WHITESPACE " "
+                  CONST_BLOCK_PAT
+                    CONST_KW "const"
+                    WHITESPACE " "
+                    BLOCK_EXPR
+                      STMT_LIST
+                        L_CURLY "{"
+                        WHITESPACE " "
+                        LITERAL
+                          INT_NUMBER "1"
+                        WHITESPACE " "
+                        R_CURLY "}"
+                WHITESPACE " "
+                FAT_ARROW "=>"
+                WHITESPACE " "
+                TUPLE_EXPR
+                  L_PAREN "("
+                  R_PAREN ")"
+                COMMA ","
+              WHITESPACE "\n        "
+              MATCH_ARM
+                RANGE_PAT
+                  DOT2 ".."
+                  WHITESPACE " "
+                  CONST_BLOCK_PAT
+                    CONST_KW "const"
+                    WHITESPACE " "
+                    BLOCK_EXPR
+                      STMT_LIST
+                        L_CURLY "{"
+                        WHITESPACE " "
+                        LITERAL
+                          INT_NUMBER "0"
+                        WHITESPACE " "
+                        R_CURLY "}"
+                WHITESPACE " "
+                FAT_ARROW "=>"
+                WHITESPACE " "
+                TUPLE_EXPR
+                  L_PAREN "("
+                  R_PAREN ")"
+                COMMA ","
+              WHITESPACE "\n        "
+              MATCH_ARM
+                RANGE_PAT
+                  CONST_BLOCK_PAT
+                    CONST_KW "const"
+                    WHITESPACE " "
+                    BLOCK_EXPR
+                      STMT_LIST
+                        L_CURLY "{"
+                        WHITESPACE " "
+                        LITERAL
+                          INT_NUMBER "2"
+                        WHITESPACE " "
+                        R_CURLY "}"
+                  WHITESPACE " "
+                  DOT2 ".."
+                WHITESPACE " "
+                FAT_ARROW "=>"
+                WHITESPACE " "
+                TUPLE_EXPR
+                  L_PAREN "("
+                  R_PAREN ")"
+                COMMA ","
+              WHITESPACE "\n    "
+              R_CURLY "}"
+        WHITESPACE "\n\n    "
+        LET_STMT
+          LET_KW "let"
+          WHITESPACE " "
+          TUPLE_PAT
+            L_PAREN "("
+            CONST_BLOCK_PAT
+              CONST_KW "const"
+              WHITESPACE " "
+              BLOCK_EXPR
+                STMT_LIST
+                  L_CURLY "{"
+                  WHITESPACE " "
+                  TUPLE_EXPR
+                    L_PAREN "("
+                    R_PAREN ")"
+                  WHITESPACE " "
+                  R_CURLY "}"
+            COMMA ","
+            R_PAREN ")"
+          WHITESPACE " "
+          EQ "="
+          WHITESPACE " "
+          TUPLE_EXPR
+            L_PAREN "("
+            R_PAREN ")"
+          SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rs b/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rs
index dce9defac2..6ecdee849b 100644
--- a/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rs
+++ b/crates/parser/test_data/parser/inline/ok/0156_const_block_pat.rs
@@ -1,4 +1,12 @@
 fn main() {
     let const { 15 } = ();
     let const { foo(); bar() } = ();
+
+    match 42 {
+        const { 0 } .. const { 1 } => (),
+        .. const { 0 } => (),
+        const { 2 } .. => (),
+    }
+
+    let (const { () },) = ();
 }
diff --git a/crates/parser/test_data/parser/inline/ok/0207_exclusive_range_pat.rast b/crates/parser/test_data/parser/inline/ok/0207_exclusive_range_pat.rast
new file mode 100644
index 0000000000..fd2c422d0d
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0207_exclusive_range_pat.rast
@@ -0,0 +1,58 @@
+SOURCE_FILE
+  FN
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "main"
+    PARAM_LIST
+      L_PAREN "("
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        WHITESPACE "\n    "
+        MATCH_EXPR
+          MATCH_KW "match"
+          WHITESPACE " "
+          LITERAL
+            INT_NUMBER "42"
+          WHITESPACE " "
+          MATCH_ARM_LIST
+            L_CURLY "{"
+            WHITESPACE "\n        "
+            MATCH_ARM
+              RANGE_PAT
+                DOT2 ".."
+                LITERAL_PAT
+                  LITERAL
+                    INT_NUMBER "0"
+              WHITESPACE " "
+              FAT_ARROW "=>"
+              WHITESPACE " "
+              BLOCK_EXPR
+                STMT_LIST
+                  L_CURLY "{"
+                  R_CURLY "}"
+            WHITESPACE "\n        "
+            MATCH_ARM
+              RANGE_PAT
+                LITERAL_PAT
+                  LITERAL
+                    INT_NUMBER "1"
+                DOT2 ".."
+                LITERAL_PAT
+                  LITERAL
+                    INT_NUMBER "2"
+              WHITESPACE " "
+              FAT_ARROW "=>"
+              WHITESPACE " "
+              BLOCK_EXPR
+                STMT_LIST
+                  L_CURLY "{"
+                  R_CURLY "}"
+            WHITESPACE "\n    "
+            R_CURLY "}"
+        WHITESPACE "\n"
+        R_CURLY "}"
+  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0207_exclusive_range_pat.rs b/crates/parser/test_data/parser/inline/ok/0207_exclusive_range_pat.rs
new file mode 100644
index 0000000000..e80505d8bd
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0207_exclusive_range_pat.rs
@@ -0,0 +1,6 @@
+fn main() {
+    match 42 {
+        ..0 => {}
+        1..2 => {}
+    }
+}