Add a new private version to the builtin dict type

Issue #26058: Add a new private version to the builtin dict type, incremented
at each dictionary creation and at each dictionary change.

Implementation of the PEP 509.
diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py
index 43600e4..d6e72a6 100644
--- a/Lib/test/test_ordered_dict.py
+++ b/Lib/test/test_ordered_dict.py
@@ -655,7 +655,7 @@
         size = support.calcobjsize
         check = self.check_sizeof
 
-        basicsize = size('n2P' + '3PnPn2P') + calcsize('2nP2n')
+        basicsize = size('n2P3PnPn2P') + 8 + calcsize('2nP2n')
         entrysize = calcsize('n2P')
         p = calcsize('P')
         nodesize = calcsize('Pn2P')
diff --git a/Lib/test/test_pep509.py b/Lib/test/test_pep509.py
new file mode 100644
index 0000000..5671f9f
--- /dev/null
+++ b/Lib/test/test_pep509.py
@@ -0,0 +1,186 @@
+"""
+Test implementation of the PEP 509: dictionary versionning.
+"""
+import unittest
+from test import support
+
+# PEP 509 is implemented in CPython but other Python implementations
+# don't require to implement it
+_testcapi = support.import_module('_testcapi')
+
+
+class DictVersionTests(unittest.TestCase):
+    type2test = dict
+
+    def setUp(self):
+        self.seen_versions = set()
+        self.dict = None
+
+    def check_version_unique(self, mydict):
+        version = _testcapi.dict_get_version(mydict)
+        self.assertNotIn(version, self.seen_versions)
+        self.seen_versions.add(version)
+
+    def check_version_changed(self, mydict, method, *args, **kw):
+        result = method(*args, **kw)
+        self.check_version_unique(mydict)
+        return result
+
+    def check_version_dont_change(self, mydict, method, *args, **kw):
+        version1 = _testcapi.dict_get_version(mydict)
+        self.seen_versions.add(version1)
+
+        result = method(*args, **kw)
+
+        version2 = _testcapi.dict_get_version(mydict)
+        self.assertEqual(version2, version1, "version changed")
+
+        return  result
+
+    def new_dict(self, *args, **kw):
+        d = self.type2test(*args, **kw)
+        self.check_version_unique(d)
+        return d
+
+    def test_constructor(self):
+        # new empty dictionaries must all have an unique version
+        empty1 = self.new_dict()
+        empty2 = self.new_dict()
+        empty3 = self.new_dict()
+
+        # non-empty dictionaries must also have an unique version
+        nonempty1 = self.new_dict(x='x')
+        nonempty2 = self.new_dict(x='x', y='y')
+
+    def test_copy(self):
+        d = self.new_dict(a=1, b=2)
+
+        d2 = self.check_version_dont_change(d, d.copy)
+
+        # dict.copy() must create a dictionary with a new unique version
+        self.check_version_unique(d2)
+
+    def test_setitem(self):
+        d = self.new_dict()
+
+        # creating new keys must change the version
+        self.check_version_changed(d, d.__setitem__, 'x', 'x')
+        self.check_version_changed(d, d.__setitem__, 'y', 'y')
+
+        # changing values must change the version
+        self.check_version_changed(d, d.__setitem__, 'x', 1)
+        self.check_version_changed(d, d.__setitem__, 'y', 2)
+
+    def test_setitem_same_value(self):
+        value = object()
+        d = self.new_dict()
+
+        # setting a key must change the version
+        self.check_version_changed(d, d.__setitem__, 'key', value)
+
+        # setting a key to the same value with dict.__setitem__
+        # must change the version
+        self.check_version_changed(d, d.__setitem__, 'key', value)
+
+        # setting a key to the same value with dict.update
+        # must change the version
+        self.check_version_changed(d, d.update, key=value)
+
+        d2 = self.new_dict(key=value)
+        self.check_version_changed(d, d.update, d2)
+
+    def test_setitem_equal(self):
+        class AlwaysEqual:
+            def __eq__(self, other):
+                return True
+
+        value1 = AlwaysEqual()
+        value2 = AlwaysEqual()
+        self.assertTrue(value1 == value2)
+        self.assertFalse(value1 != value2)
+
+        d = self.new_dict()
+        self.check_version_changed(d, d.__setitem__, 'key', value1)
+
+        # setting a key to a value equal to the current value
+        # with dict.__setitem__() must change the version
+        self.check_version_changed(d, d.__setitem__, 'key', value2)
+
+        # setting a key to a value equal to the current value
+        # with dict.update() must change the version
+        self.check_version_changed(d, d.update, key=value1)
+
+        d2 = self.new_dict(key=value2)
+        self.check_version_changed(d, d.update, d2)
+
+    def test_setdefault(self):
+        d = self.new_dict()
+
+        # setting a key with dict.setdefault() must change the version
+        self.check_version_changed(d, d.setdefault, 'key', 'value1')
+
+        # don't change the version if the key already exists
+        self.check_version_dont_change(d, d.setdefault, 'key', 'value2')
+
+    def test_delitem(self):
+        d = self.new_dict(key='value')
+
+        # deleting a key with dict.__delitem__() must change the version
+        self.check_version_changed(d, d.__delitem__, 'key')
+
+        # don't change the version if the key doesn't exist
+        self.check_version_dont_change(d, self.assertRaises, KeyError,
+                                       d.__delitem__, 'key')
+
+    def test_pop(self):
+        d = self.new_dict(key='value')
+
+        # pop() must change the version if the key exists
+        self.check_version_changed(d, d.pop, 'key')
+
+        # pop() must not change the version if the key does not exist
+        self.check_version_dont_change(d, self.assertRaises, KeyError,
+                                       d.pop, 'key')
+
+    def test_popitem(self):
+        d = self.new_dict(key='value')
+
+        # popitem() must change the version if the dict is not empty
+        self.check_version_changed(d, d.popitem)
+
+        # popitem() must not change the version if the dict is empty
+        self.check_version_dont_change(d, self.assertRaises, KeyError,
+                                       d.popitem)
+
+    def test_update(self):
+        d = self.new_dict(key='value')
+
+        # update() calling with no argument must not change the version
+        self.check_version_dont_change(d, d.update)
+
+        # update() must change the version
+        self.check_version_changed(d, d.update, key='new value')
+
+        d2 = self.new_dict(key='value 3')
+        self.check_version_changed(d, d.update, d2)
+
+    def test_clear(self):
+        d = self.new_dict(key='value')
+
+        # clear() must change the version if the dict is not empty
+        self.check_version_changed(d, d.clear)
+
+        # clear() must not change the version if the dict is empty
+        self.check_version_dont_change(d, d.clear)
+
+
+class Dict(dict):
+    pass
+
+
+class DictSubtypeVersionTests(DictVersionTests):
+    type2test = Dict
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 6084d2d..aa89506 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -937,9 +937,9 @@
         # method-wrapper (descriptor object)
         check({}.__iter__, size('2P'))
         # dict
-        check({}, size('n2P') + calcsize('2nP2n') + 8 + (8*2//3)*calcsize('n2P'))
+        check({}, size('n2P') + 8 + calcsize('2nP2n') + 8 + (8*2//3)*calcsize('n2P'))
         longdict = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8}
-        check(longdict, size('n2P') + calcsize('2nP2n') + 16 + (16*2//3)*calcsize('n2P'))
+        check(longdict, size('n2P') + 8 + calcsize('2nP2n') + 16 + (16*2//3)*calcsize('n2P'))
         # dictionary-keyview
         check({}.keys(), size('P'))
         # dictionary-valueview
@@ -1103,7 +1103,7 @@
         class newstyleclass(object): pass
         check(newstyleclass, s)
         # dict with shared keys
-        check(newstyleclass().__dict__, size('n2P' + '2nP2n'))
+        check(newstyleclass().__dict__, size('n2P' + '2nP2n') + 8)
         # unicode
         # each tuple contains a string and its expected character size
         # don't put any static strings here, as they may contain