rust-lang/rust-analyzer · error

unexpected token, expected `{}`

Error message

unexpected token, expected `{}`

What it means

The ungrammar parser's `expect` bumps a token and verifies its kind matches what the grammar rule requires. On mismatch it reports the source location plus 'unexpected token, expected `<what>`', where `what` is a human description of the expected token.

Source

Thrown at lib/ungrammar/src/parser.rs:55

impl Parser {
    fn new(mut tokens: Vec<lexer::Token>) -> Parser {
        tokens.reverse();
        Parser { tokens, ..Parser::default() }
    }

    fn peek(&self) -> Option<&lexer::Token> {
        self.peek_n(0)
    }
    fn peek_n(&self, n: usize) -> Option<&lexer::Token> {
        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(|| {

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Read the attached location and check the token right there against the expected one named in the message
  2. Add the missing `=` after the node name in a rule definition
  3. Balance parentheses in grouping expressions `(...)`
  4. Compare against a working .ungrammar (e.g. rust-analyzer's own rust.ungram) for correct syntax

Example fix

// before
Expr BinaryExpr
// after
Expr = BinaryExpr
Defensive patterns

Strategy: try-catch

Validate before calling

// every top-level rule line should contain '=' before any rule body
fn rule_missing_equals(src: &str) -> Vec<&str> {
    src.lines().filter(|l| {
        let l = l.trim();
        !l.is_empty() && !l.starts_with('=') && l.split_once('=')
            .map(|(name, _)| name.trim().split_whitespace().count() != 1)
            .unwrap_or(!l.starts_with('(') )
    }).collect()
}

Try / catch

match ungrammar::Grammar::parse(src) {
    Err(e) if e.to_string().contains("unexpected token, expected") => {
        eprintln!("syntax error in grammar: {e}")
    }
    r => r?,
}

Prevention

When it happens

Trigger: A `.ungrammar` file violates rule syntax: e.g. missing `=` after a rule name (`Foo bar`), an unbalanced `)` without `(`, or a token appearing where `=`, `|`, `(` etc. is required.

Common situations: Hand-written grammar rules with typos; forgetting `=` between node name and rule; editing rules and deleting a required separator.

Understand the failure class

Related errors


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