rust-lang/rust · critical
no sub-expr expected for {:?}
Error message
no sub-expr expected for {:?} What it means
unreachable! in expr_guaranteed_to_constitute_read_for_never (compiler/rustc_middle/src/hir/mod.rs:299). When walking a place-expression child of a parent Expr, the match covers every ExprKind that can have a value-producing sub-expression; the leaf kinds (ConstBlock, Loop, Lit, Path, Continue, OffsetOf, Err) are listed as unreachable because they have no direct sub-exprs. Hitting this means the HIR contained an Expr child under one of those leaf kinds, which the compiler considers malformed.
Source
Thrown at compiler/rustc_middle/src/hir/mod.rs:299
| ExprKind::Block(_, _)
| ExprKind::AssignOp(_, _, _)
| ExprKind::Index(_, _, _)
| ExprKind::Break(_, _)
| ExprKind::Ret(_)
| ExprKind::Become(_)
| ExprKind::InlineAsm(_)
| ExprKind::Struct(_, _, _)
| ExprKind::Repeat(_, _)
| ExprKind::Yield(_, _) => true,
// These expressions have no (direct) sub-exprs.
ExprKind::ConstBlock(_)
| ExprKind::Loop(_, _, _, _)
| ExprKind::Lit(_)
| ExprKind::Path(_)
| ExprKind::Continue(_)
| ExprKind::OffsetOf(_, _)
| ExprKind::Err(_) => unreachable!("no sub-expr expected for {:?}", expr.kind),
}
}
// If we have a subpattern that performs a read, we want to consider this
// to diverge for compatibility to support something like `let x: () = *never_ptr;`.
Node::LetStmt(LetStmt { init: Some(target), pat, .. }) => {
assert_eq!(target.hir_id, expr.hir_id);
pat.is_guaranteed_to_constitute_read_for_never()
}
// These nodes (if they have a sub-expr) do constitute a read.
Node::Block(_)
| Node::Arm(_)
| Node::ExprField(_)
| Node::AnonConst(_)
| Node::ConstBlock(_)
| Node::ConstArg(_)
| Node::Stmt(_)View on GitHub (pinned to 22057b88b0)
Solutions
- Report as a rustc ICE with the ExprKind shown in the message and the source span.
- Bisect across nightly versions to find the change that introduced the unexpected sub-expression (likely a parser or HIR refactor).
- If you write proc-macros, ensure generated Expr nodes do not attach child Exprs to leaf kinds like Lit/Path/Loop.
Defensive patterns
Strategy: type-guard
Validate before calling
// Before requesting a sub-expression, verify the parent
// expression kind actually carries one.
use rustc_hir::{Expr, ExprKind, HirId};
use rustc_middle::ty::TyCtxt;
fn has_expected_sub_expr(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Binary(..)
| ExprKind::Assign(..)
| ExprKind::AssignOp(..)
| ExprKind::Call(..)
| ExprKind::MethodCall(..)
| ExprKind::Match(..)
)
} Type guard
// Narrow an Expr to one that is safe to descend into for a
// sub-expression.
use rustc_hir::{Expr, ExprKind};
fn expr_with_sub_expr<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
match expr.kind {
ExprKind::Binary(_, lhs, _) => Some(lhs),
ExprKind::Assign(lhs, _, _) => Some(lhs),
ExprKind::Call(callee, _) => Some(callee),
ExprKind::Match(scrut, _, _) => Some(scrut),
_ => None, // kinds with no sub-expr => do not descend
}
} Prevention
- Never call next_sub_expr / sub_expr iteration on an ExprKind you have not matched first
- Handle every ExprKind variant in your visitor, returning early for leaf kinds like Lit, Path, ConstBlock
- Write exhaustive match arms (and rely on the compiler's non-exhaustive warnings) rather than a default arm that descends blindly
When it happens
Trigger: An Expr node whose kind is one of the listed leaf variants unexpectedly has a child expression passed to expr_guaranteed_to_constitute_read_for_never. Caused by malformed HIR from a parser/recovery bug, a proc-macro that synthesized an inconsistent Expr tree, or a refactor adding a new sub-expr to a leaf kind without updating this match.
Common situations: Nightly-only features or proc-macros that build unusual Expr shapes; changes to the never-type/`!` coercion logic; recovery after a syntax error producing an ExprKind::Err with children.
Related errors
- no sub-expr expected for {parent_node:?}
- already handled
- not a module: {node:?}
- processing delegation
- Not a HIR owner
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/c3bce49a265f8fb1.json.
Report an issue: GitHub.