Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
is_int_rprimitive,
is_list_rprimitive,
is_none_rprimitive,
is_object_rprimitive,
object_rprimitive,
set_rprimitive,
)
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions mypyc/lib-rt/misc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions mypyc/lib-rt/mypyc_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 10 additions & 0 deletions mypyc/primitives/generic_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ERR_NEG_INT,
binary_op,
custom_op,
custom_primitive_op,
function_op,
method_op,
unary_op,
Expand Down Expand Up @@ -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,
)
48 changes: 48 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 48 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading