pydantic/monty · critical

LoadCell/StoreCell: expected cell reference in local slot {s

Error message

LoadCell/StoreCell: expected cell reference in local slot {slot}, found {other:?}

What it means

`cell_id_from_local` extracts a cell's HeapId from a frame's local slot; this panic fires when the slot holds a non-ref value instead of the expected `Value::Ref(cell_id)`. Because the compiler guarantees cell-holding slots are initialized with cell refs before LoadCell/StoreCell run, seeing another value means frame setup or slot writes broke the invariant — an internal error, not a Python exception.

Source

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

            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(())
        }
    }

    /// Extracts the cell `HeapId` from a local variable slot on the stack.
    ///
    /// Cell variables are stored as `Value::Ref(cell_id)` in the frame's locals region.
    fn cell_id_from_local(&self, slot: u16) -> HeapId {
        match &self.stack[self.current_frame.stack_base + slot as usize] {
            Value::Ref(cell_id) => *cell_id,
            other => panic!("LoadCell/StoreCell: expected cell reference in local slot {slot}, found {other:?}"),
        }
    }

    /// Whether `slot` holds a cell captured from an enclosing function (a
    /// free variable), as opposed to a cell this frame owns. Module frames
    /// (`function_id: None`) own all their cells — the only module-level
    /// cells are inlined-comprehension captures.
    fn is_free_var_slot(&self, slot: u16) -> bool {
        self.current_frame().function_id.is_some_and(|id| {
            self.interns
                .get_function(id)
                .free_var_slots
                .iter()
                .any(|s| s.as_u16() == slot)
        })
    }

    /// Creates a NameError for an unbound free variable.

View on GitHub (pinned to adc986b362)

Solutions

  1. Report the reproducer to Monty maintainers — a cell slot contains a non-ref value
  2. Check that compiler cell-slot allocation matches the VM's `current_frame.stack_base + slot` indexing after any frame-layout change
  3. Run closure-heavy test cases (`make test-cases`) plus memory-model-checks to isolate the frame-setup bug

Example fix

// not applicable — internal invariant; the panic message includes the offending value for diagnosis
Defensive patterns

Strategy: fallback

Try / catch

// Internal panic — no user-side catch; isolate and report.
match monty.run(code, limits) {
    Ok(res) => res,
    Err(e) => report_bug(code, e),
}

Prevention

When it happens

Trigger: Executing LoadCell or StoreCell (or DeleteCell, which shares this helper) when the local slot for a cell variable contains a plain value rather than a cell reference; reachable only through a VM/compiler bug or heap corruption.

Common situations: Altering bytecode emission for closures in the compiler; changing frame layout (stack_base / locals region) so slots shift; fuzzing deep closure patterns.

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/bf3b37be6cd7fb47. Report an issue: GitHub.