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

  1. Remove the constraint from the placeholder in the replacement template, keeping only the bare name: `bar($x)`.
  2. Move any filtering logic into the search pattern's placeholder constraints instead.
  3. 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

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


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