[pypy-svn] r57193 - in pypy/dist/pypy: interpreter interpreter/test module/__builtin__ module/__builtin__/test objspace/flow objspace/flow/test

arigo at codespeak.net arigo at codespeak.net
Mon Aug 11 12:47:00 CEST 2008


Author: arigo
Date: Mon Aug 11 12:46:58 2008
New Revision: 57193

Added:
   pypy/dist/pypy/module/__builtin__/abstractinst.py
      - copied unchanged from r57133, pypy/branch/isinstance-refactor/pypy/module/__builtin__/abstractinst.py
   pypy/dist/pypy/module/__builtin__/test/test_abstractinst.py
      - copied unchanged from r57133, pypy/branch/isinstance-refactor/pypy/module/__builtin__/test/test_abstractinst.py
Modified:
   pypy/dist/pypy/interpreter/baseobjspace.py
   pypy/dist/pypy/interpreter/error.py
   pypy/dist/pypy/interpreter/function.py
   pypy/dist/pypy/interpreter/test/test_function.py
   pypy/dist/pypy/module/__builtin__/__init__.py
   pypy/dist/pypy/module/__builtin__/interp_classobj.py
   pypy/dist/pypy/module/__builtin__/operation.py
   pypy/dist/pypy/module/__builtin__/test/test_classobj.py
   pypy/dist/pypy/objspace/flow/objspace.py
   pypy/dist/pypy/objspace/flow/test/test_objspace.py
Log:
Merge the isinstance-refactor branch:

    * merge the two variants of "abstract isinstance" checking.
    
    * add tests for, hopefully, all the various obscure cases of
      isinstance() and issubclass()

    * implement them in a new module, abstractinst.py, following
      the CPython logic closely

    * the BaseObjSpace class only provides minimal implementations
      by default now as space.abstract_isxxx_w(), but the __buitin__
      module patches the space to install the full version.  A bit
      hackish but it looks less messy overall - no flow space hacks
      needed, and no need for strange imports from BaseObjSpace
      to pypy.module.__builtin__.interp_classobj.

    * the overzealous test compliance hack is gone, so I guess
      we'll need to do a small edit in one of CPython's tests.

    * minor speed-up in old-style instances' __getattribute__().

    * one more test for the flow space.


Modified: pypy/dist/pypy/interpreter/baseobjspace.py
==============================================================================
--- pypy/dist/pypy/interpreter/baseobjspace.py	(original)
+++ pypy/dist/pypy/interpreter/baseobjspace.py	Mon Aug 11 12:46:58 2008
@@ -654,16 +654,13 @@
     def exception_match(self, w_exc_type, w_check_class):
         """Checks if the given exception type matches 'w_check_class'."""
         if self.is_w(w_exc_type, w_check_class):
-            return True
-        if self.is_true(self.abstract_issubclass(w_exc_type, w_check_class)):
-            return True
-
-        if self.is_true(self.isinstance(w_check_class, self.w_tuple)):
-            exclst_w = self.unpacktuple(w_check_class)
-            for w_e in exclst_w:
-                if self.exception_match(w_exc_type, w_e):
-                    return True
-        return False
+            return True   # fast path (also here to handle string exceptions)
+        try:
+            return self.abstract_issubclass_w(w_exc_type, w_check_class)
+        except OperationError, e:
+            if e.match(self, self.w_TypeError):   # string exceptions maybe
+                return False
+            raise
 
     def call_obj_args(self, w_callable, w_obj, args):
         if not self.config.objspace.disable_call_speedhacks:
@@ -690,8 +687,8 @@
                         func = w_func.w_function
                         if isinstance(func, Function):
                             return func.funccall(w_inst, *args_w)
-                elif args_w and self.is_true(
-                        self.abstract_isinstance(args_w[0], w_func.w_class)):
+                elif args_w and (
+                        self.abstract_isinstance_w(args_w[0], w_func.w_class)):
                     w_func = w_func.w_function
 
             if isinstance(w_func, Function):
@@ -713,9 +710,9 @@
                     # reuse callable stack place for w_inst
                     frame.settopvalue(w_inst, nargs)
                     nargs += 1
-                elif nargs > 0 and self.is_true(
-                    self.abstract_isinstance(frame.peekvalue(nargs-1),   #    :-(
-                                             w_func.w_class)):
+                elif nargs > 0 and (
+                    self.abstract_isinstance_w(frame.peekvalue(nargs-1),   #    :-(
+                                               w_func.w_class)):
                     w_func = w_func.w_function
 
             if isinstance(w_func, Function):
@@ -767,61 +764,34 @@
         w_objtype = self.type(w_obj)
         return self.issubtype(w_objtype, w_type)
 
-    def abstract_issubclass(self, w_obj, w_cls, failhard=False):
-        try:
-            return self.issubtype(w_obj, w_cls)
-        except OperationError, e:
-            if not e.match(self, self.w_TypeError):
-                raise
-            try:
-                self.getattr(w_cls, self.wrap('__bases__')) # type sanity check
-                return self.recursive_issubclass(w_obj, w_cls)
-            except OperationError, e:
-                if failhard or not (e.match(self, self.w_TypeError) or
-                                    e.match(self, self.w_AttributeError)):
-                    raise
-                else:
-                    return self.w_False
-
-    def recursive_issubclass(self, w_obj, w_cls):
-        if self.is_w(w_obj, w_cls):
-            return self.w_True
-        for w_base in self.unpackiterable(self.getattr(w_obj,
-                                                       self.wrap('__bases__'))):
-            if self.is_true(self.recursive_issubclass(w_base, w_cls)):
-                return self.w_True
-        return self.w_False
-
-    def abstract_isinstance(self, w_obj, w_cls):
-        try:
-            return self.isinstance(w_obj, w_cls)
-        except OperationError, e:
-            if not e.match(self, self.w_TypeError):
-                raise
-            try:
-                w_objcls = self.getattr(w_obj, self.wrap('__class__'))
-                return self.abstract_issubclass(w_objcls, w_cls)
-            except OperationError, e:
-                if not (e.match(self, self.w_TypeError) or
-                        e.match(self, self.w_AttributeError)):
-                    raise
-                return self.w_False
-
-    def abstract_isclass(self, w_obj):
-        if self.is_true(self.isinstance(w_obj, self.w_type)):
-            return self.w_True
-        if self.findattr(w_obj, self.wrap('__bases__')) is not None:
-            return self.w_True
-        else:
-            return self.w_False
+    def abstract_issubclass_w(self, w_cls1, w_cls2):
+        # Equivalent to 'issubclass(cls1, cls2)'.  The code below only works
+        # for the simple case (new-style class, new-style class).
+        # This method is patched with the full logic by the __builtin__
+        # module when it is loaded.
+        return self.is_true(self.issubtype(w_cls1, w_cls2))
+
+    def abstract_isinstance_w(self, w_obj, w_cls):
+        # Equivalent to 'isinstance(obj, cls)'.  The code below only works
+        # for the simple case (new-style instance, new-style class).
+        # This method is patched with the full logic by the __builtin__
+        # module when it is loaded.
+        return self.is_true(self.isinstance(w_obj, w_cls))
+
+    def abstract_isclass_w(self, w_obj):
+        # Equivalent to 'isinstance(obj, type)'.  The code below only works
+        # for the simple case (new-style instance without special stuff).
+        # This method is patched with the full logic by the __builtin__
+        # module when it is loaded.
+        return self.is_true(self.isinstance(w_obj, self.w_type))
 
     def abstract_getclass(self, w_obj):
-        try:
-            return self.getattr(w_obj, self.wrap('__class__'))
-        except OperationError, e:
-            if e.match(self, self.w_TypeError) or e.match(self, self.w_AttributeError):
-                return self.type(w_obj)
-            raise
+        # Equivalent to 'obj.__class__'.  The code below only works
+        # for the simple case (new-style instance without special stuff).
+        # This method is patched with the full logic by the __builtin__
+        # module when it is loaded.
+        return self.type(w_obj)
+
 
     def eval(self, expression, w_globals, w_locals):
         "NOT_RPYTHON: For internal debugging."

Modified: pypy/dist/pypy/interpreter/error.py
==============================================================================
--- pypy/dist/pypy/interpreter/error.py	(original)
+++ pypy/dist/pypy/interpreter/error.py	Mon Aug 11 12:46:58 2008
@@ -151,15 +151,14 @@
             while space.is_true(space.isinstance(w_type, space.w_tuple)):
                 w_type = space.getitem(w_type, space.wrap(0))
 
-        if space.is_true(space.abstract_isclass(w_type)):
+        if space.abstract_isclass_w(w_type):
             if space.is_w(w_value, space.w_None):
                 # raise Type: we assume we have to instantiate Type
                 w_value = space.call_function(w_type)
                 w_type = space.abstract_getclass(w_value)
             else:
                 w_valuetype = space.abstract_getclass(w_value)
-                if space.is_true(space.abstract_issubclass(w_valuetype,
-                                                           w_type)):
+                if space.abstract_issubclass_w(w_valuetype, w_type):
                     # raise Type, Instance: let etype be the exact type of value
                     w_type = w_valuetype
                 else:

Modified: pypy/dist/pypy/interpreter/function.py
==============================================================================
--- pypy/dist/pypy/interpreter/function.py	(original)
+++ pypy/dist/pypy/interpreter/function.py	Mon Aug 11 12:46:58 2008
@@ -322,8 +322,8 @@
 
         # unbound method
         w_firstarg = args.firstarg()
-        if w_firstarg is not None and space.is_true(
-                space.abstract_isinstance(w_firstarg, self.w_class)):
+        if w_firstarg is not None and (
+                space.abstract_isinstance_w(w_firstarg, self.w_class)):
             pass  # ok
         else:
             myname = self.getname(space,"")
@@ -351,7 +351,7 @@
             # only allow binding to a more specific class than before
             if (w_cls is not None and
                 not space.is_w(w_cls, space.w_None) and
-                not space.is_true(space.abstract_issubclass(w_cls, self.w_class))):
+                not space.abstract_issubclass_w(w_cls, self.w_class)):
                 return space.wrap(self)    # subclass test failed
             else:
                 return descr_function_get(space, self.w_function, w_obj, w_cls)

Modified: pypy/dist/pypy/interpreter/test/test_function.py
==============================================================================
--- pypy/dist/pypy/interpreter/test/test_function.py	(original)
+++ pypy/dist/pypy/interpreter/test/test_function.py	Mon Aug 11 12:46:58 2008
@@ -356,6 +356,46 @@
             __metaclass__ = A().foo
         assert Fun[:2] == ('Fun', ())
 
+    def test_unbound_abstract_typecheck(self):
+        import new
+        def f(*args):
+            return args
+        m = new.instancemethod(f, None, "foobar")
+        raises(TypeError, m)
+        raises(TypeError, m, None)
+        raises(TypeError, m, "egg")
+
+        m = new.instancemethod(f, None, (str, int))     # really obscure...
+        assert m(4) == (4,)
+        assert m("uh") == ("uh",)
+        raises(TypeError, m, [])
+
+        class MyBaseInst(object):
+            pass
+        class MyInst(MyBaseInst):
+            def __init__(self, myclass):
+                self.myclass = myclass
+            def __class__(self):
+                if self.myclass is None:
+                    raise AttributeError
+                return self.myclass
+            __class__ = property(__class__)
+        class MyClass(object):
+            pass
+        BBase = MyClass()
+        BSub1 = MyClass()
+        BSub2 = MyClass()
+        BBase.__bases__ = ()
+        BSub1.__bases__ = (BBase,)
+        BSub2.__bases__ = (BBase,)
+        x = MyInst(BSub1)
+        m = new.instancemethod(f, None, BSub1)
+        assert m(x) == (x,)
+        raises(TypeError, m, MyInst(BBase))
+        raises(TypeError, m, MyInst(BSub2))
+        raises(TypeError, m, MyInst(None))
+        raises(TypeError, m, MyInst(42))
+
 
 class TestMethod: 
     def setup_method(self, method):

Modified: pypy/dist/pypy/module/__builtin__/__init__.py
==============================================================================
--- pypy/dist/pypy/module/__builtin__/__init__.py	(original)
+++ pypy/dist/pypy/module/__builtin__/__init__.py	Mon Aug 11 12:46:58 2008
@@ -87,8 +87,8 @@
         'coerce'        : 'operation.coerce',
         'divmod'        : 'operation.divmod',
         '_issubtype'    : 'operation._issubtype',
-        'issubclass'    : 'operation.issubclass',
-        'isinstance'    : 'operation.isinstance',
+        'issubclass'    : 'abstractinst.app_issubclass',
+        'isinstance'    : 'abstractinst.app_isinstance',
         'getattr'       : 'operation.getattr',
         'setattr'       : 'operation.setattr',
         'delattr'       : 'operation.delattr',
@@ -151,6 +151,12 @@
                 # xxx hide the installer
                 space.delitem(self.w_dict, space.wrap(name))
                 del self.loaders[name]
+        # install the more general version of isinstance() & co. in the space
+        from pypy.module.__builtin__ import abstractinst as ab
+        space.abstract_isinstance_w = ab.abstract_isinstance_w.__get__(space)
+        space.abstract_issubclass_w = ab.abstract_issubclass_w.__get__(space)
+        space.abstract_isclass_w = ab.abstract_isclass_w.__get__(space)
+        space.abstract_getclass = ab.abstract_getclass.__get__(space)
 
     def startup(self, space):
         # install zipimport hook if --withmod-zipimport is used

Modified: pypy/dist/pypy/module/__builtin__/interp_classobj.py
==============================================================================
--- pypy/dist/pypy/module/__builtin__/interp_classobj.py	(original)
+++ pypy/dist/pypy/module/__builtin__/interp_classobj.py	Mon Aug 11 12:46:58 2008
@@ -85,6 +85,16 @@
                                      space.wrap("__bases__ items must be classes"))
         self.bases_w = bases_w
 
+    def is_subclass_of(self, other):
+        assert isinstance(other, W_ClassObject)
+        if self is other:
+            return True
+        for base in self.bases_w:
+            assert isinstance(base, W_ClassObject)
+            if base.is_subclass_of(other):
+                return True
+        return False
+
     def lookup(self, space, w_attr):
         # returns w_value or interplevel None
         w_result = space.finditem(self.w_dict, w_attr)
@@ -225,6 +235,7 @@
         except OperationError, e:
             if e.match(space, space.w_AttributeError):
                 return space.w_NotImplemented
+            raise
         else:
             if w_meth is None:
                 return space.w_NotImplemented
@@ -314,11 +325,6 @@
 
 
     def getattr(self, space, w_name, exc=True):
-        name = space.str_w(w_name)
-        if name == "__dict__":
-            return self.w_dict
-        elif name == "__class__":
-            return self.w_class
         w_result = space.finditem(self.w_dict, w_name)
         if w_result is not None:
             return w_result
@@ -328,7 +334,7 @@
                 raise OperationError(
                     space.w_AttributeError,
                     space.wrap("%s instance has no attribute %s" % (
-                        self.w_class.name, name)))
+                        self.w_class.name, space.str_w(w_name))))
             else:
                 return None
         w_descr_get = space.lookup(w_value, '__get__')
@@ -337,7 +343,12 @@
         return space.call_function(w_descr_get, w_value, self, self.w_class)
 
     def descr_getattribute(self, space, w_attr):
-        #import pdb; pdb.set_trace()
+        name = space.str_w(w_attr)
+        if len(name) >= 8 and name[0] == '_':
+            if name == "__dict__":
+                return self.w_dict
+            elif name == "__class__":
+                return self.w_class
         try:
             return self.getattr(space, w_attr)
         except OperationError, e:

Modified: pypy/dist/pypy/module/__builtin__/operation.py
==============================================================================
--- pypy/dist/pypy/module/__builtin__/operation.py	(original)
+++ pypy/dist/pypy/module/__builtin__/operation.py	Mon Aug 11 12:46:58 2008
@@ -220,95 +220,3 @@
 function).  Note that classes are callable."""
     return space.callable(w_object)
 
-
-
-def _recursive_issubclass(space, w_cls, w_klass_or_tuple): # returns interp-level bool
-    if space.is_w(w_cls, w_klass_or_tuple):
-        return True
-    try:
-        w_bases = space.getattr(w_cls, space.wrap("__bases__"))
-    except OperationError, e:
-        if e.match(space, space.w_AttributeError):
-            return False
-        else:
-            raise
-    w_iterator = space.iter(w_bases)
-    while True:
-        try:
-            w_base = space.next(w_iterator)
-        except OperationError, e:
-            if not e.match(space, space.w_StopIteration):
-                raise
-            break
-        if _recursive_issubclass(space, w_base, w_klass_or_tuple):
-            return True
-    return False
-
-def _issubclass(space, w_cls, w_klass_or_tuple, check_cls, depth): # returns interp-level bool
-    if depth == 0:
-        # XXX overzealous test compliance hack
-        raise OperationError(space.w_RuntimeError, space.wrap("maximum recursion depth exceeded"))
-    if space.is_true(space.issubtype(space.type(w_klass_or_tuple), space.w_tuple)):
-        w_iter = space.iter(w_klass_or_tuple)
-        while True:
-            try:
-                w_klass = space.next(w_iter)
-            except OperationError, e:
-                if not e.match(space, space.w_StopIteration):
-                   raise
-                break
-            if _issubclass(space, w_cls, w_klass, True, depth - 1):
-                return True
-        return False
-
-    try:
-        return space.is_true(space.issubtype(w_cls, w_klass_or_tuple))
-    except OperationError, e:
-        if e.match(space, space.w_TypeError):
-            w_bases = space.wrap('__bases__')
-            if check_cls:
-                try:
-                    space.getattr(w_cls, w_bases)
-                except OperationError, e:
-                    if not e.match(space, space.w_AttributeError):
-                        raise
-                    raise OperationError(space.w_TypeError, space.wrap('arg 1 must be a class or type'))
-            try:
-                space.getattr(w_klass_or_tuple, w_bases)
-            except OperationError, e:
-                if not e.match(space, space.w_AttributeError):
-                    raise
-                raise OperationError(space.w_TypeError, space.wrap('arg 2 must be a class or type or a tuple thereof'))
-            return _recursive_issubclass(space, w_cls, w_klass_or_tuple)
-        else:
-            raise
-
-
-def issubclass(space, w_cls, w_klass_or_tuple):
-    """Check whether a class 'cls' is a subclass (i.e., a derived class) of
-another class.  When using a tuple as the second argument, check whether
-'cls' is a subclass of any of the classes listed in the tuple."""
-    return space.wrap(issubclass_w(space, w_cls, w_klass_or_tuple))
-
-def issubclass_w(space, w_cls, w_klass_or_tuple):
-    return _issubclass(space, w_cls, w_klass_or_tuple, True, space.sys.recursionlimit)
-
-
-def isinstance(space, w_obj, w_klass_or_tuple):
-    """Check whether an object is an instance of a class (or of a subclass
-thereof).  When using a tuple as the second argument, check whether 'obj'
-is an instance of any of the classes listed in the tuple."""
-    w_objtype = space.type(w_obj)
-    if issubclass_w(space, w_objtype, w_klass_or_tuple):
-        return space.w_True
-    try:
-        w_objcls = space.getattr(w_obj, space.wrap("__class__"))
-    except OperationError, e:
-        if e.match(space, space.w_AttributeError):
-            return space.w_False
-        else:
-            raise
-    if space.is_w(w_objcls, w_objtype):
-        return space.w_False
-    else:
-        return space.wrap(_issubclass(space, w_objcls, w_klass_or_tuple, False, space.sys.recursionlimit))

Modified: pypy/dist/pypy/module/__builtin__/test/test_classobj.py
==============================================================================
--- pypy/dist/pypy/module/__builtin__/test/test_classobj.py	(original)
+++ pypy/dist/pypy/module/__builtin__/test/test_classobj.py	Mon Aug 11 12:46:58 2008
@@ -15,6 +15,35 @@
         assert a.__class__ is A
         assert a.__dict__ == {'b': 2}
 
+    def test_isinstance(self):
+        class A:
+            pass
+        class B(A):
+            pass
+        class C(A):
+            pass
+        assert isinstance(B(), A)
+        assert isinstance(B(), B)
+        assert not isinstance(B(), C)
+        assert not isinstance(A(), B)
+        assert isinstance(B(), (A, C))
+        assert isinstance(B(), (C, (), (C, B)))
+        assert not isinstance(B(), ())
+
+    def test_issubclass(self):
+        class A:
+            pass
+        class B(A):
+            pass
+        class C(A):
+            pass
+        assert issubclass(A, A)
+        assert not issubclass(A, B)
+        assert not issubclass(A, C)
+        assert issubclass(B, A)
+        assert issubclass(B, B)
+        assert not issubclass(B, C)
+
     def test_mutate_class_special(self):
         class A:
             a = 1
@@ -415,6 +444,21 @@
         raises(TypeError, "a + 1.1")
         assert l == [1, 1.1]
 
+    def test_binaryop_raises(self):
+        class A:
+            def __add__(self, other):
+                raise this_exception
+            def __iadd__(self, other):
+                raise this_exception
+
+        a = A()
+        this_exception = ValueError
+        raises(ValueError, "a + 1")
+        raises(ValueError, "a += 1")
+        this_exception = AttributeError
+        raises(AttributeError, "a + 1")
+        raises(AttributeError, "a += 1")
+
     def test_iadd(self):
         class A:
             def __init__(self):
@@ -618,13 +662,14 @@
 
     def test_catch_attributeerror_of_descriptor(self):
         def booh(self):
-            raise AttributeError, "booh"
+            raise this_exception, "booh"
 
         class E:
             __eq__ = property(booh)
             __iadd__ = property(booh)
 
         e = E()
+        this_exception = AttributeError
         raises(TypeError, "e += 1")
         # does not crash
         E() == E()
@@ -632,6 +677,9 @@
             __init__ = property(booh)
         raises(AttributeError, I)
 
+        this_exception = ValueError
+        raises(ValueError, "e += 1")
+
     def test_multiple_inheritance_more(self):
         l = []
         class A:    # classic class
@@ -686,6 +734,10 @@
         assert Y() != X()
 
     def test_assignment_to_del(self):
+        import sys
+        if not hasattr(sys, 'pypy_objspaceclass'):
+            skip("assignment to __del__ doesn't give a warning in CPython")
+
         import warnings
         
         warnings.simplefilter('error', RuntimeWarning)

Modified: pypy/dist/pypy/objspace/flow/objspace.py
==============================================================================
--- pypy/dist/pypy/objspace/flow/objspace.py	(original)
+++ pypy/dist/pypy/objspace/flow/objspace.py	Mon Aug 11 12:46:58 2008
@@ -218,18 +218,6 @@
                 return ecls
         return None
 
-    def abstract_issubclass(self, w_obj, w_cls, failhard=False):
-        return self.issubtype(w_obj, w_cls)
-
-    def abstract_isinstance(self, w_obj, w_cls):
-        return self.isinstance(w_obj, w_cls)
-
-    def abstract_isclass(self, w_obj):
-        return self.isinstance(w_obj, self.w_type)
-
-    def abstract_getclass(self, w_obj):
-        return self.type(w_obj)
-
 
     def build_flow(self, func, constargs={}):
         """

Modified: pypy/dist/pypy/objspace/flow/test/test_objspace.py
==============================================================================
--- pypy/dist/pypy/objspace/flow/test/test_objspace.py	(original)
+++ pypy/dist/pypy/objspace/flow/test/test_objspace.py	Mon Aug 11 12:46:58 2008
@@ -1,7 +1,7 @@
 import new
 import py
 from pypy.objspace.flow.model import Constant, Block, Link, Variable, traverse
-from pypy.objspace.flow.model import flatten
+from pypy.objspace.flow.model import flatten, mkentrymap
 from pypy.interpreter.argument import Arguments
 from pypy.translator.simplify import simplify_graph
 from pypy.objspace.flow.objspace import FlowObjSpace 
@@ -416,6 +416,27 @@
         x = self.codetest(self.catch_simple_call)
 
     #__________________________________________________________
+    def multiple_catch_simple_call():
+        try:
+            user_defined_function()
+        except (IndexError, OSError):
+            return -1
+        return 0
+    
+    def test_multiple_catch_simple_call(self):
+        graph = self.codetest(self.multiple_catch_simple_call)
+        simplify_graph(graph)
+        assert self.all_operations(graph) == {'simple_call': 1}
+        entrymap = mkentrymap(graph)
+        links = entrymap[graph.returnblock]
+        assert len(links) == 3
+        assert (dict.fromkeys([link.exitcase for link in links]) ==
+                dict.fromkeys([None, IndexError, OSError]))
+        links = entrymap[graph.exceptblock]
+        assert len(links) == 1
+        assert links[0].exitcase is Exception
+
+    #__________________________________________________________
     def dellocal():
         x = 1
         del x



More information about the Pypy-commit mailing list