{"record":{"id":"d8dddfea10959a9a","repo":"pydantic/monty","slug":"cycle-values-are-not-hashable","errorCode":null,"errorMessage":"cycle values are not hashable","messagePattern":"cycle values are not hashable","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty-types/src/object.rs","lineNumber":641,"sourceCode":"                    bi.to_signed_bytes_le().hash(state);\n                }\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(),","sourceCodeStart":623,"sourceCodeEnd":659,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-types/src/object.rs#L623-L659","documentation":"hash() on a MontyObject hits a dedicated panic for Self::Cycle values. Cycle is an internal placeholder used while hashing/traversing containers to mark a value already being hashed; a top-level hash of a cycle means a self-referential value reached the hash, which has no well-defined hash, so the library panics instead of producing an unstable hash.","triggerScenarios":"Calling hash (via hash_of on the host side) with a MontyObject::Cycle — a self-referential structure produced by deserialization of cyclic Python data — instead of the resolved object it stands for.","commonSituations":"Host code hashing values returned from a Monty run that contained self-referential lists/dicts; custom code constructing MontyObject::Cycle directly; a bug in cycle resolution before hashing.","solutions":["Resolve the cycle before hashing: hash the concrete container the Cycle refers to, or hash a cycle-free copy of the data","Skip hashing values known to be recursive (detect cycles in your own traversal and hash a placeholder like a type name)","Do not construct MontyObject::Cycle manually; let the library's cycle-detecting traversal manage it"],"exampleFix":"// before\nlet h = hash_of(&cycle_marked_value); // panics\n\n// after\nlet resolved = resolve_cycles(value); // produce a DAG/acyclic copy\nlet h = hash_of(&resolved);","handlingStrategy":"validation","validationCode":"// Rust host-side\ncannot_hash_cycles = matches!(value, MontyObject::Cycle(_, _));","typeGuard":"fn is_cycle(v: &MontyObject) -> bool { matches!(v, MontyObject::Cycle(_, _)) }","tryCatchPattern":"// hash() panics rather than returning Err — guard before calling\nassert!(!is_cycle(&value), 'resolve cycles before hashing');\nlet h = hash_of(&value);","preventionTips":["Never construct MontyObject::Cycle in host code","Resolve or copy cycle-free data before hashing","Track recursion yourself and hash a type placeholder for recursive values"],"tags":["rust","hashing","cycle","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"}