[pypy-commit] pypy no-class-specialize: Merge ClassDesc definition and classdef.py into classdesc.py

rlamy noreply at buildbot.pypy.org
Wed Oct 14 02:09:36 CEST 2015


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: no-class-specialize
Changeset: r80187:4f5e9f83eb3b
Date: 2015-10-14 01:09 +0100
http://bitbucket.org/pypy/pypy/changeset/4f5e9f83eb3b/

Log:	Merge ClassDesc definition and classdef.py into classdesc.py

diff --git a/rpython/annotator/bookkeeper.py b/rpython/annotator/bookkeeper.py
--- a/rpython/annotator/bookkeeper.py
+++ b/rpython/annotator/bookkeeper.py
@@ -14,7 +14,7 @@
     s_None, s_ImpossibleValue, SomeBool, SomeTuple,
     SomeImpossibleValue, SomeUnicodeString, SomeList, HarmlesslyBlocked,
     SomeWeakRef, SomeByteArray, SomeConstantType, SomeProperty, AnnotatorError)
-from rpython.annotator.classdef import InstanceSource, ClassDef
+from rpython.annotator.classdesc import InstanceSource, ClassDef, ClassDesc
 from rpython.annotator.listdef import ListDef, ListItem
 from rpython.annotator.dictdef import DictDef
 from rpython.annotator import description
@@ -359,7 +359,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)
diff --git a/rpython/annotator/classdef.py b/rpython/annotator/classdesc.py
rename from rpython/annotator/classdef.py
rename to rpython/annotator/classdesc.py
--- a/rpython/annotator/classdef.py
+++ b/rpython/annotator/classdesc.py
@@ -1,9 +1,15 @@
 """
 Type inference for user-defined classes.
 """
+from __future__ import absolute_import
+import types
+from rpython.flowspace.model import Constant
+from rpython.tool.sourcetools import func_with_new_name
 from rpython.annotator.model import (
-    SomePBC, s_ImpossibleValue, unionof, s_None, AnnotatorError)
-from rpython.annotator import description
+    SomePBC, s_ImpossibleValue, unionof, s_None, AnnotatorError, SomeInteger,
+    SomeString)
+from rpython.annotator.description import (
+    Desc, FunctionDesc, MethodDesc, NODEFAULT)
 
 
 # The main purpose of a ClassDef is to collect information about class/instance
@@ -96,7 +102,7 @@
             self.modified(classdef)
         self.read_locations.update(other.read_locations)
 
-    def mutated(self, homedef): # reflow from attr read positions
+    def mutated(self, homedef):  # reflow from attr read positions
         s_newvalue = self.getvalue()
 
         for position in self.read_locations:
@@ -105,17 +111,21 @@
         # check for method demotion and after-the-fact method additions
         if isinstance(s_newvalue, SomePBC):
             attr = self.name
-            if s_newvalue.getKind() == description.MethodDesc:
+            if s_newvalue.getKind() == 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)
-                                    )
+                                    raise Exception("demoting method %s "
+                                                    "to settled class %s not "
+                                                    "allowed" %
+                                                    (self.name, homedef)
+                                                    )
+                                #self.bookkeeper.warning("demoting method %s "
+                                #                        "to base class %s" %
+                                #                        (self.name, homedef))
                                 break
 
         # check for attributes forbidden by slots or _attrs_
@@ -124,9 +134,8 @@
                 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))
+                        "the attribute %r goes here to %r, but it is "
+                        "forbidden here" % (self.name, homedef))
 
     def modified(self, classdef='?'):
         self.readonly = False
@@ -182,7 +191,7 @@
                 # 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
+                    attrdef.mutated(cdef)  # reflow from all read positions
                 return
         else:
             # remember the source in self.attr_sources
@@ -200,7 +209,7 @@
                         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
+                            attrdef.mutated(subdef)  # reflow from all read positions
 
     def locate_attribute(self, attr):
         while True:
@@ -326,11 +335,11 @@
         for desc in pbc.descriptions:
             # pick methods but ignore already-bound methods, which can come
             # from an instance attribute
-            if (isinstance(desc, description.MethodDesc)
+            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
+                    pass  # subclasses methods are always candidates
                 elif self.issubclass(methclassdef):
                     # upward consider only the best match
                     if uplookup is None or methclassdef.issubclass(uplookup):
@@ -341,7 +350,7 @@
                     # clsdef1.lookup_filter(pbc) includes
                     # clsdef2.lookup_filter(pbc) (see formal proof...)
                 else:
-                    continue # not matching
+                    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)
@@ -394,7 +403,8 @@
         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")
+        raise Exception("ClassDefs are used as knowntype for instances but "
+                        "cannot be used as immutablevalue arguments directly")
 
 # ____________________________________________________________
 
@@ -432,3 +442,465 @@
 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
+    settled = False
+    _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]
+
+        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 '_settled_' in cls.__dict__:
+            self.settled = bool(cls.__dict__['_settled_'])
+
+        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)
+
+    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):
+        from rpython.annotator.classdef import ClassDef
+        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 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):
+            from rpython.annotator.model import SomePBC
+            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:
+            if '_immutable_fields_' in cdesc.classdict:
+                if (search1 in cdesc.classdict['_immutable_fields_'].value or
+                        search2 in cdesc.classdict['_immutable_fields_'].value):
+                    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
+
+    def mergeattrfamilies(self, others, attrname):
+        """Merge the attr families of the given Descs into one."""
+        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
+        changed, rep, attrfamily = access_sets.find(self)
+        for desc in others:
+            changed1, rep, attrfamily = access_sets.union(rep, desc)
+            changed = changed or changed1
+        return changed
+
+# ____________________________________________________________
+
+class Sample(object):
+    __slots__ = 'x'
+MemberDescriptorTypes = [type(Sample.x)]
+del Sample
+try:
+    MemberDescriptorTypes.append(type(OSError.errno))
+except AttributeError:    # on CPython <= 2.4
+    pass
+
+# ____________________________________________________________
+
+FORCE_ATTRIBUTES_INTO_CLASSES = {
+    EnvironmentError: {'errno': SomeInteger(),
+                       'strerror': SomeString(can_be_None=True),
+                       'filename': SomeString(can_be_None=True)},
+}
+
+try:
+    WindowsError
+except NameError:
+    pass
+else:
+    FORCE_ATTRIBUTES_INTO_CLASSES[WindowsError] = {'winerror': SomeInteger()}
diff --git a/rpython/annotator/description.py b/rpython/annotator/description.py
--- a/rpython/annotator/description.py
+++ b/rpython/annotator/description.py
@@ -2,13 +2,12 @@
 import types
 from rpython.annotator.signature import (
     enforce_signature_args, enforce_signature_return, finish_type)
-from rpython.flowspace.model import Constant, FunctionGraph
+from rpython.flowspace.model import FunctionGraph
 from rpython.flowspace.bytecode import cpython_code_signature
 from rpython.annotator.argument import rawshape, ArgErr, simple_args
-from rpython.tool.sourcetools import valid_identifier, func_with_new_name
+from rpython.tool.sourcetools import valid_identifier
 from rpython.tool.pairtype import extendabletype
-from rpython.annotator.model import (
-    AnnotatorError, SomeInteger, SomeString, s_ImpossibleValue)
+from rpython.annotator.model import AnnotatorError, s_ImpossibleValue
 
 class CallFamily(object):
     """A family of Desc objects that could be called from common call sites.
@@ -188,6 +187,8 @@
 class NoStandardGraph(Exception):
     """The function doesn't have a single standard non-specialized graph."""
 
+NODEFAULT = object()
+
 class FunctionDesc(Desc):
     knowntype = types.FunctionType
 
@@ -404,443 +405,6 @@
 
             return s_sigs
 
-def is_mixin(cls):
-    return cls.__dict__.get('_mixin_', False)
-
-NODEFAULT = object()
-
-class ClassDesc(Desc):
-    knowntype = type
-    instance_level = False
-    all_enforced_attrs = None   # or a set
-    settled = False
-    _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]
-
-        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 '_settled_' in cls.__dict__:
-            self.settled = bool(cls.__dict__['_settled_'])
-
-        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)
-
-    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):
-        from rpython.annotator.classdef import ClassDef
-        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 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):
-            from rpython.annotator.model import SomePBC
-            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:
-            if '_immutable_fields_' in cdesc.classdict:
-                if (search1 in cdesc.classdict['_immutable_fields_'].value or
-                        search2 in cdesc.classdict['_immutable_fields_'].value):
-                    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
-
-    def mergeattrfamilies(self, others, attrname):
-        """Merge the attr families of the given Descs into one."""
-        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
-        changed, rep, attrfamily = access_sets.find(self)
-        for desc in others:
-            changed1, rep, attrfamily = access_sets.union(rep, desc)
-            changed = changed or changed1
-        return changed
-
-
 class MethodDesc(Desc):
     knowntype = types.MethodType
 
@@ -1061,7 +625,6 @@
         func_args = self.func_args(args)
         return self.funcdesc.get_graph(func_args, op)
 
-
     @staticmethod
     def consider_call_site(descs, args, s_result, op):
         cnt, keys, star = rawshape(args)
@@ -1073,29 +636,3 @@
 
     def rowkey(self):
         return self.funcdesc
-
-# ____________________________________________________________
-
-class Sample(object):
-    __slots__ = 'x'
-MemberDescriptorTypes = [type(Sample.x)]
-del Sample
-try:
-    MemberDescriptorTypes.append(type(OSError.errno))
-except AttributeError:    # on CPython <= 2.4
-    pass
-
-# ____________________________________________________________
-
-FORCE_ATTRIBUTES_INTO_CLASSES = {
-    EnvironmentError: {'errno': SomeInteger(),
-                       'strerror': SomeString(can_be_None=True),
-                       'filename': SomeString(can_be_None=True)},
-}
-
-try:
-    WindowsError
-except NameError:
-    pass
-else:
-    FORCE_ATTRIBUTES_INTO_CLASSES[WindowsError] = {'winerror': SomeInteger()}
diff --git a/rpython/annotator/model.py b/rpython/annotator/model.py
--- a/rpython/annotator/model.py
+++ b/rpython/annotator/model.py
@@ -466,7 +466,7 @@
             if desc.pyobj is not None:
                 self.const = desc.pyobj
         elif len(descriptions) > 1:
-            from rpython.annotator.description import ClassDesc
+            from rpython.annotator.classdesc import ClassDesc
             if self.getKind() is ClassDesc:
                 # a PBC of several classes: enforce them all to be
                 # built, without support for specialization.  See
diff --git a/rpython/annotator/test/test_description.py b/rpython/annotator/test/test_description.py
--- a/rpython/annotator/test/test_description.py
+++ b/rpython/annotator/test/test_description.py
@@ -1,4 +1,4 @@
-from rpython.annotator.description import ClassDesc, is_mixin
+from rpython.annotator.classdesc import ClassDesc, is_mixin
 
 class FakeBookkeeper:
     def __init__(self):
diff --git a/rpython/annotator/unaryop.py b/rpython/annotator/unaryop.py
--- a/rpython/annotator/unaryop.py
+++ b/rpython/annotator/unaryop.py
@@ -875,7 +875,7 @@
     def getattr(self, s_attr):
         assert s_attr.is_constant()
         if s_attr.const == '__name__':
-            from rpython.annotator.description import ClassDesc
+            from rpython.annotator.classdesc import ClassDesc
             if self.getKind() is ClassDesc:
                 return SomeString()
         bookkeeper = getbookkeeper()
diff --git a/rpython/rtyper/normalizecalls.py b/rpython/rtyper/normalizecalls.py
--- a/rpython/rtyper/normalizecalls.py
+++ b/rpython/rtyper/normalizecalls.py
@@ -6,6 +6,7 @@
 from rpython.rtyper.error import TyperError
 from rpython.rtyper.rmodel import getgcflavor
 from rpython.tool.sourcetools import valid_identifier
+from rpython.annotator.classdesc import ClassDesc
 
 
 def normalize_call_familes(annotator):
@@ -213,7 +214,7 @@
             descs = access_set.descs
             if len(descs) <= 1:
                 continue
-            if not isinstance(descs.iterkeys().next(), description.ClassDesc):
+            if not isinstance(descs.iterkeys().next(), ClassDesc):
                 continue
             classdefs = [desc.getuniqueclassdef() for desc in descs]
             commonbase = classdefs[0]
@@ -241,7 +242,7 @@
         if len(family.descs) <= 1:
             continue
         descs = family.descs.keys()
-        if not isinstance(descs[0], description.ClassDesc):
+        if not isinstance(descs[0], ClassDesc):
             continue
         # Note that if classes are in the same callfamily, their __init__
         # attribute must be in the same attrfamily as well.
diff --git a/rpython/rtyper/rpbc.py b/rpython/rtyper/rpbc.py
--- a/rpython/rtyper/rpbc.py
+++ b/rpython/rtyper/rpbc.py
@@ -3,7 +3,8 @@
 from rpython.flowspace.model import FunctionGraph, Link, Block, SpaceOperation
 from rpython.annotator import model as annmodel
 from rpython.annotator.description import (
-    FunctionDesc, ClassDesc, MethodDesc, FrozenDesc, MethodOfFrozenDesc)
+    FunctionDesc, MethodDesc, FrozenDesc, MethodOfFrozenDesc)
+from rpython.annotator.classdesc import ClassDesc
 from rpython.flowspace.model import Constant
 from rpython.annotator.argument import simple_args
 from rpython.rlib.debug import ll_assert


More information about the pypy-commit mailing list