Author: Maciej Fijalkowski <fij...@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) _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit