{"record":{"id":"bad655acf4bc9b3a","repo":"pydantic/monty","slug":"undefined-found-while-converting-to-montyobject","errorCode":null,"errorMessage":"Undefined found while converting to MontyObject","messagePattern":"Undefined found while converting to MontyObject","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty/src/object_bridge.rs","lineNumber":318,"sourceCode":"    /// `vm.heap.read(id)` so the resulting [`HeapRead`] keeps the heap entry\n    /// alive (through its reader count) without retaining a borrow on\n    /// `vm.heap`. Recursing can run a user-defined `__repr__` (via\n    /// [`repr_or_error`] on nested instances), so mutable containers (list,\n    /// dict, set, dataclass attrs) snapshot ALL children up front — the\n    /// `inc_ref`s keep each child alive and the snapshot keeps iteration valid\n    /// even if that `__repr__` mutates the container. Immutable containers\n    /// (tuple, namedtuple, frozenset) clone per-item: their length and slots\n    /// cannot change mid-iteration.\n    fn from_value_inner(object: &Value, vm: &mut VM<'_>, visited: &mut AHashSet<HeapId>) -> Self {\n        // Check depth limit before processing\n        let Ok(mut guard) = vm.recursion_guard() else {\n            return Self::Repr(\"<deeply nested>\".to_owned());\n        };\n        let vm = &mut *guard;\n\n        let interns = vm.interns;\n        match object {\n            Value::Undefined => panic!(\"Undefined found while converting to MontyObject\"),\n            Value::Ellipsis => Self::Ellipsis,\n            Value::NotImplemented => Self::NotImplemented,\n            Value::None => Self::None,\n            Value::Bool(b) => Self::Bool(*b),\n            Value::Int(i) => Self::Int(*i),\n            Value::Float(f) => Self::Float(*f),\n            Value::InternString(string_id) => Self::String(interns.get_str(*string_id).to_owned()),\n            Value::InternBytes(bytes_id) => Self::Bytes(interns.get_bytes(*bytes_id).to_owned()),\n            Value::InternLongInt(li_id) => Self::BigInt(interns.get_long_int(*li_id).clone()),\n            Value::Ref(id) => {\n                // Check for cycle\n                if visited.contains(id) {\n                    // Cycle detected - return appropriate placeholder\n                    return match vm.heap.get(*id) {\n                        // A deque exports as a list, so it takes a list's placeholder\n                        // (as its repr does too).\n                        HeapData::List(_) | HeapData::Deque(_) => Self::Cycle(id.index(), \"[...]\".to_owned()),\n                        HeapData::Tuple(_) | HeapData::NamedTuple(_) => Self::Cycle(id.index(), \"(...)\".to_owned()),","sourceCodeStart":300,"sourceCodeEnd":336,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/object_bridge.rs#L300-L336","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet obj = MontyObject::from_value(value, vm)?;\n// after\nassert!(!matches!(value, Value::Undefined), 'undefined leaked to bridge');\nlet obj = MontyObject::from_value(value, vm)?;","handlingStrategy":"type-guard","validationCode":"// reject undefined before bridging\nif matches!(value, Value::Undefined) { return Err(BridgeError::undefined_value()); }","typeGuard":"fn is_undefined(v: &Value) -> bool { matches!(v, Value::Undefined) }","tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","internal-invariant","host-bridge"],"backgroundTag":"internal-invariant-violation","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}