bpo-43908: Add Py_TPFLAGS_IMMUTABLETYPE flag (GH-25520)

Introduce Py_TPFLAGS_IMMUTABLETYPE flag for immutable type objects, and
modify PyType_Ready() to set it for static types.

Co-authored-by: Victor Stinner <vstinner@python.org>
diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst
index 9efe3aa..4c75a12 100644
--- a/Doc/c-api/typeobj.rst
+++ b/Doc/c-api/typeobj.rst
@@ -1177,6 +1177,18 @@
 
       .. versionadded:: 3.10
 
+   .. data:: Py_TPFLAGS_IMMUTABLETYPE
+
+      This bit is set for type objects that are immutable: type attributes cannot be set nor deleted.
+
+      :c:func:`PyType_Ready` automatically applies this flag to static types.
+
+      **Inheritance:**
+
+      This flag is not inherited.
+
+      .. versionadded:: 3.10
+
 
 .. c:member:: const char* PyTypeObject.tp_doc
 
diff --git a/Include/object.h b/Include/object.h
index 695f015..d8476f9 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -320,6 +320,9 @@ Code can use PyType_HasFeature(type_ob, flag_value) to test whether the
 given type object has a specified feature.
 */
 
+/* Set if the type object is immutable: type attributes cannot be set nor deleted */
+#define Py_TPFLAGS_IMMUTABLETYPE (1UL << 8)
+
 /* Set if the type object is dynamically allocated */
 #define Py_TPFLAGS_HEAPTYPE (1UL << 9)
 
diff --git a/Misc/NEWS.d/next/C API/2021-04-22-10-46-40.bpo-43908.Co3YhZ.rst b/Misc/NEWS.d/next/C API/2021-04-22-10-46-40.bpo-43908.Co3YhZ.rst
new file mode 100644
index 0000000..0413c20
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2021-04-22-10-46-40.bpo-43908.Co3YhZ.rst
@@ -0,0 +1,3 @@
+Introduce :const:`Py_TPFLAGS_IMMUTABLETYPE` flag for immutable type objects, and
+modify :c:func:`PyType_Ready` to set it for static types. Patch by
+Erlend E. Aasland.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 254d12c..e1c8be4 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3875,7 +3875,7 @@ static int
 type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
 {
     int res;
-    if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+    if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) {
         PyErr_Format(
             PyExc_TypeError,
             "can't set attributes of built-in/extension type '%s'",
@@ -6229,6 +6229,11 @@ PyType_Ready(PyTypeObject *type)
 
     type->tp_flags |= Py_TPFLAGS_READYING;
 
+    /* Historically, all static types were immutable. See bpo-43908 */
+    if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+        type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
+    }
+
     if (type_ready(type) < 0) {
         type->tp_flags &= ~Py_TPFLAGS_READYING;
         return -1;