rust-lang/rust-analyzer · error
Undefined node: {}
Error message
Undefined node: {} What it means
After parsing, `finish` walks all declared nodes and bails 'Undefined node: <name>' if any node's rule is still the DUMMY_RULE sentinel — i.e. the node was referenced (or declared) but never given a rule definition. It guarantees every node in the grammar is fully defined.
Source
Thrown at lib/ungrammar/src/parser.rs:65
self.tokens.iter().nth_back(n)
}
fn bump(&mut self) -> Result<lexer::Token> {
self.tokens.pop().ok_or_else(|| format_err!("unexpected EOF"))
}
fn expect(&mut self, kind: TokenKind, what: &str) -> Result<()> {
let token = self.bump()?;
if token.kind != kind {
bail!(token.loc, "unexpected token, expected `{}`", what);
}
Ok(())
}
fn is_eof(&self) -> bool {
self.tokens.is_empty()
}
fn finish(self) -> Result<Grammar> {
for node_data in &self.grammar.nodes {
if matches!(node_data.rule, DUMMY_RULE) {
crate::error::bail!("Undefined node: {}", node_data.name)
}
}
Ok(self.grammar)
}
fn intern_node(&mut self, name: String) -> Node {
let len = self.node_table.len();
let grammar = &mut self.grammar;
*self.node_table.entry(name.clone()).or_insert_with(|| {
grammar.nodes.push(NodeData { name, rule: DUMMY_RULE });
Node(len)
})
}
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)View on GitHub (pinned to e8f7e90aa3)
Solutions
- Add the missing rule definition `NodeName = ...` for the reported node
- Check spelling/case of the referenced name against its definition
- Search the grammar for all usages of the name and confirm exactly one `Name =` definition exists
- If the node should be a token instead, quote it ('name') rather than referencing it as a node
Example fix
// before Expr = BinExpr '+' BinExpr // BinExpr never defined // after Expr = BinExpr '+' BinExpr BinExpr = Name
Defensive patterns
Strategy: validation
Validate before calling
// check every referenced node identifier has a `Name =` definition
fn undefined_nodes(src: &str) -> Vec<&str> {
let defined: std::collections::HashSet<&str> = src.lines()
.filter_map(|l| l.split_once('='))
.map(|(n, _)| n.trim())
.collect();
src.split(|c: char| !c.is_alphanumeric() && c != '_')
.filter(|w| !w.is_empty() && w.chars().next().map(|c| c.is_uppercase()).unwrap_or(false))
.filter(|w| !defined.contains(*w) && !"Node".eq(*w))
.collect()
} Try / catch
match ungrammar::Grammar::parse(src) {
Err(e) if e.to_string().starts_with("Undefined node") => {
eprintln!("define the missing node rule: {e}")
}
r => r?,
} Prevention
- Grep the grammar for each node name and confirm one `Name =` definition exists
- Rename references and definitions together (single commit)
- Parse the grammar in CI so undefined references fail builds
When it happens
Trigger: Referencing a node name in a rule without ever defining `ThatNode = ...`; forward references that were never resolved because the defining rule is missing or misspelled (case-sensitive).
Common situations: Refactoring grammar names and forgetting to update one reference; typos in node names; deleting a rule that is still referenced elsewhere.
Related errors
- unexpected token, expected `{}`
- expected ident
- duplicate rule: `{}`
- 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/80d0ee4d38c78868.
Report an issue: GitHub.