diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 8e2bb0c238257b3..a6a6e8cbdc1053b 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 unittest import dis from test import support @@ -108,6 +109,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 @requires_specialization class TypeCacheWithSpecializationTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-09-55-50.gh-issue-148450.2MEVqH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-09-55-50.gh-issue-148450.2MEVqH.rst new file mode 100644 index 000000000000000..2a7d0d9bb3a7f7e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-09-55-50.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 51d1ce573d12e13..7ae4cce02cf6834 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5398,6 +5398,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. For this backport, keep it simple and invalidate + first while holding TYPE_LOCK. Immutable types are skipped because + set_flags_recursive() does not modify them. */ + 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(); }