https://github.com/python/cpython/commit/29837a952ce5d7e1ae9585341a2a08a236f0e52c
commit: 29837a952ce5d7e1ae9585341a2a08a236f0e52c
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-08-12T14:40:04+02:00
summary:

gh-155503: Add more PyType C API tests (#155505)

Add tests on functions:

* PyType_ClearCache()
* PyType_GetFlags()
* PyType_IsSubtype()
* PyType_Ready()

Move PyType limited C API tests from _testcapi to _testlimitedcapi.
Add a new Modules/_testlimitedcapi/type.c file.

files:
A Modules/_testlimitedcapi/type.c
M Include/object.h
M Lib/test/test_capi/test_type.py
M Lib/test/test_type_cache.py
M Modules/Setup.stdlib.in
M Modules/_testcapi/type.c
M Modules/_testlimitedcapi.c
M Modules/_testlimitedcapi/parts.h
M PCbuild/_testlimitedcapi.vcxproj
M PCbuild/_testlimitedcapi.vcxproj.filters

diff --git a/Include/object.h b/Include/object.h
index 20c2dab4401fef0..549b91ebecf4f22 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -490,7 +490,7 @@ given type object has a specified feature.
 #define Py_TPFLAGS_SEQUENCE (1 << 5)
 /* Set if instances of the type object are treated as mappings for pattern 
matching */
 #define Py_TPFLAGS_MAPPING (1 << 6)
-#endif
+#endif  // Py_LIMITED_API
 
 /* Disallow creating instances of the type: set tp_new to NULL and don't create
  * the "__new__" key in the type dictionary. */
diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py
index e6a8ef9eed6fc42..90b1f57d1c3c9d3 100644
--- a/Lib/test/test_capi/test_type.py
+++ b/Lib/test/test_capi/test_type.py
@@ -2,6 +2,9 @@
 import unittest
 
 _testcapi = import_helper.import_module('_testcapi')
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+
+NULL = None
 
 
 class BuiltinStaticTypesTests(unittest.TestCase):
@@ -39,15 +42,18 @@ def test_tp_mro_is_set(self):
 
 class TypeTests(unittest.TestCase):
     def test_get_type_name(self):
+        # Test PyType_GetName(), PyType_GetQualName(),
+        # PyType_GetFullyQualifiedName() and PyType_GetModuleName().
+
         class MyType:
             pass
 
-        from _testcapi import (
+        from _testlimitedcapi import (
             get_type_name, get_type_qualname,
             get_type_fullyqualname, get_type_module_name)
 
         from collections import OrderedDict
-        ht = _testcapi.get_heaptype_for_name()
+        ht = _testlimitedcapi.get_heaptype_for_name()
         for cls, fullname, modname, qualname, name in (
             (int,
              'int',
@@ -107,6 +113,15 @@ class MyType:
         MyType.__module__ = 123
         self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
 
+        # CRASHES get_type_name(NULL)
+        # CRASHES get_type_qualname(NULL)
+        # CRASHES get_type_fullyqualname(NULL)
+        # CRASHES get_type_module_name(NULL)
+        # CRASHES get_type_name(object()): argument must be a type
+        # CRASHES get_type_qualname(object()): argument must be a type
+        # CRASHES get_type_fullyqualname(object()): argument must be a type
+        # CRASHES get_type_module_name(object()): argument must be a type
+
     def test_get_base_by_token(self):
         def get_base_by_token(src, key, comparable=True):
             def run(use_mro):
@@ -215,7 +230,7 @@ class H2(int): pass
 
     def test_freeze(self):
         # test PyType_Freeze()
-        type_freeze = _testcapi.type_freeze
+        type_freeze = _testlimitedcapi.type_freeze
 
         # simple case, no inherante
         class MyType:
@@ -245,12 +260,15 @@ class D(A, C): pass
         # as well
         type_freeze(D)
 
+        # CRASHES type_freeze(NULL)
+        # CRASHES type_freeze(object()): argument must be a type
+
     @unittest.skipIf(
         Py_GIL_DISABLED and refleak_helper.hunting_for_refleaks(),
         "Specialization failure triggers gh-127773")
     def test_freeze_meta(self):
         """test PyType_Freeze() with overridden MRO"""
-        type_freeze = _testcapi.type_freeze
+        type_freeze = _testlimitedcapi.type_freeze
 
         class Base:
             value = 1
@@ -299,3 +317,181 @@ def test_extension_managed_weakref_nogc_type(self):
                "flag but not Py_TPFLAGS_HAVE_GC flag")
         with self.assertRaisesRegex(SystemError, msg):
             _testcapi.create_managed_weakref_nogc_type()
+
+    def test_type_ready(self):
+        # Test PyType_Ready(): calling it on initialized types
+        # must not raise an exception.
+        type_ready = _testlimitedcapi.type_ready
+
+        class HeapType:
+            pass
+
+        type_ready(int)
+        type_ready(dict)
+        type_ready(HeapType)
+
+        # CRASHES type_ready(NULL)
+        # CRASHES type_ready(123): argument must be a type
+
+    def test_type_clearcache(self):
+        # Test PyType_ClearCache()
+        type_clearcache = _testlimitedcapi.type_clearcache
+        version_tag = type_clearcache()
+        self.assertEqual(type(version_tag), int)
+        self.assertGreaterEqual(version_tag, 0)
+
+    def test_type_getflags(self):
+        # Test PyType_GetFlags()
+        type_getflags = _testlimitedcapi.type_getflags
+
+        from _testlimitedcapi import (
+            Py_TPFLAGS_HEAPTYPE,
+            Py_TPFLAGS_HAVE_GC,
+            Py_TPFLAGS_HAVE_FINALIZE,
+            Py_TPFLAGS_HAVE_VERSION_TAG,
+            Py_TPFLAGS_VALID_VERSION_TAG,
+            Py_TPFLAGS_HAVE_VECTORCALL,
+            Py_TPFLAGS_DISALLOW_INSTANTIATION,
+            Py_TPFLAGS_IMMUTABLETYPE,
+            Py_TPFLAGS_READY,
+            Py_TPFLAGS_READYING,
+            Py_TPFLAGS_LONG_SUBCLASS,
+            Py_TPFLAGS_LIST_SUBCLASS,
+            Py_TPFLAGS_TUPLE_SUBCLASS,
+            Py_TPFLAGS_BYTES_SUBCLASS,
+            Py_TPFLAGS_UNICODE_SUBCLASS,
+            Py_TPFLAGS_DICT_SUBCLASS,
+            Py_TPFLAGS_BASE_EXC_SUBCLASS,
+            Py_TPFLAGS_TYPE_SUBCLASS,
+            Py_TPFLAGS_IS_ABSTRACT,
+            Py_TPFLAGS_BASETYPE,
+            _Py_TPFLAGS_MATCH_SELF,
+            Py_TPFLAGS_ITEMS_AT_END,
+            Py_TPFLAGS_METHOD_DESCRIPTOR,
+        )
+        from _testcapi import (
+            _Py_TPFLAGS_STATIC_BUILTIN,
+            Py_TPFLAGS_SEQUENCE,
+            Py_TPFLAGS_MAPPING,
+            Py_TPFLAGS_INLINE_VALUES,
+            Py_TPFLAGS_MANAGED_WEAKREF,
+            Py_TPFLAGS_MANAGED_DICT,
+        )
+
+        def check_flag(flags, flag, expected):
+            self.assertEqual(bool(flags & flag), expected)
+
+        def check_subclasses(test_type, flags):
+            for flag, base_type in (
+                (Py_TPFLAGS_LONG_SUBCLASS, int),
+                (Py_TPFLAGS_LIST_SUBCLASS, list),
+                (Py_TPFLAGS_TUPLE_SUBCLASS, tuple),
+                (Py_TPFLAGS_BYTES_SUBCLASS, bytes),
+                (Py_TPFLAGS_UNICODE_SUBCLASS, str),
+                (Py_TPFLAGS_DICT_SUBCLASS, dict),
+                (Py_TPFLAGS_BASE_EXC_SUBCLASS, BaseException),
+                (Py_TPFLAGS_TYPE_SUBCLASS, type),
+            ):
+                with self.subTest(test_type=test_type, flag=flag, 
base_type=base_type):
+                    check_flag(flags, flag, issubclass(test_type, base_type))
+
+        def check_type(test_type, static_type, have_gc=False, 
have_vectorcall=False,
+                       is_base_type=True, sequence=False, mapping=False,
+                       match_self=True, items_at_end=False):
+            heap_type = not static_type
+
+            flags = type_getflags(test_type)
+            check_flag(flags, _Py_TPFLAGS_STATIC_BUILTIN, static_type)
+            check_flag(flags, Py_TPFLAGS_HEAPTYPE, heap_type)
+            check_flag(flags, Py_TPFLAGS_HAVE_GC, have_gc)
+            check_subclasses(test_type, flags)
+            check_flag(flags, Py_TPFLAGS_HAVE_VECTORCALL, have_vectorcall)
+            check_flag(flags, Py_TPFLAGS_DISALLOW_INSTANTIATION, False)
+            check_flag(flags, Py_TPFLAGS_IMMUTABLETYPE, static_type)
+            check_flag(flags, Py_TPFLAGS_READY, True)
+            check_flag(flags, Py_TPFLAGS_READYING, False)
+            check_flag(flags, Py_TPFLAGS_IS_ABSTRACT, False)
+            check_flag(flags, Py_TPFLAGS_BASETYPE, is_base_type)
+            check_flag(flags, Py_TPFLAGS_SEQUENCE, sequence)
+            check_flag(flags, Py_TPFLAGS_MAPPING, mapping)
+
+            check_flag(flags, Py_TPFLAGS_INLINE_VALUES, heap_type)
+            check_flag(flags, Py_TPFLAGS_MANAGED_WEAKREF, heap_type)
+            check_flag(flags, Py_TPFLAGS_MANAGED_DICT, heap_type)
+            check_flag(flags, Py_TPFLAGS_ITEMS_AT_END, items_at_end)
+            check_flag(flags, Py_TPFLAGS_METHOD_DESCRIPTOR, False)
+
+            check_flag(flags, _Py_TPFLAGS_MATCH_SELF, match_self)
+
+            # Flags kept for backward compatibility
+            check_flag(flags, Py_TPFLAGS_HAVE_FINALIZE, False)
+            check_flag(flags, Py_TPFLAGS_HAVE_VERSION_TAG, False)
+            check_flag(flags, Py_TPFLAGS_VALID_VERSION_TAG, False)
+
+        # Scalar types
+        check_type(int, static_type=True)
+        check_type(bool, static_type=True,
+                   is_base_type=False)
+        check_type(float, static_type=True)
+        check_type(complex, static_type=True,
+                   match_self=False)
+        check_type(bytes, static_type=True)
+        check_type(bytearray, static_type=True)
+        check_type(str, static_type=True)
+
+        # Collection types
+        check_type(tuple, static_type=True, have_gc=True,
+                   sequence=True)
+        check_type(list, static_type=True, have_gc=True,
+                   sequence=True)
+        check_type(dict, static_type=True, have_gc=True,
+                   mapping=True)
+        check_type(frozendict, static_type=True, have_gc=True,
+                   mapping=True)
+        check_type(set, static_type=True, have_gc=True)
+        check_type(frozenset, static_type=True, have_gc=True)
+
+        # Other types
+        check_type(BaseException, static_type=True, have_gc=True,
+                   match_self=False)
+        check_type(type, static_type=True, have_gc=True,
+                   have_vectorcall=True,
+                   match_self=False,
+                   items_at_end=True)
+
+        # Heap type
+        class HeapType:
+            pass
+        check_type(HeapType, static_type=False, have_gc=True, match_self=False)
+
+        # CRASHES type_getflags(NULL)
+
+    def test_type_issubtype(self):
+        # Test PyType_IsSubtype()
+        _type_issubtype = _testlimitedcapi.type_issubtype
+
+        def type_issubtype(type1, type2):
+            res = _type_issubtype(type1, type2)
+            self.assertIn(res, (0, 1))
+            return bool(res)
+
+        class MyList(list):
+            pass
+
+        self.assertTrue(type_issubtype(bool, int))
+        self.assertTrue(type_issubtype(MyList, list))
+
+        self.assertFalse(type_issubtype(int, type))
+        self.assertFalse(type_issubtype(frozendict, dict))
+        self.assertFalse(type_issubtype(MyList, tuple))
+
+    def test_type_modified(self):
+        # Test PyType_Modified()
+        type_modified = _testlimitedcapi.type_modified
+
+        class MyType:
+            pass
+        type_modified(MyType)
+
+        # CRASHES type_modified(NULL)
+        # CRASHES type_modified({}): argument must be a type
diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py
index 9827f2498554a5b..0031e4b59c67c9b 100644
--- a/Lib/test/test_type_cache.py
+++ b/Lib/test/test_type_cache.py
@@ -11,13 +11,15 @@
 except ImportError:
     _clear_type_cache = None
 
-# Skip this test if the _testcapi module isn't available.
+# Skip this test if the _testcapi modules are not available.
 _testcapi = import_helper.import_module("_testcapi")
+_testlimitedcapi = import_helper.import_module("_testlimitedcapi")
 _testinternalcapi = import_helper.import_module("_testinternalcapi")
+
 type_get_version = _testcapi.type_get_version
 type_assign_specific_version_unsafe = 
_testinternalcapi.type_assign_specific_version_unsafe
 type_assign_version = _testcapi.type_assign_version
-type_modified = _testcapi.type_modified
+type_modified = _testlimitedcapi.type_modified
 
 def clear_type_cache():
     with warnings.catch_warnings():
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index eaf8777a56c59b6..440d5a71608ba9d 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -174,7 +174,7 @@
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c 
_testinternalcapi/complex.c _testinternalcapi/interpreter.c 
_testinternalcapi/tuple.c _testinternalcapi/typecache.c
 @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c 
_testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c 
_testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c 
_testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c 
_testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c 
_testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c 
_testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c 
_testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c 
_testlimitedcapi/tuple.c _testlimitedcapi/unicode.c 
_testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c 
_testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c 
_testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c 
_testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c 
_testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c 
_testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c 
_testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c 
_testlimitedcapi/tuple.c _testlimitedcapi/unicode.c 
_testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c 
_testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c 
_testlimitedcapi/type.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
 
diff --git a/Modules/_testcapi/type.c b/Modules/_testcapi/type.c
index f566efa0ca15ae6..91203c7bf0f678a 100644
--- a/Modules/_testcapi/type.c
+++ b/Modules/_testcapi/type.c
@@ -1,3 +1,7 @@
+// Thin wrappers to PyType functions.
+// Do no check PyType_Check() so Python tests can pass arbitrary objects,
+// even if it's likely to crash.
+
 #include "parts.h"
 #include "util.h"
 
@@ -13,50 +17,11 @@ static PyType_Spec HeapTypeNameType_Spec = {
     .slots = HeapTypeNameType_slots,
 };
 
-static PyObject *
-get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    return PyType_FromSpec(&HeapTypeNameType_Spec);
-}
-
-
-static PyObject *
-get_type_name(PyObject *self, PyObject *type)
-{
-    assert(PyType_Check(type));
-    return PyType_GetName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-get_type_qualname(PyObject *self, PyObject *type)
-{
-    assert(PyType_Check(type));
-    return PyType_GetQualName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-get_type_fullyqualname(PyObject *self, PyObject *type)
-{
-    assert(PyType_Check(type));
-    return PyType_GetFullyQualifiedName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-get_type_module_name(PyObject *self, PyObject *type)
-{
-    assert(PyType_Check(type));
-    return PyType_GetModuleName((PyTypeObject *)type);
-}
-
 
+// Test for PyType_GetDict()
 static PyObject *
 test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored))
 {
-    /* Test for PyType_GetDict */
-
     // Assert ints have a `to_bytes` method
     PyObject *long_dict = PyType_GetDict(&PyLong_Type);
     assert(long_dict);
@@ -77,6 +42,7 @@ test_get_type_dict(PyObject *self, PyObject 
*Py_UNUSED(ignored))
 }
 
 
+// Test PyType_GetSlot()
 static PyObject *
 test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored))
 {
@@ -135,14 +101,12 @@ test_get_statictype_slots(PyObject *self, PyObject 
*Py_UNUSED(ignored))
 
 // Get type->tp_version_tag
 static PyObject *
-type_get_version(PyObject *self, PyObject *type)
+type_get_version(PyObject *self, PyObject *arg)
 {
-    if (!PyType_Check(type)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a type");
-        return NULL;
-    }
-    PyObject *res = PyLong_FromUnsignedLong(
-        ((PyTypeObject *)type)->tp_version_tag);
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    PyObject *res = PyLong_FromUnsignedLong(type->tp_version_tag);
     if (res == NULL) {
         assert(PyErr_Occurred());
         return NULL;
@@ -150,27 +114,12 @@ type_get_version(PyObject *self, PyObject *type)
     return res;
 }
 
-static PyObject *
-type_modified(PyObject *self, PyObject *arg)
-{
-    if (!PyType_Check(arg)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a type");
-        return NULL;
-    }
-    PyTypeObject *type = (PyTypeObject*)arg;
-
-    PyType_Modified(type);
-    Py_RETURN_NONE;
-}
-
 
+// Test PyUnstable_Type_AssignVersionTag()
 static PyObject *
 type_assign_version(PyObject *self, PyObject *arg)
 {
-    if (!PyType_Check(arg)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a type");
-        return NULL;
-    }
+    NULLABLE(arg);
     PyTypeObject *type = (PyTypeObject*)arg;
 
     int res = PyUnstable_Type_AssignVersionTag(type);
@@ -178,13 +127,11 @@ type_assign_version(PyObject *self, PyObject *arg)
 }
 
 
+// Get PyTypeObject.tp_bases
 static PyObject *
 type_get_tp_bases(PyObject *self, PyObject *arg)
 {
-    if (!PyType_Check(arg)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a type");
-        return NULL;
-    }
+    NULLABLE(arg);
     PyTypeObject *type = (PyTypeObject*)arg;
 
     PyObject *bases = type->tp_bases;
@@ -194,16 +141,15 @@ type_get_tp_bases(PyObject *self, PyObject *arg)
     return Py_NewRef(bases);
 }
 
+
+// Get PyTypeObject.tp_mro
 static PyObject *
 type_get_tp_mro(PyObject *self, PyObject *arg)
 {
-    if (!PyType_Check(arg)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a type");
-        return NULL;
-    }
+    NULLABLE(arg);
     PyTypeObject *type = (PyTypeObject*)arg;
 
-    PyObject *mro = ((PyTypeObject *)type)->tp_mro;
+    PyObject *mro = type->tp_mro;
     if (mro == NULL) {
         Py_RETURN_NONE;
     }
@@ -211,41 +157,38 @@ type_get_tp_mro(PyObject *self, PyObject *arg)
 }
 
 
-static PyObject *
-type_freeze(PyObject *module, PyObject *arg)
-{
-    if (!PyType_Check(arg)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a type");
-        return NULL;
-    }
-    PyTypeObject *type = (PyTypeObject*)arg;
-
-    if (PyType_Freeze(type) < 0) {
-        return NULL;
-    }
-    Py_RETURN_NONE;
-}
-
-
 static PyMethodDef test_methods[] = {
-    {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS},
-    {"get_type_name", get_type_name, METH_O},
-    {"get_type_qualname",  get_type_qualname, METH_O},
-    {"get_type_fullyqualname", get_type_fullyqualname, METH_O},
-    {"get_type_module_name", get_type_module_name, METH_O},
     {"test_get_type_dict", test_get_type_dict, METH_NOARGS},
     {"test_get_statictype_slots", test_get_statictype_slots,     METH_NOARGS},
     {"type_get_version", type_get_version, METH_O, 
PyDoc_STR("type->tp_version_tag")},
-    {"type_modified", type_modified, METH_O, PyDoc_STR("PyType_Modified")},
     {"type_assign_version", type_assign_version, METH_O, 
PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
     {"type_get_tp_bases", type_get_tp_bases, METH_O},
     {"type_get_tp_mro", type_get_tp_mro, METH_O},
-    {"type_freeze", type_freeze, METH_O},
     {NULL},
 };
 
 int
 _PyTestCapi_Init_Type(PyObject *m)
 {
-    return PyModule_AddFunctions(m, test_methods);
+    if (PyModule_AddFunctions(m, test_methods) < 0) {
+        return -1;
+    }
+
+#define ADD_INT(macro) \
+    do { \
+        if (PyModule_AddIntConstant(m, #macro, macro) < 0) { \
+            return -1; \
+        } \
+    } while (0)
+
+    // Flags excluded from the limited C API
+    ADD_INT(_Py_TPFLAGS_STATIC_BUILTIN);
+    ADD_INT(Py_TPFLAGS_INLINE_VALUES);
+    ADD_INT(Py_TPFLAGS_MANAGED_WEAKREF);
+    ADD_INT(Py_TPFLAGS_MANAGED_DICT);
+    ADD_INT(Py_TPFLAGS_SEQUENCE);
+    ADD_INT(Py_TPFLAGS_MAPPING);
+
+#undef ADD_INT
+    return 0;
 }
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index 0a562ea9c03110b..b602d3219f5a523 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -107,5 +107,8 @@ PyInit__testlimitedcapi(void)
     if (_PyTestLimitedCAPI_Init_Run(mod) < 0) {
         return NULL;
     }
+    if (_PyTestLimitedCAPI_Init_Type(mod) < 0) {
+        return NULL;
+    }
     return mod;
 }
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 32c1bbc1b71c977..35fbdea1d434e07 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -48,5 +48,6 @@ int _PyTestLimitedCAPI_Init_Version(PyObject *module);
 int _PyTestLimitedCAPI_Init_File(PyObject *module);
 int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
 int _PyTestLimitedCAPI_Init_Run(PyObject *module);
+int _PyTestLimitedCAPI_Init_Type(PyObject *module);
 
 #endif // Py_TESTLIMITEDCAPI_PARTS_H
diff --git a/Modules/_testlimitedcapi/type.c b/Modules/_testlimitedcapi/type.c
new file mode 100644
index 000000000000000..640468ae08dcbb6
--- /dev/null
+++ b/Modules/_testlimitedcapi/type.c
@@ -0,0 +1,222 @@
+// Thin wrappers to PyType functions.
+// Do no check PyType_Check() so Python tests can pass arbitrary objects,
+// even if it's likely to crash.
+
+// Need limited C API version 3.14 for PyType_Freeze()
+#include "pyconfig.h"   // Py_GIL_DISABLED
+#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
+#  define Py_LIMITED_API 0x030e0000
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+static PyType_Slot HeapTypeNameType_slots[] = {
+    {0},
+};
+
+static PyType_Spec HeapTypeNameType_Spec = {
+    .name = "_testcapi.HeapTypeNameType",
+    .basicsize = sizeof(PyObject),
+    .flags = Py_TPFLAGS_DEFAULT,
+    .slots = HeapTypeNameType_slots,
+};
+
+
+// Test PyType_FromSpec() with a minimum PyType_Spec
+static PyObject*
+get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    return PyType_FromSpec(&HeapTypeNameType_Spec);
+}
+
+
+// Test PyType_GetName()
+static PyObject*
+get_type_name(PyObject *self, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    return PyType_GetName(type);
+}
+
+
+// Test PyType_GetQualName()
+static PyObject*
+get_type_qualname(PyObject *self, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    return PyType_GetQualName(type);
+}
+
+
+// Test PyType_GetFullyQualifiedName()
+static PyObject*
+get_type_fullyqualname(PyObject *self, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    return PyType_GetFullyQualifiedName(type);
+}
+
+
+// Test PyType_GetModuleName()
+static PyObject*
+get_type_module_name(PyObject *self, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    return PyType_GetModuleName(type);
+}
+
+
+// Test PyType_Modified()
+static PyObject*
+type_modified(PyObject *self, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    PyType_Modified(type);
+    Py_RETURN_NONE;
+}
+
+
+// Test PyType_Ready()
+static PyObject*
+type_ready(PyObject *self, PyObject *arg)
+{
+    assert(!PyErr_Occurred());
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    if (PyType_Ready(type) < 0) {
+        assert(PyErr_Occurred());
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    Py_RETURN_NONE;
+}
+
+
+// Test PyType_Freeze()
+static PyObject *
+type_freeze(PyObject *module, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    if (PyType_Freeze(type) < 0) {
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+
+// Test PyType_ClearCache()
+static PyObject *
+type_clearcache(PyObject *module, PyObject *Py_UNUSED(arg))
+{
+    // Since Python 3.16, PyType_ClearCache() is a no-op as the type cache is
+    // now implemented per-type. It still returns the current version tag.
+    unsigned int version_tag = PyType_ClearCache();
+    assert(!PyErr_Occurred());
+    return PyLong_FromUnsignedLong(version_tag);
+}
+
+
+// Test PyType_GetFlags()
+static PyObject *
+type_getflags(PyObject *module, PyObject *arg)
+{
+    NULLABLE(arg);
+    PyTypeObject *type = (PyTypeObject*)arg;
+
+    unsigned long flags = PyType_GetFlags(type);
+    assert(!PyErr_Occurred());
+    return PyLong_FromUnsignedLong(flags);
+}
+
+
+// Test PyType_IsSubtype()
+static PyObject *
+type_issubtype(PyObject *module, PyObject *args)
+{
+    PyTypeObject *type1, *type2;
+    if (!PyArg_ParseTuple(args, "O!O!",
+                          &PyType_Type, &type1,
+                          &PyType_Type, &type2)) {
+        return NULL;
+    }
+
+    int is_subtype = PyType_IsSubtype(type1, type2);
+    return PyLong_FromLong(is_subtype);
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS},
+    {"get_type_name", get_type_name, METH_O},
+    {"get_type_qualname",  get_type_qualname, METH_O},
+    {"get_type_fullyqualname", get_type_fullyqualname, METH_O},
+    {"get_type_module_name", get_type_module_name, METH_O},
+    {"type_ready", type_ready, METH_O},
+    {"type_modified", type_modified, METH_O},
+    {"type_freeze", type_freeze, METH_O},
+    {"type_clearcache", type_clearcache, METH_NOARGS},
+    {"type_getflags", type_getflags, METH_O},
+    {"type_issubtype", type_issubtype, METH_VARARGS},
+    {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Type(PyObject *m)
+{
+    if (PyModule_AddFunctions(m, test_methods) < 0) {
+        return -1;
+    }
+
+#define ADD_INT(macro) \
+    do { \
+        if (PyModule_AddIntConstant(m, #macro, macro) < 0) { \
+            return -1; \
+        } \
+    } while (0)
+
+    ADD_INT(Py_TPFLAGS_DEFAULT);
+
+    ADD_INT(Py_TPFLAGS_HAVE_FINALIZE);
+    ADD_INT(Py_TPFLAGS_HAVE_GC);
+    ADD_INT(Py_TPFLAGS_HAVE_VERSION_TAG);
+    ADD_INT(Py_TPFLAGS_HAVE_VECTORCALL);
+
+    ADD_INT(Py_TPFLAGS_DISALLOW_INSTANTIATION );
+    ADD_INT(Py_TPFLAGS_IMMUTABLETYPE);
+    ADD_INT(Py_TPFLAGS_HEAPTYPE);
+    ADD_INT(Py_TPFLAGS_BASETYPE);
+    ADD_INT(Py_TPFLAGS_READY);
+    ADD_INT(Py_TPFLAGS_READYING);
+    ADD_INT(Py_TPFLAGS_METHOD_DESCRIPTOR);
+    ADD_INT(Py_TPFLAGS_VALID_VERSION_TAG);
+    ADD_INT(Py_TPFLAGS_IS_ABSTRACT);
+    ADD_INT(_Py_TPFLAGS_MATCH_SELF);
+    ADD_INT(Py_TPFLAGS_ITEMS_AT_END);
+
+    ADD_INT(Py_TPFLAGS_LONG_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_LIST_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_TUPLE_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_BYTES_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_UNICODE_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_DICT_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_BASE_EXC_SUBCLASS);
+    ADD_INT(Py_TPFLAGS_TYPE_SUBCLASS);
+
+#undef ADD_INT
+    return 0;
+}
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 785bb151e081293..c2bdea923f0f6d9 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -120,6 +120,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\run.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\type.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters 
b/PCbuild/_testlimitedcapi.vcxproj.filters
index 51dc9950a103769..4c999949474b976 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -36,6 +36,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\run.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\type.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi.c" />
   </ItemGroup>
   <ItemGroup>

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to