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
27 changes: 23 additions & 4 deletions Lib/test/test_genericalias.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,10 @@ def __deepcopy__(self, memo):
aliases = [
GenericAlias(list, T),
GenericAlias(deque, T),
GenericAlias(X, T)
GenericAlias(X, T),
X[T],
list[T],
deque[T],
] + _UNPACKED_TUPLES
for alias in aliases:
with self.subTest(alias=alias):
Expand Down Expand Up @@ -420,10 +423,26 @@ def test_union_generic(self):
self.assertEqual(a.__parameters__, (T,))

def test_dir(self):
dir_of_gen_alias = set(dir(list[int]))
ga = list[int]
dir_of_gen_alias = set(dir(ga))
self.assertTrue(dir_of_gen_alias.issuperset(dir(list)))
for generic_alias_property in ("__origin__", "__args__", "__parameters__"):
self.assertIn(generic_alias_property, dir_of_gen_alias)
for generic_alias_property in (
"__origin__", "__args__", "__parameters__",
"__unpacked__",
):
with self.subTest(generic_alias_property=generic_alias_property):
self.assertIn(generic_alias_property, dir_of_gen_alias)
for blocked in (
"__bases__",
"__copy__",
"__deepcopy__",
):
with self.subTest(blocked=blocked):
self.assertNotIn(blocked, dir_of_gen_alias)

for entry in dir_of_gen_alias:
with self.subTest(entry=entry):
getattr(ga, entry) # must not raise `AttributeError`

def test_weakref(self):
for t in self.generic_types:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove non-existent :meth:`~object.__copy__`, :meth:`~object.__deepcopy__`, and :attr:`~type.__bases__` from the :meth:`~object.__dir__` entries of :class:`types.GenericAlias`.
22 changes: 20 additions & 2 deletions Objects/genericaliasobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args,

static const char* const attr_exceptions[] = {
"__class__",
"__bases__",
"__origin__",
"__args__",
"__unpacked__",
Expand All @@ -635,6 +634,11 @@ static const char* const attr_exceptions[] = {
"__mro_entries__",
"__reduce_ex__", // needed so we don't look up object.__reduce_ex__
"__reduce__",
NULL,
};

static const char* const attr_blocked[] = {
"__bases__",
"__copy__",
"__deepcopy__",
NULL,
Expand All @@ -645,15 +649,29 @@ ga_getattro(PyObject *self, PyObject *name)
{
gaobject *alias = (gaobject *)self;
if (PyUnicode_Check(name)) {
// When we check blocked attrs, we don't allow to proxy them to `__origin__`.
// Otherwise, we can break existing code.
for (const char * const *p = attr_blocked; ; p++) {
if (*p == NULL) {
break;
}
if (_PyUnicode_EqualToASCIIString(name, *p)) {
goto generic_getattr;
}
}

// When we see own attrs, it has a priority over `__origin__`'s attr.
for (const char * const *p = attr_exceptions; ; p++) {
if (*p == NULL) {
return PyObject_GetAttr(alias->origin, name);
}
if (_PyUnicode_EqualToASCIIString(name, *p)) {
break;
goto generic_getattr;
}
}
}

generic_getattr:
return PyObject_GenericGetAttr(self, name);
}

Expand Down
Loading