{"record":{"id":"adf7e9f2124bbe8a","repo":"rust-lang/rust-analyzer","slug":"duplicate-rule","errorCode":null,"errorMessage":"duplicate rule: `{}`","messagePattern":"duplicate rule: `(.+?)`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/ungrammar/src/parser.rs","lineNumber":96,"sourceCode":"    fn intern_token(&mut self, name: String) -> Token {\n        let len = self.token_table.len();\n        let grammar = &mut self.grammar;\n        *self.token_table.entry(name.clone()).or_insert_with(|| {\n            grammar.tokens.push(TokenData { name });\n            Token(len)\n        })\n    }\n}\n\nfn node(p: &mut Parser) -> Result<()> {\n    let token = p.bump()?;\n    let node = match token.kind {\n        TokenKind::Node(it) => p.intern_node(it),\n        _ => bail!(token.loc, \"expected ident\"),\n    };\n    p.expect(TokenKind::Eq, \"=\")?;\n    if !matches!(p.grammar[node].rule, DUMMY_RULE) {\n        bail!(token.loc, \"duplicate rule: `{}`\", p.grammar[node].name)\n    }\n\n    let rule = rule(p)?;\n    p.grammar.nodes[node.0].rule = rule;\n    Ok(())\n}\n\nfn rule(p: &mut Parser) -> Result<Rule> {\n    if let Some(lexer::Token { kind: TokenKind::Pipe, loc }) = p.peek() {\n        bail!(\n            *loc,\n            \"The first element in a sequence of productions or alternatives \\\n            must not have a leading pipe (`|`)\"\n        );\n    }\n\n    let lhs = seq_rule(p)?;\n    let mut alt = vec![lhs];","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/lib/ungrammar/src/parser.rs#L78-L114","documentation":"`node` throws `duplicate rule: \\`{name}\\`` when a node name appears as a left-hand side more than once in the grammar. Each node is interned once; if `p.grammar[node].rule` is already set (not DUMMY_RULE), a second `Name = ...` definition would silently overwrite the first, so the parser rejects it instead.","triggerScenarios":"Parsing a `.ungram` file containing two definitions for the same node, e.g. `Expr = ...` appearing twice at top level. Triggered via `parse` whenever an identifier token maps to an already-defined node.","commonSituations":"Merging two grammar files without deduplication, copy-pasting a block that redefines an existing node, refactoring node names and forgetting to delete the old definition.","solutions":["Search the .ungram file for the duplicated node name and delete or rename one of the definitions.","If the intent is an alternative production, merge them into one rule using `|` alternatives instead of a second definition.","Uniquely rename one node if two concepts genuinely share a name."],"exampleFix":"// before (.ungram)\nExpr = PathExpr | Literal\nExpr = BinExpr\n\n// after (.ungram)\nExpr = PathExpr | Literal | BinExpr","handlingStrategy":"validation","validationCode":"// detect duplicate node names before parsing\nfn has_duplicate_nodes(src: &str) -> bool {\n    let mut names: Vec<&str> = src.lines()\n        .filter(|l| l.contains('=') && !l.trim_start().starts_with('|'))\n        .filter_map(|l| l.split('=').next())\n        .map(|s| s.trim())\n        .collect();\n    let n = names.len();\n    names.sort_unstable();\n    names.dedup().len() != n\n}","typeGuard":null,"tryCatchPattern":"match ungrammar::parse(src) {\n    Ok(g) => g,\n    Err(e) if e.to_string().starts_with(\"duplicate rule\") => {\n        eprintln!(\"dedupe your grammar: {}\", e);\n        ungrammar::parse(&dedupe(src)).expect(\"deduped grammar parses\")\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["grep for `^Name =` duplicates when merging grammar files.","Prefer adding `|` alternatives to the existing rule over redefining a node.","Add a codegen snapshot test so redefinition fails loudly in CI."],"tags":["parser","ungrammar","duplicate-definition"],"backgroundTag":"duplicate-definition","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"}