pydantic/monty · error
Undefined found while converting to MontyObject
Error message
Undefined found while converting to MontyObject
What it means
Converting an interpreter Value into a host-facing MontyObject panics on the Value::Undefined variant. Undefined is an internal sentinel that must never survive to the host boundary; hitting the panic means an undefined value leaked out of the VM, which is an interpreter bug rather than user error.
Source
Thrown at crates/monty/src/object_bridge.rs:318
/// `vm.heap.read(id)` so the resulting [`HeapRead`] keeps the heap entry
/// alive (through its reader count) without retaining a borrow on
/// `vm.heap`. Recursing can run a user-defined `__repr__` (via
/// [`repr_or_error`] on nested instances), so mutable containers (list,
/// dict, set, dataclass attrs) snapshot ALL children up front — the
/// `inc_ref`s keep each child alive and the snapshot keeps iteration valid
/// even if that `__repr__` mutates the container. Immutable containers
/// (tuple, namedtuple, frozenset) clone per-item: their length and slots
/// cannot change mid-iteration.
fn from_value_inner(object: &Value, vm: &mut VM<'_>, visited: &mut AHashSet<HeapId>) -> Self {
// Check depth limit before processing
let Ok(mut guard) = vm.recursion_guard() else {
return Self::Repr("<deeply nested>".to_owned());
};
let vm = &mut *guard;
let interns = vm.interns;
match object {
Value::Undefined => panic!("Undefined found while converting to MontyObject"),
Value::Ellipsis => Self::Ellipsis,
Value::NotImplemented => Self::NotImplemented,
Value::None => Self::None,
Value::Bool(b) => Self::Bool(*b),
Value::Int(i) => Self::Int(*i),
Value::Float(f) => Self::Float(*f),
Value::InternString(string_id) => Self::String(interns.get_str(*string_id).to_owned()),
Value::InternBytes(bytes_id) => Self::Bytes(interns.get_bytes(*bytes_id).to_owned()),
Value::InternLongInt(li_id) => Self::BigInt(interns.get_long_int(*li_id).clone()),
Value::Ref(id) => {
// Check for cycle
if visited.contains(id) {
// Cycle detected - return appropriate placeholder
return match vm.heap.get(*id) {
// A deque exports as a list, so it takes a list's placeholder
// (as its repr does too).
HeapData::List(_) | HeapData::Deque(_) => Self::Cycle(id.index(), "[...]".to_owned()),
HeapData::Tuple(_) | HeapData::NamedTuple(_) => Self::Cycle(id.index(), "(...)".to_owned()),View on GitHub (pinned to adc986b362)
Solutions
- Find the producer of the Undefined value (usually a name-resolution or slot-initialization path) and make it raise NameError instead of yielding Undefined.
- Add an upstream assertion so Undefined is caught at the point of creation rather than at the host boundary.
- If you control the value source, validate the VM result for Undefined before invoking the conversion.
Example fix
// before let obj = MontyObject::from_value(value, vm)?; // after assert!(!matches!(value, Value::Undefined), 'undefined leaked to bridge'); let obj = MontyObject::from_value(value, vm)?;
Defensive patterns
Strategy: type-guard
Validate before calling
// reject undefined before bridging
if matches!(value, Value::Undefined) { return Err(BridgeError::undefined_value()); } Type guard
fn is_undefined(v: &Value) -> bool { matches!(v, Value::Undefined) } Prevention
- Make name-resolution paths raise NameError instead of yielding Undefined
- Assert no Undefined values at VM return boundaries in debug builds
- Cover new opcodes with tests that return every value variant
When it happens
Trigger: Calling the host bridge (MontyObject::from_value / return-value conversion) on a result containing Value::Undefined — e.g. an uninitialized slot, a failed lookup that returned Undefined instead of raising, or a value read from a stale scope cell.
Common situations: Contributors adding new opcodes or name-resolution paths that leave a slot Undefined; binding layers (pydantic_monty, monty-js) surfacing results that never should have contained Undefined.
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
- gather commit frame id is not a GatherFuture
- gather item is not a Coroutine, ExternalFuture, or GatherFut
- Cannot get identity of Dereferenced object
- invalid ascii byte
- Dereferenced found while converting to MontyObject
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/bad655acf4bc9b3a.
Report an issue: GitHub.