pydantic/monty · critical

LoadCell: entry is not a Cell

Error message

LoadCell: entry is not a Cell

What it means

`load_cell` reads the heap entry for a local slot's cell to push its value; this panic fires when the entry stored at `cell_id` is not a `Cell`. Cells live at known fixed slots in a frame's locals region, so a mismatch means the heap entry was replaced by another type — an interpreter invariant failure, not a Python-level exception (a genuinely unbound cell raises NameError/UnboundLocalError instead).

Source

Thrown at crates/monty/src/bytecode/vm/mod.rs:2478

        if matches!(self.globals[slot as usize], Value::Undefined) {
            let name = self.global_name(slot);
            return Err(self.name_error(slot, name));
        }
        let old_value = mem::replace(&mut self.globals[slot as usize], Value::Undefined);
        old_value.drop_with(self);
        Ok(())
    }

    /// Loads from a closure cell and pushes onto the stack.
    ///
    /// The cell `HeapId` is read from the frame's local variable slot on the stack
    /// (cells are stored as `Value::Ref(cell_id)` at known positions in the locals region).
    /// Returns a `NameError` if the cell value is undefined (free variable not bound).
    fn load_cell(&mut self, slot: u16) -> RunResult<()> {
        let cell_id = self.cell_id_from_local(slot);
        let value = match self.heap.get(cell_id) {
            HeapData::Cell(c) => c.0.clone_with_heap(self),
            _ => panic!("LoadCell: entry is not a Cell"),
        };

        // An undefined value raises the error CPython picks by cell kind: the
        // free-variable NameError only for a cell *captured* from an enclosing
        // function; an unbound cell this frame owns (a local captured by
        // nested functions) is an ordinary UnboundLocalError, like any local.
        if matches!(value, Value::Undefined) {
            value.drop_with(self);
            let name = self.current_frame.code.local_name(slot);
            Err(if self.is_free_var_slot(slot) {
                self.free_var_error(name)
            } else {
                self.unbound_local_error(slot, name)
            })
        } else {
            self.push(value);
            Ok(())
        }

View on GitHub (pinned to adc986b362)

Solutions

  1. File a bug with the reproducing Python snippet — the cell HeapId points at a non-Cell heap entry
  2. Audit code paths that write to local slots holding cells (StoreCell, frame setup) for type-tagging mistakes
  3. Run the monty test suite plus memory-model-checks to find the refcount/drop path corrupting the entry

Example fix

// not applicable — internal interpreter bug, not a caller-fixable condition
Defensive patterns

Strategy: fallback

Try / catch

// Not a catchable library error — treat as a bug report.
match monty.run(closure_code, limits) {
    Ok(_) => (),
    Err(e) => report_bug_with_repro(closure_code, e),
}

Prevention

When it happens

Trigger: Executing a `LoadCell` opcode (reading a free variable or a local captured by a nested function) when the HeapId in the local slot no longer points to a `HeapData::Cell`; only via heap corruption, an interpreter bug, or fuzzing.

Common situations: Fuzzing Monty (crates/fuzz string_input_panic target); hacking on closures/cell implementation in the VM; a patch that stores a non-cell Value into a cell slot or mishandles heap reuse.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/e5075b21c2f76a4a. Report an issue: GitHub.