Skip to content
Open
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
14 changes: 14 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,20 @@ without the dedicated syntax, as documented below.
>>> Alias.__name__
'Alias'

.. attribute:: __qualname__

The :term:`qualified name` of the type alias:

.. doctest::

>>> class Class:
... type Alias = int
...
>>> Class.Alias.__qualname__
'Class.Alias'

.. versionadded:: 3.15

.. attribute:: __module__

The name of the module in which the type alias was defined::
Expand Down
65 changes: 53 additions & 12 deletions Lib/test/test_type_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
Callable, TypeAliasType, TypeVar, TypeVarTuple, ParamSpec, Unpack, get_args,
)

type GlobalTypeAlias = int

def get_type_alias():
type TypeAliasInFunc = str
return TypeAliasInFunc

class TypeParamsInvalidTest(unittest.TestCase):
def test_name_collisions(self):
Expand Down Expand Up @@ -70,6 +75,8 @@ def inner[B](self):


class TypeParamsAliasValueTest(unittest.TestCase):
type TypeAliasInClass = dict

def test_alias_value_01(self):
type TA1 = int

Expand Down Expand Up @@ -142,33 +149,67 @@ def test_subscripting(self):
self.assertIs(specialized2.__origin__, VeryGeneric)
self.assertEqual(specialized2.__args__, (int, str, float, [bool, range]))

def test___name__(self):
type TypeAliasLocal = GlobalTypeAlias

self.assertEqual(GlobalTypeAlias.__name__, 'GlobalTypeAlias')
self.assertEqual(get_type_alias().__name__, 'TypeAliasInFunc')
self.assertEqual(self.TypeAliasInClass.__name__, 'TypeAliasInClass')
self.assertEqual(TypeAliasLocal.__name__, 'TypeAliasLocal')

with self.assertRaisesRegex(
AttributeError,
"readonly attribute",
):
setattr(TypeAliasLocal, '__name__', 'TA')

def test___qualname__(self):
type TypeAliasLocal = GlobalTypeAlias

self.assertEqual(GlobalTypeAlias.__qualname__,
'GlobalTypeAlias')
self.assertEqual(get_type_alias().__qualname__,
'get_type_alias.<locals>.TypeAliasInFunc')
self.assertEqual(self.TypeAliasInClass.__qualname__,
'TypeParamsAliasValueTest.TypeAliasInClass')
self.assertEqual(TypeAliasLocal.__qualname__,
'TypeParamsAliasValueTest.test___qualname__.<locals>.TypeAliasLocal')

with self.assertRaisesRegex(
AttributeError,
"readonly attribute",
):
setattr(TypeAliasLocal, '__qualname__', 'TA')

def test_repr(self):
type Simple = int
type VeryGeneric[T, *Ts, **P] = Callable[P, tuple[T, *Ts]]
self.assertEqual(repr(Simple), Simple.__qualname__)

self.assertEqual(repr(Simple), "Simple")
self.assertEqual(repr(VeryGeneric), "VeryGeneric")
type VeryGeneric[T, *Ts, **P] = Callable[P, tuple[T, *Ts]]
self.assertEqual(repr(VeryGeneric), VeryGeneric.__qualname__)
fullname = f"{VeryGeneric.__module__}.{VeryGeneric.__qualname__}"
self.assertEqual(repr(VeryGeneric[int, bytes, str, [float, object]]),
"VeryGeneric[int, bytes, str, [float, object]]")
f"{fullname}[int, bytes, str, [float, object]]")
self.assertEqual(repr(VeryGeneric[int, []]),
"VeryGeneric[int, []]")
f"{fullname}[int, []]")
self.assertEqual(repr(VeryGeneric[int, [VeryGeneric[int], list[str]]]),
"VeryGeneric[int, [VeryGeneric[int], list[str]]]")
f"{fullname}[int, [{fullname}[int], list[str]]]")

def test_recursive_repr(self):
type Recursive = Recursive
self.assertEqual(repr(Recursive), "Recursive")
self.assertEqual(repr(Recursive), Recursive.__qualname__)

type X = list[Y]
type Y = list[X]
self.assertEqual(repr(X), "X")
self.assertEqual(repr(Y), "Y")
self.assertEqual(repr(X), X.__qualname__)
self.assertEqual(repr(Y), Y.__qualname__)

type GenericRecursive[X] = list[X | GenericRecursive[X]]
self.assertEqual(repr(GenericRecursive), "GenericRecursive")
self.assertEqual(repr(GenericRecursive[int]), "GenericRecursive[int]")
self.assertEqual(repr(GenericRecursive), GenericRecursive.__qualname__)
fullname = f"{GenericRecursive.__module__}.{GenericRecursive.__qualname__}"
self.assertEqual(repr(GenericRecursive[int]), f"{fullname}[int]")
self.assertEqual(repr(GenericRecursive[GenericRecursive[int]]),
"GenericRecursive[GenericRecursive[int]]")
f"{fullname}[{fullname}[int]]")

def test_raising(self):
type MissingName = list[_My_X]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Attribute ``__qualname__`` is added to :class:`typing.TypeAliasType`.
Patch by Mikhail Efimov.
29 changes: 21 additions & 8 deletions Objects/typevarobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ typedef struct {
typedef struct {
PyObject_HEAD
PyObject *name;
PyObject *qualname;
PyObject *type_params;
PyObject *compute_value;
PyObject *value;
Expand Down Expand Up @@ -1852,6 +1853,7 @@ typealias_dealloc(PyObject *self)
_PyObject_GC_UNTRACK(self);
typealiasobject *ta = typealiasobject_CAST(self);
Py_DECREF(ta->name);
Py_DECREF(ta->qualname);
Py_XDECREF(ta->type_params);
Py_XDECREF(ta->compute_value);
Py_XDECREF(ta->value);
Expand All @@ -1878,11 +1880,12 @@ static PyObject *
typealias_repr(PyObject *self)
{
typealiasobject *ta = (typealiasobject *)self;
return Py_NewRef(ta->name);
return Py_NewRef(ta->qualname);
}

static PyMemberDef typealias_members[] = {
{"__name__", _Py_T_OBJECT, offsetof(typealiasobject, name), Py_READONLY},
{"__qualname__", _Py_T_OBJECT, offsetof(typealiasobject, qualname), Py_READONLY},
{0}
};

Expand Down Expand Up @@ -1997,7 +2000,7 @@ typealias_check_type_params(PyObject *type_params, int *err) {
}

static PyObject *
typelias_convert_type_params(PyObject *type_params)
typealias_convert_type_params(PyObject *type_params)
{
if (
type_params == NULL
Expand All @@ -2012,14 +2015,15 @@ typelias_convert_type_params(PyObject *type_params)
}

static typealiasobject *
typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value,
PyObject *value, PyObject *module)
typealias_alloc(PyObject *name, PyObject *qualname, PyObject *type_params,
PyObject *compute_value, PyObject *value, PyObject *module)
{
typealiasobject *ta = PyObject_GC_New(typealiasobject, &_PyTypeAlias_Type);
if (ta == NULL) {
return NULL;
}
ta->name = Py_NewRef(name);
ta->qualname = Py_NewRef(qualname);
ta->type_params = Py_XNewRef(type_params);
ta->compute_value = Py_XNewRef(compute_value);
ta->value = Py_XNewRef(value);
Expand Down Expand Up @@ -2112,8 +2116,10 @@ typealias_new_impl(PyTypeObject *type, PyObject *name, PyObject *value,
if (module == NULL) {
return NULL;
}
PyObject *ta = (PyObject *)typealias_alloc(name, checked_params, NULL, value,
module);

// It's impossible to determine qualname, so we use name instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could make it a new constructor parameter: X = TypeAliasType("X", int, qualname="foo.X"). Probably not worth the extra complexity though.

PyObject *ta = (PyObject *)typealias_alloc(
name, name, checked_params, NULL, value, module);
Py_DECREF(module);
return ta;
}
Expand Down Expand Up @@ -2179,10 +2185,17 @@ _Py_make_typealias(PyThreadState* unused, PyObject *args)
assert(PyTuple_GET_SIZE(args) == 3);
PyObject *name = PyTuple_GET_ITEM(args, 0);
assert(PyUnicode_Check(name));
PyObject *type_params = typelias_convert_type_params(PyTuple_GET_ITEM(args, 1));
PyObject *type_params = typealias_convert_type_params(PyTuple_GET_ITEM(args, 1));
PyObject *compute_value = PyTuple_GET_ITEM(args, 2);
assert(PyFunction_Check(compute_value));
return (PyObject *)typealias_alloc(name, type_params, compute_value, NULL, NULL);

PyFunctionObject *compute_func = (PyFunctionObject *)compute_value;
PyCodeObject *code_obj = (PyCodeObject *)compute_func->func_code;
PyObject *qualname = code_obj->co_qualname;
assert(qualname != NULL);

return (PyObject *)typealias_alloc(
name, qualname, type_params, compute_value, NULL, NULL);
}

PyDoc_STRVAR(generic_doc,
Expand Down
Loading