diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 7469a1047f81d70..0390e6496109ec0 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -1,4 +1,5 @@ """ Tests for the internal type cache in CPython. """ +import collections.abc import dis import unittest import warnings @@ -114,6 +115,25 @@ class HolderSub(Holder): Holder.set_value() HolderSub.value + def test_abc_register_invalidates_subclass_versions(self): + class Parent: + pass + + class Child(Parent): + pass + + type_assign_version(Parent) + type_assign_version(Child) + parent_version = type_get_version(Parent) + child_version = type_get_version(Child) + if parent_version == 0 or child_version == 0: + self.skipTest("Could not assign valid type versions") + + collections.abc.Mapping.register(Parent) + + self.assertEqual(type_get_version(Parent), 0) + self.assertEqual(type_get_version(Child), 0) + @support.cpython_only class TypeCacheWithSpecializationTests(unittest.TestCase): def tearDown(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst new file mode 100644 index 000000000000000..2a7d0d9bb3a7f7e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst @@ -0,0 +1 @@ +Fix ``abc.register()`` so it invalidates type version tags for registered classes. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0db171807aca4ba..0f1660f8f40b217 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6066,6 +6066,15 @@ void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long flags) { BEGIN_TYPE_LOCK(); + /* Ideally, changing flags and invalidating the old version tag would happen + in one step. In 3.14, invalidate first while holding TYPE_LOCK so readers + cannot assign a fresh tag from stale flags. Immutable types are skipped by + set_flags_recursive(). */ + if (!PyType_HasFeature(self, Py_TPFLAGS_IMMUTABLETYPE) && + (self->tp_flags & mask) != flags) + { + type_modified_unlocked(self); + } set_flags_recursive(self, mask, flags); END_TYPE_LOCK(); }