[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