Commit c372d2db authored by Stefan Behnel's avatar Stefan Behnel

Implement @cython.exceptval() decorator to make the "except x" signature...

Implement @cython.exceptval() decorator to make the "except x" signature declaration available in pure Python mode.
Closes #1653.
parent 4ed0f663
......@@ -27,6 +27,10 @@ Features added
types. This can be disabled with the directive ``annotation_typing=False``.
(Github issue #1850)
* New decorator ``@cython.exceptval(x=None, check=False)`` that makes the signature
declarations ``except x``, ``except? x`` and ``except *`` available to pure Python
code. Original patch by Antonio Cuni. (Github issue #1653)
* Signature annotations are now included in the signature docstring generated by
the ``embedsignature`` directive. Patch by Lisandro Dalcin (Github issue #1781).
......
......@@ -2710,11 +2710,13 @@ class DefNode(FuncDefNode):
self.num_required_kw_args = rk
self.num_required_args = r
def as_cfunction(self, cfunc=None, scope=None, overridable=True, returns=None, modifiers=None):
def as_cfunction(self, cfunc=None, scope=None, overridable=True, returns=None, except_val=None, modifiers=None):
if self.star_arg:
error(self.star_arg.pos, "cdef function cannot have star argument")
if self.starstar_arg:
error(self.starstar_arg.pos, "cdef function cannot have starstar argument")
exception_value, exception_check = except_val or (None, False)
if cfunc is None:
cfunc_args = []
for formal_arg in self.args:
......@@ -2727,7 +2729,7 @@ class DefNode(FuncDefNode):
args=cfunc_args,
has_varargs=False,
exception_value=None,
exception_check=False,
exception_check=exception_check,
nogil=False,
with_gil=False,
is_overridable=overridable)
......@@ -2745,11 +2747,10 @@ class DefNode(FuncDefNode):
if type is None or type is PyrexTypes.py_object_type:
formal_arg.type = type_arg.type
formal_arg.name_declarator = name_declarator
from . import ExprNodes
if cfunc_type.exception_value is None:
exception_value = None
else:
exception_value = ExprNodes.ConstNode(
if exception_value is None and cfunc_type.exception_value is not None:
from .ExprNodes import ConstNode
exception_value = ConstNode(
self.pos, value=cfunc_type.exception_value, type=cfunc_type.return_type)
declarator = CFuncDeclaratorNode(self.pos,
base=CNameDeclaratorNode(self.pos, name=self.name, cname=None),
......
......@@ -143,6 +143,7 @@ _directive_defaults = {
'initializedcheck' : True,
'embedsignature' : False,
'locals' : {},
'exceptval' : (None, False), # (except value, check=False)
'auto_cpdef': False,
'auto_pickle': None,
'cdivision': False, # was True before 0.12
......@@ -292,6 +293,9 @@ directive_scopes = { # defaults to available everywhere
'auto_pickle': ('module', 'cclass'),
'final' : ('cclass', 'function'),
'inline' : ('function',),
'returns' : ('function',),
'exceptval' : ('function',),
'locals' : ('function',),
'staticmethod' : ('function',), # FIXME: analysis currently lacks more specific function scope
'no_gc_clear' : ('cclass',),
'no_gc' : ('cclass',),
......
......@@ -902,9 +902,25 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
return None
def try_to_parse_directive(self, optname, args, kwds, pos):
directivetype = Options.directive_types.get(optname)
if optname == 'np_pythran' and not self.context.cpp:
raise PostParseError(pos, 'The %s directive can only be used in C++ mode.' % optname)
elif optname == 'exceptval':
arg_error = len(args) > 1
check = False
if kwds and kwds.key_value_pairs:
kw = kwds.key_value_pairs[0]
if (len(kwds.key_value_pairs) == 1 and
kw.key.is_string_literal and kw.key.value == 'check' and
isinstance(kw.value, ExprNodes.BoolNode)):
check = kw.value.value
else:
arg_error = True
if arg_error:
raise PostParseError(
pos, 'The exceptval directive takes 0 or 1 positional arguments and the boolean keyword "check"')
return ('exceptval', (args[0] if args else None, check))
directivetype = Options.directive_types.get(optname)
if len(args) == 1 and isinstance(args[0], ExprNodes.NoneNode):
return optname, Options.get_directive_defaults()[optname]
elif directivetype is bool:
......@@ -934,7 +950,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
'The %s directive takes no prepositional arguments' % optname)
return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs])
elif directivetype is list:
if kwds and len(kwds) != 0:
if kwds and len(kwds.key_value_pairs) != 0:
raise PostParseError(pos,
'The %s directive takes no keyword arguments' % optname)
return optname, [ str(arg.value) for arg in args ]
......@@ -2324,19 +2340,22 @@ class AdjustDefByDirectives(CythonTransform, SkipDeclarations):
modifiers = []
if 'inline' in self.directives:
modifiers.append('inline')
except_val = self.directives.get('exceptval')
return_type_node = self.directives.get('returns')
if return_type_node is None and self.directives['annotation_typing']:
return_type_node = node.return_type_annotation
if 'ccall' in self.directives:
node = node.as_cfunction(
overridable=True, returns=return_type_node, modifiers=modifiers)
overridable=True, modifiers=modifiers,
returns=return_type_node, except_val=except_val)
return self.visit(node)
if 'cfunc' in self.directives:
if self.in_py_class:
error(node.pos, "cfunc directive is not allowed here")
else:
node = node.as_cfunction(
overridable=False, returns=return_type_node, modifiers=modifiers)
overridable=False, modifiers=modifiers,
returns=return_type_node, except_val=except_val)
return self.visit(node)
if 'inline' in modifiers:
error(node.pos, "Python functions cannot be declared 'inline'")
......
......@@ -111,7 +111,9 @@ returns = wraparound = boundscheck = initializedcheck = nonecheck = \
overflowcheck = embedsignature = cdivision = cdivision_warnings = \
always_allows_keywords = profile = linetrace = infer_type = \
unraisable_tracebacks = freelist = \
lambda arg: _EmptyDecoratorAndManager()
lambda _: _EmptyDecoratorAndManager()
exceptval = lambda _=None, check=False: _EmptyDecoratorAndManager()
optimization = _Optimization()
......
......@@ -195,6 +195,14 @@ Static typing
* ``@cython.returns(<type>)`` specifies the function's return type.
* ``@cython.exceptval(value=None, *, check=False)`` specifies the function's exception
return value and exception check semantics as follows::
@exceptval(-1) # cdef int func() except -1:
@exceptval(-1, check=False) # cdef int func() except -1:
@exceptval(check=True) # cdef int func() except *:
@exceptval(-1, check=True) # cdef int func() except? -1:
* Python annotations can be used to declare argument types, as shown in the
following example. To avoid conflicts with other kinds of annotation
usages, this can be disabled with the directive ``annotation_typing=False``.
......@@ -204,6 +212,15 @@ Static typing
def func(a_pydict: dict, a_cint: cython.int) -> tuple:
...
This can be combined with the ``@cython.exceptval()`` decorator for non-Python
return types::
@cython.exceptval(-1):
def func(x : cython.int) -> cython.int:
if x < 0:
raise ValueError("need integer >= 0")
return x+1
Since version 0.27, Cython also supports the variable annotations defined
in `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_. This allows to
declare types of variables in a Python 3.6 compatible way as follows::
......
......@@ -266,3 +266,78 @@ def count_digits_in_carray(digits):
assert 0 <= digit <= 9
counts[digit] += 1
return counts
@cython.test_assert_path_exists("//CFuncDeclaratorNode//IntNode[@value = '-1']")
@cython.ccall
@cython.returns(cython.long)
@cython.exceptval(-1)
def ccall_except(x):
"""
>>> ccall_except(41)
42
>>> ccall_except(0)
Traceback (most recent call last):
ValueError
"""
if x == 0:
raise ValueError
return x+1
@cython.test_assert_path_exists("//CFuncDeclaratorNode//IntNode[@value = '-1']")
@cython.cfunc
@cython.returns(cython.long)
@cython.exceptval(-1)
def cdef_except(x):
"""
>>> call_cdef_except(41)
42
>>> call_cdef_except(0)
Traceback (most recent call last):
ValueError
"""
if x == 0:
raise ValueError
return x+1
def call_cdef_except(x):
return cdef_except(x)
@cython.test_assert_path_exists("//CFuncDeclaratorNode//IntNode[@value = '-1']")
@cython.ccall
@cython.returns(cython.long)
@cython.exceptval(-1, check=True)
def ccall_except_check(x):
"""
>>> ccall_except_check(41)
42
>>> ccall_except_check(-2)
-1
>>> ccall_except_check(0)
Traceback (most recent call last):
ValueError
"""
if x == 0:
raise ValueError
return x+1
@cython.test_fail_if_path_exists("//CFuncDeclaratorNode//IntNode[@value = '-1']")
@cython.test_assert_path_exists("//CFuncDeclaratorNode")
@cython.ccall
@cython.returns(cython.long)
@cython.exceptval(check=True)
def ccall_except_check_always(x):
"""
>>> ccall_except_check_always(41)
42
>>> ccall_except_check_always(0)
Traceback (most recent call last):
ValueError
"""
if x == 0:
raise ValueError
return x+1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment