{"record":{"id":"87d43994d01b5405","repo":"rust-lang/rust-analyzer","slug":"not-a-blockexpr","errorCode":null,"errorMessage":"not a BlockExpr","messagePattern":"not a BlockExpr","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/span/src/ast_id.rs","lineNumber":712,"sourceCode":"                                    last_block_node,\n                                    already_allocated @ ContainsItems::No,\n                                )) = blocks.last_mut()\n                                    && (is_item\n                                        || (kind == ErasedFileAstIdKind::MacroCall && {\n                                            let mut anc = node.ancestors();\n                                            _ = anc.next();\n                                            anc.next().is_some_and(|it| {\n                                                it.kind() == SyntaxKind::MACRO_EXPR\n                                            }) && anc.next().is_some_and(|it| {\n                                                it.kind() == SyntaxKind::EXPR_STMT\n                                                    || it.kind() == SyntaxKind::STMT_LIST\n                                            })\n                                        }))\n                                {\n                                    let parent = parent_of(parent_idx, &res);\n                                    let block_ast_id =\n                                        block_expr_ast_id(last_block_node, &mut index_map, parent)\n                                            .expect(\"not a BlockExpr\");\n                                    res.arena\n                                        .alloc((SyntaxNodePtr::new(last_block_node), block_ast_id));\n                                    *already_allocated = ContainsItems::Yes;\n                                }\n\n                                let parent = parent_of(parent_idx, &res);\n                                let ast_id =\n                                    ErasedFileAstId::ast_id_for(&node, &mut index_map, parent)\n                                        .expect(\"this node should have an ast id\");\n                                let idx = res.arena.alloc((SyntaxNodePtr::new(&node), ast_id));\n\n                                next_layer.extend(node.children().map(|child| (child, Some(idx))));\n                                preorder.skip_subtree();\n                            }\n                        }\n                        syntax::WalkEvent::Leave(node) => {\n                            if ast::BlockExpr::can_cast(node.kind()) {\n                                let block = blocks.pop();","sourceCodeStart":694,"sourceCodeEnd":730,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/span/src/ast_id.rs#L694-L730","documentation":"AstIdMap::from_source builds a map of syntax nodes to AST ids for a file. When it descends into an expression-position node it expects the last block-like node to be a BlockExpr and converts it via block_expr_ast_id; if the node is not actually a BlockExpr the expect panics with 'not a BlockExpr'. This is an internal invariant about which nodes are allocated as item-free blocks.","triggerScenarios":"A malformed or unusual syntax tree — often produced by hand-built fixtures, macro-expanded or recovered parse trees — where a node classified as the 'last block node' in an item-free block chain fails ast::BlockExpr::cast during from_source/AstIdMap construction (e.g. when computing AST ids for a body with nested trailing expressions).","commonSituations":"Hitting it while working on rust-analyzer itself: changes to block/item inference, misfired macros producing non-block expressions in block position, or fuzzing/corrupted source input creating degenerate parse trees.","solutions":["Reduce the triggering Rust source to a minimal file and check whether the parse tree contains a non-block node in block position.","Update rust-analyzer — this is usually an internal bug fixed upstream; search the issue tracker for 'not a BlockExpr'.","If triggered by a macro, rewrite or fix the macro so it expands to a proper block expression.","For rust-analyzer developers: fix the node-classification logic so only true BlockExprs reach block_expr_ast_id."],"exampleFix":"// macro expands a bare expression where a block is required\n// before\nm!(foo); // expands to `foo` (an expr, not a block)\n// after\nm!({ foo }); // expands to `{ foo }`, a BlockExpr","handlingStrategy":"fallback","validationCode":"if ast::BlockExpr::can_cast(last_block_node.kind()) { /* safe to map */ }","typeGuard":"fn is_block_expr(node: &SyntaxNode) -> bool { ast::BlockExpr::can_cast(node.kind()) }","tryCatchPattern":null,"preventionTips":["Ensure macros expand to block expressions where blocks are required","Keep parser/syntax/span crates version-synced","Fuzz/minimum-reproduce parse trees that reach AstIdMap::from_source"],"tags":["rust-analyzer","ast","internal-invariant","panic"],"backgroundTag":"ast-invariant-violated","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"}