rust-lang/rust · error

cloning statement `NodeId`s is prohibited by default, the vi

Error message

cloning statement `NodeId`s is prohibited by default, the visitor should implement custom statement visiting

What it means

This panic in `walk_flat_map_stmt` fires when a `MutVisitor`'s `flat_map_stmt` expands a single statement into *multiple* statements (more than one element in the returned `SmallVec`). The default `NodeId`-cloning behavior would assign the same `NodeId` to all resulting statements, which corrupts node identity; the visitor must instead override statement visiting to assign fresh ids.

Source

Thrown at compiler/rustc_ast/src/mut_visit.rs:356

pub fn walk_filter_map_expr<T: MutVisitor>(vis: &mut T, mut e: Box<Expr>) -> Option<Box<Expr>> {
    vis.visit_expr(&mut e);
    Some(e)
}

pub fn walk_flat_map_stmt<T: MutVisitor>(
    vis: &mut T,
    Stmt { kind, span, mut id }: Stmt,
) -> SmallVec<[Stmt; 1]> {
    vis.visit_id(&mut id);
    let mut stmts: SmallVec<[Stmt; 1]> = walk_flat_map_stmt_kind(vis, kind)
        .into_iter()
        .map(|kind| Stmt { id, kind, span })
        .collect();
    match &mut stmts[..] {
        [] => {}
        [stmt] => vis.visit_span(&mut stmt.span),
        _ => panic!(
            "cloning statement `NodeId`s is prohibited by default, \
             the visitor should implement custom statement visiting"
        ),
    }
    stmts
}

pub fn walk_flat_map_stmt_kind<T: MutVisitor>(
    vis: &mut T,
    kind: StmtKind,
) -> SmallVec<[StmtKind; 1]> {
    match kind {
        StmtKind::Let(mut local) => smallvec![StmtKind::Let({
            vis.visit_local(&mut local);
            local
        })],
        StmtKind::Item(item) => vis.flat_map_item(item).into_iter().map(StmtKind::Item).collect(),
        StmtKind::Expr(expr) => vis.filter_map_expr(expr).into_iter().map(StmtKind::Expr).collect(),

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Override `flat_map_stmt` (or `visit_stmt`) on your `MutVisitor` to assign distinct `NodeId`s to each output statement (e.g., via `NodeId` reservation / `rustc_session`'s id allocator).
  2. Avoid expanding a single statement into multiple in the default walk — restructure so the visitor emits one statement per input, or pre-expands outside the flat-map.
  3. Reproduce with `RUST_BACKTRACE=1` to confirm which `flat_map_*` is multiplying statements.

Example fix

// before
impl<'a> MutVisitor for MyVis { /* no flat_map_stmt override; default walk expands */ }

// after
impl<'a> MutVisitor for MyVis {
    fn flat_map_stmt(&mut self, mut st: Stmt) -> SmallVec<[Stmt; 1]> {
        // assign fresh ids to each expanded statement
        ...
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// The default flat_map visitor clones NodeId when one stmt expands to many.
// Override statement visiting in your MutVisitor:
impl MutVisitor for MyVisitor {
    fn visit_stmt(&mut self, stmt: &mut Stmt) {
        // assign fresh Node_id, or fold flat_map yourself
    }
}

Prevention

When it happens

Trigger: A `MutVisitor` implementation whose default `walk_flat_map_stmt` returns more than one `Stmt` (via `flat_map_stmt_kind` producing multiple `StmtKind`s). This happens when a visitor's `flat_map_item` / `filter_map_expr` (called from `walk_flat_map_stmt_kind`) expands a single `Item`/`Expr` statement into several — e.g., desugaring one stmt into many, or macro-like rewrites.

Common situations: Custom lints, AST rewriters, or desugaring passes in rustc that expand statements but forget to implement the matching `visit_stmt` / `flat_map_stmt` override. Refactors that add expansion behavior to an existing visitor. Hit most often when porting `MutVisitor`-based transformations.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/9da4f4357138331b.json. Report an issue: GitHub.