Skip to content

Commit 722f4dd

Browse files
authored
[mypyc] Optimize type(x) and x.__class__ (#19691)
Using `Py_TYPE` avoids a function call. Also specialize `x.__class__` for instances of native classes -- it's treated as equivalent to `type(x)`. This is not generally possible, so only do it for native instances where we can make more assumptions.
1 parent 68809c0 commit 722f4dd

File tree

7 files changed

+103
-2
lines changed

7 files changed

+103
-2
lines changed

mypyc/irbuild/expression.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
Value,
7070
)
7171
from mypyc.ir.rtypes import (
72+
RInstance,
7273
RTuple,
7374
bool_rprimitive,
7475
int_rprimitive,
@@ -226,6 +227,11 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value:
226227
):
227228
return builder.primitive_op(name_op, [obj], expr.line)
228229

230+
if isinstance(obj.type, RInstance) and expr.name == "__class__":
231+
# A non-native class could override "__class__" using "__getattribute__", so
232+
# only apply to RInstance types.
233+
return builder.primitive_op(type_op, [obj], expr.line)
234+
229235
# Special case: for named tuples transform attribute access to faster index access.
230236
typ = get_proper_type(builder.types.get(expr.expr))
231237
if isinstance(typ, TupleType) and typ.partial_fallback.type.is_named_tuple:

mypyc/lib-rt/CPy.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,12 @@ static inline bool CPy_TypeCheck(PyObject *o, PyObject *type) {
877877
return PyObject_TypeCheck(o, (PyTypeObject *)type);
878878
}
879879

880+
static inline PyObject *CPy_TYPE(PyObject *obj) {
881+
PyObject *result = (PyObject *)Py_TYPE(obj);
882+
Py_INCREF(result);
883+
return result;
884+
}
885+
880886
PyObject *CPy_CalculateMetaclass(PyObject *type, PyObject *o);
881887
PyObject *CPy_GetCoro(PyObject *obj);
882888
PyObject *CPyIter_Send(PyObject *iter, PyObject *val);

mypyc/primitives/misc_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@
214214
type_op = function_op(
215215
name="builtins.type",
216216
arg_types=[object_rprimitive],
217-
c_function_name="PyObject_Type",
217+
c_function_name="CPy_TYPE",
218218
return_type=object_rprimitive,
219219
error_kind=ERR_NEVER,
220220
)

mypyc/test-data/fixtures/ir.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __pow__(self, other: T_contra, modulo: _M) -> T_co: ...
3939
]
4040

4141
class object:
42+
__class__: type
4243
def __init__(self) -> None: pass
4344
def __eq__(self, x: object) -> bool: pass
4445
def __ne__(self, x: object) -> bool: pass

mypyc/test-data/irbuild-classes.test

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,3 +1516,45 @@ L0:
15161516
r0 = CPy_GetName(t)
15171517
r1 = cast(str, r0)
15181518
return r1
1519+
1520+
[case testTypeOfObject]
1521+
class C: pass
1522+
class D(C): pass
1523+
1524+
def generic_type(x: object) -> type[object]:
1525+
return type(x)
1526+
1527+
def generic_class(x: object) -> type[object]:
1528+
return x.__class__
1529+
1530+
def native_type(x: C) -> type[object]:
1531+
return type(x)
1532+
1533+
def native_class(x: C) -> type[object]:
1534+
return x.__class__
1535+
[out]
1536+
def generic_type(x):
1537+
x, r0 :: object
1538+
L0:
1539+
r0 = CPy_TYPE(x)
1540+
return r0
1541+
def generic_class(x):
1542+
x :: object
1543+
r0 :: str
1544+
r1 :: object
1545+
L0:
1546+
r0 = '__class__'
1547+
r1 = CPyObject_GetAttr(x, r0)
1548+
return r1
1549+
def native_type(x):
1550+
x :: __main__.C
1551+
r0 :: object
1552+
L0:
1553+
r0 = CPy_TYPE(x)
1554+
return r0
1555+
def native_class(x):
1556+
x :: __main__.C
1557+
r0 :: object
1558+
L0:
1559+
r0 = CPy_TYPE(x)
1560+
return r0

mypyc/test-data/irbuild-try.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def foo(x):
412412
r36 :: bit
413413
L0:
414414
r0 = PyObject_Vectorcall(x, 0, 0, 0)
415-
r1 = PyObject_Type(r0)
415+
r1 = CPy_TYPE(r0)
416416
r2 = '__exit__'
417417
r3 = CPyObject_GetAttr(r1, r2)
418418
r4 = '__enter__'

mypyc/test-data/run-classes.test

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3163,3 +3163,49 @@ def foo(): pass
31633163
class Thing:
31643164
def __init__(self):
31653165
self.__name__ = "xyz"
3166+
3167+
[case testTypeOfObject]
3168+
from typing import Any
3169+
3170+
from dynamic import Dyn
3171+
3172+
class Foo: pass
3173+
class Bar(Foo): pass
3174+
3175+
def generic_type(x) -> type[object]:
3176+
return x.__class__
3177+
3178+
def test_built_in_type() -> None:
3179+
i: Any = int
3180+
l: Any = list
3181+
assert type(i()) is i().__class__
3182+
assert type(i()) is int
3183+
assert type(l()) is list
3184+
n = 5
3185+
assert n.__class__ is i
3186+
3187+
def test_native_class() -> None:
3188+
f_any: Any = Foo()
3189+
b_any: Any = Bar()
3190+
f: Foo = f_any
3191+
b: Foo = b_any
3192+
if int("1"): # use int("1") to avoid constant folding
3193+
assert type(f) is Foo
3194+
assert type(b) is Bar
3195+
if int("2"):
3196+
assert f.__class__ is Foo
3197+
assert b.__class__ is Bar
3198+
if int("3"):
3199+
assert f_any.__class__ is Foo
3200+
assert b_any.__class__ is Bar
3201+
if int("4"):
3202+
assert type(f_any) is Foo
3203+
assert type(b_any) is Bar
3204+
3205+
def test_python_class() -> None:
3206+
d = Dyn()
3207+
assert type(d) is Dyn
3208+
assert d.__class__ is Dyn
3209+
3210+
[file dynamic.py]
3211+
class Dyn: pass

0 commit comments

Comments
 (0)