[pypy-commit] pypy default: (Greg Price, fijal reviewing) Merge signatures branch.

fijal noreply at buildbot.pypy.org
Fri Dec 7 09:33:39 CET 2012


Author: Maciej Fijalkowski <fijall at gmail.com>
Branch: 
Changeset: r59360:dc68348c70d7
Date: 2012-12-07 10:33 +0200
http://bitbucket.org/pypy/pypy/changeset/dc68348c70d7/

Log:	(Greg Price, fijal reviewing) Merge signatures branch.

	This branch adds a decorator that let's you specify the signatures
	of functions. Does not support full set yet, but supersedes
	annenforceargs decorator.

diff --git a/pypy/annotation/annrpython.py b/pypy/annotation/annrpython.py
--- a/pypy/annotation/annrpython.py
+++ b/pypy/annotation/annrpython.py
@@ -1,3 +1,5 @@
+from __future__ import absolute_import
+
 import types
 from pypy.tool.ansi_print import ansi_log
 from pypy.tool.pairtype import pair
@@ -373,7 +375,12 @@
         # Merge the new 'cells' with each of the block's existing input
         # variables.
         oldcells = [self.binding(a) for a in block.inputargs]
-        unions = [annmodel.unionof(c1,c2) for c1, c2 in zip(oldcells,inputcells)]
+        try:
+            unions = [annmodel.unionof(c1,c2) for c1, c2 in zip(oldcells,inputcells)]
+        except annmodel.UnionError, e:
+            e.args = e.args + (
+                ErrorWrapper(gather_error(self, graph, block, None)),)
+            raise
         # if the merged cells changed, we must redo the analysis
         if unions != oldcells:
             self.bindinputargs(graph, block, unions)
diff --git a/pypy/annotation/bookkeeper.py b/pypy/annotation/bookkeeper.py
--- a/pypy/annotation/bookkeeper.py
+++ b/pypy/annotation/bookkeeper.py
@@ -1,6 +1,9 @@
 """
 The Bookkeeper class.
 """
+
+from __future__ import absolute_import
+
 import sys, types, inspect, weakref
 
 from pypy.objspace.flow.model import Constant
diff --git a/pypy/annotation/description.py b/pypy/annotation/description.py
--- a/pypy/annotation/description.py
+++ b/pypy/annotation/description.py
@@ -1,4 +1,6 @@
+from __future__ import absolute_import
 import types, py
+from pypy.annotation.signature import enforce_signature_args, enforce_signature_return
 from pypy.objspace.flow.model import Constant, FunctionGraph
 from pypy.objspace.flow.bytecode import cpython_code_signature
 from pypy.objspace.flow.argument import rawshape, ArgErr
@@ -275,12 +277,17 @@
             policy = self.bookkeeper.annotator.policy
             self.specializer = policy.get_specializer(tag)
         enforceargs = getattr(self.pyobj, '_annenforceargs_', None)
+        signature = getattr(self.pyobj, '_signature_', None)
+        if enforceargs and signature:
+            raise Exception("%r: signature and enforceargs cannot both be used" % (self,))
         if enforceargs:
             if not callable(enforceargs):
                 from pypy.annotation.policy import Sig
                 enforceargs = Sig(*enforceargs)
                 self.pyobj._annenforceargs_ = enforceargs
             enforceargs(self, inputcells) # can modify inputcells in-place
+        if signature:
+            enforce_signature_args(self, signature[0], inputcells) # mutates inputcells
         if getattr(self.pyobj, '_annspecialcase_', '').endswith("call_location"):
             return self.specializer(self, inputcells, op)
         else:
@@ -297,6 +304,10 @@
             new_args = args.unmatch_signature(self.signature, inputcells)
             inputcells = self.parse_arguments(new_args, graph)
             result = schedule(graph, inputcells)
+            signature = getattr(self.pyobj, '_signature_', None)
+            if signature:
+                result = enforce_signature_return(self, signature[1], result)
+                self.bookkeeper.annotator.addpendingblock(graph, graph.returnblock, [result])
         # Some specializations may break the invariant of returning
         # annotations that are always more general than the previous time.
         # We restore it here:
diff --git a/pypy/annotation/model.py b/pypy/annotation/model.py
--- a/pypy/annotation/model.py
+++ b/pypy/annotation/model.py
@@ -27,6 +27,7 @@
 #    \_____________________________________________________/
 #
 
+from __future__ import absolute_import
 
 from types import BuiltinFunctionType, MethodType, FunctionType
 import pypy
diff --git a/pypy/annotation/signature.py b/pypy/annotation/signature.py
--- a/pypy/annotation/signature.py
+++ b/pypy/annotation/signature.py
@@ -1,3 +1,5 @@
+
+from __future__ import absolute_import
 
 import types
 from pypy.annotation.model import SomeBool, SomeInteger, SomeString,\
@@ -128,3 +130,25 @@
                                              s_arg,
                                              s_input))
         inputcells[:] = args_s
+
+def finish_type(paramtype, bookkeeper, func):
+    from pypy.annotation.types import SelfTypeMarker
+    if isinstance(paramtype, SomeObject):
+        return paramtype
+    elif isinstance(paramtype, SelfTypeMarker):
+        raise Exception("%r argument declared as annotation.types.self(); class needs decorator rlib.signature.finishsigs()" % (func,))
+    else:
+        return paramtype(bookkeeper)
+
+def enforce_signature_args(funcdesc, paramtypes, actualtypes):
+    assert len(paramtypes) == len(actualtypes)
+    params_s = [finish_type(paramtype, funcdesc.bookkeeper, funcdesc.pyobj) for paramtype in paramtypes]
+    for i, (s_param, s_actual) in enumerate(zip(params_s, actualtypes)):
+        if not s_param.contains(s_actual):
+            raise Exception("%r argument %d:\n"
+                            "expected %s,\n"
+                            "     got %s" % (funcdesc, i+1, s_param, s_actual))
+    actualtypes[:] = params_s
+
+def enforce_signature_return(funcdesc, sigtype, inferredtype):
+    return finish_type(sigtype, funcdesc.bookkeeper, funcdesc.pyobj)
diff --git a/pypy/annotation/types.py b/pypy/annotation/types.py
new file mode 100644
--- /dev/null
+++ b/pypy/annotation/types.py
@@ -0,0 +1,54 @@
+from pypy.annotation import model
+from pypy.annotation.listdef import ListDef
+from pypy.annotation.dictdef import DictDef
+
+
+def none():
+    return model.s_None
+
+
+def float():
+    return model.SomeFloat()
+
+def singlefloat():
+    return model.SomeSingleFloat()
+
+def longfloat():
+    return model.SomeLongFloat()
+
+
+def int():
+    return model.SomeInteger()
+
+
+def unicode():
+    return model.SomeUnicodeString()
+
+def str():
+    return model.SomeString()
+
+def char():
+    return model.SomeChar()
+
+
+def list(element):
+    listdef = ListDef(None, element, mutated=True, resized=True)
+    return model.SomeList(listdef)
+
+def array(element):
+    listdef = ListDef(None, element, mutated=True, resized=False)
+    return model.SomeList(listdef)
+
+def dict(keytype, valuetype):
+    dictdef = DictDef(None, keytype, valuetype)
+    return model.SomeDict(dictdef)
+
+
+def instance(class_):
+    return lambda bookkeeper: model.SomeInstance(bookkeeper.getuniqueclassdef(class_))
+
+class SelfTypeMarker(object):
+    pass
+
+def self():
+    return SelfTypeMarker()
diff --git a/pypy/annotation/unaryop.py b/pypy/annotation/unaryop.py
--- a/pypy/annotation/unaryop.py
+++ b/pypy/annotation/unaryop.py
@@ -2,6 +2,8 @@
 Unary operations on SomeValues.
 """
 
+from __future__ import absolute_import
+
 from types import MethodType
 from pypy.annotation.model import \
      SomeObject, SomeInteger, SomeBool, SomeString, SomeChar, SomeList, \
diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py
--- a/pypy/rlib/objectmodel.py
+++ b/pypy/rlib/objectmodel.py
@@ -194,6 +194,7 @@
     return decorator
 
 
+
 # ____________________________________________________________
 
 class Symbolic(object):
diff --git a/pypy/rlib/signature.py b/pypy/rlib/signature.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/signature.py
@@ -0,0 +1,39 @@
+from pypy.annotation import types
+
+def signature(*paramtypes, **kwargs):
+    """Decorate a function to specify its type signature.
+
+    Usage:
+      @signature(param1type, param2type, ..., returns=returntype)
+      def foo(...)
+
+    The arguments paramNtype and returntype should be instances
+    of the classes in pypy.annotation.types.
+    """
+    returntype = kwargs.pop('returns', None)
+    if returntype is None:
+        raise TypeError, "signature: parameter 'returns' required"
+
+    def decorator(f):
+        f._signature_ = (paramtypes, returntype)
+        return f
+    return decorator
+
+
+def finishsigs(cls):
+    """Decorate a class to finish any method signatures involving types.self().
+
+    This is required if any method has a signature with types.self() in it.
+    """
+    # A bit annoying to have to use this, but it avoids performing any
+    # terrible hack in the implementation.  Eventually we'll offer signatures
+    # on classes, and then that decorator can do this on the side.
+    def fix(sigtype):
+        if isinstance(sigtype, types.SelfTypeMarker):
+            return types.instance(cls)
+        return sigtype
+    for attr in cls.__dict__.values():
+        if hasattr(attr, '_signature_'):
+            paramtypes, returntype = attr._signature_
+            attr._signature_ = (tuple(fix(t) for t in paramtypes), fix(returntype))
+    return cls
diff --git a/pypy/rlib/test/test_objectmodel.py b/pypy/rlib/test/test_objectmodel.py
--- a/pypy/rlib/test/test_objectmodel.py
+++ b/pypy/rlib/test/test_objectmodel.py
@@ -1,5 +1,6 @@
 import py
 from pypy.rlib.objectmodel import *
+from pypy.annotation import types, model
 from pypy.translator.translator import TranslationContext, graphof
 from pypy.rpython.test.tool import BaseRtypingTest, LLRtypeMixin, OORtypeMixin
 from pypy.rpython.test.test_llinterp import interpret
@@ -486,6 +487,7 @@
     TYPES = [v.concretetype for v in graph.getargs()]
     assert TYPES == [lltype.Signed, lltype.Float]
 
+
 def getgraph(f, argtypes):
     from pypy.translator.translator import TranslationContext, graphof
     from pypy.translator.backendopt.all import backend_optimizations
diff --git a/pypy/rlib/test/test_signature.py b/pypy/rlib/test/test_signature.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/test/test_signature.py
@@ -0,0 +1,211 @@
+import py
+from pypy.rlib.signature import signature, finishsigs
+from pypy.annotation import types, model
+from pypy.translator.translator import TranslationContext, graphof
+
+
+def annotate_at(f):
+    t = TranslationContext()
+    a = t.buildannotator()
+    a.annotate_helper(f, [model.s_ImpossibleValue]*f.func_code.co_argcount)
+    return a
+
+def sigof(a, f):
+    # returns [param1, param2, ..., ret]
+    g = graphof(a.translator, f)
+    return [a.bindings[v] for v in g.startblock.inputargs] + [a.bindings[g.getreturnvar()]]
+
+def getsig(f):
+    a = annotate_at(f)
+    return sigof(a, f)
+
+def check_annotator_fails(caller):
+    exc = py.test.raises(Exception, annotate_at, caller).value
+    assert caller.func_name in repr(exc.args)
+
+
+def test_signature_bookkeeping():
+    @signature('x', 'y', returns='z')
+    def f(a, b):
+        return a + len(b)
+    f.foo = 'foo'
+    assert f._signature_ == (('x', 'y'), 'z')
+    assert f.func_name == 'f'
+    assert f.foo == 'foo'
+    assert f(1, 'hello') == 6
+
+def test_signature_basic():
+    @signature(types.int(), types.str(), returns=types.char())
+    def f(a, b):
+        return b[a]
+    assert getsig(f) == [model.SomeInteger(), model.SomeString(), model.SomeChar()]
+
+def test_signature_arg_errors():
+    @signature(types.int(), types.str(), returns=types.int())
+    def f(a, b):
+        return a + len(b)
+    @check_annotator_fails
+    def ok_for_body(): # would give no error without signature
+        f(2.0, 'b')
+    @check_annotator_fails
+    def bad_for_body(): # would give error inside 'f' body, instead errors at call
+        f('a', 'b')
+
+def test_signature_return():
+    @signature(returns=types.str())
+    def f():
+        return 'a'
+    assert getsig(f) == [model.SomeString()]
+
+    @signature(types.str(), returns=types.str())
+    def f(x):
+        return x
+    def g():
+        return f('a')
+    a = annotate_at(g)
+    assert sigof(a, f) == [model.SomeString(), model.SomeString()]
+
+def test_signature_return_errors():
+    @check_annotator_fails
+    @signature(returns=types.int())
+    def int_not_char():
+        return 'a'
+    @check_annotator_fails
+    @signature(types.str(), returns=types.int())
+    def str_to_int(s):
+        return s
+
+
+def test_signature_none():
+    @signature(returns=types.none())
+    def f():
+        pass
+    assert getsig(f) == [model.s_None]
+
+def test_signature_float():
+    @signature(types.longfloat(), types.singlefloat(), returns=types.float())
+    def f(a, b):
+        return 3.0
+    assert getsig(f) == [model.SomeLongFloat(), model.SomeSingleFloat(), model.SomeFloat()]
+
+def test_signature_unicode():
+    @signature(types.unicode(), returns=types.int())
+    def f(u):
+        return len(u)
+    assert getsig(f) == [model.SomeUnicodeString(), model.SomeInteger()]
+
+
+def test_signature_list():
+    @signature(types.list(types.int()), returns=types.int())
+    def f(a):
+        return len(a)
+    argtype = getsig(f)[0]
+    assert isinstance(argtype, model.SomeList)
+    item = argtype.listdef.listitem
+    assert item.s_value == model.SomeInteger()
+    assert item.resized == True
+
+    @check_annotator_fails
+    def ok_for_body():
+        f(['a'])
+    @check_annotator_fails
+    def bad_for_body():
+        f('a')
+
+    @signature(returns=types.list(types.char()))
+    def ff():
+        return ['a']
+    @check_annotator_fails
+    def mutate_broader():
+        ff()[0] = 'abc'
+    @check_annotator_fails
+    def mutate_unrelated():
+        ff()[0] = 1
+    @check_annotator_fails
+    @signature(types.list(types.char()), returns=types.int())
+    def mutate_in_body(l):
+        l[0] = 'abc'
+        return len(l)
+
+    def can_append():
+        l = ff()
+        l.append('b')
+    getsig(can_append)
+
+def test_signature_array():
+    @signature(returns=types.array(types.int()))
+    def f():
+        return [1]
+    rettype = getsig(f)[0]
+    assert isinstance(rettype, model.SomeList)
+    item = rettype.listdef.listitem
+    assert item.s_value == model.SomeInteger()
+    assert item.resized == False
+
+    def try_append():
+        l = f()
+        l.append(2)
+    check_annotator_fails(try_append)
+
+def test_signature_dict():
+    @signature(returns=types.dict(types.str(), types.int()))
+    def f():
+        return {'a': 1, 'b': 2}
+    rettype = getsig(f)[0]
+    assert isinstance(rettype, model.SomeDict)
+    assert rettype.dictdef.dictkey.s_value   == model.SomeString()
+    assert rettype.dictdef.dictvalue.s_value == model.SomeInteger()
+
+
+def test_signature_instance():
+    class C1(object):
+        pass
+    class C2(C1):
+        pass
+    class C3(C2):
+        pass
+    @signature(types.instance(C3), returns=types.instance(C2))
+    def f(x):
+        assert isinstance(x, C2)
+        return x
+    argtype, rettype = getsig(f)
+    assert isinstance(argtype, model.SomeInstance)
+    assert argtype.classdef.classdesc.pyobj == C3
+    assert isinstance(rettype, model.SomeInstance)
+    assert rettype.classdef.classdesc.pyobj == C2
+
+    @check_annotator_fails
+    def ok_for_body():
+        f(C2())
+    @check_annotator_fails
+    def bad_for_body():
+        f(C1())
+
+def test_signature_self():
+    @finishsigs
+    class C(object):
+        @signature(types.self(), types.self(), returns=types.none())
+        def f(self, other):
+            pass
+    class D1(C):
+        pass
+    class D2(C):
+        pass
+
+    def g():
+        D1().f(D2())
+    a = annotate_at(g)
+
+    argtype = sigof(a, C.__dict__['f'])[0]
+    assert isinstance(argtype, model.SomeInstance)
+    assert argtype.classdef.classdesc.pyobj == C
+
+def test_signature_self_error():
+    class C(object):
+        @signature(types.self(), returns=types.none())
+        def incomplete_sig_meth(self):
+            pass
+
+    exc = py.test.raises(Exception, annotate_at, C.incomplete_sig_meth).value
+    assert 'incomplete_sig_meth' in repr(exc.args)
+    assert 'finishsigs' in repr(exc.args)


More information about the pypy-commit mailing list