rust-lang/rust-analyzer · error
duplicate rule: `{}`
Error message
duplicate rule: `{}` What it means
`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.
Source
Thrown at lib/ungrammar/src/parser.rs:96
fn intern_token(&mut self, name: String) -> Token {
let len = self.token_table.len();
let grammar = &mut self.grammar;
*self.token_table.entry(name.clone()).or_insert_with(|| {
grammar.tokens.push(TokenData { name });
Token(len)
})
}
}
fn node(p: &mut Parser) -> Result<()> {
let token = p.bump()?;
let node = match token.kind {
TokenKind::Node(it) => p.intern_node(it),
_ => bail!(token.loc, "expected ident"),
};
p.expect(TokenKind::Eq, "=")?;
if !matches!(p.grammar[node].rule, DUMMY_RULE) {
bail!(token.loc, "duplicate rule: `{}`", p.grammar[node].name)
}
let rule = rule(p)?;
p.grammar.nodes[node.0].rule = rule;
Ok(())
}
fn rule(p: &mut Parser) -> Result<Rule> {
if let Some(lexer::Token { kind: TokenKind::Pipe, loc }) = p.peek() {
bail!(
*loc,
"The first element in a sequence of productions or alternatives \
must not have a leading pipe (`|`)"
);
}
let lhs = seq_rule(p)?;
let mut alt = vec![lhs];View on GitHub (pinned to e8f7e90aa3)
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.
Example fix
// before (.ungram) Expr = PathExpr | Literal Expr = BinExpr // after (.ungram) Expr = PathExpr | Literal | BinExpr
Defensive patterns
Strategy: validation
Validate before calling
// detect duplicate node names before parsing
fn has_duplicate_nodes(src: &str) -> bool {
let mut names: Vec<&str> = src.lines()
.filter(|l| l.contains('=') && !l.trim_start().starts_with('|'))
.filter_map(|l| l.split('=').next())
.map(|s| s.trim())
.collect();
let n = names.len();
names.sort_unstable();
names.dedup().len() != n
} Try / catch
match ungrammar::parse(src) {
Ok(g) => g,
Err(e) if e.to_string().starts_with("duplicate rule") => {
eprintln!("dedupe your grammar: {}", e);
ungrammar::parse(&dedupe(src)).expect("deduped grammar parses")
}
Err(e) => return Err(e.into()),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- unexpected token, expected `{}`
- Undefined node: {}
- expected ident
- The first element in a sequence of productions or alternativ
- unexpected token
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/adf7e9f2124bbe8a.
Report an issue: GitHub.