Author: mattip <matti.pi...@gmail.com>
Branch: numpy-1.10
Changeset: r80686:9286403425c3
Date: 2015-11-15 18:07 +0200
http://bitbucket.org/pypy/pypy/changeset/9286403425c3/

Log:    merge default into branch

diff too long, truncating to 2000 out of 4464 lines

diff --git a/lib_pypy/cffi.egg-info/PKG-INFO b/lib_pypy/cffi.egg-info/PKG-INFO
--- a/lib_pypy/cffi.egg-info/PKG-INFO
+++ b/lib_pypy/cffi.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: cffi
-Version: 1.3.0
+Version: 1.3.1
 Summary: Foreign Function Interface for Python calling C code.
 Home-page: http://cffi.readthedocs.org
 Author: Armin Rigo, Maciej Fijalkowski
diff --git a/lib_pypy/cffi/__init__.py b/lib_pypy/cffi/__init__.py
--- a/lib_pypy/cffi/__init__.py
+++ b/lib_pypy/cffi/__init__.py
@@ -4,8 +4,8 @@
 from .api import FFI, CDefError, FFIError
 from .ffiplatform import VerificationError, VerificationMissing
 
-__version__ = "1.3.0"
-__version_info__ = (1, 3, 0)
+__version__ = "1.3.1"
+__version_info__ = (1, 3, 1)
 
 # The verifier module file names are based on the CRC32 of a string that
 # contains the following version number.  It may be older than __version__
diff --git a/lib_pypy/cffi/model.py b/lib_pypy/cffi/model.py
--- a/lib_pypy/cffi/model.py
+++ b/lib_pypy/cffi/model.py
@@ -514,12 +514,17 @@
         if self.baseinttype is not None:
             return self.baseinttype.get_cached_btype(ffi, finishlist)
         #
+        from . import api
         if self.enumvalues:
             smallest_value = min(self.enumvalues)
             largest_value = max(self.enumvalues)
         else:
-            smallest_value = 0
-            largest_value = 0
+            import warnings
+            warnings.warn("%r has no values explicitly defined; next version "
+                          "will refuse to guess which integer type it is "
+                          "meant to be (unsigned/signed, int/long)"
+                          % self._get_c_name())
+            smallest_value = largest_value = 0
         if smallest_value < 0:   # needs a signed type
             sign = 1
             candidate1 = PrimitiveType("int")
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -29,3 +29,7 @@
 
 Support common use-cases for __array_interface__, passes upstream tests
 
+.. branch: no-class-specialize
+
+Some refactoring of class handling in the annotator. 
+Remove class specialisation and _settled_ flag.
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -28,7 +28,6 @@
     """This is the abstract root class of all wrapped objects that live
     in a 'normal' object space like StdObjSpace."""
     __slots__ = ()
-    _settled_ = True
     user_overridden_class = False
 
     def getdict(self, space):
diff --git a/pypy/module/_cffi_backend/__init__.py 
b/pypy/module/_cffi_backend/__init__.py
--- a/pypy/module/_cffi_backend/__init__.py
+++ b/pypy/module/_cffi_backend/__init__.py
@@ -2,7 +2,7 @@
 from pypy.interpreter.mixedmodule import MixedModule
 from rpython.rlib import rdynload, clibffi
 
-VERSION = "1.3.0"
+VERSION = "1.3.1"
 
 FFI_DEFAULT_ABI = clibffi.FFI_DEFAULT_ABI
 try:
diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py 
b/pypy/module/_cffi_backend/test/_backend_test_c.py
--- a/pypy/module/_cffi_backend/test/_backend_test_c.py
+++ b/pypy/module/_cffi_backend/test/_backend_test_c.py
@@ -1,7 +1,7 @@
 # ____________________________________________________________
 
 import sys
-assert __version__ == "1.3.0", ("This test_c.py file is for testing a version"
+assert __version__ == "1.3.1", ("This test_c.py file is for testing a version"
                                 " of cffi that differs from the one that we"
                                 " get from 'import _cffi_backend'")
 if sys.version_info < (3,):
diff --git a/pypy/module/_minimal_curses/interp_curses.py 
b/pypy/module/_minimal_curses/interp_curses.py
--- a/pypy/module/_minimal_curses/interp_curses.py
+++ b/pypy/module/_minimal_curses/interp_curses.py
@@ -13,7 +13,7 @@
     def __init__(self, msg):
         self.msg = msg
 
-from rpython.annotator.description import FORCE_ATTRIBUTES_INTO_CLASSES
+from rpython.annotator.classdesc import FORCE_ATTRIBUTES_INTO_CLASSES
 from rpython.annotator.model import SomeString
 
 # this is necessary due to annmixlevel
diff --git a/pypy/module/_multiprocessing/interp_win32.py 
b/pypy/module/_multiprocessing/interp_win32.py
--- a/pypy/module/_multiprocessing/interp_win32.py
+++ b/pypy/module/_multiprocessing/interp_win32.py
@@ -17,7 +17,7 @@
     NMPWAIT_WAIT_FOREVER
     ERROR_PIPE_CONNECTED ERROR_SEM_TIMEOUT ERROR_PIPE_BUSY
     ERROR_NO_SYSTEM_RESOURCES ERROR_BROKEN_PIPE ERROR_MORE_DATA
-    ERROR_ALREADY_EXISTS
+    ERROR_ALREADY_EXISTS ERROR_NO_DATA
 """.split()
 
 class CConfig:
diff --git a/pypy/module/cpyext/pystrtod.py b/pypy/module/cpyext/pystrtod.py
--- a/pypy/module/cpyext/pystrtod.py
+++ b/pypy/module/cpyext/pystrtod.py
@@ -5,10 +5,23 @@
 from rpython.rlib import rdtoa
 from rpython.rlib import rfloat
 from rpython.rlib import rposix, jit
+from rpython.rlib.rarithmetic import intmask
 from rpython.rtyper.lltypesystem import lltype
 from rpython.rtyper.lltypesystem import rffi
 
 
+# PyOS_double_to_string's "type", if non-NULL, will be set to one of:
+Py_DTST_FINITE = 0
+Py_DTST_INFINITE = 1
+Py_DTST_NAN = 2
+
+# Match the "type" back to values in CPython
+DOUBLE_TO_STRING_TYPES_MAP = {
+    rfloat.DIST_FINITE: Py_DTST_FINITE,
+    rfloat.DIST_INFINITY: Py_DTST_INFINITE,
+    rfloat.DIST_NAN: Py_DTST_NAN
+}
+
 @cpython_api([rffi.CCHARP, rffi.CCHARPP, PyObject], rffi.DOUBLE, error=-1.0)
 @jit.dont_look_inside       # direct use of _get_errno()
 def PyOS_string_to_double(space, s, endptr, w_overflow_exception):
@@ -68,3 +81,42 @@
     finally:
         if not user_endptr:
             lltype.free(endptr, flavor='raw')
+
+@cpython_api([rffi.DOUBLE, lltype.Char, rffi.INT_real, rffi.INT_real, 
rffi.INTP], rffi.CCHARP)
+def PyOS_double_to_string(space, val, format_code, precision, flags, ptype):
+    """Convert a double val to a string using supplied
+    format_code, precision, and flags.
+
+    format_code must be one of 'e', 'E', 'f', 'F',
+    'g', 'G' or 'r'.  For 'r', the supplied precision
+    must be 0 and is ignored.  The 'r' format code specifies the
+    standard repr() format.
+
+    flags can be zero or more of the values Py_DTSF_SIGN,
+    Py_DTSF_ADD_DOT_0, or Py_DTSF_ALT, or-ed together:
+
+    Py_DTSF_SIGN means to always precede the returned string with a sign
+    character, even if val is non-negative.
+
+    Py_DTSF_ADD_DOT_0 means to ensure that the returned string will not look
+    like an integer.
+
+    Py_DTSF_ALT means to apply "alternate" formatting rules.  See the
+    documentation for the PyOS_snprintf() '#' specifier for
+    details.
+
+    If ptype is non-NULL, then the value it points to will be set to one of
+    Py_DTST_FINITE, Py_DTST_INFINITE, or Py_DTST_NAN, signifying that
+    val is a finite number, an infinite number, or not a number, respectively.
+
+    The return value is a pointer to buffer with the converted string or
+    NULL if the conversion failed. The caller is responsible for freeing the
+    returned string by calling PyMem_Free().
+    """
+    buffer, rtype = rfloat.double_to_string(val, format_code,
+                                            intmask(precision),
+                                            intmask(flags))
+    if ptype != lltype.nullptr(rffi.INTP.TO):
+        ptype[0] = rffi.cast(rffi.INT, DOUBLE_TO_STRING_TYPES_MAP[rtype])
+    bufp = rffi.str2charp(buffer)
+    return bufp
diff --git a/pypy/module/cpyext/test/test_pystrtod.py 
b/pypy/module/cpyext/test/test_pystrtod.py
--- a/pypy/module/cpyext/test/test_pystrtod.py
+++ b/pypy/module/cpyext/test/test_pystrtod.py
@@ -1,5 +1,6 @@
 import math
 
+from pypy.module.cpyext import pystrtod
 from pypy.module.cpyext.test.test_api import BaseApiTest
 from rpython.rtyper.lltypesystem import rffi
 from rpython.rtyper.lltypesystem import lltype
@@ -91,3 +92,76 @@
         api.PyErr_Clear()
         rffi.free_charp(s)
         lltype.free(endp, flavor='raw')
+
+
+class TestPyOS_double_to_string(BaseApiTest):
+
+    def test_format_code(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(150.0, 'e', 1, 0, ptype)
+        assert '1.5e+02' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_FINITE == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_precision(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(3.14159269397, 'g', 5, 0, ptype)
+        assert '3.1416' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_FINITE == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_flags_sign(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(-3.14, 'g', 3, 1, ptype)
+        assert '-3.14' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_FINITE == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_flags_add_dot_0(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(3, 'g', 5, 2, ptype)
+        assert '3.0' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_FINITE == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_flags_alt(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(314., 'g', 3, 4, ptype)
+        assert '314.' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_FINITE == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_ptype_nan(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(float('nan'), 'g', 3, 4, ptype)
+        assert 'nan' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_NAN == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_ptype_infinity(self, api):
+        ptype = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        r = api.PyOS_double_to_string(1e200 * 1e200, 'g', 0, 0, ptype)
+        assert 'inf' == rffi.charp2str(r)
+        type_value = rffi.cast(lltype.Signed, ptype[0])
+        assert pystrtod.Py_DTST_INFINITE == type_value
+        rffi.free_charp(r)
+        lltype.free(ptype, flavor='raw')
+
+    def test_ptype_null(self, api):
+        ptype = lltype.nullptr(rffi.INTP.TO)
+        r = api.PyOS_double_to_string(3.14, 'g', 3, 0, ptype)
+        assert '3.14' == rffi.charp2str(r)
+        assert ptype == lltype.nullptr(rffi.INTP.TO)
+        rffi.free_charp(r)
\ No newline at end of file
diff --git a/pypy/module/marshal/interp_marshal.py 
b/pypy/module/marshal/interp_marshal.py
--- a/pypy/module/marshal/interp_marshal.py
+++ b/pypy/module/marshal/interp_marshal.py
@@ -156,9 +156,6 @@
     put_tuple_w(TYPE, tuple_w)  puts tuple_w, an unwrapped list of wrapped 
objects
     """
 
-    # _annspecialcase_ = "specialize:ctr_location" # polymorphic
-    # does not work with subclassing
-
     def __init__(self, space, writer, version):
         self.space = space
         ## self.put = putfunc
diff --git a/pypy/module/micronumpy/loop.py b/pypy/module/micronumpy/loop.py
--- a/pypy/module/micronumpy/loop.py
+++ b/pypy/module/micronumpy/loop.py
@@ -684,8 +684,9 @@
     arr_iter, arr_state = arr.create_iter()
     arr_dtype = arr.get_dtype()
     index_dtype = index.get_dtype()
-    # XXX length of shape of index as well?
-    while not index_iter.done(index_state):
+    # support the deprecated form where arr([True]) will return arr[0, ...]
+    # by iterating over res_iter, not index_iter
+    while not res_iter.done(res_state):
         getitem_filter_driver.jit_merge_point(shapelen=shapelen,
                                               index_dtype=index_dtype,
                                               arr_dtype=arr_dtype,
diff --git a/pypy/module/micronumpy/test/test_ndarray.py 
b/pypy/module/micronumpy/test/test_ndarray.py
--- a/pypy/module/micronumpy/test/test_ndarray.py
+++ b/pypy/module/micronumpy/test/test_ndarray.py
@@ -2235,6 +2235,9 @@
         c = array([True,False,True],bool)
         b = a[c]
         assert (a[c] == [[1, 2, 3], [7, 8, 9]]).all()
+        c = array([True])
+        b = a[c]
+        assert b.shape == (1, 3)
 
     def test_bool_array_index_setitem(self):
         from numpy import arange, array
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py 
b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
@@ -1336,7 +1336,8 @@
         # these depend on user-defined data, so should not be shared
         assert ffi1.typeof("struct foo") is not ffi2.typeof("struct foo")
         assert ffi1.typeof("union foo *") is not ffi2.typeof("union foo*")
-        assert ffi1.typeof("enum foo") is not ffi2.typeof("enum foo")
+        # the following test is an opaque enum, which we no longer support
+        #assert ffi1.typeof("enum foo") is not ffi2.typeof("enum foo")
         # sanity check: twice 'ffi1'
         assert ffi1.typeof("struct foo*") is ffi1.typeof("struct foo *")
 
@@ -1348,6 +1349,17 @@
         assert ffi.getctype("pe") == 'e *'
         assert ffi.getctype("e1*") == 'e1 *'
 
+    def test_opaque_enum(self):
+        ffi = FFI(backend=self.Backend())
+        ffi.cdef("enum foo;")
+        from cffi import __version_info__
+        if __version_info__ < (1, 4):
+            py.test.skip("re-enable me in version 1.4")
+        e = py.test.raises(CDefError, ffi.cast, "enum foo", -1)
+        assert str(e.value) == (
+            "'enum foo' has no values explicitly defined: refusing to guess "
+            "which integer type it is meant to be (unsigned/signed, int/long)")
+
     def test_new_ctype(self):
         ffi = FFI(backend=self.Backend())
         p = ffi.new("int *")
diff --git a/pypy/objspace/std/bytearrayobject.py 
b/pypy/objspace/std/bytearrayobject.py
--- a/pypy/objspace/std/bytearrayobject.py
+++ b/pypy/objspace/std/bytearrayobject.py
@@ -1231,6 +1231,21 @@
     def setitem(self, index, char):
         self.data[index] = char
 
+    def getslice(self, start, stop, step, size):
+        if size == 0:
+            return ""
+        if step == 1:
+            assert 0 <= start <= stop
+            if start == 0 and stop == len(self.data):
+                return "".join(self.data)
+            return "".join(self.data[start:stop])
+        return Buffer.getslice(self, start, stop, step, size)
+
+    def setslice(self, start, string):
+        # No bounds checks.
+        for i in range(len(string)):
+            self.data[start + i] = string[i]
+
 
 @specialize.argtype(1)
 def _memcmp(selfvalue, buffer, length):
diff --git a/pypy/objspace/std/test/test_tupleobject.py 
b/pypy/objspace/std/test/test_tupleobject.py
--- a/pypy/objspace/std/test/test_tupleobject.py
+++ b/pypy/objspace/std/test/test_tupleobject.py
@@ -413,8 +413,9 @@
             from __pypy__ import specialized_zip_2_lists
         except ImportError:
             specialized_zip_2_lists = zip
-        raises(TypeError, specialized_zip_2_lists, [], ())
-        raises(TypeError, specialized_zip_2_lists, (), [])
+        else:
+            raises(TypeError, specialized_zip_2_lists, [], ())
+            raises(TypeError, specialized_zip_2_lists, (), [])
         assert specialized_zip_2_lists([], []) == [
             ]
         assert specialized_zip_2_lists([2, 3], []) == [
diff --git a/pypy/tool/ann_override.py b/pypy/tool/ann_override.py
--- a/pypy/tool/ann_override.py
+++ b/pypy/tool/ann_override.py
@@ -2,6 +2,7 @@
 from rpython.annotator.policy import AnnotatorPolicy
 from rpython.flowspace.model import Constant
 from rpython.annotator import specialize
+from rpython.annotator.classdesc import InstanceSource, ClassDef
 
 
 
@@ -20,7 +21,6 @@
 
     def specialize__wrap(self,  funcdesc, args_s):
         from pypy.interpreter.baseobjspace import W_Root
-        from rpython.annotator.classdef import ClassDef
         W_Root_def = funcdesc.bookkeeper.getuniqueclassdef(W_Root)
         typ = args_s[1].knowntype
         if isinstance(typ, ClassDef):
@@ -50,54 +50,34 @@
                 typ = (None, str)
         return funcdesc.cachedgraph(typ)
 
-    def _remember_immutable(self, t, cached):
-        # for jit benefit
-        if cached not in t._immutable_fields_: # accessed this way just
-                                               # for convenience
-            t._immutable_fields_.append(cached)
-
-    def attach_lookup(self, t, attr):
-        cached = "cached_%s" % attr
-        if not t.is_heaptype() and not t.is_cpytype():
-            self._remember_immutable(t, cached)
-            setattr(t, cached, t._lookup(attr))
-            return True
-        return False
-
-    def attach_lookup_in_type_where(self, t, attr):
-        cached = "cached_where_%s" % attr
-        if not t.is_heaptype() and not t.is_cpytype():
-            self._remember_immutable(t, cached)
-            setattr(t, cached, t._lookup_where(attr))
-            return True
-        return False
-
     def consider_lookup(self, bookkeeper, attr):
-        from rpython.annotator.classdef import InstanceSource
         assert attr not in self.lookups
         from pypy.objspace.std import typeobject
         cached = "cached_%s" % attr
         clsdef = bookkeeper.getuniqueclassdef(typeobject.W_TypeObject)
         classdesc = clsdef.classdesc
+        classdesc.immutable_fields.add(cached)
         classdesc.classdict[cached] = Constant(None)
         clsdef.add_source_for_attribute(cached, classdesc)
         for t in self.pypytypes:
-            if self.attach_lookup(t, attr):
+            if not (t.is_heaptype() or t.is_cpytype()):
+                setattr(t, cached, t._lookup(attr))
                 source = InstanceSource(bookkeeper, t)
                 clsdef.add_source_for_attribute(cached, source)
         self.lookups[attr] = True
 
     def consider_lookup_in_type_where(self, bookkeeper, attr):
-        from rpython.annotator.classdef import InstanceSource
         assert attr not in self.lookups_where
         from pypy.objspace.std import typeobject
         cached = "cached_where_%s" % attr
         clsdef = bookkeeper.getuniqueclassdef(typeobject.W_TypeObject)
         classdesc = clsdef.classdesc
+        classdesc.immutable_fields.add(cached)
         classdesc.classdict[cached] = Constant((None, None))
         clsdef.add_source_for_attribute(cached, classdesc)
         for t in self.pypytypes:
-            if self.attach_lookup_in_type_where(t, attr):
+            if not (t.is_heaptype() or t.is_cpytype()):
+                setattr(t, cached, t._lookup_where(attr))
                 source = InstanceSource(bookkeeper, t)
                 clsdef.add_source_for_attribute(cached, source)
         self.lookups_where[attr] = True
@@ -135,18 +115,19 @@
     def event(self, bookkeeper, what, x):
         from pypy.objspace.std import typeobject
         if isinstance(x, typeobject.W_TypeObject):
-            from rpython.annotator.classdef import InstanceSource
             clsdef = bookkeeper.getuniqueclassdef(typeobject.W_TypeObject)
             self.pypytypes[x] = True
             #print "TYPE", x
             for attr in self.lookups:
-                if attr and self.attach_lookup(x, attr):
+                if attr and not (x.is_heaptype() or x.is_cpytype()):
                     cached = "cached_%s" % attr
+                    setattr(x, cached, x._lookup(attr))
                     source = InstanceSource(bookkeeper, x)
                     clsdef.add_source_for_attribute(cached, source)
             for attr in self.lookups_where:
-                if attr and self.attach_lookup_in_type_where(x, attr):
+                if attr and not (x.is_heaptype() or x.is_cpytype()):
                     cached = "cached_where_%s" % attr
+                    setattr(x, cached, x._lookup_where(attr))
                     source = InstanceSource(bookkeeper, x)
                     clsdef.add_source_for_attribute(cached, source)
         return
diff --git a/rpython/annotator/bookkeeper.py b/rpython/annotator/bookkeeper.py
--- a/rpython/annotator/bookkeeper.py
+++ b/rpython/annotator/bookkeeper.py
@@ -14,8 +14,8 @@
     SomeDict, SomeBuiltin, SomePBC, SomeInteger, TLS, SomeUnicodeCodePoint,
     s_None, s_ImpossibleValue, SomeBool, SomeTuple,
     SomeImpossibleValue, SomeUnicodeString, SomeList, HarmlesslyBlocked,
-    SomeWeakRef, SomeByteArray, SomeConstantType, SomeProperty, AnnotatorError)
-from rpython.annotator.classdef import InstanceSource, ClassDef
+    SomeWeakRef, SomeByteArray, SomeConstantType, SomeProperty)
+from rpython.annotator.classdesc import ClassDef, ClassDesc
 from rpython.annotator.listdef import ListDef, ListItem
 from rpython.annotator.dictdef import DictDef
 from rpython.annotator import description
@@ -23,7 +23,6 @@
 from rpython.annotator.argument import simple_args
 from rpython.rlib.objectmodel import r_dict, r_ordereddict, Symbolic
 from rpython.tool.algo.unionfind import UnionFind
-from rpython.tool.flattenrec import FlattenRecursion
 from rpython.rtyper import extregistry
 
 
@@ -163,9 +162,7 @@
             s_callable.consider_call_site(args, s_result, call_op)
 
     def getuniqueclassdef(self, cls):
-        """Get the ClassDef associated with the given user cls.
-        Avoid using this!  It breaks for classes that must be specialized.
-        """
+        """Get the ClassDef associated with the given user cls."""
         assert cls is not object
         desc = self.getdesc(cls)
         return desc.getuniqueclassdef()
@@ -334,8 +331,9 @@
                  and x.__class__.__module__ != '__builtin__':
             if hasattr(x, '_cleanup_'):
                 x._cleanup_()
-            self.see_mutable(x)
-            result = SomeInstance(self.getuniqueclassdef(x.__class__))
+            classdef = self.getuniqueclassdef(x.__class__)
+            classdef.see_instance(x)
+            result = SomeInstance(classdef)
         elif x is None:
             return s_None
         else:
@@ -362,7 +360,7 @@
                 if pyobj.__module__ == '__builtin__': # avoid making classdefs 
for builtin types
                     result = self.getfrozen(pyobj)
                 else:
-                    result = description.ClassDesc(self, pyobj)
+                    result = ClassDesc(self, pyobj)
             elif isinstance(pyobj, types.MethodType):
                 if pyobj.im_self is None:   # unbound
                     return self.getdesc(pyobj.im_func)
@@ -375,11 +373,11 @@
                         self.getdesc(pyobj.im_self))            # frozendesc
                 else: # regular method
                     origincls, name = origin_of_meth(pyobj)
-                    self.see_mutable(pyobj.im_self)
+                    classdef = self.getuniqueclassdef(pyobj.im_class)
+                    classdef.see_instance(pyobj.im_self)
                     assert pyobj == getattr(pyobj.im_self, name), (
                         "%r is not %s.%s ??" % (pyobj, pyobj.im_self, name))
                     # emulate a getattr to make sure it's on the classdef
-                    classdef = self.getuniqueclassdef(pyobj.im_class)
                     classdef.find_attribute(name)
                     result = self.getmethoddesc(
                         self.getdesc(pyobj.im_func),            # funcdesc
@@ -400,15 +398,6 @@
             self.descs[pyobj] = result
             return result
 
-    def have_seen(self, x):
-        # this might need to expand some more.
-        if x in self.descs:
-            return True
-        elif (x.__class__, x) in self.seen_mutable:
-            return True
-        else:
-            return False
-
     def getfrozen(self, pyobj):
         return description.FrozenDesc(self, pyobj)
 
@@ -425,22 +414,6 @@
             self.methoddescs[key] = result
             return result
 
-    _see_mutable_flattenrec = FlattenRecursion()
-
-    def see_mutable(self, x):
-        key = (x.__class__, x)
-        if key in self.seen_mutable:
-            return
-        clsdef = self.getuniqueclassdef(x.__class__)
-        self.seen_mutable[key] = True
-        self.event('mutable', x)
-        source = InstanceSource(self, x)
-        def delayed():
-            for attr in source.all_instance_attributes():
-                clsdef.add_source_for_attribute(attr, source)
-                # ^^^ can trigger reflowing
-        self._see_mutable_flattenrec(delayed)
-
     def valueoftype(self, t):
         return annotationoftype(t, self)
 
@@ -495,6 +468,20 @@
 
         return s_result
 
+    def getattr_locations(self, clsdesc, attrname):
+        attrdef = clsdesc.classdef.find_attribute(attrname)
+        return attrdef.read_locations
+
+    def record_getattr(self, clsdesc, attrname):
+        locations = self.getattr_locations(clsdesc, attrname)
+        locations.add(self.position_key)
+
+    def update_attr(self, clsdef, attrdef):
+        locations = self.getattr_locations(clsdef.classdesc, attrdef.name)
+        for position in locations:
+            self.annotator.reflowfromposition(position)
+        attrdef.validate(homedef=clsdef)
+
     def pbc_call(self, pbc, args, emulated=None):
         """Analyse a call to a SomePBC() with the given args (list of
         annotations).
diff --git a/rpython/annotator/builtin.py b/rpython/annotator/builtin.py
--- a/rpython/annotator/builtin.py
+++ b/rpython/annotator/builtin.py
@@ -5,13 +5,14 @@
 from collections import OrderedDict
 
 from rpython.annotator.model import (
-    SomeInteger, SomeObject, SomeChar, SomeBool, SomeString, SomeTuple,
+    SomeInteger, SomeChar, SomeBool, SomeString, SomeTuple,
     SomeUnicodeCodePoint, SomeFloat, unionof, SomeUnicodeString,
     SomePBC, SomeInstance, SomeDict, SomeList, SomeWeakRef, SomeIterator,
     SomeOrderedDict, SomeByteArray, add_knowntypedata, s_ImpossibleValue,)
 from rpython.annotator.bookkeeper import (
     getbookkeeper, immutablevalue, BUILTIN_ANALYZERS, analyzer_for)
 from rpython.annotator import description
+from rpython.annotator.classdesc import ClassDef
 from rpython.flowspace.model import Constant
 import rpython.rlib.rarithmetic
 import rpython.rlib.objectmodel
@@ -124,7 +125,6 @@
 
 def our_issubclass(cls1, cls2):
     """ we're going to try to be less silly in the face of old-style classes"""
-    from rpython.annotator.classdef import ClassDef
     if cls2 is object:
         return True
     def classify(cls):
diff --git a/rpython/annotator/classdef.py b/rpython/annotator/classdef.py
deleted file mode 100644
--- a/rpython/annotator/classdef.py
+++ /dev/null
@@ -1,434 +0,0 @@
-"""
-Type inference for user-defined classes.
-"""
-from rpython.annotator.model import (
-    SomePBC, s_ImpossibleValue, unionof, s_None, AnnotatorError)
-from rpython.annotator import description
-
-
-# The main purpose of a ClassDef is to collect information about class/instance
-# attributes as they are really used.  An Attribute object is stored in the
-# most general ClassDef where an attribute of that name is read/written:
-#    classdef.attrs = {'attrname': Attribute()}
-#
-# The following invariants hold:
-#
-# (A) if an attribute is read/written on an instance of class A, then the
-#     classdef of A or a parent class of A has an Attribute object 
corresponding
-#     to that name.
-#
-# (I) if B is a subclass of A, then they don't both have an Attribute for the
-#     same name.  (All information from B's Attribute must be merged into A's.)
-#
-# Additionally, each ClassDef records an 'attr_sources': it maps attribute 
names
-# to a list of 'source' objects that want to provide a constant value for this
-# attribute at the level of this class.  The attr_sources provide information
-# higher in the class hierarchy than concrete Attribute()s.  It is for the case
-# where (so far or definitely) the user program only reads/writes the attribute
-# at the level of a subclass, but a value for this attribute could possibly
-# exist in the parent class or in an instance of a parent class.
-#
-# The point of not automatically forcing the Attribute instance up to the
-# parent class which has a class attribute of the same name is apparent with
-# multiple subclasses:
-#
-#                                    A
-#                                 attr=s1
-#                                  /   \
-#                                 /     \
-#                                B       C
-#                             attr=s2  attr=s3
-#
-# XXX this does not seem to be correct, but I don't know how to phrase
-#     it correctly. See test_specific_attributes in test_annrpython
-#
-# In this case, as long as 'attr' is only read/written from B or C, the
-# Attribute on B says that it can be 's1 or s2', and the Attribute on C says
-# it can be 's1 or s3'.  Merging them into a single Attribute on A would give
-# the more imprecise 's1 or s2 or s3'.
-#
-# The following invariant holds:
-#
-# (II) if a class A has an Attribute, the 'attr_sources' for the same name is
-#      empty.  It is also empty on all subclasses of A.  (The information goes
-#      into the Attribute directly in this case.)
-#
-# The following invariant holds:
-#
-#  (III) for a class A, each attrsource that comes from the class (as opposed 
to
-#        from a prebuilt instance) must be merged into all Attributes of the
-#        same name in all subclasses of A, if any.  (Parent class attributes 
can
-#        be visible in reads from instances of subclasses.)
-
-class Attribute(object):
-    # readonly-ness
-    # SomeThing-ness
-    # NB.  an attribute is readonly if it is a constant class attribute.
-    #      Both writing to the instance attribute and discovering prebuilt
-    #      instances that have the attribute set will turn off readonly-ness.
-
-    def __init__(self, name, bookkeeper):
-        assert name != '__class__'
-        self.name = name
-        self.bookkeeper = bookkeeper
-        self.s_value = s_ImpossibleValue
-        self.readonly = True
-        self.attr_allowed = True
-        self.read_locations = {}
-
-    def add_constant_source(self, classdef, source):
-        s_value = source.s_get_value(classdef, self.name)
-        if source.instance_level:
-            # a prebuilt instance source forces readonly=False, see above
-            self.modified(classdef)
-        s_new_value = unionof(self.s_value, s_value)    # XXX "source %r attr 
%s" % (source, self.name),
-        self.s_value = s_new_value
-
-    def getvalue(self):
-        # Same as 'self.s_value' for historical reasons.
-        return self.s_value
-
-    def merge(self, other, classdef='?'):
-        assert self.name == other.name
-        s_new_value = unionof(self.s_value, other.s_value)  # XXX "%s attr %s" 
% (classdef, self.name)
-        self.s_value = s_new_value
-        if not other.readonly:
-            self.modified(classdef)
-        self.read_locations.update(other.read_locations)
-
-    def mutated(self, homedef): # reflow from attr read positions
-        s_newvalue = self.getvalue()
-
-        for position in self.read_locations:
-            self.bookkeeper.annotator.reflowfromposition(position)
-
-        # check for method demotion and after-the-fact method additions
-        if isinstance(s_newvalue, SomePBC):
-            attr = self.name
-            if s_newvalue.getKind() == description.MethodDesc:
-                # is method
-                if homedef.classdesc.read_attribute(attr, None) is None:
-                    if not homedef.check_missing_attribute_update(attr):
-                        for desc in s_newvalue.descriptions:
-                            if desc.selfclassdef is None:
-                                if homedef.classdesc.settled:
-                                    raise AnnotatorError(
-                                        "demoting method %s to settled class "
-                                        "%s not allowed" % (self.name, homedef)
-                                    )
-                                break
-
-        # check for attributes forbidden by slots or _attrs_
-        if homedef.classdesc.all_enforced_attrs is not None:
-            if self.name not in homedef.classdesc.all_enforced_attrs:
-                self.attr_allowed = False
-                if not self.readonly:
-                    raise NoSuchAttrError(
-                        "the attribute %r goes here to %r, "
-                        "but it is forbidden here" % (
-                        self.name, homedef))
-
-    def modified(self, classdef='?'):
-        self.readonly = False
-        if not self.attr_allowed:
-            raise NoSuchAttrError(
-                "Attribute %r on %r should be read-only.\n" % (self.name,
-                                                               classdef) +
-                "This error can be caused by another 'getattr' that promoted\n"
-                "the attribute here; the list of read locations is:\n" +
-                '\n'.join([str(loc[0]) for loc in self.read_locations]))
-
-class ClassDef(object):
-    "Wraps a user class."
-
-    def __init__(self, bookkeeper, classdesc):
-        self.bookkeeper = bookkeeper
-        self.attrs = {}          # {name: Attribute}
-        self.classdesc = classdesc
-        self.name = self.classdesc.name
-        self.shortname = self.name.split('.')[-1]
-        self.subdefs = []
-        self.attr_sources = {}   # {name: list-of-sources}
-        self.read_locations_of__class__ = {}
-        self.repr = None
-        self.extra_access_sets = {}
-
-        if classdesc.basedesc:
-            self.basedef = classdesc.basedesc.getuniqueclassdef()
-            self.basedef.subdefs.append(self)
-            self.basedef.see_new_subclass(self)
-        else:
-            self.basedef = None
-
-        self.parentdefs = dict.fromkeys(self.getmro())
-
-    def setup(self, sources):
-        # collect the (supposed constant) class attributes
-        for name, source in sources.items():
-            self.add_source_for_attribute(name, source)
-        if self.bookkeeper:
-            self.bookkeeper.event('classdef_setup', self)
-
-    def add_source_for_attribute(self, attr, source):
-        """Adds information about a constant source for an attribute.
-        """
-        for cdef in self.getmro():
-            if attr in cdef.attrs:
-                # the Attribute() exists already for this class (or a parent)
-                attrdef = cdef.attrs[attr]
-                s_prev_value = attrdef.s_value
-                attrdef.add_constant_source(self, source)
-                # we should reflow from all the reader's position,
-                # but as an optimization we try to see if the attribute
-                # has really been generalized
-                if attrdef.s_value != s_prev_value:
-                    attrdef.mutated(cdef) # reflow from all read positions
-                return
-        else:
-            # remember the source in self.attr_sources
-            sources = self.attr_sources.setdefault(attr, [])
-            sources.append(source)
-            # register the source in any Attribute found in subclasses,
-            # to restore invariant (III)
-            # NB. add_constant_source() may discover new subdefs but the
-            #     right thing will happen to them because self.attr_sources
-            #     was already updated
-            if not source.instance_level:
-                for subdef in self.getallsubdefs():
-                    if attr in subdef.attrs:
-                        attrdef = subdef.attrs[attr]
-                        s_prev_value = attrdef.s_value
-                        attrdef.add_constant_source(self, source)
-                        if attrdef.s_value != s_prev_value:
-                            attrdef.mutated(subdef) # reflow from all read 
positions
-
-    def locate_attribute(self, attr):
-        while True:
-            for cdef in self.getmro():
-                if attr in cdef.attrs:
-                    return cdef
-            self.generalize_attr(attr)
-            # the return value will likely be 'self' now, but not always -- see
-            # test_annrpython.test_attr_moving_from_subclass_to_class_to_parent
-
-    def find_attribute(self, attr):
-        return self.locate_attribute(attr).attrs[attr]
-
-    def __repr__(self):
-        return "<ClassDef '%s'>" % (self.name,)
-
-    def has_no_attrs(self):
-        for clsdef in self.getmro():
-            if clsdef.attrs:
-                return False
-        return True
-
-    def commonbase(self, other):
-        while other is not None and not self.issubclass(other):
-            other = other.basedef
-        return other
-
-    def getmro(self):
-        while self is not None:
-            yield self
-            self = self.basedef
-
-    def issubclass(self, otherclsdef):
-        return otherclsdef in self.parentdefs
-
-    def getallsubdefs(self):
-        pending = [self]
-        seen = {}
-        for clsdef in pending:
-            yield clsdef
-            for sub in clsdef.subdefs:
-                if sub not in seen:
-                    pending.append(sub)
-                    seen[sub] = True
-
-    def _generalize_attr(self, attr, s_value):
-        # first remove the attribute from subclasses -- including us!
-        # invariant (I)
-        subclass_attrs = []
-        constant_sources = []    # [(classdef-of-origin, source)]
-        for subdef in self.getallsubdefs():
-            if attr in subdef.attrs:
-                subclass_attrs.append(subdef.attrs[attr])
-                del subdef.attrs[attr]
-            if attr in subdef.attr_sources:
-                # accumulate attr_sources for this attribute from all 
subclasses
-                lst = subdef.attr_sources[attr]
-                for source in lst:
-                    constant_sources.append((subdef, source))
-                del lst[:]    # invariant (II)
-
-        # accumulate attr_sources for this attribute from all parents, too
-        # invariant (III)
-        for superdef in self.getmro():
-            if attr in superdef.attr_sources:
-                for source in superdef.attr_sources[attr]:
-                    if not source.instance_level:
-                        constant_sources.append((superdef, source))
-
-        # create the Attribute and do the generalization asked for
-        newattr = Attribute(attr, self.bookkeeper)
-        if s_value:
-            #if newattr.name == 'intval' and getattr(s_value, 'unsigned', 
False):
-            #    import pdb; pdb.set_trace()
-            newattr.s_value = s_value
-
-        # keep all subattributes' values
-        for subattr in subclass_attrs:
-            newattr.merge(subattr, classdef=self)
-
-        # store this new Attribute, generalizing the previous ones from
-        # subclasses -- invariant (A)
-        self.attrs[attr] = newattr
-
-        # add the values of the pending constant attributes
-        # completes invariants (II) and (III)
-        for origin_classdef, source in constant_sources:
-            newattr.add_constant_source(origin_classdef, source)
-
-        # reflow from all read positions
-        newattr.mutated(self)
-
-    def generalize_attr(self, attr, s_value=None):
-        # if the attribute exists in a superclass, generalize there,
-        # as imposed by invariant (I)
-        for clsdef in self.getmro():
-            if attr in clsdef.attrs:
-                clsdef._generalize_attr(attr, s_value)
-                break
-        else:
-            self._generalize_attr(attr, s_value)
-
-    def about_attribute(self, name):
-        """This is the interface for the code generators to ask about
-           the annotation given to a attribute."""
-        for cdef in self.getmro():
-            if name in cdef.attrs:
-                s_result = cdef.attrs[name].s_value
-                if s_result != s_ImpossibleValue:
-                    return s_result
-                else:
-                    return None
-        return None
-
-    def lookup_filter(self, pbc, name=None, flags={}):
-        """Selects the methods in the pbc that could possibly be seen by
-        a lookup performed on an instance of 'self', removing the ones
-        that cannot appear.
-        """
-        d = []
-        uplookup = None
-        updesc = None
-        for desc in pbc.descriptions:
-            # pick methods but ignore already-bound methods, which can come
-            # from an instance attribute
-            if (isinstance(desc, description.MethodDesc)
-                    and desc.selfclassdef is None):
-                methclassdef = desc.originclassdef
-                if methclassdef is not self and methclassdef.issubclass(self):
-                    pass # subclasses methods are always candidates
-                elif self.issubclass(methclassdef):
-                    # upward consider only the best match
-                    if uplookup is None or methclassdef.issubclass(uplookup):
-                        uplookup = methclassdef
-                        updesc = desc
-                    continue
-                    # for clsdef1 >= clsdef2, we guarantee that
-                    # clsdef1.lookup_filter(pbc) includes
-                    # clsdef2.lookup_filter(pbc) (see formal proof...)
-                else:
-                    continue # not matching
-                # bind the method by giving it a selfclassdef.  Use the
-                # more precise subclass that it's coming from.
-                desc = desc.bind_self(methclassdef, flags)
-            d.append(desc)
-        if uplookup is not None:
-            d.append(updesc.bind_self(self, flags))
-
-        if d:
-            return SomePBC(d, can_be_None=pbc.can_be_None)
-        elif pbc.can_be_None:
-            return s_None
-        else:
-            return s_ImpossibleValue
-
-    def check_missing_attribute_update(self, name):
-        # haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaack
-        # sometimes, new methods can show up on classes, added
-        # e.g. by W_TypeObject._freeze_() -- the multimethod
-        # implementations.  Check that here...
-        found = False
-        parents = list(self.getmro())
-        parents.reverse()
-        for base in parents:
-            if base.check_attr_here(name):
-                found = True
-        return found
-
-    def check_attr_here(self, name):
-        source = self.classdesc.find_source_for(name)
-        if source is not None:
-            # oups! new attribute showed up
-            self.add_source_for_attribute(name, source)
-            # maybe it also showed up in some subclass?
-            for subdef in self.getallsubdefs():
-                if subdef is not self:
-                    subdef.check_attr_here(name)
-            return True
-        else:
-            return False
-
-    def see_new_subclass(self, classdef):
-        for position in self.read_locations_of__class__:
-            self.bookkeeper.annotator.reflowfromposition(position)
-        if self.basedef is not None:
-            self.basedef.see_new_subclass(classdef)
-
-    def read_attr__class__(self):
-        position = self.bookkeeper.position_key
-        self.read_locations_of__class__[position] = True
-        return SomePBC([subdef.classdesc for subdef in self.getallsubdefs()])
-
-    def _freeze_(self):
-        raise Exception("ClassDefs are used as knowntype for instances but 
cannot be used as immutablevalue arguments directly")
-
-# ____________________________________________________________
-
-class InstanceSource(object):
-    instance_level = True
-
-    def __init__(self, bookkeeper, obj):
-        self.bookkeeper = bookkeeper
-        self.obj = obj
-
-    def s_get_value(self, classdef, name):
-        try:
-            v = getattr(self.obj, name)
-        except AttributeError:
-            all_enforced_attrs = classdef.classdesc.all_enforced_attrs
-            if all_enforced_attrs and name in all_enforced_attrs:
-                return s_ImpossibleValue
-            raise
-        s_value = self.bookkeeper.immutablevalue(v)
-        return s_value
-
-    def all_instance_attributes(self):
-        result = getattr(self.obj, '__dict__', {}).keys()
-        tp = self.obj.__class__
-        if isinstance(tp, type):
-            for basetype in tp.__mro__:
-                slots = basetype.__dict__.get('__slots__')
-                if slots:
-                    if isinstance(slots, str):
-                        result.append(slots)
-                    else:
-                        result.extend(slots)
-        return result
-
-class NoSuchAttrError(AnnotatorError):
-    """Raised when an attribute is found on a class where __slots__
-     or _attrs_ forbits it."""
diff --git a/rpython/annotator/classdesc.py b/rpython/annotator/classdesc.py
new file mode 100644
--- /dev/null
+++ b/rpython/annotator/classdesc.py
@@ -0,0 +1,944 @@
+"""
+Type inference for user-defined classes.
+"""
+from __future__ import absolute_import
+import types
+
+from rpython.flowspace.model import Constant
+from rpython.tool.flattenrec import FlattenRecursion
+from rpython.tool.sourcetools import func_with_new_name
+from rpython.tool.uid import Hashable
+from rpython.annotator.model import (
+    SomePBC, s_ImpossibleValue, unionof, s_None, AnnotatorError, SomeInteger,
+    SomeString, SomeImpossibleValue, SomeList, HarmlesslyBlocked)
+from rpython.annotator.description import (
+    Desc, FunctionDesc, MethodDesc, NODEFAULT)
+
+
+# The main purpose of a ClassDef is to collect information about class/instance
+# attributes as they are really used.  An Attribute object is stored in the
+# most general ClassDef where an attribute of that name is read/written:
+#    classdef.attrs = {'attrname': Attribute()}
+#
+# The following invariants hold:
+#
+# (A) if an attribute is read/written on an instance of class A, then the
+#     classdef of A or a parent class of A has an Attribute object 
corresponding
+#     to that name.
+#
+# (I) if B is a subclass of A, then they don't both have an Attribute for the
+#     same name.  (All information from B's Attribute must be merged into A's.)
+#
+# Additionally, each ClassDef records an 'attr_sources': it maps attribute 
names
+# to a list of 'source' objects that want to provide a constant value for this
+# attribute at the level of this class.  The attr_sources provide information
+# higher in the class hierarchy than concrete Attribute()s.  It is for the case
+# where (so far or definitely) the user program only reads/writes the attribute
+# at the level of a subclass, but a value for this attribute could possibly
+# exist in the parent class or in an instance of a parent class.
+#
+# The point of not automatically forcing the Attribute instance up to the
+# parent class which has a class attribute of the same name is apparent with
+# multiple subclasses:
+#
+#                                    A
+#                                 attr=s1
+#                                  /   \
+#                                 /     \
+#                                B       C
+#                             attr=s2  attr=s3
+#
+# XXX this does not seem to be correct, but I don't know how to phrase
+#     it correctly. See test_specific_attributes in test_annrpython
+#
+# In this case, as long as 'attr' is only read/written from B or C, the
+# Attribute on B says that it can be 's1 or s2', and the Attribute on C says
+# it can be 's1 or s3'.  Merging them into a single Attribute on A would give
+# the more imprecise 's1 or s2 or s3'.
+#
+# The following invariant holds:
+#
+# (II) if a class A has an Attribute, the 'attr_sources' for the same name is
+#      empty.  It is also empty on all subclasses of A.  (The information goes
+#      into the Attribute directly in this case.)
+#
+# The following invariant holds:
+#
+#  (III) for a class A, each attrsource that comes from the class (as opposed 
to
+#        from a prebuilt instance) must be merged into all Attributes of the
+#        same name in all subclasses of A, if any.  (Parent class attributes 
can
+#        be visible in reads from instances of subclasses.)
+
+class Attribute(object):
+    # readonly-ness
+    # SomeThing-ness
+    # NB.  an attribute is readonly if it is a constant class attribute.
+    #      Both writing to the instance attribute and discovering prebuilt
+    #      instances that have the attribute set will turn off readonly-ness.
+
+    def __init__(self, name):
+        assert name != '__class__'
+        self.name = name
+        self.s_value = s_ImpossibleValue
+        self.readonly = True
+        self.attr_allowed = True
+        self.read_locations = set()
+
+    def add_constant_source(self, classdef, source):
+        s_value = source.s_get_value(classdef, self.name)
+        if source.instance_level:
+            # a prebuilt instance source forces readonly=False, see above
+            self.modified(classdef)
+        s_new_value = unionof(self.s_value, s_value)
+        self.s_value = s_new_value
+
+    def merge(self, other, classdef):
+        assert self.name == other.name
+        s_new_value = unionof(self.s_value, other.s_value)
+        self.s_value = s_new_value
+        if not other.readonly:
+            self.modified(classdef)
+        self.read_locations.update(other.read_locations)
+
+    def validate(self, homedef):
+        s_newvalue = self.s_value
+        # check for after-the-fact method additions
+        if isinstance(s_newvalue, SomePBC):
+            attr = self.name
+            if s_newvalue.getKind() == MethodDesc:
+                # is method
+                if homedef.classdesc.read_attribute(attr, None) is None:
+                    homedef.check_missing_attribute_update(attr)
+
+        # check for attributes forbidden by slots or _attrs_
+        if homedef.classdesc.all_enforced_attrs is not None:
+            if self.name not in homedef.classdesc.all_enforced_attrs:
+                self.attr_allowed = False
+                if not self.readonly:
+                    raise NoSuchAttrError(
+                        "the attribute %r goes here to %r, but it is "
+                        "forbidden here" % (self.name, homedef))
+
+    def modified(self, classdef='?'):
+        self.readonly = False
+        if not self.attr_allowed:
+            from rpython.annotator.bookkeeper import getbookkeeper
+            bk = getbookkeeper()
+            classdesc = classdef.classdesc
+            locations = bk.getattr_locations(classdesc, self.name)
+            raise NoSuchAttrError(
+                "Attribute %r on %r should be read-only.\n" % (self.name,
+                                                               classdef) +
+                "This error can be caused by another 'getattr' that promoted\n"
+                "the attribute here; the list of read locations is:\n" +
+                '\n'.join([str(loc[0]) for loc in locations]))
+
+class ClassDef(object):
+    "Wraps a user class."
+
+    def __init__(self, bookkeeper, classdesc):
+        self.bookkeeper = bookkeeper
+        self.attrs = {}          # {name: Attribute}
+        self.classdesc = classdesc
+        self.name = self.classdesc.name
+        self.shortname = self.name.split('.')[-1]
+        self.subdefs = []
+        self.attr_sources = {}   # {name: list-of-sources}
+        self.read_locations_of__class__ = {}
+        self.repr = None
+        self.extra_access_sets = {}
+        self.instances_seen = set()
+
+        if classdesc.basedesc:
+            self.basedef = classdesc.basedesc.getuniqueclassdef()
+            self.basedef.subdefs.append(self)
+            self.basedef.see_new_subclass(self)
+        else:
+            self.basedef = None
+
+        self.parentdefs = dict.fromkeys(self.getmro())
+
+    def setup(self, sources):
+        # collect the (supposed constant) class attributes
+        for name, source in sources.items():
+            self.add_source_for_attribute(name, source)
+        if self.bookkeeper:
+            self.bookkeeper.event('classdef_setup', self)
+
+    def s_getattr(self, attrname, flags):
+        attrdef = self.find_attribute(attrname)
+        s_result = attrdef.s_value
+        # hack: if s_result is a set of methods, discard the ones
+        #       that can't possibly apply to an instance of self.
+        # XXX do it more nicely
+        if isinstance(s_result, SomePBC):
+            s_result = self.lookup_filter(s_result, attrname, flags)
+        elif isinstance(s_result, SomeImpossibleValue):
+            self.check_missing_attribute_update(attrname)
+            # blocking is harmless if the attribute is explicitly listed
+            # in the class or a parent class.
+            for basedef in self.getmro():
+                if basedef.classdesc.all_enforced_attrs is not None:
+                    if attrname in basedef.classdesc.all_enforced_attrs:
+                        raise HarmlesslyBlocked("get enforced attr")
+        elif isinstance(s_result, SomeList):
+            s_result = self.classdesc.maybe_return_immutable_list(
+                attrname, s_result)
+        return s_result
+
+    def add_source_for_attribute(self, attr, source):
+        """Adds information about a constant source for an attribute.
+        """
+        for cdef in self.getmro():
+            if attr in cdef.attrs:
+                # the Attribute() exists already for this class (or a parent)
+                attrdef = cdef.attrs[attr]
+                s_prev_value = attrdef.s_value
+                attrdef.add_constant_source(self, source)
+                # we should reflow from all the reader's position,
+                # but as an optimization we try to see if the attribute
+                # has really been generalized
+                if attrdef.s_value != s_prev_value:
+                    self.bookkeeper.update_attr(cdef, attrdef)
+                return
+        else:
+            # remember the source in self.attr_sources
+            sources = self.attr_sources.setdefault(attr, [])
+            sources.append(source)
+            # register the source in any Attribute found in subclasses,
+            # to restore invariant (III)
+            # NB. add_constant_source() may discover new subdefs but the
+            #     right thing will happen to them because self.attr_sources
+            #     was already updated
+            if not source.instance_level:
+                for subdef in self.getallsubdefs():
+                    if attr in subdef.attrs:
+                        attrdef = subdef.attrs[attr]
+                        s_prev_value = attrdef.s_value
+                        attrdef.add_constant_source(self, source)
+                        if attrdef.s_value != s_prev_value:
+                            self.bookkeeper.update_attr(subdef, attrdef)
+
+    def get_owner(self, attrname):
+        """Return the classdef owning the attribute `attrname`."""
+        for cdef in self.getmro():
+            if attrname in cdef.attrs:
+                return cdef
+        else:
+            return None
+
+
+    def locate_attribute(self, attr):
+        cdef = self.get_owner(attr)
+        if cdef:
+            return cdef
+        else:
+            self._generalize_attr(attr, s_value=None)
+            return self
+
+    def find_attribute(self, attr):
+        return self.locate_attribute(attr).attrs[attr]
+
+    def __repr__(self):
+        return "<ClassDef '%s'>" % (self.name,)
+
+    def has_no_attrs(self):
+        for clsdef in self.getmro():
+            if clsdef.attrs:
+                return False
+        return True
+
+    def commonbase(self, other):
+        while other is not None and not self.issubclass(other):
+            other = other.basedef
+        return other
+
+    def getmro(self):
+        while self is not None:
+            yield self
+            self = self.basedef
+
+    def issubclass(self, otherclsdef):
+        return otherclsdef in self.parentdefs
+
+    def getallsubdefs(self):
+        pending = [self]
+        seen = {}
+        for clsdef in pending:
+            yield clsdef
+            for sub in clsdef.subdefs:
+                if sub not in seen:
+                    pending.append(sub)
+                    seen[sub] = True
+
+    def _generalize_attr(self, attr, s_value):
+        # create the Attribute and do the generalization asked for
+        newattr = Attribute(attr)
+        if s_value:
+            newattr.s_value = s_value
+
+        # remove the attribute from subclasses -- including us!
+        # invariant (I)
+        constant_sources = []    # [(classdef-of-origin, source)]
+        for subdef in self.getallsubdefs():
+            if attr in subdef.attrs:
+                subattr = subdef.attrs[attr]
+                newattr.merge(subattr, classdef=self)
+                del subdef.attrs[attr]
+            if attr in subdef.attr_sources:
+                # accumulate attr_sources for this attribute from all 
subclasses
+                lst = subdef.attr_sources[attr]
+                for source in lst:
+                    constant_sources.append((subdef, source))
+                del lst[:]    # invariant (II)
+
+        # accumulate attr_sources for this attribute from all parents, too
+        # invariant (III)
+        for superdef in self.getmro():
+            if attr in superdef.attr_sources:
+                for source in superdef.attr_sources[attr]:
+                    if not source.instance_level:
+                        constant_sources.append((superdef, source))
+
+        # store this new Attribute, generalizing the previous ones from
+        # subclasses -- invariant (A)
+        self.attrs[attr] = newattr
+
+        # add the values of the pending constant attributes
+        # completes invariants (II) and (III)
+        for origin_classdef, source in constant_sources:
+            newattr.add_constant_source(origin_classdef, source)
+
+        # reflow from all read positions
+        self.bookkeeper.update_attr(self, newattr)
+
+    def generalize_attr(self, attr, s_value=None):
+        # if the attribute exists in a superclass, generalize there,
+        # as imposed by invariant (I)
+        clsdef = self.get_owner(attr)
+        if clsdef:
+            clsdef._generalize_attr(attr, s_value)
+        else:
+            self._generalize_attr(attr, s_value)
+
+    def about_attribute(self, name):
+        """This is the interface for the code generators to ask about
+           the annotation given to a attribute."""
+        for cdef in self.getmro():
+            if name in cdef.attrs:
+                s_result = cdef.attrs[name].s_value
+                if s_result != s_ImpossibleValue:
+                    return s_result
+                else:
+                    return None
+        return None
+
+    def lookup_filter(self, pbc, name=None, flags={}):
+        """Selects the methods in the pbc that could possibly be seen by
+        a lookup performed on an instance of 'self', removing the ones
+        that cannot appear.
+        """
+        d = []
+        uplookup = None
+        updesc = None
+        for desc in pbc.descriptions:
+            # pick methods but ignore already-bound methods, which can come
+            # from an instance attribute
+            if (isinstance(desc, MethodDesc) and desc.selfclassdef is None):
+                methclassdef = desc.originclassdef
+                if methclassdef is not self and methclassdef.issubclass(self):
+                    pass  # subclasses methods are always candidates
+                elif self.issubclass(methclassdef):
+                    # upward consider only the best match
+                    if uplookup is None or methclassdef.issubclass(uplookup):
+                        uplookup = methclassdef
+                        updesc = desc
+                    continue
+                    # for clsdef1 >= clsdef2, we guarantee that
+                    # clsdef1.lookup_filter(pbc) includes
+                    # clsdef2.lookup_filter(pbc) (see formal proof...)
+                else:
+                    continue  # not matching
+                # bind the method by giving it a selfclassdef.  Use the
+                # more precise subclass that it's coming from.
+                desc = desc.bind_self(methclassdef, flags)
+            d.append(desc)
+        if uplookup is not None:
+            d.append(updesc.bind_self(self, flags))
+
+        if d:
+            return SomePBC(d, can_be_None=pbc.can_be_None)
+        elif pbc.can_be_None:
+            return s_None
+        else:
+            return s_ImpossibleValue
+
+    def check_missing_attribute_update(self, name):
+        # haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaack
+        # sometimes, new methods can show up on classes, added
+        # e.g. by W_TypeObject._freeze_() -- the multimethod
+        # implementations.  Check that here...
+        found = False
+        parents = list(self.getmro())
+        parents.reverse()
+        for base in parents:
+            if base.check_attr_here(name):
+                found = True
+        return found
+
+    def check_attr_here(self, name):
+        source = self.classdesc.find_source_for(name)
+        if source is not None:
+            # oups! new attribute showed up
+            self.add_source_for_attribute(name, source)
+            # maybe it also showed up in some subclass?
+            for subdef in self.getallsubdefs():
+                if subdef is not self:
+                    subdef.check_attr_here(name)
+            return True
+        else:
+            return False
+
+    _see_instance_flattenrec = FlattenRecursion()
+
+    def see_instance(self, x):
+        assert isinstance(x, self.classdesc.pyobj)
+        key = Hashable(x)
+        if key in self.instances_seen:
+            return
+        self.instances_seen.add(key)
+        self.bookkeeper.event('mutable', x)
+        source = InstanceSource(self.bookkeeper, x)
+        def delayed():
+            for attr in source.all_instance_attributes():
+                self.add_source_for_attribute(attr, source)
+                # ^^^ can trigger reflowing
+        self._see_instance_flattenrec(delayed)
+
+    def see_new_subclass(self, classdef):
+        for position in self.read_locations_of__class__:
+            self.bookkeeper.annotator.reflowfromposition(position)
+        if self.basedef is not None:
+            self.basedef.see_new_subclass(classdef)
+
+    def read_attr__class__(self):
+        position = self.bookkeeper.position_key
+        self.read_locations_of__class__[position] = True
+        return SomePBC([subdef.classdesc for subdef in self.getallsubdefs()])
+
+    def _freeze_(self):
+        raise Exception("ClassDefs are used as knowntype for instances but "
+                        "cannot be used as immutablevalue arguments directly")
+
+# ____________________________________________________________
+
+class InstanceSource(object):
+    instance_level = True
+
+    def __init__(self, bookkeeper, obj):
+        self.bookkeeper = bookkeeper
+        self.obj = obj
+
+    def s_get_value(self, classdef, name):
+        try:
+            v = getattr(self.obj, name)
+        except AttributeError:
+            all_enforced_attrs = classdef.classdesc.all_enforced_attrs
+            if all_enforced_attrs and name in all_enforced_attrs:
+                return s_ImpossibleValue
+            raise
+        s_value = self.bookkeeper.immutablevalue(v)
+        return s_value
+
+    def all_instance_attributes(self):
+        result = getattr(self.obj, '__dict__', {}).keys()
+        tp = self.obj.__class__
+        if isinstance(tp, type):
+            for basetype in tp.__mro__:
+                slots = basetype.__dict__.get('__slots__')
+                if slots:
+                    if isinstance(slots, str):
+                        result.append(slots)
+                    else:
+                        result.extend(slots)
+        return result
+
+class NoSuchAttrError(AnnotatorError):
+    """Raised when an attribute is found on a class where __slots__
+     or _attrs_ forbits it."""
+
+
+def is_mixin(cls):
+    return cls.__dict__.get('_mixin_', False)
+
+
+class ClassDesc(Desc):
+    knowntype = type
+    instance_level = False
+    all_enforced_attrs = None   # or a set
+    _detect_invalid_attrs = None
+
+    def __init__(self, bookkeeper, cls,
+                 name=None, basedesc=None, classdict=None):
+        super(ClassDesc, self).__init__(bookkeeper, cls)
+        if '__NOT_RPYTHON__' in cls.__dict__:
+            raise AnnotatorError('Bad class')
+
+        if name is None:
+            name = cls.__module__ + '.' + cls.__name__
+        self.name = name
+        self.basedesc = basedesc
+        if classdict is None:
+            classdict = {}    # populated below
+        self.classdict = classdict     # {attr: Constant-or-Desc}
+        if cls.__dict__.get('_annspecialcase_', ''):
+            raise AnnotatorError(
+                "Class specialization has been removed. The "
+                "'_annspecialcase_' class tag is now unsupported.")
+        self.classdef = None
+
+        if is_mixin(cls):
+            raise AnnotatorError("cannot use directly the class %r because "
+                                 "it is a _mixin_" % (cls,))
+
+        assert cls.__module__ != '__builtin__'
+        baselist = list(cls.__bases__)
+
+        # special case: skip BaseException, and pretend
+        # that all exceptions ultimately inherit from Exception instead
+        # of BaseException (XXX hack)
+        if cls is Exception:
+            baselist = []
+        elif baselist == [BaseException]:
+            baselist = [Exception]
+
+        immutable_fields = cls.__dict__.get('_immutable_fields_', [])
+        # To prevent confusion, we forbid strings. Any other bona fide sequence
+        # of strings is OK.
+        if isinstance(immutable_fields, basestring):
+            raise AnnotatorError(
+                "In class %s, '_immutable_fields_' must be a sequence of "
+                "attribute names, not a string." % cls)
+        self.immutable_fields = set(immutable_fields)
+
+        mixins_before = []
+        mixins_after = []
+        base = object
+        for b1 in baselist:
+            if b1 is object:
+                continue
+            if is_mixin(b1):
+                if base is object:
+                    mixins_before.append(b1)
+                else:
+                    mixins_after.append(b1)
+            else:
+                assert base is object, ("multiple inheritance only supported "
+                                        "with _mixin_: %r" % (cls,))
+                base = b1
+        if mixins_before and mixins_after:
+            raise Exception("unsupported: class %r has mixin bases both"
+                            " before and after the regular base" % (self,))
+        self.add_mixins(mixins_after, check_not_in=base)
+        self.add_mixins(mixins_before)
+        self.add_sources_for_class(cls)
+
+        if base is not object:
+            self.basedesc = bookkeeper.getdesc(base)
+
+        if '__slots__' in cls.__dict__ or '_attrs_' in cls.__dict__:
+            attrs = {}
+            for decl in ('__slots__', '_attrs_'):
+                decl = cls.__dict__.get(decl, [])
+                if isinstance(decl, str):
+                    decl = (decl,)
+                decl = dict.fromkeys(decl)
+                attrs.update(decl)
+            if self.basedesc is not None:
+                if self.basedesc.all_enforced_attrs is None:
+                    raise Exception("%r has slots or _attrs_, "
+                                    "but not its base class" % (cls,))
+                attrs.update(self.basedesc.all_enforced_attrs)
+            self.all_enforced_attrs = attrs
+
+        if (self.is_builtin_exception_class() and
+                self.all_enforced_attrs is None):
+            if cls not in FORCE_ATTRIBUTES_INTO_CLASSES:
+                self.all_enforced_attrs = []    # no attribute allowed
+
+    def add_source_attribute(self, name, value, mixin=False):
+        if isinstance(value, property):
+            # special case for property object
+            if value.fget is not None:
+                newname = name + '__getter__'
+                func = func_with_new_name(value.fget, newname)
+                self.add_source_attribute(newname, func, mixin)
+            if value.fset is not None:
+                newname = name + '__setter__'
+                func = func_with_new_name(value.fset, newname)
+                self.add_source_attribute(newname, func, mixin)
+            self.classdict[name] = Constant(value)
+            return
+
+        if isinstance(value, types.FunctionType):
+            # for debugging
+            if not hasattr(value, 'class_'):
+                value.class_ = self.pyobj
+            if mixin:
+                # make a new copy of the FunctionDesc for this class,
+                # but don't specialize further for all subclasses
+                funcdesc = FunctionDesc(self.bookkeeper, value)
+                self.classdict[name] = funcdesc
+                return
+            # NB. if value is, say, AssertionError.__init__, then we
+            # should not use getdesc() on it.  Never.  The problem is
+            # that the py lib has its own AssertionError.__init__ which
+            # is of type FunctionType.  But bookkeeper.immutablevalue()
+            # will do the right thing in s_get_value().
+        if isinstance(value, staticmethod) and mixin:
+            # make a new copy of staticmethod
+            func = value.__get__(42)
+            value = staticmethod(func_with_new_name(func, func.__name__))
+
+        if type(value) in MemberDescriptorTypes:
+            # skip __slots__, showing up in the class as 'member' objects
+            return
+        if name == '__init__' and self.is_builtin_exception_class():
+            # pretend that built-in exceptions have no __init__,
+            # unless explicitly specified in builtin.py
+            from rpython.annotator.builtin import BUILTIN_ANALYZERS
+            value = getattr(value, 'im_func', value)
+            if value not in BUILTIN_ANALYZERS:
+                return
+        self.classdict[name] = Constant(value)
+
+    def add_mixins(self, mixins, check_not_in=object):
+        if not mixins:
+            return
+        A = type('tmp', tuple(mixins) + (object,), {})
+        mro = A.__mro__
+        assert mro[0] is A and mro[-1] is object
+        mro = mro[1:-1]
+        #
+        skip = set()
+        def add(cls):
+            if cls is not object:
+                for base in cls.__bases__:
+                    add(base)
+                for name in cls.__dict__:
+                    skip.add(name)
+        add(check_not_in)
+        #
+        for base in reversed(mro):
+            assert is_mixin(base), (
+                "Mixin class %r has non mixin base class %r" % (mixins, base))
+            for name, value in base.__dict__.items():
+                if name in skip:
+                    continue
+                self.add_source_attribute(name, value, mixin=True)
+            if '_immutable_fields_' in base.__dict__:
+                self.immutable_fields.update(
+                    set(base.__dict__['_immutable_fields_']))
+
+
+    def add_sources_for_class(self, cls):
+        for name, value in cls.__dict__.items():
+            self.add_source_attribute(name, value)
+
+    def getclassdef(self, key):
+        return self.getuniqueclassdef()
+
+    def _init_classdef(self):
+        classdef = ClassDef(self.bookkeeper, self)
+        self.bookkeeper.classdefs.append(classdef)
+        self.classdef = classdef
+
+        # forced attributes
+        cls = self.pyobj
+        if cls in FORCE_ATTRIBUTES_INTO_CLASSES:
+            for name, s_value in FORCE_ATTRIBUTES_INTO_CLASSES[cls].items():
+                classdef.generalize_attr(name, s_value)
+                classdef.find_attribute(name).modified(classdef)
+
+        # register all class attributes as coming from this ClassDesc
+        # (as opposed to prebuilt instances)
+        classsources = {}
+        for attr in self.classdict:
+            classsources[attr] = self    # comes from this ClassDesc
+        classdef.setup(classsources)
+        # look for a __del__ method and annotate it if it's there
+        if '__del__' in self.classdict:
+            from rpython.annotator.model import s_None, SomeInstance
+            s_func = self.s_read_attribute('__del__')
+            args_s = [SomeInstance(classdef)]
+            s = self.bookkeeper.emulate_pbc_call(classdef, s_func, args_s)
+            assert s_None.contains(s)
+        return classdef
+
+    def getuniqueclassdef(self):
+        if self.classdef is None:
+            self._init_classdef()
+        return self.classdef
+
+    def pycall(self, whence, args, s_previous_result, op=None):
+        from rpython.annotator.model import SomeInstance, SomeImpossibleValue
+        classdef = self.getuniqueclassdef()
+        s_instance = SomeInstance(classdef)
+        # look up __init__ directly on the class, bypassing the normal
+        # lookup mechanisms ClassDef (to avoid influencing Attribute placement)
+        s_init = self.s_read_attribute('__init__')
+        if isinstance(s_init, SomeImpossibleValue):
+            # no __init__: check that there are no constructor args
+            if not self.is_exception_class():
+                try:
+                    args.fixedunpack(0)
+                except ValueError:
+                    raise Exception("default __init__ takes no argument"
+                                    " (class %s)" % (self.name,))
+            elif self.pyobj is Exception:
+                # check explicitly against "raise Exception, x" where x
+                # is a low-level exception pointer
+                try:
+                    [s_arg] = args.fixedunpack(1)
+                except ValueError:
+                    pass
+                else:
+                    from rpython.rtyper.llannotation import SomePtr
+                    assert not isinstance(s_arg, SomePtr)
+        else:
+            # call the constructor
+            args = args.prepend(s_instance)
+            s_init.call(args)
+        return s_instance
+
+    def is_exception_class(self):
+        return issubclass(self.pyobj, BaseException)
+
+    def is_builtin_exception_class(self):
+        if self.is_exception_class():
+            if self.pyobj.__module__ == 'exceptions':
+                return True
+            if issubclass(self.pyobj, AssertionError):
+                return True
+        return False
+
+    def lookup(self, name):
+        cdesc = self
+        while name not in cdesc.classdict:
+            cdesc = cdesc.basedesc
+            if cdesc is None:
+                return None
+        else:
+            return cdesc
+
+    def get_param(self, name, default=None, inherit=True):
+        cls = self.pyobj
+        if inherit:
+            return getattr(cls, name, default)
+        else:
+            return cls.__dict__.get(name, default)
+
+    def read_attribute(self, name, default=NODEFAULT):
+        cdesc = self.lookup(name)
+        if cdesc is None:
+            if default is NODEFAULT:
+                raise AttributeError
+            else:
+                return default
+        else:
+            return cdesc.classdict[name]
+
+    def s_read_attribute(self, name):
+        # look up an attribute in the class
+        cdesc = self.lookup(name)
+        if cdesc is None:
+            return s_ImpossibleValue
+        else:
+            # delegate to s_get_value to turn it into an annotation
+            return cdesc.s_get_value(None, name)
+
+    def s_get_value(self, classdef, name):
+        obj = self.classdict[name]
+        if isinstance(obj, Constant):
+            value = obj.value
+            if isinstance(value, staticmethod):   # special case
+                value = value.__get__(42)
+                classdef = None   # don't bind
+            elif isinstance(value, classmethod):
+                raise AnnotatorError("classmethods are not supported")
+            s_value = self.bookkeeper.immutablevalue(value)
+            if classdef is not None:
+                s_value = s_value.bind_callables_under(classdef, name)
+        elif isinstance(obj, Desc):
+            if classdef is not None:
+                obj = obj.bind_under(classdef, name)
+            s_value = SomePBC([obj])
+        else:
+            raise TypeError("classdict should not contain %r" % (obj,))
+        return s_value
+
+    def create_new_attribute(self, name, value):
+        assert name not in self.classdict, "name clash: %r" % (name,)
+        self.classdict[name] = Constant(value)
+
+    def find_source_for(self, name):
+        if name in self.classdict:
+            return self
+        # check whether there is a new attribute
+        cls = self.pyobj
+        if name in cls.__dict__:
+            self.add_source_attribute(name, cls.__dict__[name])
+            if name in self.classdict:
+                return self
+        return None
+
+    def maybe_return_immutable_list(self, attr, s_result):
+        # hack: 'x.lst' where lst is listed in _immutable_fields_ as
+        # either 'lst[*]' or 'lst?[*]'
+        # should really return an immutable list as a result.  Implemented
+        # by changing the result's annotation (but not, of course, doing an
+        # actual copy in the rtyper). Tested in rpython.rtyper.test.test_rlist,
+        # test_immutable_list_out_of_instance.
+        if self._detect_invalid_attrs and attr in self._detect_invalid_attrs:
+            raise Exception("field %r was migrated to %r from a subclass in "
+                            "which it was declared as _immutable_fields_" %
+                            (attr, self.pyobj))
+        search1 = '%s[*]' % (attr,)
+        search2 = '%s?[*]' % (attr,)
+        cdesc = self
+        while cdesc is not None:
+            immutable_fields = cdesc.immutable_fields
+            if immutable_fields:
+                if (search1 in immutable_fields or search2 in 
immutable_fields):
+                    s_result.listdef.never_resize()
+                    s_copy = s_result.listdef.offspring()
+                    s_copy.listdef.mark_as_immutable()
+                    #
+                    cdesc = cdesc.basedesc
+                    while cdesc is not None:
+                        if cdesc._detect_invalid_attrs is None:
+                            cdesc._detect_invalid_attrs = set()
+                        cdesc._detect_invalid_attrs.add(attr)
+                        cdesc = cdesc.basedesc
+                    #
+                    return s_copy
+            cdesc = cdesc.basedesc
+        return s_result     # common case
+
+    @staticmethod
+    def consider_call_site(descs, args, s_result, op):
+        descs[0].getcallfamily()
+        descs[0].mergecallfamilies(*descs[1:])
+        from rpython.annotator.model import SomeInstance, SomePBC, s_None
+        if len(descs) == 1:
+            # call to a single class, look at the result annotation
+            # in case it was specialized
+            if not isinstance(s_result, SomeInstance):
+                raise Exception("calling a class didn't return an instance??")
+            classdefs = [s_result.classdef]
+        else:
+            # call to multiple classes: specialization not supported
+            classdefs = [desc.getuniqueclassdef() for desc in descs]
+            # If some of the classes have an __init__ and others not, then
+            # we complain, even though in theory it could work if all the
+            # __init__s take no argument.  But it's messy to implement, so
+            # let's just say it is not RPython and you have to add an empty
+            # __init__ to your base class.
+            has_init = False
+            for desc in descs:
+                s_init = desc.s_read_attribute('__init__')
+                has_init |= isinstance(s_init, SomePBC)
+            basedesc = ClassDesc.getcommonbase(descs)
+            s_init = basedesc.s_read_attribute('__init__')
+            parent_has_init = isinstance(s_init, SomePBC)
+            if has_init and not parent_has_init:
+                raise AnnotatorError(
+                    "some subclasses among %r declare __init__(),"
+                    " but not the common parent class" % (descs,))
+        # make a PBC of MethodDescs, one for the __init__ of each class
+        initdescs = []
+        for desc, classdef in zip(descs, classdefs):
+            s_init = desc.s_read_attribute('__init__')
+            if isinstance(s_init, SomePBC):
+                assert len(s_init.descriptions) == 1, (
+                    "unexpected dynamic __init__?")
+                initfuncdesc, = s_init.descriptions
+                if isinstance(initfuncdesc, FunctionDesc):
+                    from rpython.annotator.bookkeeper import getbookkeeper
+                    initmethdesc = getbookkeeper().getmethoddesc(
+                        initfuncdesc, classdef, classdef, '__init__')
+                    initdescs.append(initmethdesc)
+        # register a call to exactly these __init__ methods
+        if initdescs:
+            initdescs[0].mergecallfamilies(*initdescs[1:])
+            MethodDesc.consider_call_site(initdescs, args, s_None, op)
+
+    def getallbases(self):
+        desc = self
+        while desc is not None:
+            yield desc
+            desc = desc.basedesc
+
+    @staticmethod
+    def getcommonbase(descs):
+        commondesc = descs[0]
+        for desc in descs[1:]:
+            allbases = set(commondesc.getallbases())
+            while desc not in allbases:
+                assert desc is not None, "no common base for %r" % (descs,)
+                desc = desc.basedesc
+            commondesc = desc
+        return commondesc
+
+    def rowkey(self):
+        return self
+
+    def getattrfamily(self, attrname):
+        "Get the ClassAttrFamily object for attrname. Possibly creates one."
+        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
+        _, _, attrfamily = access_sets.find(self)
+        return attrfamily
+
+    def queryattrfamily(self, attrname):
+        """Retrieve the ClassAttrFamily object for attrname if there is one,
+           otherwise return None."""
+        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
+        try:
+            return access_sets[self]
+        except KeyError:
+            return None
+
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to