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

  1. Rename the second occurrence to a distinct placeholder, e.g. `foo($x, $y)`.
  2. If you need the arguments to be equal, use placeholder constraints (e.g. `$y == $x` constraint syntax) instead of repeating the name.
  3. Restructure the pattern so each matched node is captured once.
  4. 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

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


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/f3a5949dbe71bdf2. Report an issue: GitHub.