Author: Carl Friedrich Bolz <[email protected]>
Branch: guard-compatible
Changeset: r83139:76268c65c9d5
Date: 2016-03-18 13:57 +0100
http://bitbucket.org/pypy/pypy/changeset/76268c65c9d5/

Log:    make every map of a class have a version that is updated when the
        class is changed

diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py
--- a/pypy/objspace/std/mapdict.py
+++ b/pypy/objspace/std/mapdict.py
@@ -26,8 +26,11 @@
 # we want to propagate knowledge that the result cannot be negative
 
 
+class Version(object):
+    pass
+
 class AbstractAttribute(object):
-    _immutable_fields_ = ['terminator']
+    _immutable_fields_ = ['terminator', 'version?']
     cache_attrs = None
     _size_estimate = 0
 
@@ -35,6 +38,12 @@
         self.space = space
         assert isinstance(terminator, Terminator)
         self.terminator = terminator
+        # the maps have their own versions, if the terminator version is not
+        # None
+        if terminator.version is not None:
+            self.version = Version()
+        else:
+            self.version = None
 
     @jit.elidable_compatible()
     def getclass_from_terminator(self):
@@ -159,6 +168,9 @@
         attr = cache.get((name, index), None)
         if attr is None:
             attr = PlainAttribute(name, index, self)
+            if self.terminator.all_children is None:
+                self.terminator.all_children = []
+            self.terminator.all_children.append(attr)
             cache[name, index] = attr
         return attr
 
@@ -295,11 +307,28 @@
 
 
 class Terminator(AbstractAttribute):
-    _immutable_fields_ = ['w_cls']
+    _immutable_fields_ = ['w_cls', 'version?']
 
     def __init__(self, space, w_cls):
+        if w_cls._version_tag is None:
+            self.version = None
+        else:
+            self.version = Version()
         AbstractAttribute.__init__(self, space, self)
         self.w_cls = w_cls
+        self.all_children = None
+
+    def mutated_w_cls_version(self, version):
+        if version is None:
+            self.version = None
+        else:
+            self.version = Version()
+        if self.all_children is not None:
+            for map in self.all_children:
+                if version is None:
+                    map.version = None
+                else:
+                    map.version = Version()
 
     def _read_terminator(self, obj, name, index):
         return None
@@ -335,6 +364,10 @@
         Terminator.__init__(self, space, w_cls)
         self.devolved_dict_terminator = DevolvedDictTerminator(space, w_cls)
 
+    def mutated_w_cls_version(self, version):
+        self.devolved_dict_terminator.mutated_w_cls_version(version)
+        Terminator.mutated_w_cls_version(self, version)
+
     def materialize_r_dict(self, space, obj, dict_w):
         result = Object()
         result.space = space
diff --git a/pypy/objspace/std/test/test_mapdict.py 
b/pypy/objspace/std/test/test_mapdict.py
--- a/pypy/objspace/std/test/test_mapdict.py
+++ b/pypy/objspace/std/test/test_mapdict.py
@@ -17,6 +17,7 @@
 class Class(object):
     def __init__(self, hasdict=True):
         self.hasdict = True
+        self._version_tag = None
         if hasdict:
             self.terminator = DictTerminator(space, self)
         else:
@@ -34,7 +35,7 @@
         hasdict = False
 
 def test_plain_attribute():
-    w_cls = "class"
+    w_cls = Class()
     aa = PlainAttribute("b", DICT,
                         PlainAttribute("a", DICT,
                                        Terminator(space, w_cls)))
@@ -62,14 +63,14 @@
     assert aa.get_terminator() is aa.back.back
 
 def test_huge_chain():
-    current = Terminator(space, "cls")
+    current = Terminator(space, Class())
     for i in range(20000):
         current = PlainAttribute(str(i), DICT, current)
     assert current.find_map_attr("0", DICT).storageindex == 0
 
 
 def test_search():
-    aa = PlainAttribute("b", DICT, PlainAttribute("a", DICT, Terminator(None, 
None)))
+    aa = PlainAttribute("b", DICT, PlainAttribute("a", DICT, Terminator(None, 
Class())))
     assert aa.search(DICT) is aa
     assert aa.search(SLOTS_STARTING_FROM) is None
     assert aa.search(SPECIAL) is None
@@ -1209,6 +1210,7 @@
         got = x.a
         assert got == 'd'
 
+
 class AppTestGlobalCaching(AppTestWithMapDict):
     spaceconfig = {"objspace.std.withmethodcachecounter": True,
                    "objspace.std.withmapdict": True}
diff --git a/pypy/objspace/std/test/test_versionedtype.py 
b/pypy/objspace/std/test/test_versionedtype.py
--- a/pypy/objspace/std/test/test_versionedtype.py
+++ b/pypy/objspace/std/test/test_versionedtype.py
@@ -260,6 +260,68 @@
         assert space.float_w(cell.w_value) == 2.2
 
 
+class TestVersionedTypeMapDict(test_typeobject.TestTypeObject):
+    spaceconfig = {"objspace.std.withtypeversion": True,
+                   "objspace.std.withmapdict": True}
+
+    def get_three_classes_and_instances(self):
+        space = self.space
+        w_types = space.appexec([], """():
+            class A(object):
+                def f(self): pass
+            class B(A):
+                pass
+            class X:
+                pass
+            class Y(object):
+                pass
+            class C(Y, X):
+                pass
+            a = A()
+            a.x = 1
+            b = B()
+            b.x = 1
+            c = C()
+            c.x = 1
+            c.y = 2
+            return A, B, C, a, b, c
+        """)
+        return space.unpackiterable(w_types)
+
+    def test_update_map_version_too(self):
+        space = self.space
+        w_A, w_B, w_C, a, b, c = self.get_three_classes_and_instances()
+        def get_versions(cls, *maps):
+            result = [cls.version_tag(), cls.terminator.version,
+                      cls.terminator.devolved_dict_terminator.version]
+            result += [m.version for m in maps]
+            return result
+        def all_different(v1s, v2s):
+            for v1, v2 in zip(v1s, v2s):
+                assert v1 is not v2
+        aversions = get_versions(w_A, a.map)
+        bversions = get_versions(w_B, b.map)
+
+        assert w_C.version_tag() is None
+        assert w_C.terminator.version is None
+        assert c.map.version is None
+        # all versions are different
+        assert len(set(aversions)) == len(aversions)
+        assert len(set(bversions)) == len(bversions)
+
+        space.setattr(w_B, space.wrap("a"), space.wrap(1))
+        assert get_versions(w_A, a.map) == aversions
+        all_different(get_versions(w_B, b.map), bversions)
+        bversions = get_versions(w_B, b.map)
+
+        space.setattr(w_A, space.wrap("f"), space.wrap(5))
+        all_different(get_versions(w_A, a.map), aversions)
+        all_different(get_versions(w_B, b.map), bversions)
+
+        space.delattr(w_A, space.wrap("f"))
+        all_different(get_versions(w_A, a.map), aversions)
+        all_different(get_versions(w_B, b.map), bversions)
+
 
 class AppTestVersionedType(test_typeobject.AppTestTypeObject):
     spaceconfig = {"objspace.std.withtypeversion": True}
diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py
--- a/pypy/objspace/std/typeobject.py
+++ b/pypy/objspace/std/typeobject.py
@@ -217,7 +217,7 @@
 
         if (space.config.objspace.std.withtypeversion
             and w_self._version_tag is not None):
-            w_self._version_tag = VersionTag()
+            w_self._set_version_tag(VersionTag())
 
         subclasses_w = w_self.get_subclasses()
         for w_subclass in subclasses_w:
@@ -234,6 +234,11 @@
     def _pure_version_tag(w_self):
         return w_self._version_tag
 
+    def _set_version_tag(self, version_tag):
+        self._version_tag = version_tag
+        if self.space.config.objspace.std.withmapdict:
+            self.terminator.mutated_w_cls_version(version_tag)
+
     def getattribute_if_not_from_object(w_self):
         """ this method returns the applevel __getattribute__ if that is not
         the one from object, in which case it returns None """
@@ -837,10 +842,10 @@
         w_type.version_tag() is not None and
         not is_mro_purely_of_types(w_type.mro_w)):
         # Disable method cache if the hierarchy isn't pure.
-        w_type._version_tag = None
+        w_type._set_version_tag(None)
         for w_subclass in w_type.get_subclasses():
             if isinstance(w_subclass, W_TypeObject):
-                w_subclass._version_tag = None
+                w_subclass._set_version_tag(None)
 
 def descr__base(space, w_type):
     w_type = _check(space, w_type)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to