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.syntax

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Call `parent_match()` only on arms freshly obtained from a live, attached tree
  2. Refresh node references after any tree mutation instead of reusing pre-edit handles
  3. Use `and_then(ast::MatchExpr::cast)` to get an `Option<ast::MatchExpr>` when the tree provenance is uncertain
  4. 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

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


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