Skip to content

Commit 8d6a97b

Browse files
committed
call C len
1 parent 19697af commit 8d6a97b

File tree

2 files changed

+96
-2
lines changed

2 files changed

+96
-2
lines changed

mypyc/irbuild/ll_builder.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,9 +2441,35 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val
24412441
offset = Integer(1, c_pyssize_t_rprimitive, line)
24422442
return self.int_op(short_int_rprimitive, size_value, offset, IntOp.LEFT_SHIFT, line)
24432443

2444+
# --- Optimized dispatch for RInstance (native/user-defined classes) ---
24442445
if isinstance(typ, RInstance):
24452446
# TODO: Support use_pyssize_t
24462447
assert not use_pyssize_t
2448+
class_ir = typ.class_ir
2449+
2450+
# Only optimize for native extension classes (not built-in base, not Python subclass)
2451+
if class_ir.is_ext_class and not class_ir.inherits_python and class_ir.has_method("__len__"):
2452+
# 1. Direct C call for final native methods and exact type
2453+
if class_ir.is_method_final("__len__"):
2454+
decl = class_ir.method_decl("__len__")
2455+
length = self.call(decl, [val], [ARG_POS], [None], line)
2456+
2457+
# Coerce/check result and error handling as before
2458+
length = self.coerce(length, int_rprimitive, line)
2459+
ok, fail = BasicBlock(), BasicBlock()
2460+
cond = self.binary_op(length, Integer(0), ">=", line)
2461+
self.add_bool_branch(cond, ok, fail)
2462+
self.activate_block(fail)
2463+
self.add(
2464+
RaiseStandardError(
2465+
RaiseStandardError.VALUE_ERROR, "__len__() should return >= 0", line
2466+
)
2467+
)
2468+
self.add(Unreachable())
2469+
self.activate_block(ok)
2470+
return length
2471+
2472+
# 4. Fallback: generic method call for non-native or ambiguous cases
24472473
length = self.gen_method_call(val, "__len__", [], int_rprimitive, line)
24482474
length = self.coerce(length, int_rprimitive, line)
24492475
ok, fail = BasicBlock(), BasicBlock()

mypyc/test-data/irbuild-dunders.test

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,92 @@
11
# Test cases for (some) dunder methods
22

33
[case testDundersLen]
4+
from typing import final
5+
46
class C:
57
def __len__(self) -> int:
68
return 2
7-
9+
@final
10+
class D:
11+
def __len__(self) -> int:
12+
return 2
13+
class E:
14+
@final
15+
def __len__(self) -> int:
16+
return 2
17+
class F(C):
18+
"""def __len__(self) -> int:
19+
return 3"""
820
def f(c: C) -> int:
921
return len(c)
22+
def g(d: D) -> int:
23+
return len(d)
24+
def h(e: E) -> int:
25+
return len(e)
26+
def i(f: F) -> int:
27+
return len(f)
1028
[out]
1129
def C.__len__(self):
1230
self :: __main__.C
1331
L0:
1432
return 4
33+
def D.__len__(self):
34+
self :: __main__.D
35+
L0:
36+
return 4
37+
def E.__len__(self):
38+
self :: __main__.E
39+
L0:
40+
return 4
1541
def f(c):
1642
c :: __main__.C
1743
r0 :: int
1844
r1 :: bit
1945
r2 :: bool
2046
L0:
21-
r0 = c.__len__()
47+
r0 = C.__len__(c)
48+
r1 = int_ge r0, 0
49+
if r1 goto L2 else goto L1 :: bool
50+
L1:
51+
r2 = raise ValueError('__len__() should return >= 0')
52+
unreachable
53+
L2:
54+
return r0
55+
def g(d):
56+
d :: __main__.D
57+
r0 :: int
58+
r1 :: bit
59+
r2 :: bool
60+
L0:
61+
r0 = D.__len__(d)
62+
r1 = int_ge r0, 0
63+
if r1 goto L2 else goto L1 :: bool
64+
L1:
65+
r2 = raise ValueError('__len__() should return >= 0')
66+
unreachable
67+
L2:
68+
return r0
69+
def h(e):
70+
e :: __main__.E
71+
r0 :: int
72+
r1 :: bit
73+
r2 :: bool
74+
L0:
75+
r0 = E.__len__(e)
76+
r1 = int_ge r0, 0
77+
if r1 goto L2 else goto L1 :: bool
78+
L1:
79+
r2 = raise ValueError('__len__() should return >= 0')
80+
unreachable
81+
L2:
82+
return r0
83+
def i(f):
84+
f :: __main__.F
85+
r0 :: int
86+
r1 :: bit
87+
r2 :: bool
88+
L0:
89+
r0 = C.__len__(f)
2290
r1 = int_ge r0, 0
2391
if r1 goto L2 else goto L1 :: bool
2492
L1:

0 commit comments

Comments
 (0)