{"record":{"id":"f3a5949dbe71bdf2","repo":"rust-lang/rust-analyzer","slug":"placeholder-repeats-more-than-once","errorCode":null,"errorMessage":"Placeholder `{}` repeats more than once","messagePattern":"Placeholder `(.+?)` repeats more than once","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ide-ssr/src/parsing.rs","lineNumber":221,"sourceCode":"    fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {\n        let raw_pattern = pattern_str.parse()?;\n        let parsed_rules = ParsedRule::new(&raw_pattern, None)?;\n        Ok(SsrPattern { parsed_rules })\n    }\n}\n\n/// Returns `pattern_str`, parsed as a search or replace pattern. If `remove_whitespace` is true,\n/// then any whitespace tokens will be removed, which we do for the search pattern, but not for the\n/// replace pattern.\nfn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {\n    let mut res = Vec::new();\n    let mut placeholder_names = FxHashSet::default();\n    let mut tokens = tokenize(pattern_str)?.into_iter();\n    while let Some(token) = tokens.next() {\n        if token.kind == T![$] {\n            let placeholder = parse_placeholder(&mut tokens)?;\n            if !placeholder_names.insert(placeholder.ident.clone()) {\n                bail!(\"Placeholder `{}` repeats more than once\", placeholder.ident);\n            }\n            res.push(PatternElement::Placeholder(placeholder));\n        } else {\n            res.push(PatternElement::Token(token));\n        }\n    }\n    Ok(res)\n}\n\n/// Checks for errors in a rule. e.g. the replace pattern referencing placeholders that the search\n/// pattern didn't define.\nfn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {\n    let mut defined_placeholders = FxHashSet::default();\n    for p in &rule.pattern.tokens {\n        if let PatternElement::Placeholder(placeholder) = p {\n            defined_placeholders.insert(&placeholder.ident);\n        }\n    }","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/ide-ssr/src/parsing.rs#L203-L239","documentation":"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!`.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rename the second occurrence to a distinct placeholder, e.g. `foo($x, $y)`.","If you need the arguments to be equal, use placeholder constraints (e.g. `$y == $x` constraint syntax) instead of repeating the name.","Restructure the pattern so each matched node is captured once.","Check the SSR documentation for equality/limit constraints before reusing placeholder names."],"exampleFix":"// before\nlet pattern = SsrPattern::new(\"foo($x, $x)\");\n// after (constraints)\nlet pattern = SsrPattern::from_str(\"foo($x, $y)\").constraints(\"$y == $x\").unwrap();","handlingStrategy":"validation","validationCode":"use std::collections::HashSet;\nfn has_duplicate_placeholders(pat: &str) -> bool {\n    let mut seen = HashSet::new();\n    let mut dup = false;\n    let toks: Vec<&str> = pat.split(|c: char| !c.is_alphanumeric() && c != '_');\n    for w in toks {\n        if let Some(name) = w.strip_prefix('$') {\n            if !seen.insert(name) { dup = true; }\n        }\n    }\n    dup\n}","typeGuard":"fn unique_placeholders<'a>(names: impl Iterator<Item = &'a str>) -> bool {\n    let mut s = std::collections::HashSet::new();\n    names.all(move |n| s.insert(n))\n}","tryCatchPattern":"match SsrPattern::new(pat) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"repeats more than once\") => {\n        eprintln!(\"Each $placeholder in the search pattern must be unique; use distinct names + constraints\");\n        return Ok(());\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Never repeat a $name within one search pattern; use distinct placeholders","Express equality between captures with placeholder constraints, not name reuse","Regex backreference habits do not carry over — check SSR docs for constraint syntax"],"tags":["rust","ide-ssr","placeholder","duplicate-binding","pattern"],"backgroundTag":"duplicate-placeholder-binding","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}