{"record":{"id":"524540e65d7764ef","repo":"rust-lang/rust-analyzer","slug":"unmatched-closing-delimiter-from-syntax-fixup","errorCode":null,"errorMessage":"unmatched closing delimiter from syntax fixup","messagePattern":"unmatched closing delimiter from syntax fixup","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/syntax-bridge/src/lib.rs","lineNumber":243,"sourceCode":"                let found_expected_delimiter =\n                    builder.expected_delimiters().enumerate().find(|(_, delim)| match delim {\n                        tt::DelimiterKind::Parenthesis => char == ')',\n                        tt::DelimiterKind::Brace => char == '}',\n                        tt::DelimiterKind::Bracket => char == ']',\n                        tt::DelimiterKind::Invisible => false,\n                    });\n                if let Some((idx, _)) = found_expected_delimiter {\n                    for _ in 0..=idx {\n                        builder.close(span);\n                    }\n                    continue;\n                }\n\n                let delim = match char {\n                    '(' => tt::DelimiterKind::Parenthesis,\n                    '{' => tt::DelimiterKind::Brace,\n                    '[' => tt::DelimiterKind::Bracket,\n                    _ => panic!(\"unmatched closing delimiter from syntax fixup\"),\n                };\n\n                // Start a new subtree\n                builder.open(delim, span);\n                continue;\n            }\n            Some(leaf) => leaf.clone(),\n            None => match token.kind(conv) {\n                // Desugar doc comments into doc attributes\n                kind @ (INNER_DOC_COMMENT | OUTER_DOC_COMMENT) => {\n                    let span = conv.span_for(abs_range);\n                    conv.convert_doc_comment(&token, kind == INNER_DOC_COMMENT, span, &mut builder);\n                    continue;\n                }\n                kind if kind.is_punct() && kind != UNDERSCORE => {\n                    let found_expected_delimiter =\n                        builder.expected_delimiters().enumerate().find(|(_, delim)| match delim {\n                            tt::DelimiterKind::Parenthesis => kind == T![')'],","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/syntax-bridge/src/lib.rs#L225-L261","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Balance-check delimiters before converting to a token tree:\nfn delimiters_balanced(tokens: &[tt::TokenTree]) -> bool {\n    let mut depth = 0i32;\n    for tt in tokens {\n        match tt {\n            tt::TokenTree::Subtree(_) => { /* handled recursively */ }\n            tt::TokenTree::Leaf(tt::Leaf::Punct(p)) => match p.char {\n                '(' | '{' | '[' => depth += 1,\n                ')' | '}' | ']' => { depth -= 1; if depth < 0 { return false; } }\n                _ => {}\n            },\n            _ => {}\n        }\n    }\n    depth == 0\n}","typeGuard":null,"tryCatchPattern":"let tt = std::panic::catch_unwind(AssertUnwindSafe(||\n    syntax_node_to_token_tree(&node, span_map, span, mode)\n)).ok()?; // fall back to reparse without fixup","preventionTips":["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"],"tags":["rust","panic","macros","syntax-fixup","token-tree"],"backgroundTag":"unbalanced-delimiter-fixup","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}