diff --git a/Include/object.h b/Include/object.h
index 20c2dab4401fef..549b91ebecf4f2 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 e6a8ef9eed6fc4..90b1f57d1c3c9d 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 9827f2498554a5..0031e4b59c67c9 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 eaf8777a56c59b..440d5a71608ba9 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 f566efa0ca15ae..91203c7bf0f678 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 0a562ea9c03110..b602d3219f5a52 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 32c1bbc1b71c97..35fbdea1d434e0 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 00000000000000..640468ae08dcbb
--- /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 785bb151e08129..c2bdea923f0f6d 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -120,6 +120,7 @@
+
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index 51dc9950a10376..4c999949474b97 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -36,6 +36,7 @@
+