From 98cc5fa88bc1c0ae7e8eb1e5dab0c064c0a57791 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 16 May 2023 07:10:48 -0700 Subject: [PATCH 1/7] gh-104549: Set __module__ on TypeAliasType --- Lib/test/test_type_aliases.py | 7 +++++++ Objects/typevarobject.c | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index d2abb932f589f7..75a121132b013b 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -155,6 +155,7 @@ def test_basic(self): self.assertEqual(TA.__name__, "TA") self.assertIs(TA.__value__, int) self.assertEqual(TA.__type_params__, ()) + self.assertEqual(TA.__module__, __name__) def test_generic(self): T = TypeVar("T") @@ -162,12 +163,14 @@ def test_generic(self): self.assertEqual(TA.__name__, "TA") self.assertEqual(TA.__value__, list[T]) self.assertEqual(TA.__type_params__, (T,)) + self.assertEqual(TA.__module__, __name__) def test_keywords(self): TA = TypeAliasType(name="TA", value=int) self.assertEqual(TA.__name__, "TA") self.assertIs(TA.__value__, int) self.assertEqual(TA.__type_params__, ()) + self.assertEqual(TA.__module__, __name__) def test_errors(self): with self.assertRaises(TypeError): @@ -202,3 +205,7 @@ def test_union(self): union3 = list[range] | Alias1 self.assertIsInstance(union3, types.UnionType) self.assertEqual(get_args(union3), (list[range], Alias1)) + + def test_module(self): + type Alias = int + self.assertEqual(Alias.__module__, __name__) diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index b0578756f7dfcc..8866860c9a31a4 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -48,6 +48,7 @@ typedef struct { PyObject *type_params; PyObject *compute_value; PyObject *value; + PyObject *module; } typealiasobject; #include "clinic/typevarobject.c.h" @@ -1252,6 +1253,7 @@ typealias_dealloc(PyObject *self) Py_XDECREF(ta->type_params); Py_XDECREF(ta->compute_value); Py_XDECREF(ta->value); + Py_XDECREF(ta->module); Py_TYPE(self)->tp_free(self); Py_DECREF(tp); } @@ -1309,16 +1311,30 @@ typealias_type_params(PyObject *self, void *unused) return Py_NewRef(ta->type_params); } +static PyObject * +typealias_module(PyObject *self, void *unused) +{ + typealiasobject *ta = (typealiasobject *)self; + if (ta->module != NULL) { + return Py_NewRef(ta->module); + } + if (ta->compute_value != NULL) { + return PyFunction_GetModule(ta->compute_value); + } + Py_RETURN_NONE; +} + static PyGetSetDef typealias_getset[] = { {"__parameters__", typealias_parameters, (setter)NULL, NULL, NULL}, {"__type_params__", typealias_type_params, (setter)NULL, NULL, NULL}, {"__value__", typealias_value, (setter)NULL, NULL, NULL}, + {"__module__", typealias_module, (setter)NULL, NULL, NULL}, {0} }; static typealiasobject * typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value, - PyObject *value) + PyObject *value, PyObject *module) { PyTypeObject *tp = PyInterpreterState_Get()->cached_objects.typealias_type; typealiasobject *ta = PyObject_GC_New(typealiasobject, tp); @@ -1329,6 +1345,7 @@ typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value, ta->type_params = Py_IsNone(type_params) ? NULL : Py_XNewRef(type_params); ta->compute_value = Py_XNewRef(compute_value); ta->value = Py_XNewRef(value); + ta->module = Py_XNewRef(module); _PyObject_GC_TRACK(ta); return ta; } @@ -1339,6 +1356,7 @@ typealias_traverse(typealiasobject *self, visitproc visit, void *arg) Py_VISIT(self->type_params); Py_VISIT(self->compute_value); Py_VISIT(self->value); + Py_VISIT(self->module); return 0; } @@ -1348,6 +1366,7 @@ typealias_clear(typealiasobject *self) Py_CLEAR(self->type_params); Py_CLEAR(self->compute_value); Py_CLEAR(self->value); + Py_CLEAR(self->module); return 0; } @@ -1401,7 +1420,14 @@ typealias_new_impl(PyTypeObject *type, PyObject *name, PyObject *value, PyErr_SetString(PyExc_TypeError, "type_params must be a tuple"); return NULL; } - return (PyObject *)typealias_alloc(name, type_params, NULL, value); + PyObject *module = caller(); + if (module == NULL) { + return NULL; + } + PyObject *ta = (PyObject *)typealias_alloc(name, type_params, NULL, value, + module); + Py_DECREF(module); + return ta; } PyDoc_STRVAR(typealias_doc, @@ -1445,7 +1471,8 @@ _Py_make_typealias(PyThreadState* unused, PyObject *args) assert(PyUnicode_Check(name)); PyObject *type_params = PyTuple_GET_ITEM(args, 1); PyObject *compute_value = PyTuple_GET_ITEM(args, 2); - return (PyObject *)typealias_alloc(name, type_params, compute_value, NULL); + assert(PyFunction_Check(compute_value)); + return (PyObject *)typealias_alloc(name, type_params, compute_value, NULL, NULL); } PyDoc_STRVAR(generic_doc, From 00a1aae3511d5b55e381caebe411a37f8e153994 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 16 May 2023 18:13:07 -0700 Subject: [PATCH 2/7] Add some tests --- Lib/test/mod_generics_cache.py | 5 ++++- Lib/test/test_type_aliases.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/test/mod_generics_cache.py b/Lib/test/mod_generics_cache.py index 9d8b56cf03c364..6c1ee2fec8374d 100644 --- a/Lib/test/mod_generics_cache.py +++ b/Lib/test/mod_generics_cache.py @@ -1,6 +1,6 @@ """Module for testing the behavior of generics across different modules.""" -from typing import TypeVar, Generic, Optional +from typing import TypeVar, Generic, Optional, TypeAliasType default_a: Optional['A'] = None default_b: Optional['B'] = None @@ -19,3 +19,6 @@ class A(Generic[T]): my_inner_a1: 'B.A' my_inner_a2: A my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__ + +type Alias = int +OldStyle = TypeAliasType("OldStyle", int) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 75a121132b013b..54a921b557aab9 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -1,6 +1,7 @@ import types import unittest from test.support import check_syntax_error, run_code +from test import mod_generics_cache from typing import Callable, TypeAliasType, TypeVar, get_args @@ -207,5 +208,10 @@ def test_union(self): self.assertEqual(get_args(union3), (list[range], Alias1)) def test_module(self): + self.assertEqual(TypeAliasType.__module__, "typing") type Alias = int self.assertEqual(Alias.__module__, __name__) + self.assertEqual(mod_generics_cache.Alias.__module__, + mod_generics_cache.__name__) + self.assertEqual(mod_generics_cache.OldStyle.__module__, + mod_generics_cache.__name__) From f98dfcccb93cc45ee13eee86123b917918eab19c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 16 May 2023 18:33:43 -0700 Subject: [PATCH 3/7] Not that one --- Lib/test/test_type_aliases.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 54a921b557aab9..408f52202ee118 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -208,7 +208,6 @@ def test_union(self): self.assertEqual(get_args(union3), (list[range], Alias1)) def test_module(self): - self.assertEqual(TypeAliasType.__module__, "typing") type Alias = int self.assertEqual(Alias.__module__, __name__) self.assertEqual(mod_generics_cache.Alias.__module__, From 90a846c7a529623756178ae807dd1a8c9b92f0d0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 17 May 2023 19:44:18 -0700 Subject: [PATCH 4/7] Add pickling tests, fix refcounting bug --- Lib/test/test_type_aliases.py | 7 +++++++ Objects/typevarobject.c | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 408f52202ee118..5ed15b5f478411 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -1,3 +1,4 @@ +import pickle import types import unittest from test.support import check_syntax_error, run_code @@ -214,3 +215,9 @@ def test_module(self): mod_generics_cache.__name__) self.assertEqual(mod_generics_cache.OldStyle.__module__, mod_generics_cache.__name__) + + def test_pickling(self): + pickled = pickle.dumps(mod_generics_cache.Alias) + self.assertIs(pickle.loads(pickled), mod_generics_cache.Alias) + pickled = pickle.dumps(mod_generics_cache.OldStyle) + self.assertIs(pickle.loads(pickled), mod_generics_cache.OldStyle) diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index a41c26c7ae6518..38f233c76bd6af 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -1319,7 +1319,8 @@ typealias_module(PyObject *self, void *unused) return Py_NewRef(ta->module); } if (ta->compute_value != NULL) { - return PyFunction_GetModule(ta->compute_value); + // PyFunction_GetModule() returns a borrowed reference + return Py_NewRef(PyFunction_GetModule(ta->compute_value)); } Py_RETURN_NONE; } From dbcbc66398619e5b107caf2a12a16593c3421b76 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 17 May 2023 19:59:53 -0700 Subject: [PATCH 5/7] Make it a static type --- Include/internal/pycore_global_objects.h | 1 - Include/internal/pycore_typevarobject.h | 2 + Lib/test/test_type_aliases.py | 1 + Modules/_typingmodule.c | 5 ++- Objects/object.c | 3 +- Objects/typevarobject.c | 49 ++++++++++++------------ Objects/unionobject.c | 7 ++-- 7 files changed, 38 insertions(+), 30 deletions(-) diff --git a/Include/internal/pycore_global_objects.h b/Include/internal/pycore_global_objects.h index 40cc04d5d1702c..5a3fb132c745ab 100644 --- a/Include/internal/pycore_global_objects.h +++ b/Include/internal/pycore_global_objects.h @@ -75,7 +75,6 @@ struct _Py_interp_cached_objects { PyTypeObject *paramspec_type; PyTypeObject *paramspecargs_type; PyTypeObject *paramspeckwargs_type; - PyTypeObject *typealias_type; }; #define _Py_INTERP_STATIC_OBJECT(interp, NAME) \ diff --git a/Include/internal/pycore_typevarobject.h b/Include/internal/pycore_typevarobject.h index 2035e47e923059..c9fa97d6820757 100644 --- a/Include/internal/pycore_typevarobject.h +++ b/Include/internal/pycore_typevarobject.h @@ -16,6 +16,8 @@ extern PyObject *_Py_subscript_generic(PyThreadState *, PyObject *); extern int _Py_initialize_generic(PyInterpreterState *); extern void _Py_clear_generic_types(PyInterpreterState *); +extern PyTypeObject _PyTypeAlias_Type; + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 5ed15b5f478411..56d150d67afb6f 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -209,6 +209,7 @@ def test_union(self): self.assertEqual(get_args(union3), (list[range], Alias1)) def test_module(self): + self.assertEqual(TypeAliasType.__module__, "typing") type Alias = int self.assertEqual(Alias.__module__, __name__) self.assertEqual(mod_generics_cache.Alias.__module__, diff --git a/Modules/_typingmodule.c b/Modules/_typingmodule.c index ed2999c0b68b24..ac636c58aebb54 100644 --- a/Modules/_typingmodule.c +++ b/Modules/_typingmodule.c @@ -6,6 +6,7 @@ #include "Python.h" #include "internal/pycore_interp.h" +#include "internal/pycore_typevarobject.h" #include "clinic/_typingmodule.c.h" /*[clinic input] @@ -56,9 +57,11 @@ _typing_exec(PyObject *m) EXPORT_TYPE("ParamSpec", paramspec_type); EXPORT_TYPE("ParamSpecArgs", paramspecargs_type); EXPORT_TYPE("ParamSpecKwargs", paramspeckwargs_type); - EXPORT_TYPE("TypeAliasType", typealias_type); EXPORT_TYPE("Generic", generic_type); #undef EXPORT_TYPE + if (PyModule_AddObjectRef(m, "TypeAliasType", &_PyTypeAlias_Type) < 0) { + return -1; + } return 0; } diff --git a/Objects/object.c b/Objects/object.c index f3118665430d9f..ece0c5e21e77fa 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -14,7 +14,7 @@ #include "pycore_pymem.h" // _PyMem_IsPtrFreed() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_symtable.h" // PySTEntry_Type -#include "pycore_typevarobject.h" // _PyTypeVar_Type etc., _Py_initialize_generic +#include "pycore_typevarobject.h" // _PyTypeAlias_Type, _Py_initialize_generic #include "pycore_typeobject.h" // _PyBufferWrapper_Type #include "pycore_unionobject.h" // _PyUnion_Type #include "pycore_interpreteridobject.h" // _PyInterpreterID_Type @@ -2112,6 +2112,7 @@ static PyTypeObject* static_types[] = { &_PyWeakref_CallableProxyType, &_PyWeakref_ProxyType, &_PyWeakref_RefType, + &_PyTypeAlias_Type, // subclasses: _PyTypes_FiniTypes() deallocates them before their base // class diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index 38f233c76bd6af..6730ebfc064e35 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -1337,8 +1337,7 @@ static typealiasobject * typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value, PyObject *value, PyObject *module) { - PyTypeObject *tp = PyInterpreterState_Get()->cached_objects.typealias_type; - typealiasobject *ta = PyObject_GC_New(typealiasobject, tp); + typealiasobject *ta = PyObject_GC_New(typealiasobject, &_PyTypeAlias_Type); if (ta == NULL) { return NULL; } @@ -1439,28 +1438,32 @@ Type aliases are created through the type statement:\n\ type Alias = int\n\ "); -static PyType_Slot typealias_slots[] = { - {Py_tp_doc, (void *)typealias_doc}, - {Py_tp_members, typealias_members}, - {Py_tp_methods, typealias_methods}, - {Py_tp_getset, typealias_getset}, - {Py_mp_subscript, typealias_subscript}, - {Py_tp_dealloc, typealias_dealloc}, - {Py_tp_alloc, PyType_GenericAlloc}, - {Py_tp_new, typealias_new}, - {Py_tp_free, PyObject_GC_Del}, - {Py_tp_traverse, (traverseproc)typealias_traverse}, - {Py_tp_clear, (inquiry)typealias_clear}, - {Py_tp_repr, typealias_repr}, - {Py_nb_or, _Py_union_type_or}, - {0, 0}, +static PyNumberMethods typealias_as_number = { + .nb_or = _Py_union_type_or, +}; + +static PyMappingMethods typealias_as_mapping = { + .mp_subscript = typealias_subscript, }; -PyType_Spec typealias_spec = { - .name = "typing.TypeAliasType", - .basicsize = sizeof(typealiasobject), - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC, - .slots = typealias_slots, +PyTypeObject _PyTypeAlias_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "typing.TypeAliasType", + .tp_basicsize = sizeof(typealiasobject), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC, + .tp_doc = typealias_doc, + .tp_members = typealias_members, + .tp_methods = typealias_methods, + .tp_getset = typealias_getset, + .tp_alloc = PyType_GenericAlloc, + .tp_dealloc = typealias_dealloc, + .tp_new = typealias_new, + .tp_free = PyObject_GC_Del, + .tp_traverse = (traverseproc)typealias_traverse, + .tp_clear = (inquiry)typealias_clear, + .tp_repr = typealias_repr, + .tp_as_number = &typealias_as_number, + .tp_as_mapping = &typealias_as_mapping, }; PyObject * @@ -1631,7 +1634,6 @@ int _Py_initialize_generic(PyInterpreterState *interp) MAKE_TYPE(paramspec); MAKE_TYPE(paramspecargs); MAKE_TYPE(paramspeckwargs); - MAKE_TYPE(typealias); #undef MAKE_TYPE return 0; } @@ -1644,5 +1646,4 @@ void _Py_clear_generic_types(PyInterpreterState *interp) Py_CLEAR(interp->cached_objects.paramspec_type); Py_CLEAR(interp->cached_objects.paramspecargs_type); Py_CLEAR(interp->cached_objects.paramspeckwargs_type); - Py_CLEAR(interp->cached_objects.typealias_type); } diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 9806678b804857..f509a161bb9564 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -1,6 +1,7 @@ // types.UnionType -- used to represent e.g. Union[int, str], int | str #include "Python.h" #include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK +#include "pycore_typevarobject.h" // _PyTypeAlias_Type #include "pycore_unionobject.h" #include "structmember.h" @@ -150,11 +151,11 @@ is_unionable(PyObject *obj) if (obj == Py_None || PyType_Check(obj) || _PyGenericAlias_Check(obj) || - _PyUnion_Check(obj)) { + _PyUnion_Check(obj) || + Py_IS_TYPE(obj, &_PyTypeAlias_Type)) { return 1; } - PyInterpreterState *interp = PyInterpreterState_Get(); - return Py_IS_TYPE(obj, interp->cached_objects.typealias_type); + return 0; } PyObject * From d11bfb2edbfe2c95ccf985c269fec09decd340f6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 17 May 2023 21:02:52 -0700 Subject: [PATCH 6/7] Add to globals-to-fix.tsv --- Tools/c-analyzer/cpython/globals-to-fix.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index e2b93a3a2ec274..622c98d16283a8 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -90,6 +90,7 @@ Objects/typeobject.c - _PyBufferWrapper_Type - Objects/typeobject.c - PyBaseObject_Type - Objects/typeobject.c - PySuper_Type - Objects/typeobject.c - PyType_Type - +Objects/typevarobject.c - _PyTypeAlias_Type - Objects/unicodeobject.c - PyUnicodeIter_Type - Objects/unicodeobject.c - PyUnicode_Type - Objects/weakrefobject.c - _PyWeakref_CallableProxyType - From 7898e78756449555b61cd0f277f279611411e571 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 17 May 2023 22:35:43 -0700 Subject: [PATCH 7/7] fix warnings --- Modules/_typingmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_typingmodule.c b/Modules/_typingmodule.c index ac636c58aebb54..39a124a26adf31 100644 --- a/Modules/_typingmodule.c +++ b/Modules/_typingmodule.c @@ -59,7 +59,7 @@ _typing_exec(PyObject *m) EXPORT_TYPE("ParamSpecKwargs", paramspeckwargs_type); EXPORT_TYPE("Generic", generic_type); #undef EXPORT_TYPE - if (PyModule_AddObjectRef(m, "TypeAliasType", &_PyTypeAlias_Type) < 0) { + if (PyModule_AddObjectRef(m, "TypeAliasType", (PyObject *)&_PyTypeAlias_Type) < 0) { return -1; } return 0;