diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index ffc7202ae56..7f0b5061e29 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -2,7 +2,7 @@
 //! process of matching, placeholder values are recorded.
 
 use crate::{
-    parsing::{Constraint, NodeKind, Placeholder},
+    parsing::{Constraint, NodeKind, Placeholder, Var},
     resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
     SsrMatches,
 };
@@ -56,10 +56,6 @@ pub struct Match {
     pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>,
 }
 
-/// Represents a `$var` in an SSR query.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub(crate) struct Var(pub String);
-
 /// Information about a placeholder bound in a match.
 #[derive(Debug)]
 pub(crate) struct PlaceholderMatch {
@@ -182,10 +178,9 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
                 // We validated the range for the node when we started the match, so the placeholder
                 // probably can't fail range validation, but just to be safe...
                 self.validate_range(&original_range)?;
-                matches_out.placeholder_values.insert(
-                    Var(placeholder.ident.to_string()),
-                    PlaceholderMatch::new(code, original_range),
-                );
+                matches_out
+                    .placeholder_values
+                    .insert(placeholder.ident.clone(), PlaceholderMatch::new(code, original_range));
             }
             return Ok(());
         }
@@ -487,7 +482,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
                 }
                 if let Phase::Second(match_out) = phase {
                     match_out.placeholder_values.insert(
-                        Var(placeholder.ident.to_string()),
+                        placeholder.ident.clone(),
                         PlaceholderMatch::from_range(FileRange {
                             file_id: self.sema.original_range(code).file_id,
                             range: first_matched_token
diff --git a/crates/ssr/src/parsing.rs b/crates/ssr/src/parsing.rs
index 9570e96e36e..05b66dcd780 100644
--- a/crates/ssr/src/parsing.rs
+++ b/crates/ssr/src/parsing.rs
@@ -8,7 +8,7 @@
 use crate::errors::bail;
 use crate::{SsrError, SsrPattern, SsrRule};
 use rustc_hash::{FxHashMap, FxHashSet};
-use std::str::FromStr;
+use std::{fmt::Display, str::FromStr};
 use syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
 use test_utils::mark;
 
@@ -34,12 +34,16 @@ pub(crate) enum PatternElement {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) struct Placeholder {
     /// The name of this placeholder. e.g. for "$a", this would be "a"
-    pub(crate) ident: SmolStr,
+    pub(crate) ident: Var,
     /// A unique name used in place of this placeholder when we parse the pattern as Rust code.
     stand_in_name: String,
     pub(crate) constraints: Vec<Constraint>,
 }
 
+/// Represents a `$var` in an SSR query.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub(crate) struct Var(pub String);
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum Constraint {
     Kind(NodeKind),
@@ -205,7 +209,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
         if token.kind == T![$] {
             let placeholder = parse_placeholder(&mut tokens)?;
             if !placeholder_names.insert(placeholder.ident.clone()) {
-                bail!("Name `{}` repeats more than once", placeholder.ident);
+                bail!("Placeholder `{}` repeats more than once", placeholder.ident);
             }
             res.push(PatternElement::Placeholder(placeholder));
         } else {
@@ -228,7 +232,7 @@ fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
     for p in &rule.template.tokens {
         if let PatternElement::Placeholder(placeholder) = p {
             if !defined_placeholders.contains(&placeholder.ident) {
-                undefined.push(format!("${}", placeholder.ident));
+                undefined.push(placeholder.ident.to_string());
             }
             if !placeholder.constraints.is_empty() {
                 bail!("Replacement placeholders cannot have constraints");
@@ -344,7 +348,17 @@ impl NodeKind {
 
 impl Placeholder {
     fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self {
-        Self { stand_in_name: format!("__placeholder_{}", name), constraints, ident: name }
+        Self {
+            stand_in_name: format!("__placeholder_{}", name),
+            constraints,
+            ident: Var(name.to_string()),
+        }
+    }
+}
+
+impl Display for Var {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "${}", self.0)
     }
 }
 
diff --git a/crates/ssr/src/replacing.rs b/crates/ssr/src/replacing.rs
index 8f8fe6149a5..496a21e6e17 100644
--- a/crates/ssr/src/replacing.rs
+++ b/crates/ssr/src/replacing.rs
@@ -1,6 +1,5 @@
 //! Code for applying replacement templates for matches that have previously been found.
 
-use crate::matching::Var;
 use crate::{resolving::ResolvedRule, Match, SsrMatches};
 use rustc_hash::{FxHashMap, FxHashSet};
 use syntax::ast::{self, AstToken};
@@ -114,7 +113,7 @@ impl ReplacementRenderer<'_> {
     fn render_token(&mut self, token: &SyntaxToken) {
         if let Some(placeholder) = self.rule.get_placeholder(&token) {
             if let Some(placeholder_value) =
-                self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string()))
+                self.match_info.placeholder_values.get(&placeholder.ident)
             {
                 let range = &placeholder_value.range.range;
                 let mut matched_text =
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs
index 0d0a0009064..65cd387530d 100644
--- a/crates/ssr/src/tests.rs
+++ b/crates/ssr/src/tests.rs
@@ -31,7 +31,7 @@ fn parser_two_delimiters() {
 fn parser_repeated_name() {
     assert_eq!(
         parse_error_text("foo($a, $a) ==>>"),
-        "Parse error: Name `a` repeats more than once"
+        "Parse error: Placeholder `$a` repeats more than once"
     );
 }