Skip to content

Commit 681df79

Browse files
jgarzikclaude
andcommitted
Fix list contains reflected __eq__, update bugs.md
list_contains now tries the search value's __eq__ when the element's __eq__ returns NotImplemented. This makes ALWAYS_EQ/NEVER_EQ sentinel comparison patterns work correctly for list `in` operator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 87790a9 commit 681df79

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

bugs.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@
101101
**Root cause**: `dict_keys_equal` returned "not equal" for any non-string, non-numeric heap pointers. Tuple keys, frozenset keys, etc. all failed equality checks.
102102
**Fix**: Add `.dke_try_richcompare` fallback that calls `tp_richcompare(PY_EQ)` + `obj_is_true` for non-string heap pointer keys.
103103

104+
### 21. assertRaises double-free on fn(*args) exception (opcodes_call.asm)
105+
**Symptom**: `assertRaises(ValueError, bad)` crashes with double-free
106+
**Root cause**: CALL_FUNCTION_EX freed cfex_temp_pending, then eval_exception_unwind freed it again
107+
**Fix**: Clear cfex_temp_pending before the call, not after
108+
109+
### 22. list `in` operator doesn't call reflected __eq__ (list.asm)
110+
**Symptom**: `ALWAYS_EQ in [1]` returns False
111+
**Root cause**: list_contains only tried element's __eq__, not value's reflected __eq__
112+
**Fix**: Added `.try_reflected` path in list_contains
113+
104114
### Known Bugs Not Yet Fixed
105115
- `dict.update(x=1, y=2)` with kwargs segfaults (methods.asm)
106116
- `repr(d.keys())` returns wrong value (dict view repr not implemented)

src/pyo/list.asm

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,65 @@ DEF_FUNC list_contains, LC_FRAME
11621162
call rax
11631163
; Check for NotImplemented (NULL return = tag 0)
11641164
test edx, edx
1165+
jnz .elem_check_result
1166+
; Element's __eq__ returned NotImplemented — try reflected (value.__eq__(elem))
1167+
jmp .try_reflected
1168+
1169+
.try_reflected:
1170+
; Try the VALUE's __eq__ (reflected comparison: value.__eq__(elem))
1171+
mov rdi, [rbp - LC_VPAY] ; value payload
1172+
mov r8d, [rbp - LC_VTAG] ; value tag
1173+
; Resolve value type
1174+
cmp r8d, TAG_PTR
1175+
jne .try_reflected_nonptr
1176+
mov rax, [rdi + PyObject.ob_type]
1177+
jmp .try_reflected_have_type
1178+
.try_reflected_nonptr:
1179+
cmp r8d, TAG_SMALLINT
1180+
jne .next
1181+
lea rax, [rel int_type]
1182+
.try_reflected_have_type:
1183+
mov rax, [rax + PyTypeObject.tp_richcompare]
1184+
test rax, rax
1185+
jnz .try_reflected_richcmp
1186+
; No tp_richcompare — try dunder on heaptype
1187+
mov rax, [rdi + PyObject.ob_type]
1188+
mov rdx, [rax + PyTypeObject.tp_flags]
1189+
test rdx, TYPE_FLAG_HEAPTYPE
1190+
jz .next
1191+
; Reload elem payload + tag for the reflected call
1192+
mov rax, [rbp - LC_IDX]
1193+
mov rbx, [rbp - LC_LIST]
1194+
mov rbx, [rbx + PyListObject.ob_item]
1195+
mov rdx, [rbp - LC_LIST]
1196+
mov rdx, [rdx + PyListObject.ob_item_tags]
1197+
mov rsi, [rbx + rax * 8] ; elem payload
1198+
movzx ecx, byte [rdx + rax] ; elem tag
1199+
; dunder_call_2(self=value, other=elem, "__eq__", other_tag=elem_tag)
1200+
; rdi = value (already set)
1201+
CSTRING rdx, "__eq__"
1202+
call dunder_call_2
1203+
test edx, edx
1204+
jz .next
1205+
jmp .elem_check_result
1206+
.try_reflected_richcmp:
1207+
; Reload elem for reflected call
1208+
push rax ; save richcompare func
1209+
mov rcx, [rbp - LC_IDX]
1210+
mov rbx, [rbp - LC_LIST]
1211+
mov rbx, [rbx + PyListObject.ob_item]
1212+
mov rdx, [rbp - LC_LIST]
1213+
mov rdx, [rdx + PyListObject.ob_item_tags]
1214+
mov rsi, [rbx + rcx * 8] ; elem payload
1215+
movzx r12d, byte [rdx + rcx] ; elem tag
1216+
pop rax
1217+
; tp_richcompare(value, elem, PY_EQ, value_tag, elem_tag)
1218+
; rdi = value (already set)
1219+
mov edx, PY_EQ
1220+
mov rcx, [rbp - LC_VTAG] ; self_tag = value_tag
1221+
mov r8, r12 ; other_tag = elem_tag
1222+
call rax
1223+
test edx, edx
11651224
jz .next
11661225

11671226
.elem_check_result:

0 commit comments

Comments
 (0)