From 40bfb29e503e36602aceba512609dd797ed88ac4 Mon Sep 17 00:00:00 2001
From: Tim Neumann <timnn@google.com>
Date: Thu, 2 Jun 2022 21:36:11 +0200
Subject: [PATCH] feat: Support `$$` in macros.

The implementation mirrors what `rustc` currently does [1]. Part of #11952.

[1]: https://github.com/rust-lang/rust/blob/0595ea1d12cf745e0a672d05341429ecb0917e66/compiler/rustc_expand/src/mbe/quoted.rs#L230-L241
---
 .../hir-def/src/macro_expansion_tests/mbe.rs  | 68 +++++++++++++++++++
 .../macro_expansion_tests/mbe/meta_syntax.rs  |  6 ++
 crates/mbe/src/parser.rs                      |  8 +++
 3 files changed, 82 insertions(+)

diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index befef6547cb..93d2f648ae2 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -1544,3 +1544,71 @@ struct Foo;
 "##]],
     )
 }
+
+#[test]
+fn test_dollar_dollar() {
+    check(
+        r#"
+macro_rules! register_struct { ($Struct:ident) => {
+    macro_rules! register_methods { ($$($method:ident),*) => {
+        macro_rules! implement_methods { ($$$$($$val:expr),*) => {
+            struct $Struct;
+            impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*}
+        }}
+    }}
+}}
+
+register_struct!(Foo);
+register_methods!(alpha, beta);
+implement_methods!(1, 2, 3);
+"#,
+        expect![[r#"
+macro_rules! register_struct { ($Struct:ident) => {
+    macro_rules! register_methods { ($$($method:ident),*) => {
+        macro_rules! implement_methods { ($$$$($$val:expr),*) => {
+            struct $Struct;
+            impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*}
+        }}
+    }}
+}}
+
+macro_rules !register_methods {
+    ($($method: ident), *) = > {
+        macro_rules!implement_methods {
+            ($$($val: expr), *) = > {
+                struct Foo;
+                impl Foo {
+                    $(fn $method()-> & 'static[u32] {
+                        &[$$($$val), *]
+                    }
+                    )*
+                }
+            }
+        }
+    }
+}
+macro_rules !implement_methods {
+    ($($val: expr), *) = > {
+        struct Foo;
+        impl Foo {
+            fn alpha()-> & 'static[u32] {
+                &[$($val), *]
+            }
+            fn beta()-> & 'static[u32] {
+                &[$($val), *]
+            }
+        }
+    }
+}
+struct Foo;
+impl Foo {
+    fn alpha() -> & 'static[u32] {
+        &[1, 2, 3]
+    }
+    fn beta() -> & 'static[u32] {
+        &[1, 2, 3]
+    }
+}
+"#]],
+    )
+}
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
index 2de10ddbdf9..636a66ad535 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
@@ -56,6 +56,9 @@ macro_rules! f2 { ($i:) => ($i) }
 f2!();
 macro_rules! f3 { ($i:_) => () }
 f3!();
+
+macro_rules! m1 { ($$i) => () }
+m1!();
 "#,
         expect![[r#"
 macro_rules! i1 { invalid }
@@ -74,6 +77,9 @@ macro_rules! f2 { ($i:) => ($i) }
 /* error: invalid macro definition: missing fragment specifier */
 macro_rules! f3 { ($i:_) => () }
 /* error: invalid macro definition: missing fragment specifier */
+
+macro_rules! m1 { ($$i) => () }
+/* error: invalid macro definition: `$$` is not allowed on the pattern side */
 "#]],
     )
 }
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 9be8d7085da..6c7be598418 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -135,6 +135,14 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
                         let id = lit.id;
                         Op::Var { name, kind, id }
                     }
+                    tt::Leaf::Punct(punct @ tt::Punct { char: '$', .. }) => match mode {
+                        Mode::Pattern => {
+                            return Err(ParseError::unexpected(
+                                "`$$` is not allowed on the pattern side",
+                            ))
+                        }
+                        Mode::Template => Op::Leaf(tt::Leaf::Punct(*punct)),
+                    },
                     tt::Leaf::Punct(_) | tt::Leaf::Literal(_) => {
                         return Err(ParseError::expected("expected ident"))
                     }