Commit a3ffd72f authored by Stefan Behnel's avatar Stefan Behnel

Attempt to use a faster exception value for return type annotations that check for exceptions.

Inside of a module, it is safe to convert a function declared
    cdef int func() except *:
        ...

into

    cdef int func() except? -1

because a) we need an exception return value anyway and b) there will always be an explicit exception check afterwards. Thus, changing the function implementation itself is safe. We must exclude functions that are only declared but not implemented since we do not control their signature and it is not safe to assume a specific exception return value if it is not declared.
parent 69e08695
...@@ -514,7 +514,7 @@ class CNameDeclaratorNode(CDeclaratorNode): ...@@ -514,7 +514,7 @@ class CNameDeclaratorNode(CDeclaratorNode):
default = None default = None
def analyse(self, base_type, env, nonempty=0): def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if nonempty and self.name == '': if nonempty and self.name == '':
# May have mistaken the name for the type. # May have mistaken the name for the type.
if base_type.is_ptr or base_type.is_array or base_type.is_buffer: if base_type.is_ptr or base_type.is_array or base_type.is_buffer:
...@@ -540,11 +540,11 @@ class CPtrDeclaratorNode(CDeclaratorNode): ...@@ -540,11 +540,11 @@ class CPtrDeclaratorNode(CDeclaratorNode):
def analyse_templates(self): def analyse_templates(self):
return self.base.analyse_templates() return self.base.analyse_templates()
def analyse(self, base_type, env, nonempty=0): def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if base_type.is_pyobject: if base_type.is_pyobject:
error(self.pos, "Pointer base type cannot be a Python object") error(self.pos, "Pointer base type cannot be a Python object")
ptr_type = PyrexTypes.c_ptr_type(base_type) ptr_type = PyrexTypes.c_ptr_type(base_type)
return self.base.analyse(ptr_type, env, nonempty=nonempty) return self.base.analyse(ptr_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CReferenceDeclaratorNode(CDeclaratorNode): class CReferenceDeclaratorNode(CDeclaratorNode):
...@@ -555,11 +555,11 @@ class CReferenceDeclaratorNode(CDeclaratorNode): ...@@ -555,11 +555,11 @@ class CReferenceDeclaratorNode(CDeclaratorNode):
def analyse_templates(self): def analyse_templates(self):
return self.base.analyse_templates() return self.base.analyse_templates()
def analyse(self, base_type, env, nonempty=0): def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if base_type.is_pyobject: if base_type.is_pyobject:
error(self.pos, "Reference base type cannot be a Python object") error(self.pos, "Reference base type cannot be a Python object")
ref_type = PyrexTypes.c_ref_type(base_type) ref_type = PyrexTypes.c_ref_type(base_type)
return self.base.analyse(ref_type, env, nonempty=nonempty) return self.base.analyse(ref_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CArrayDeclaratorNode(CDeclaratorNode): class CArrayDeclaratorNode(CDeclaratorNode):
...@@ -568,7 +568,7 @@ class CArrayDeclaratorNode(CDeclaratorNode): ...@@ -568,7 +568,7 @@ class CArrayDeclaratorNode(CDeclaratorNode):
child_attrs = ["base", "dimension"] child_attrs = ["base", "dimension"]
def analyse(self, base_type, env, nonempty=0): def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if (base_type.is_cpp_class and base_type.is_template_type()) or base_type.is_cfunction: if (base_type.is_cpp_class and base_type.is_template_type()) or base_type.is_cfunction:
from .ExprNodes import TupleNode from .ExprNodes import TupleNode
if isinstance(self.dimension, TupleNode): if isinstance(self.dimension, TupleNode):
...@@ -582,7 +582,7 @@ class CArrayDeclaratorNode(CDeclaratorNode): ...@@ -582,7 +582,7 @@ class CArrayDeclaratorNode(CDeclaratorNode):
base_type = error_type base_type = error_type
else: else:
base_type = base_type.specialize_here(self.pos, values) base_type = base_type.specialize_here(self.pos, values)
return self.base.analyse(base_type, env, nonempty=nonempty) return self.base.analyse(base_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
if self.dimension: if self.dimension:
self.dimension = self.dimension.analyse_const_expression(env) self.dimension = self.dimension.analyse_const_expression(env)
if not self.dimension.type.is_int: if not self.dimension.type.is_int:
...@@ -603,7 +603,7 @@ class CArrayDeclaratorNode(CDeclaratorNode): ...@@ -603,7 +603,7 @@ class CArrayDeclaratorNode(CDeclaratorNode):
if base_type.is_cfunction: if base_type.is_cfunction:
error(self.pos, "Array element cannot be a function") error(self.pos, "Array element cannot be a function")
array_type = PyrexTypes.c_array_type(base_type, size) array_type = PyrexTypes.c_array_type(base_type, size)
return self.base.analyse(array_type, env, nonempty=nonempty) return self.base.analyse(array_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CFuncDeclaratorNode(CDeclaratorNode): class CFuncDeclaratorNode(CDeclaratorNode):
...@@ -646,7 +646,7 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -646,7 +646,7 @@ class CFuncDeclaratorNode(CDeclaratorNode):
else: else:
return None return None
def analyse(self, return_type, env, nonempty=0, directive_locals=None): def analyse(self, return_type, env, nonempty=0, directive_locals=None, visibility=None, in_pxd=False):
if directive_locals is None: if directive_locals is None:
directive_locals = {} directive_locals = {}
if nonempty: if nonempty:
...@@ -698,6 +698,16 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -698,6 +698,16 @@ class CFuncDeclaratorNode(CDeclaratorNode):
and self.exception_check != '+'): and self.exception_check != '+'):
error(self.pos, "Exception clause not allowed for function returning Python object") error(self.pos, "Exception clause not allowed for function returning Python object")
else: else:
if self.exception_value is None and self.exception_check and self.exception_check != '+':
# Use an explicit exception return value to speed up exception checks.
# Even if it is not declared, we can use the default exception value of the return type,
# unless the function is some kind of external function that we do not control.
if return_type.exception_value is not None and (visibility != 'extern' and not in_pxd):
# Extension types are more difficult because the signature must match the base type signature.
if not env.is_c_class_scope:
from .ExprNodes import ConstNode
self.exception_value = ConstNode(
self.pos, value=return_type.exception_value, type=return_type)
if self.exception_value: if self.exception_value:
self.exception_value = self.exception_value.analyse_const_expression(env) self.exception_value = self.exception_value.analyse_const_expression(env)
if self.exception_check == '+': if self.exception_check == '+':
...@@ -752,7 +762,7 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -752,7 +762,7 @@ class CFuncDeclaratorNode(CDeclaratorNode):
error(self.pos, "cannot have both '%s' and '%s' " error(self.pos, "cannot have both '%s' and '%s' "
"calling conventions" % (current, callspec)) "calling conventions" % (current, callspec))
func_type.calling_convention = callspec func_type.calling_convention = callspec
return self.base.analyse(func_type, env) return self.base.analyse(func_type, env, visibility=visibility, in_pxd=in_pxd)
def declare_optional_arg_struct(self, func_type, env, fused_cname=None): def declare_optional_arg_struct(self, func_type, env, fused_cname=None):
""" """
...@@ -792,12 +802,12 @@ class CConstDeclaratorNode(CDeclaratorNode): ...@@ -792,12 +802,12 @@ class CConstDeclaratorNode(CDeclaratorNode):
child_attrs = ["base"] child_attrs = ["base"]
def analyse(self, base_type, env, nonempty=0): def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if base_type.is_pyobject: if base_type.is_pyobject:
error(self.pos, error(self.pos,
"Const base type cannot be a Python object") "Const base type cannot be a Python object")
const = PyrexTypes.c_const_type(base_type) const = PyrexTypes.c_const_type(base_type)
return self.base.analyse(const, env, nonempty=nonempty) return self.base.analyse(const, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CArgDeclNode(Node): class CArgDeclNode(Node):
...@@ -1314,9 +1324,11 @@ class CVarDefNode(StatNode): ...@@ -1314,9 +1324,11 @@ class CVarDefNode(StatNode):
if create_extern_wrapper: if create_extern_wrapper:
declarator.overridable = False declarator.overridable = False
if isinstance(declarator, CFuncDeclaratorNode): if isinstance(declarator, CFuncDeclaratorNode):
name_declarator, type = declarator.analyse(base_type, env, directive_locals=self.directive_locals) name_declarator, type = declarator.analyse(
base_type, env, directive_locals=self.directive_locals, visibility=visibility, in_pxd=self.in_pxd)
else: else:
name_declarator, type = declarator.analyse(base_type, env) name_declarator, type = declarator.analyse(
base_type, env, visibility=visibility, in_pxd=self.in_pxd)
if not type.is_complete(): if not type.is_complete():
if not (self.visibility == 'extern' and type.is_array or type.is_memoryviewslice): if not (self.visibility == 'extern' and type.is_array or type.is_memoryviewslice):
error(declarator.pos, "Variable type '%s' is incomplete" % type) error(declarator.pos, "Variable type '%s' is incomplete" % type)
...@@ -1568,7 +1580,8 @@ class CTypeDefNode(StatNode): ...@@ -1568,7 +1580,8 @@ class CTypeDefNode(StatNode):
def analyse_declarations(self, env): def analyse_declarations(self, env):
base = self.base_type.analyse(env) base = self.base_type.analyse(env)
name_declarator, type = self.declarator.analyse(base, env) name_declarator, type = self.declarator.analyse(
base, env, visibility=self.visibility, in_pxd=self.in_pxd)
name = name_declarator.name name = name_declarator.name
cname = name_declarator.cname cname = name_declarator.cname
...@@ -2281,10 +2294,10 @@ class CFuncDefNode(FuncDefNode): ...@@ -2281,10 +2294,10 @@ class CFuncDefNode(FuncDefNode):
if isinstance(self.declarator, CFuncDeclaratorNode): if isinstance(self.declarator, CFuncDeclaratorNode):
name_declarator, type = self.declarator.analyse( name_declarator, type = self.declarator.analyse(
base_type, env, nonempty=2 * (self.body is not None), base_type, env, nonempty=2 * (self.body is not None),
directive_locals=self.directive_locals) directive_locals=self.directive_locals, visibility=self.visibility)
else: else:
name_declarator, type = self.declarator.analyse( name_declarator, type = self.declarator.analyse(
base_type, env, nonempty=2 * (self.body is not None)) base_type, env, nonempty=2 * (self.body is not None), visibility=self.visibility)
if not type.is_cfunction: if not type.is_cfunction:
error(self.pos, "Suite attached to non-function declaration") error(self.pos, "Suite attached to non-function declaration")
# Remember the actual type according to the function header # Remember the actual type according to the function header
......
...@@ -143,7 +143,7 @@ _directive_defaults = { ...@@ -143,7 +143,7 @@ _directive_defaults = {
'initializedcheck' : True, 'initializedcheck' : True,
'embedsignature' : False, 'embedsignature' : False,
'locals' : {}, 'locals' : {},
'exceptval' : (None, False), # (except value, check=False) 'exceptval' : None, # (except value=None, check=True)
'auto_cpdef': False, 'auto_cpdef': False,
'auto_pickle': None, 'auto_pickle': None,
'cdivision': False, # was True before 0.12 'cdivision': False, # was True before 0.12
......
...@@ -905,8 +905,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -905,8 +905,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
if optname == 'np_pythran' and not self.context.cpp: if optname == 'np_pythran' and not self.context.cpp:
raise PostParseError(pos, 'The %s directive can only be used in C++ mode.' % optname) raise PostParseError(pos, 'The %s directive can only be used in C++ mode.' % optname)
elif optname == 'exceptval': elif optname == 'exceptval':
# default: exceptval(None, check=True)
arg_error = len(args) > 1 arg_error = len(args) > 1
check = False check = True
if kwds and kwds.key_value_pairs: if kwds and kwds.key_value_pairs:
kw = kwds.key_value_pairs[0] kw = kwds.key_value_pairs[0]
if (len(kwds.key_value_pairs) == 1 and if (len(kwds.key_value_pairs) == 1 and
...@@ -2344,6 +2345,12 @@ class AdjustDefByDirectives(CythonTransform, SkipDeclarations): ...@@ -2344,6 +2345,12 @@ class AdjustDefByDirectives(CythonTransform, SkipDeclarations):
return_type_node = self.directives.get('returns') return_type_node = self.directives.get('returns')
if return_type_node is None and self.directives['annotation_typing']: if return_type_node is None and self.directives['annotation_typing']:
return_type_node = node.return_type_annotation return_type_node = node.return_type_annotation
# for Python anntations, prefer safe exception handling by default
if return_type_node is not None and except_val is None:
except_val = (None, True) # except *
elif except_val is None:
# backward compatible default: no exception check
except_val = (None, False)
if 'ccall' in self.directives: if 'ccall' in self.directives:
node = node.as_cfunction( node = node.as_cfunction(
overridable=True, modifiers=modifiers, overridable=True, modifiers=modifiers,
......
...@@ -113,7 +113,7 @@ returns = wraparound = boundscheck = initializedcheck = nonecheck = \ ...@@ -113,7 +113,7 @@ returns = wraparound = boundscheck = initializedcheck = nonecheck = \
unraisable_tracebacks = freelist = \ unraisable_tracebacks = freelist = \
lambda _: _EmptyDecoratorAndManager() lambda _: _EmptyDecoratorAndManager()
exceptval = lambda _=None, check=False: _EmptyDecoratorAndManager() exceptval = lambda _=None, check=True: _EmptyDecoratorAndManager()
optimization = _Optimization() optimization = _Optimization()
......
...@@ -146,6 +146,56 @@ def call_struct_io(s : MyStruct) -> MyStruct: ...@@ -146,6 +146,56 @@ def call_struct_io(s : MyStruct) -> MyStruct:
return struct_io(s) return struct_io(s)
@cython.test_assert_path_exists(
"//CFuncDefNode",
"//CFuncDefNode//DefNode",
"//CFuncDefNode[@return_type]",
"//CFuncDefNode[@return_type.is_struct_or_union = True]",
)
@cython.ccall
def struct_convert(d) -> MyStruct:
"""
>>> d = struct_convert(dict(x=1, y=2, data=3))
>>> sorted(d.items())
[('data', 3.0), ('x', 1), ('y', 2)]
>>> struct_convert({}) # make sure we can raise exceptions through struct return values
Traceback (most recent call last):
ValueError: No value specified for struct attribute 'x'
"""
return d
@cython.test_assert_path_exists(
"//CFuncDefNode",
"//CFuncDefNode//DefNode",
"//CFuncDefNode[@return_type]",
"//CFuncDefNode[@return_type.is_int = True]",
)
@cython.ccall
def exception_default(raise_exc : cython.bint = False) -> cython.int:
"""
>>> exception_default(raise_exc=False)
10
>>> exception_default(raise_exc=True)
Traceback (most recent call last):
ValueError: huhu!
"""
if raise_exc:
raise ValueError("huhu!")
return 10
def call_exception_default(raise_exc=False):
"""
>>> call_exception_default(raise_exc=False)
10
>>> call_exception_default(raise_exc=True)
Traceback (most recent call last):
ValueError: huhu!
"""
return exception_default(raise_exc)
_WARNINGS = """ _WARNINGS = """
8:32: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly. 8:32: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
8:47: Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly. 8:47: Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.
...@@ -156,4 +206,6 @@ _WARNINGS = """ ...@@ -156,4 +206,6 @@ _WARNINGS = """
# BUG: # BUG:
46:6: 'pytypes_cpdef' redeclared 46:6: 'pytypes_cpdef' redeclared
121:0: 'struct_io' redeclared 121:0: 'struct_io' redeclared
156:0: 'struct_convert' redeclared
175:0: 'exception_default' redeclared
""" """
...@@ -13,12 +13,12 @@ setup( ...@@ -13,12 +13,12 @@ setup(
######## foo.pxd ######## ######## foo.pxd ########
cdef void bar() cdef void bar() except *
######## foo.pyx ######## ######## foo.pyx ########
cdef extern from "bar_impl.c": cdef extern from "bar_impl.c":
void bar() void bar() except *
######## bar_impl.c ######## ######## bar_impl.c ########
......
PYTHON setup.py build_ext --inplace
PYTHON -c "import foo"
PYTHON -c "import a"
######## setup.py ########
from Cython.Build import cythonize
from distutils.core import setup
setup(
ext_modules = cythonize("*.pyx"),
)
######## foo.pxd ########
cdef int bar() except *
######## foo.pyx ########
cdef extern from "bar_impl.c":
int bar() except *
######## bar_impl.c ########
static int bar() { return -1; }
######## a.pyx ########
cimport cython
from foo cimport bar
assert bar() == -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