[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