{"record":{"id":"81126dbbd886c464","repo":"pydantic/monty","slug":"python-values-are-not-hashable","errorCode":null,"errorMessage":"{} python values are not hashable","messagePattern":"(.+?) python values are not hashable","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty-types/src/object.rs","lineNumber":642,"sourceCode":"                }\n            }\n            Self::Float(f) => f.to_bits().hash(state),\n            Self::String(string) => string.hash(state),\n            Self::Bytes(bytes) => bytes.hash(state),\n            Self::Date(date) => date.hash(state),\n            Self::DateTime(datetime) => datetime.hash(state),\n            Self::Time(time) => time.hash(state),\n            Self::TimeDelta(delta) => delta.hash(state),\n            Self::TimeZone(timezone) => timezone.hash(state),\n            Self::Path(path) => path.hash(state),\n            Self::FileHandle(MontyFileHandle { path, mode, position }) => {\n                path.hash(state);\n                mode.as_str().hash(state);\n                position.hash(state);\n            }\n            Self::Type(t) => t.name().hash(state),\n            Self::Cycle(_, _) => panic!(\"cycle values are not hashable\"),\n            _ => panic!(\"{} python values are not hashable\", self.type_name()),\n        }\n    }\n}\n\nimpl PartialEq for MontyObject {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Ellipsis, Self::Ellipsis) => true,\n            (Self::NotImplemented, Self::NotImplemented) => true,\n            (Self::None, Self::None) => true,\n            (Self::Bool(a), Self::Bool(b)) => a == b,\n            (Self::Int(a), Self::Int(b)) => a == b,\n            (Self::BigInt(a), Self::BigInt(b)) => a == b,\n            // Cross-compare Int and BigInt without allocating a temporary BigInt.\n            (Self::Int(a), Self::BigInt(b)) | (Self::BigInt(b), Self::Int(a)) => b.to_i64() == Some(*a),\n            // Use to_bits() for float comparison to be consistent with Hash\n            (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),\n            (Self::String(a), Self::String(b)) => a == b,","sourceCodeStart":624,"sourceCodeEnd":660,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-types/src/object.rs#L624-L660","documentation":"Fallback panic in MontyObject::hash for any Python value type that has no defined hash (lists, dicts, sets, etc.), mirroring CPython's `unhashable type` behavior but as an unrecoverable Rust panic rather than a TypeError. The message names the offending type via type_name().","triggerScenarios":"Calling hash()/hash_of on a MontyObject whose variant is an unhashable container or object — e.g. using a list or dict as a dict key or in a set in host-side code that invokes this hash implementation.","commonSituations":"Host code using MontyObject values as HashMap keys without filtering; converting host-side sets keyed by returned sandbox values; a missing match arm for a newly added type landing in the `_` catch-all.","solutions":["Only hash hashable variants: check the type (e.g. matches!(v, MontyObject::Int(_) | MontyObject::Str(_), ...)) before hashing","Use a total, structural hash you control (e.g. hash of repr/JSON) for containers instead of this method","If a legitimate type hits this arm, add an explicit hash arm in crates/monty-types/src/object.rs for it"],"exampleFix":"// before\nmap.insert(unhashable_value, data); // panics: list python values are not hashable\n\n// after\nif is_hashable(&unhashable_value) {\n    map.insert(unhashable_value, data);\n} else {\n    map.insert(repr(&unhashable_value), data);\n}","handlingStrategy":"validation","validationCode":"fn is_hashable(v: &MontyObject) -> bool {\n    use MontyObject::*;\n    !matches!(v, List(_) | Dict(_) | Set(_) | Bytearray(_) | Cycle(_, _))\n}","typeGuard":"fn is_hashable(v: &MontyObject) -> bool {\n    use MontyObject::*;\n    matches!(v, None | Bool(_) | Int(_) | Float(_) | Str(_) | Bytes(_) | Tuple(_) | FrozenSet(_) | FileHandle(_) | Type(_))\n}","tryCatchPattern":"// panics, not Result — check hashability first\nif is_hashable(&v) { map.insert(v, data); } else { map.insert(repr(&v), data); }","preventionTips":["Treat containers (list/dict/set) as unhashable exactly like CPython","Key maps on primitives or repr strings when unsure","When adding new MontyObject variants, add an explicit hash arm instead of relying on the `_` fallback"],"tags":["rust","hashing","unhashable-type","monty-object"],"backgroundTag":"unsupported-operation","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}