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

  1. Reduce the triggering Rust source to a minimal file and check whether the parse tree contains a non-block node in block position.
  2. Update rust-analyzer — this is usually an internal bug fixed upstream; search the issue tracker for 'not a BlockExpr'.
  3. If triggered by a macro, rewrite or fix the macro so it expands to a proper block expression.
  4. 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

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


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