fix: don't treat a raw identifier as a raw string prefix

CharClasses entered raw string state whenever `r` was followed by `#`,
which also matches a raw identifier such as `r#struct`. That marked the
first few characters as InString, so replace_names copied `r#s` through
verbatim and registered the leftover `truct` as the metavariable name.
Undoing the substitution afterwards put the `$` back in the wrong place,
so `$r#struct` came out as `r#s$truct` and no longer compiled.

Only enter raw string state when the run of `#`s after `r` ends in a
quote.
diff --git a/src/comment.rs b/src/comment.rs
index 40e42b5..4162b5f 100644
--- a/src/comment.rs
+++ b/src/comment.rs
@@ -1347,6 +1347,24 @@
     }
 }
 
+/// Returns `true` if the `r` just consumed opens a raw string literal, i.e. the run of
+/// `#`s that follows it ends in a `"`. Peeking a single `#` is not enough to tell a raw
+/// string apart from a raw identifier such as `r#struct`.
+fn is_raw_string_prefix<T>(iter: &mut MultiPeek<T>) -> bool
+where
+    T: Iterator,
+    T::Item: RichChar,
+{
+    while let Some(c) = iter.peek() {
+        match c.get_char() {
+            '#' => continue,
+            '"' => return true,
+            _ => return false,
+        }
+    }
+    false
+}
+
 fn is_raw_string_suffix<T>(iter: &mut MultiPeek<T>, count: u32) -> bool
 where
     T: Iterator,
@@ -1430,7 +1448,13 @@
             CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
             CharClassesStatus::Normal => match chr {
                 'r' => match self.base.peek().map(RichChar::get_char) {
-                    Some('#') | Some('"') => {
+                    Some('"') => {
+                        char_kind = FullCodeCharKind::InString;
+                        CharClassesStatus::RawStringPrefix(0)
+                    }
+                    // `r#` opens a raw string only if the `#`s end in a `"`; otherwise
+                    // this is a raw identifier like `r#struct` and stays normal code.
+                    Some('#') if is_raw_string_prefix(&mut self.base) => {
                         char_kind = FullCodeCharKind::InString;
                         CharClassesStatus::RawStringPrefix(0)
                     }
diff --git a/tests/target/issue-7056.rs b/tests/target/issue-7056.rs
new file mode 100644
index 0000000..daa0538
--- /dev/null
+++ b/tests/target/issue-7056.rs
@@ -0,0 +1,25 @@
+// `r#` opens a raw string only when the run of `#`s ends in a `"`. A raw identifier
+// such as `r#struct` must stay classified as normal code, or the macro body rewriter
+// copies part of it through verbatim and puts the `$` back in the wrong place.
+
+#![feature(decl_macro)]
+
+macro_rules! r#struct {
+        ($r#struct:expr)  =>  {  $r#struct  }
+}
+
+macro r#decl_macro($r#fn:expr) {
+        $r#fn
+}
+
+macro_rules! old_macro {
+    ($a:expr) => {
+        $a
+    };
+}
+
+fn raw_ident_next_to_a_raw_string() {
+    let r#match = r#"still a raw string"#;
+    let r#fn = r##"hashes too"##;
+    let _ = (r#match, r#fn);
+}