rust-lang/rust-analyzer · error
Placeholder `{}` repeats more than once
Error message
Placeholder `{}` repeats more than once What it means
SSR placeholders (`$name`) in a *search* pattern must be unique: each placeholder binds exactly one captured value. When `parse_pattern` (RawPattern parsing) encounters the same placeholder name twice in one pattern, the binding is ambiguous, so it throws via `bail!`.
Source
Thrown at crates/ide-ssr/src/parsing.rs:221
fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {
let raw_pattern = pattern_str.parse()?;
let parsed_rules = ParsedRule::new(&raw_pattern, None)?;
Ok(SsrPattern { parsed_rules })
}
}
/// Returns `pattern_str`, parsed as a search or replace pattern. If `remove_whitespace` is true,
/// then any whitespace tokens will be removed, which we do for the search pattern, but not for the
/// replace pattern.
fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
let mut res = Vec::new();
let mut placeholder_names = FxHashSet::default();
let mut tokens = tokenize(pattern_str)?.into_iter();
while let Some(token) = tokens.next() {
if token.kind == T![$] {
let placeholder = parse_placeholder(&mut tokens)?;
if !placeholder_names.insert(placeholder.ident.clone()) {
bail!("Placeholder `{}` repeats more than once", placeholder.ident);
}
res.push(PatternElement::Placeholder(placeholder));
} else {
res.push(PatternElement::Token(token));
}
}
Ok(res)
}
/// Checks for errors in a rule. e.g. the replace pattern referencing placeholders that the search
/// pattern didn't define.
fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
let mut defined_placeholders = FxHashSet::default();
for p in &rule.pattern.tokens {
if let PatternElement::Placeholder(placeholder) = p {
defined_placeholders.insert(&placeholder.ident);
}
}View on GitHub (pinned to e8f7e90aa3)
Solutions
- Rename the second occurrence to a distinct placeholder, e.g. `foo($x, $y)`.
- If you need the arguments to be equal, use placeholder constraints (e.g. `$y == $x` constraint syntax) instead of repeating the name.
- Restructure the pattern so each matched node is captured once.
- Check the SSR documentation for equality/limit constraints before reusing placeholder names.
Example fix
// before
let pattern = SsrPattern::new("foo($x, $x)");
// after (constraints)
let pattern = SsrPattern::from_str("foo($x, $y)").constraints("$y == $x").unwrap(); Defensive patterns
Strategy: validation
Validate before calling
use std::collections::HashSet;
fn has_duplicate_placeholders(pat: &str) -> bool {
let mut seen = HashSet::new();
let mut dup = false;
let toks: Vec<&str> = pat.split(|c: char| !c.is_alphanumeric() && c != '_');
for w in toks {
if let Some(name) = w.strip_prefix('$') {
if !seen.insert(name) { dup = true; }
}
}
dup
} Type guard
fn unique_placeholders<'a>(names: impl Iterator<Item = &'a str>) -> bool {
let mut s = std::collections::HashSet::new();
names.all(move |n| s.insert(n))
} Try / catch
match SsrPattern::new(pat) {
Ok(p) => p,
Err(e) if e.to_string().contains("repeats more than once") => {
eprintln!("Each $placeholder in the search pattern must be unique; use distinct names + constraints");
return Ok(());
}
Err(e) => return Err(e.into()),
} Prevention
- Never repeat a $name within one search pattern; use distinct placeholders
- Express equality between captures with placeholder constraints, not name reuse
- Regex backreference habits do not carry over — check SSR docs for constraint syntax
When it happens
Trigger: Calling `SsrPattern::new`/`from_str` with a search pattern that repeats a placeholder, e.g. `foo($x, $x)` or `$a + $a ==>> ...`. Only the search pattern is checked here; the replacement is validated separately.
Common situations: Users expecting `$x, $x` to mean 'two equal arguments' (SSR instead requires `$x` and a constraint or distinct placeholders); copy-paste editing a pattern leaving a duplicated placeholder; adapting regex habits (`(\w+) \1`) to SSR syntax.
Related errors
- Replacement placeholders cannot have constraints
- Replacement contains undefined placeholders: {}
- No files to search
- Not a valid Rust expression, type, item, path or pattern
- We explicitly do not provide canonicalization API, as that i
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/f3a5949dbe71bdf2.
Report an issue: GitHub.