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

  1. Add the missing rule definition `NodeName = ...` for the reported node
  2. Check spelling/case of the referenced name against its definition
  3. Search the grammar for all usages of the name and confirm exactly one `Name =` definition exists
  4. 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

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


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