Fix to_hashable traversal stack handling

This commit is contained in:
xmarre 2026-03-16 15:34:15 +01:00
parent 6158cd5820
commit a6472b1514
2 changed files with 23 additions and 7 deletions

View File

@ -275,7 +275,8 @@ def to_hashable(obj, max_nodes=_MAX_SIGNATURE_CONTAINER_VISITS):
snapshots = {}
sort_memo = {}
processed = 0
stack = [(obj, False)]
# Keep traversal state separate from container snapshots/results.
work_stack = [(obj, False)]
def resolve_value(value):
"""Resolve a child value from the completed memo table when available."""
@ -309,8 +310,8 @@ def to_hashable(obj, max_nodes=_MAX_SIGNATURE_CONTAINER_VISITS):
return (container_tag, tuple(value for _, value in ordered_items))
while stack:
current, expanded = stack.pop()
while work_stack:
current, expanded = work_stack.pop()
current_type = type(current)
if current_type in _PRIMITIVE_SIGNATURE_TYPES or current_type is Unhashable:
@ -388,7 +389,7 @@ def to_hashable(obj, max_nodes=_MAX_SIGNATURE_CONTAINER_VISITS):
return Unhashable()
active.add(current_id)
stack.append((current, True))
work_stack.append((current, True))
if current_type is dict:
try:
items = list(current.items())
@ -398,8 +399,8 @@ def to_hashable(obj, max_nodes=_MAX_SIGNATURE_CONTAINER_VISITS):
active.discard(current_id)
continue
for key, value in reversed(items):
stack.append((value, False))
stack.append((key, False))
work_stack.append((value, False))
work_stack.append((key, False))
else:
try:
items = list(current)
@ -409,7 +410,7 @@ def to_hashable(obj, max_nodes=_MAX_SIGNATURE_CONTAINER_VISITS):
active.discard(current_id)
continue
for item in reversed(items):
stack.append((item, False))
work_stack.append((item, False))
return memo.get(id(obj), Unhashable())

View File

@ -49,6 +49,21 @@ def test_get_immediate_node_signature_canonicalizes_non_link_inputs(monkeypatch)
)
def test_to_hashable_walks_dicts_without_rebinding_traversal_stack():
live_value = {
"outer": {"nested": [2, 3]},
"items": [{"leaf": 4}],
}
assert caching.to_hashable(live_value) == (
"dict",
(
("items", ("list", (("dict", (("leaf", 4),)),))),
("outer", ("dict", (("nested", ("list", (2, 3))),))),
),
)
def test_get_immediate_node_signature_fails_closed_for_opaque_non_link_input(monkeypatch):
class OpaqueRuntimeValue:
pass