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

  1. Find the producer of the Undefined value (usually a name-resolution or slot-initialization path) and make it raise NameError instead of yielding Undefined.
  2. Add an upstream assertion so Undefined is caught at the point of creation rather than at the host boundary.
  3. 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

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


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