rust-lang/rust-analyzer · error
MatchArms are always nested in MatchExprs
Error message
MatchArms are always nested in MatchExprs
What it means
`ast::MatchArm::parent_match()` climbs two parent links from a match arm and casts to `ast::MatchExpr`. The library asserts that a `MatchArm` is always nested inside `MATCH_ARM_LIST` inside a `MatchExpr`, so failure to cast is treated as an impossible corrupt-tree condition and panics via `expect`. It is an internal invariant check, not a designed failure path.
Source
Thrown at crates/syntax/src/ast/node_ext.rs:1203
ast::Adt::Struct(it) => ast::Item::Struct(it),
ast::Adt::Union(it) => ast::Item::Union(it),
}
}
}
impl ast::MatchGuard {
pub fn condition(&self) -> Option<ast::Expr> {
support::child(&self.syntax)
}
}
impl ast::MatchArm {
pub fn parent_match(&self) -> ast::MatchExpr {
self.syntax()
.parent()
.and_then(|it| it.parent())
.and_then(ast::MatchExpr::cast)
.expect("MatchArms are always nested in MatchExprs")
}
}
impl From<ast::Item> for ast::AnyHasAttrs {
fn from(node: ast::Item) -> Self {
Self::new(node)
}
}
impl From<ast::AssocItem> for ast::AnyHasAttrs {
fn from(node: ast::AssocItem) -> Self {
Self::new(node)
}
}
impl ast::OrPat {
pub fn leading_pipe(&self) -> Option<SyntaxToken> {
self.syntaxView on GitHub (pinned to e8f7e90aa3)
Solutions
- Call `parent_match()` only on arms freshly obtained from a live, attached tree
- Refresh node references after any tree mutation instead of reusing pre-edit handles
- Use `and_then(ast::MatchExpr::cast)` to get an `Option<ast::MatchExpr>` when the tree provenance is uncertain
- Check `node.syntax().parent()` chain manually if you only need the enclosing expression loosely
Example fix
// before
let match_expr = arm.parent_match();
// after
let Some(match_expr) = arm.syntax().parent().and_then(|p| p.parent()).and_then(ast::MatchExpr::cast) else { return None; }; Defensive patterns
Strategy: validation
Validate before calling
fn is_attached_match_arm(arm: &ast::MatchArm) -> bool {
arm.syntax().parent()
.and_then(|p| p.parent())
.and_then(ast::MatchExpr::cast)
.is_some()
} Type guard
fn parent_match(arm: &ast::MatchArm) -> Option<ast::MatchExpr> {
arm.syntax().parent()?.parent().and_then(ast::MatchExpr::cast)
} Prevention
- Discard and re-resolve node references after tree edits/rewrites
- Never call parent helpers on synthetic nodes not built inside a real MatchExpr
- Wrap uncertain traversals in Option-based casts rather than the expect helpers
When it happens
Trigger: Calling `parent_match()` on a `MatchArm` that has been detached from its tree (e.g. removed or moved during an edit/rewrite), a stale arm handle after the enclosing match was replaced, or any hand-built/invalid tree where the arm's grandparent is not a `MatchExpr`.
Common situations: Holding node references across syntax-tree mutations in IDE refactors or rust-analyzer assist code; building synthetic match arms in tests or codegen without a proper match expression wrapper; running old code against trees produced by a changed grammar.
Related errors
- EnumVariants are always nested in Enums
- equivalent ancestor node should be present in target tree
- the nearest mapped ancestor must map its descendants
- We explicitly do not provide canonicalization API, as that i
- bad kind {other}
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/f100f8fe8c73cdb3.
Report an issue: GitHub.