rust-lang/rust-analyzer · error

unmatched closing delimiter from syntax fixup

Error message

unmatched closing delimiter from syntax fixup

What it means

While converting a syntax tree into a token tree (used for macro expansion and syntax fixup), the converter tracks delimiter nesting. If it reaches a closing delimiter character (`)`, `}`, `]`) with no open subtree to close, the fixup logic instead opens a new subtree for it — but only for the three known delimiters; any other char reaching that arm is impossible by construction, so it panics. It signals corrupted delimiter state produced by the syntax fixup heuristics on broken code.

Source

Thrown at crates/syntax-bridge/src/lib.rs:243

                let found_expected_delimiter =
                    builder.expected_delimiters().enumerate().find(|(_, delim)| match delim {
                        tt::DelimiterKind::Parenthesis => char == ')',
                        tt::DelimiterKind::Brace => char == '}',
                        tt::DelimiterKind::Bracket => char == ']',
                        tt::DelimiterKind::Invisible => false,
                    });
                if let Some((idx, _)) = found_expected_delimiter {
                    for _ in 0..=idx {
                        builder.close(span);
                    }
                    continue;
                }

                let delim = match char {
                    '(' => tt::DelimiterKind::Parenthesis,
                    '{' => tt::DelimiterKind::Brace,
                    '[' => tt::DelimiterKind::Bracket,
                    _ => panic!("unmatched closing delimiter from syntax fixup"),
                };

                // Start a new subtree
                builder.open(delim, span);
                continue;
            }
            Some(leaf) => leaf.clone(),
            None => match token.kind(conv) {
                // Desugar doc comments into doc attributes
                kind @ (INNER_DOC_COMMENT | OUTER_DOC_COMMENT) => {
                    let span = conv.span_for(abs_range);
                    conv.convert_doc_comment(&token, kind == INNER_DOC_COMMENT, span, &mut builder);
                    continue;
                }
                kind if kind.is_punct() && kind != UNDERSCORE => {
                    let found_expected_delimiter =
                        builder.expected_delimiters().enumerate().find(|(_, delim)| match delim {
                            tt::DelimiterKind::Parenthesis => kind == T![')'],

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Inspect the input syntax for unbalanced delimiters before conversion (pre-scan and strip stray closers)
  2. Update the syntax fixup logic so every closing delimiter is matched to an open subtree
  3. Capture a minimal reproducing fixture and add it to the fixup tests
Defensive patterns

Strategy: validation

Validate before calling

// Balance-check delimiters before converting to a token tree:
fn delimiters_balanced(tokens: &[tt::TokenTree]) -> bool {
    let mut depth = 0i32;
    for tt in tokens {
        match tt {
            tt::TokenTree::Subtree(_) => { /* handled recursively */ }
            tt::TokenTree::Leaf(tt::Leaf::Punct(p)) => match p.char {
                '(' | '{' | '[' => depth += 1,
                ')' | '}' | ']' => { depth -= 1; if depth < 0 { return false; } }
                _ => {}
            },
            _ => {}
        }
    }
    depth == 0
}

Try / catch

let tt = std::panic::catch_unwind(AssertUnwindSafe(||
    syntax_node_to_token_tree(&node, span_map, span, mode)
)).ok()?; // fall back to reparse without fixup

Prevention

When it happens

Trigger: Calling `syntax_node_to_token_tree`, `syntax_node_to_token_tree_modified`, or `parse_to_token_tree(_static_span)` on code where the fixup introduced unbalanced closing delimiters, combined with a `char` that is not one of `(`/`{`/`[` in the close-handling arm — a bug in fixup token classification rather than user input.

Common situations: Users typing incomplete code with stray closing brackets while rust-analyzer computes macro expansions/completions; fuzzer-generated broken syntax; changes to the fixup algorithm that misclassify punctuation.

Related errors


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