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
- Report the reproducer to Monty maintainers — a cell slot contains a non-ref value
- Check that compiler cell-slot allocation matches the VM's `current_frame.stack_base + slot` indexing after any frame-layout change
- 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
- Keep compiler cell-slot allocation and VM slot indexing in sync
- Run `make test-cases` on closure tests after frame-layout changes
- Include the panic's offending value (`{other:?}`) in bug reports
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
- LoadCell: entry is not a Cell
- StoreCell: entry is not a Cell
- DeleteCell: entry is not a Cell
- list_extend: expected List on heap
- pending_externals entry doesn't point to an ExternalFuture
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/bf3b37be6cd7fb47.
Report an issue: GitHub.