diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index c3d863fa96de..c0c28fc94d0e 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -76,6 +76,7 @@ is_int_rprimitive, is_list_rprimitive, is_none_rprimitive, + is_object_rprimitive, object_rprimitive, set_rprimitive, ) @@ -98,7 +99,7 @@ from mypyc.irbuild.specialize import apply_function_specialization, apply_method_specialization from mypyc.primitives.bytes_ops import bytes_slice_op from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, exact_dict_set_item_op -from mypyc.primitives.generic_ops import iter_op +from mypyc.primitives.generic_ops import iter_op, name_op from mypyc.primitives.list_ops import list_append_op, list_extend_op, list_slice_op from mypyc.primitives.misc_ops import ellipsis_op, get_module_dict_op, new_slice_op, type_op from mypyc.primitives.registry import builtin_names @@ -218,6 +219,13 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: obj = builder.accept(expr.expr, can_borrow=can_borrow) rtype = builder.node_type(expr) + if ( + is_object_rprimitive(obj.type) + and expr.name == "__name__" + and builder.options.capi_version >= (3, 11) + ): + return builder.primitive_op(name_op, [obj], expr.line) + # Special case: for named tuples transform attribute access to faster index access. typ = get_proper_type(builder.types.get(expr.expr)) if isinstance(typ, TupleType) and typ.partial_fallback.type.is_named_tuple: diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 1881aa97f308..ffa4f6a363d2 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -931,6 +931,10 @@ PyObject *CPy_GetANext(PyObject *aiter); void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_value); void CPyTrace_LogEvent(const char *location, const char *line, const char *op, const char *details); +#if CPY_3_11_FEATURES +PyObject *CPy_GetName(PyObject *obj); +#endif + #if CPY_3_14_FEATURES void CPy_SetImmortal(PyObject *obj); #endif diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index 0c9d7812ac6c..b7593491a6e6 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -1045,6 +1045,20 @@ PyObject *CPy_GetANext(PyObject *aiter) return NULL; } +#if CPY_3_11_FEATURES + +// Return obj.__name__ (specialized to type objects, which are the most common target). +PyObject *CPy_GetName(PyObject *obj) { + if (PyType_Check(obj)) { + return PyType_GetName((PyTypeObject *)obj); + } + _Py_IDENTIFIER(__name__); + PyObject *name = _PyUnicode_FromId(&PyId___name__); /* borrowed */ + return PyObject_GetAttr(obj, name); +} + +#endif + #ifdef MYPYC_LOG_TRACE // This is only compiled in if trace logging is enabled by user diff --git a/mypyc/lib-rt/mypyc_util.h b/mypyc/lib-rt/mypyc_util.h index f200d4f90def..4168d3c53ee2 100644 --- a/mypyc/lib-rt/mypyc_util.h +++ b/mypyc/lib-rt/mypyc_util.h @@ -140,6 +140,7 @@ static inline CPyTagged CPyTagged_ShortFromSsize_t(Py_ssize_t x) { } // Are we targeting Python 3.X or newer? +#define CPY_3_11_FEATURES (PY_VERSION_HEX >= 0x030b0000) #define CPY_3_12_FEATURES (PY_VERSION_HEX >= 0x030c0000) #define CPY_3_14_FEATURES (PY_VERSION_HEX >= 0x030e0000) diff --git a/mypyc/primitives/generic_ops.py b/mypyc/primitives/generic_ops.py index 54510d99cf87..4a95be4e5d4e 100644 --- a/mypyc/primitives/generic_ops.py +++ b/mypyc/primitives/generic_ops.py @@ -26,6 +26,7 @@ ERR_NEG_INT, binary_op, custom_op, + custom_primitive_op, function_op, method_op, unary_op, @@ -382,3 +383,12 @@ c_function_name="CPy_GetANext", error_kind=ERR_MAGIC, ) + +# x.__name__ (requires Python 3.11+) +name_op = custom_primitive_op( + name="__name__", + arg_types=[object_rprimitive], + return_type=object_rprimitive, + c_function_name="CPy_GetName", + error_kind=ERR_MAGIC, +) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index c7bf5de852a8..2bdbb42b8d23 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1468,3 +1468,51 @@ L1: L2: r2 = self == r0 return r2 + +[case testTypeObjectName_python3_11] +from typing import Any + +class C: pass +class D(C): pass + +def n1(t: type[object]) -> str: + return t.__name__ + +def n2(t: Any) -> str: + return t.__name__ + +def n3() -> str: + return C.__name__ + +def n4(t: type[C]) -> str: + return t.__name__ +[out] +def n1(t): + t, r0 :: object + r1 :: str +L0: + r0 = CPy_GetName(t) + r1 = cast(str, r0) + return r1 +def n2(t): + t, r0 :: object + r1 :: str +L0: + r0 = CPy_GetName(t) + r1 = cast(str, r0) + return r1 +def n3(): + r0, r1 :: object + r2 :: str +L0: + r0 = __main__.C :: type + r1 = CPy_GetName(r0) + r2 = cast(str, r1) + return r2 +def n4(t): + t, r0 :: object + r1 :: str +L0: + r0 = CPy_GetName(t) + r1 = cast(str, r0) + return r1 diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 1481f3e06871..9582eec07b1a 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -3115,3 +3115,51 @@ f(C(1, "yes")) [out] B 1 C yes + +[case testTypeObjectName] +from typing import Any +import re + +from dynamic import E, foo, Thing + +class C: pass +class D(C): pass + +def type_name(t: type[object]) -> str: + return t.__name__ + +def any_name(x: Any) -> str: + return x.__name__ + +def assert_type_name(x: Any) -> None: + assert type_name(x) == getattr(x, "__name__") + assert any_name(x) == getattr(x, "__name__") + +def assert_any_name(x: Any) -> None: + assert any_name(x) == getattr(x, "__name__") + +def test_type_name() -> None: + assert_type_name(C) + assert_type_name(D) + assert_type_name(int) + assert_type_name(E) + assert_type_name(re.Pattern) + +def test_module_name() -> None: + assert_any_name(re) + +def test_function_name() -> None: + assert_any_name(any_name) + assert_any_name(foo) + +def test_obj_name() -> None: + assert_any_name(Thing()) + +[file dynamic.py] +class E: pass + +def foo(): pass + +class Thing: + def __init__(self): + self.__name__ = "xyz"