rust-lang/rust-analyzer · error

Replacement contains undefined placeholders: {}

Error message

Replacement contains undefined placeholders: {}

What it means

Every placeholder used in the replacement template must have been defined (bound) in the search pattern; otherwise SSR would not know what text to substitute. `validate_rule` collects replacement placeholders not present in the search pattern's `defined_placeholders` and bails listing them.

Source

Thrown at crates/ide-ssr/src/parsing.rs:252

    let mut defined_placeholders = FxHashSet::default();
    for p in &rule.pattern.tokens {
        if let PatternElement::Placeholder(placeholder) = p {
            defined_placeholders.insert(&placeholder.ident);
        }
    }
    let mut undefined = Vec::new();
    for p in &rule.template.tokens {
        if let PatternElement::Placeholder(placeholder) = p {
            if !defined_placeholders.contains(&placeholder.ident) {
                undefined.push(placeholder.ident.to_string());
            }
            if !placeholder.constraints.is_empty() {
                bail!("Replacement placeholders cannot have constraints");
            }
        }
    }
    if !undefined.is_empty() {
        bail!("Replacement contains undefined placeholders: {}", undefined.join(", "));
    }
    Ok(())
}

fn tokenize(source: &str) -> Result<Vec<Token>, SsrError> {
    let lexed = parser::LexedStr::new(parser::Edition::CURRENT, source);
    if let Some((_, first_error)) = lexed.errors().next() {
        bail!("Failed to parse pattern: {}", first_error);
    }
    let mut tokens: Vec<Token> = Vec::new();
    for i in 0..lexed.len() {
        tokens.push(Token { kind: lexed.kind(i), text: lexed.text(i).into() });
    }
    Ok(tokens)
}

fn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placeholder, SsrError> {
    let mut name = None;

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Add the missing placeholder to the search pattern (e.g. `foo($x, $y) ==>> bar($x, $y)`).
  2. Remove the undefined placeholder from the replacement template.
  3. Check for case/typo mismatches between the two sides of `==>>`; names must match exactly.
  4. If the replacement needs constant text, write it literally instead of as a `$placeholder`.

Example fix

// before
"foo($x) ==>> bar($x, $y)"          // $y undefined
// after
"foo($x, $y) ==>> bar($x, $y)"
Defensive patterns

Strategy: validation

Validate before calling

fn defined_placeholders(rule: &str) -> (Vec<String>, Vec<String>) {
    let (search, repl) = rule.split_once("==>>").unwrap_or((rule, ""));
    let grab = |s: &str| {
        s.split(|c: char| !c.is_alphanumeric() && c != '_')
            .filter_map(|w| w.strip_prefix('$').map(String::from))
            .collect::<Vec<_>>()
    };
    (grab(search), grab(repl))
}
let (defined, used) = defined_placeholders(rule);
let missing: Vec<_> = used.iter().filter(|u| !defined.contains(u)).collect();
anyhow::ensure!(missing.is_empty(), "undefined replacement placeholders: {:?}", missing);

Type guard

fn all_replacement_placeholders_defined(search: &str, replacement: &str) -> bool {
    let defined: std::collections::HashSet<_> =
        search.split(|c: char| !c.is_alphanumeric() && c != '_')
            .filter_map(|w| w.strip_prefix('$')).collect();
    replacement.split(|c: char| !c.is_alphanumeric() && c != '_')
        .filter_map(|w| w.strip_prefix('$'))
        .all(|p| defined.contains(p))
}

Try / catch

match SsrPattern::from_str(rule) {
    Ok(p) => p,
    Err(e) if e.to_string().starts_with("Replacement contains undefined placeholders") => {
        eprintln!("{} — add these placeholders to the search pattern or remove them", e);
        return Ok(());
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: `SsrPattern::from_str` with a rule whose replacement references an undeclared placeholder, e.g. `foo($x) ==>> bar($x, $y)` where `$y` never appears in the search pattern.

Common situations: Typos in placeholder names (case mismatch `$Arg` vs `$arg`); hand-editing the replacement and adding a new placeholder without updating the search; generating rules programmatically where template and pattern come from different sources.

Related errors


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