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
- Add the missing placeholder to the search pattern (e.g. `foo($x, $y) ==>> bar($x, $y)`).
- Remove the undefined placeholder from the replacement template.
- Check for case/typo mismatches between the two sides of `==>>`; names must match exactly.
- 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
- Extract the placeholder set from both sides of ==>> and diff them before submitting the rule
- Watch for case-sensitivity typos in placeholder names ($arg vs $Arg)
- When generating rules programmatically, derive the template from captured names, never free-form text
- Write literal replacement text directly instead of inventing $placeholders for constants
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
- Replacement placeholders cannot have constraints
- Placeholder `{}` repeats more than once
- 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/f25ce6d9c9ba81d6.
Report an issue: GitHub.