rust-lang/rust-analyzer · error
not a BlockExpr
Error message
not a BlockExpr
What it means
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.
Source
Thrown at crates/span/src/ast_id.rs:712
last_block_node,
already_allocated @ ContainsItems::No,
)) = blocks.last_mut()
&& (is_item
|| (kind == ErasedFileAstIdKind::MacroCall && {
let mut anc = node.ancestors();
_ = anc.next();
anc.next().is_some_and(|it| {
it.kind() == SyntaxKind::MACRO_EXPR
}) && anc.next().is_some_and(|it| {
it.kind() == SyntaxKind::EXPR_STMT
|| it.kind() == SyntaxKind::STMT_LIST
})
}))
{
let parent = parent_of(parent_idx, &res);
let block_ast_id =
block_expr_ast_id(last_block_node, &mut index_map, parent)
.expect("not a BlockExpr");
res.arena
.alloc((SyntaxNodePtr::new(last_block_node), block_ast_id));
*already_allocated = ContainsItems::Yes;
}
let parent = parent_of(parent_idx, &res);
let ast_id =
ErasedFileAstId::ast_id_for(&node, &mut index_map, parent)
.expect("this node should have an ast id");
let idx = res.arena.alloc((SyntaxNodePtr::new(&node), ast_id));
next_layer.extend(node.children().map(|child| (child, Some(idx))));
preorder.skip_subtree();
}
}
syntax::WalkEvent::Leave(node) => {
if ast::BlockExpr::can_cast(node.kind()) {
let block = blocks.pop();View on GitHub (pinned to e8f7e90aa3)
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.
Example fix
// macro expands a bare expression where a block is required
// before
m!(foo); // expands to `foo` (an expr, not a block)
// after
m!({ foo }); // expands to `{ foo }`, a BlockExpr Defensive patterns
Strategy: fallback
Validate before calling
if ast::BlockExpr::can_cast(last_block_node.kind()) { /* safe to map */ } Type guard
fn is_block_expr(node: &SyntaxNode) -> bool { ast::BlockExpr::can_cast(node.kind()) } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- this node should have an ast id
- Can't find SyntaxNodePtr {:?} in AstIdMap: {:?}
- AstIdMap node mismatch with node `{ptr:?}`
- Can't find ast id {:?} in AstIdMap: {:?}
- dynamic `SyntaxKind` for `AstNode::kind()`
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/87d43994d01b5405.
Report an issue: GitHub.