rust-lang/rust-analyzer · error
Replacement placeholders cannot have constraints
Error message
Replacement placeholders cannot have constraints
What it means
Placeholder constraints (e.g. kind/type constraints) are only meaningful on *search-pattern* placeholders, where they filter what matches. The replacement template must be a plain construction; if a placeholder in the replacement template carries constraints, `validate_rule` rejects the rule.
Source
Thrown at crates/ide-ssr/src/parsing.rs:247
}
/// 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);
}
}
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() });
}View on GitHub (pinned to e8f7e90aa3)
Solutions
- Remove the constraint from the placeholder in the replacement template, keeping only the bare name: `bar($x)`.
- Move any filtering logic into the search pattern's placeholder constraints instead.
- If you wanted a literal `:` in the replacement, escape or restructure so it is not parsed as constraint syntax.
Example fix
// before "foo($x) ==>> bar($x:not(expr))" // after "foo($x:not(expr)) ==>> bar($x)"
Defensive patterns
Strategy: validation
Validate before calling
fn replacement_side(rule: &str) -> &str {
rule.split_once("==>>").map(|(_, r)| r).unwrap_or("")
}
anyhow::ensure!(
!replacement_side(rule).contains(":not(") && !replacement_side(rule).contains(":"),
"constraints are only allowed in the search pattern"
); Type guard
fn replacement_has_constraints(replacement: &str) -> bool {
replacement.contains('$') && replacement[replacement.find('$').unwrap()..]
.contains(':')
} Try / catch
match SsrPattern::from_str(rule) {
Ok(p) => p,
Err(e) if e.to_string().contains("cannot have constraints") => {
eprintln!("Strip constraints from the ==>> replacement side of the rule");
return Ok(());
}
Err(e) => return Err(e.into()),
} Prevention
- Keep the search side and replacement side symmetric in syntax: constraints only on search placeholders
- When copy-pasting rules, re-check both sides of ==>> independently
- Remember constraints filter inputs; outputs must be plain captures or literals
When it happens
Trigger: `SsrPattern::from_str` where the text after `==>>` contains a placeholder with constraint syntax, e.g. `foo($x) ==>> bar($x:not(expr))` — constraints parsed into the template's placeholders trigger this bail.
Common situations: Users symmetrically applying constraint syntax they used in the search part to the replacement part; copy-pasting a rule where `:`-constraints leaked into the replacement; misunderstanding that constraints select inputs, not shape outputs.
Related errors
- Replacement contains undefined placeholders: {}
- Placeholder `{}` repeats more than once
- No files to search
- Not a valid Rust expression, type, item, path or pattern
- Expected ident, found {:?} while parsing kind constraint
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/d3d79825d83e0b60.
Report an issue: GitHub.