[pypy-commit] pypy default: merge the better-enforceargs branch, which add actual typechecking when not we_are_translated()
antocuni
noreply at buildbot.pypy.org
Wed Jul 18 10:54:43 CEST 2012
Author: Antonio Cuni <anto.cuni at gmail.com>
Branch:
Changeset: r56120:789259d7be1a
Date: 2012-07-18 10:49 +0200
http://bitbucket.org/pypy/pypy/changeset/789259d7be1a/
Log: merge the better-enforceargs branch, which add actual typechecking
when not we_are_translated()
diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py
--- a/pypy/rlib/objectmodel.py
+++ b/pypy/rlib/objectmodel.py
@@ -3,9 +3,11 @@
RPython-compliant way.
"""
+import py
import sys
import types
import math
+import inspect
# specialize is a decorator factory for attaching _annspecialcase_
# attributes to functions: for example
@@ -106,15 +108,66 @@
specialize = _Specialize()
-def enforceargs(*args):
+def enforceargs(*types, **kwds):
""" Decorate a function with forcing of RPython-level types on arguments.
None means no enforcing.
XXX shouldn't we also add asserts in function body?
"""
+ typecheck = kwds.pop('typecheck', True)
+ if kwds:
+ raise TypeError, 'got an unexpected keyword argument: %s' % kwds.keys()
+ if not typecheck:
+ def decorator(f):
+ f._annenforceargs_ = types
+ return f
+ return decorator
+ #
+ from pypy.annotation.signature import annotationoftype
+ from pypy.annotation.model import SomeObject
def decorator(f):
- f._annenforceargs_ = args
- return f
+ def get_annotation(t):
+ if isinstance(t, SomeObject):
+ return t
+ return annotationoftype(t)
+ def typecheck(*args):
+ for i, (expected_type, arg) in enumerate(zip(types, args)):
+ if expected_type is None:
+ continue
+ s_expected = get_annotation(expected_type)
+ s_argtype = get_annotation(type(arg))
+ if not s_expected.contains(s_argtype):
+ msg = "%s argument number %d must be of type %s" % (
+ f.func_name, i+1, expected_type)
+ raise TypeError, msg
+ #
+ # we cannot simply wrap the function using *args, **kwds, because it's
+ # not RPython. Instead, we generate a function with exactly the same
+ # argument list
+ argspec = inspect.getargspec(f)
+ assert len(argspec.args) == len(types), (
+ 'not enough types provided: expected %d, got %d' %
+ (len(types), len(argspec.args)))
+ assert not argspec.varargs, '*args not supported by enforceargs'
+ assert not argspec.keywords, '**kwargs not supported by enforceargs'
+ #
+ arglist = ', '.join(argspec.args)
+ src = py.code.Source("""
+ def {name}({arglist}):
+ if not we_are_translated():
+ typecheck({arglist})
+ return {name}_original({arglist})
+ """.format(name=f.func_name, arglist=arglist))
+ #
+ mydict = {f.func_name + '_original': f,
+ 'typecheck': typecheck,
+ 'we_are_translated': we_are_translated}
+ exec src.compile() in mydict
+ result = mydict[f.func_name]
+ result.func_defaults = f.func_defaults
+ result.func_dict.update(f.func_dict)
+ result._annenforceargs_ = types
+ return result
return decorator
# ____________________________________________________________
diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py
--- a/pypy/rlib/rgc.py
+++ b/pypy/rlib/rgc.py
@@ -138,8 +138,8 @@
return hop.genop(opname, vlist, resulttype = hop.r_result.lowleveltype)
@jit.oopspec('list.ll_arraycopy(source, dest, source_start, dest_start, length)')
+ at enforceargs(None, None, int, int, int)
@specialize.ll()
- at enforceargs(None, None, int, int, int)
def ll_arraycopy(source, dest, source_start, dest_start, length):
from pypy.rpython.lltypesystem.lloperation import llop
from pypy.rlib.objectmodel import keepalive_until_here
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
@@ -420,9 +420,44 @@
def test_enforceargs_decorator():
@enforceargs(int, str, None)
def f(a, b, c):
- pass
+ return a, b, c
+ f.foo = 'foo'
+ assert f._annenforceargs_ == (int, str, None)
+ assert f.func_name == 'f'
+ assert f.foo == 'foo'
+ assert f(1, 'hello', 42) == (1, 'hello', 42)
+ exc = py.test.raises(TypeError, "f(1, 2, 3)")
+ assert exc.value.message == "f argument number 2 must be of type <type 'str'>"
+ py.test.raises(TypeError, "f('hello', 'world', 3)")
+def test_enforceargs_defaults():
+ @enforceargs(int, int)
+ def f(a, b=40):
+ return a+b
+ assert f(2) == 42
+
+def test_enforceargs_int_float_promotion():
+ @enforceargs(float)
+ def f(x):
+ return x
+ # in RPython there is an implicit int->float promotion
+ assert f(42) == 42
+
+def test_enforceargs_no_typecheck():
+ @enforceargs(int, str, None, typecheck=False)
+ def f(a, b, c):
+ return a, b, c
assert f._annenforceargs_ == (int, str, None)
+ assert f(1, 2, 3) == (1, 2, 3) # no typecheck
+
+def test_enforceargs_translates():
+ from pypy.rpython.lltypesystem import lltype
+ @enforceargs(int, float)
+ def f(a, b):
+ return a, b
+ graph = getgraph(f, [int, int])
+ 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
More information about the pypy-commit
mailing list