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.
Solutions
- Inspect the input syntax for unbalanced delimiters before conversion (pre-scan and strip stray closers)
- Update the syntax fixup logic so every closing delimiter is matched to an open subtree
- 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
- Prefer parsing complete buffers; for fragments, keep fixup enabled and current
- Update fixup tests whenever changing delimiter handling
- Fuzz syntax_node_to_token_tree with malformed code containing stray closers
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
- Next token must be ident
- Token from lexer must be single char: token =
- an edit target must still be present
- AstIdMap node mismatch with node
- bad kind
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/524540e65d7764ef.
Report an issue: GitHub.
Appendix: 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)