Commit 1c3f95c9 authored by Xavier Thompson's avatar Xavier Thompson

Add a new exception-signaling mode using enriched return values

parent 9844c461
...@@ -5933,7 +5933,7 @@ class SimpleCallNode(CallNode): ...@@ -5933,7 +5933,7 @@ class SimpleCallNode(CallNode):
else: else:
self.args = [ arg.analyse_types(env) for arg in self.args ] self.args = [ arg.analyse_types(env) for arg in self.args ]
self.analyse_c_function_call(env) self.analyse_c_function_call(env)
if func_type.exception_check == '+' or self.type.is_cyp_class: if func_type.exception_check in ('+', '~') or self.type.is_cyp_class:
self.is_temp = True self.is_temp = True
return self return self
...@@ -6228,7 +6228,7 @@ class SimpleCallNode(CallNode): ...@@ -6228,7 +6228,7 @@ class SimpleCallNode(CallNode):
def is_c_result_required(self): def is_c_result_required(self):
func_type = self.function_type() func_type = self.function_type()
if not func_type.exception_value or func_type.exception_check == '+': if not func_type.exception_value or func_type.exception_check in ('+', '~'):
return False # skip allocation of unused result temp return False # skip allocation of unused result temp
return True return True
...@@ -6330,10 +6330,14 @@ class SimpleCallNode(CallNode): ...@@ -6330,10 +6330,14 @@ class SimpleCallNode(CallNode):
if exc_val is not None: if exc_val is not None:
exc_checks.append("%s == %s" % (self.result(), func_type.return_type.cast_code(exc_val))) exc_checks.append("%s == %s" % (self.result(), func_type.return_type.cast_code(exc_val)))
if exc_check: if exc_check:
if self.nogil: if exc_check == '~':
exc_checks.append("__Pyx_ErrOccurredWithGIL()") assert self.is_temp
exc_checks.append(self.type.error_condition(self.result()))
else: else:
exc_checks.append("PyErr_Occurred()") if self.nogil:
exc_checks.append("__Pyx_ErrOccurredWithGIL()")
else:
exc_checks.append("PyErr_Occurred()")
if self.is_temp or exc_checks: if self.is_temp or exc_checks:
rhs = self.c_call_code() rhs = self.c_call_code()
if self.result(): if self.result():
......
...@@ -701,12 +701,16 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -701,12 +701,16 @@ class CFuncDeclaratorNode(CDeclaratorNode):
env.add_include_file('new') # for std::bad_alloc env.add_include_file('new') # for std::bad_alloc
env.add_include_file('stdexcept') env.add_include_file('stdexcept')
env.add_include_file('typeinfo') # for std::bad_cast env.add_include_file('typeinfo') # for std::bad_cast
elif self.exception_check == '~':
self.cpp_check(env)
env.use_utility_code(UtilityCode.load_cached("CheckedResult", "CppSupport.cpp"))
return_type = PyrexTypes.CheckedResultType(self.pos, return_type)
if (return_type.is_pyobject if (return_type.is_pyobject
and (self.exception_value or self.exception_check) and (self.exception_value or self.exception_check)
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 != '+': if self.exception_value is None and self.exception_check and self.exception_check not in ('+', '~'):
# Use an explicit exception return value to speed up exception checks. # 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, # 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. # unless the function is some kind of external function that we do not control.
...@@ -2244,14 +2248,17 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -2244,14 +2248,17 @@ class FuncDefNode(StatNode, BlockNode):
self.entry.qualified_name, 0) self.entry.qualified_name, 0)
assure_gil('error') assure_gil('error')
code.put_unraisable(self.entry.qualified_name) code.put_unraisable(self.entry.qualified_name)
default_retval = return_type.default_value if return_type.is_checked_result:
if err_val is None and default_retval: code.putln(return_type.set_error(Naming.retval_cname))
err_val = default_retval else:
if err_val is not None: default_retval = return_type.default_value
if err_val != Naming.retval_cname: if err_val is None and default_retval:
code.putln("%s = %s;" % (Naming.retval_cname, err_val)) err_val = default_retval
elif not return_type.is_void: if err_val is not None:
code.putln("__Pyx_pretend_to_initialize(&%s);" % Naming.retval_cname) if err_val != Naming.retval_cname:
code.putln("%s = %s;" % (Naming.retval_cname, err_val))
elif not return_type.is_void:
code.putln("__Pyx_pretend_to_initialize(&%s);" % Naming.retval_cname)
if is_getbuffer_slot: if is_getbuffer_slot:
assure_gil('error') assure_gil('error')
......
...@@ -2986,6 +2986,9 @@ def p_exception_value_clause(s): ...@@ -2986,6 +2986,9 @@ def p_exception_value_clause(s):
elif s.sy == '*': elif s.sy == '*':
exc_val = ExprNodes.CharNode(s.position(), value=u'*') exc_val = ExprNodes.CharNode(s.position(), value=u'*')
s.next() s.next()
elif s.sy == '~':
exc_check = '~'
s.next()
else: else:
if s.sy == '?': if s.sy == '?':
exc_check = 1 exc_check = 1
......
...@@ -264,6 +264,7 @@ class PyrexType(BaseType): ...@@ -264,6 +264,7 @@ class PyrexType(BaseType):
is_memoryviewslice = 0 is_memoryviewslice = 0
is_pythran_expr = 0 is_pythran_expr = 0
is_numpy_buffer = 0 is_numpy_buffer = 0
is_checked_result = 0
has_attributes = 0 has_attributes = 0
needs_refcounting = 0 needs_refcounting = 0
default_value = "" default_value = ""
...@@ -595,6 +596,130 @@ class CTypedefType(BaseType): ...@@ -595,6 +596,130 @@ class CTypedefType(BaseType):
return self.typedef_base_type.can_coerce_from_pyobject(env) return self.typedef_base_type.can_coerce_from_pyobject(env)
class CheckedResultType(BaseType):
# Pseudo-type to wrap the return value of c functions in a "CheckedResult"
is_checked_result = 1
is_void = 0
to_py_utility_code = None
from_py_utility_code = None
subtypes = ['checked_base_type']
def __init__(self, pos, base_type):
if base_type.is_reference:
error(pos, "Cannot use 'except ~' exception signaling with functions that return by reference")
elif base_type.is_cpp_class:
base_type.check_nullary_constructor(pos)
self.checked_base_type = base_type
def invalid_value(self):
return self.checked_base_type.invalid_value()
def resolve(self):
return self.checked_base_type.resolve()
def declaration_code(self, entity_code,
for_display = 0, dll_linkage = None, pyrex = 0):
checked_code = "CheckedResult<%s>" % self.checked_base_type.declaration_code('')
if pyrex or for_display:
base_code = checked_code
else:
base_code = public_decl(checked_code, dll_linkage)
return self.base_declaration_code(base_code, entity_code)
def as_argument_type(self):
return self.checked_base_type.as_argument_type()
def cast_code(self, expr_code):
return self.checked_base_type.cast_code(expr_code)
def specialize(self, values):
return self.checked_base_type.specialize(values)
def __repr__(self):
return "<CheckedResult[%s]>" % repr(self.checked_base_type)
def __str__(self):
return str(self.checked_base_type)
def create_to_py_utility_code(self, env):
return self.checked_base_type.create_to_py_utility_code(env)
def create_from_py_utility_code(self, env):
return self.checked_base_type.create_from_py_utility_code(env)
def to_py_call_code(self, source_code, result_code, result_type, to_py_function=None):
return self.checked_base_type.to_py_call_code(
source_code, result_code, result_type, to_py_function)
def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None):
return self.checked_base_type.from_py_call_code(
source_code, result_code, error_pos, code,
from_py_function or self.from_py_function,
error_condition or self.error_condition(result_code)
)
def overflow_check_binop(self, binop, env, const_rhs=False):
return self.checked_base_type.overflow_check_binop(binop, env, const_rhs)
def error_condition(self, result_code):
return "%s.is_error()" % result_code
def set_error(self, result_code):
return "%s.set_error();" % result_code
def __getattr__(self, name):
return getattr(self.checked_base_type, name)
def py_type_name(self):
return self.checked_base_type.py_type_name()
def can_coerce_to_pyobject(self, env):
return self.checked_base_type.can_coerce_to_pyobject(env)
def can_coerce_from_pyobject(self, env):
return self.checked_base_type.can_coerce_from_pyobject(env)
def generate_incref(self, code, cname, **kwds):
self.checked_base_type.generate_incref(code, "%s.result" % cname, **kwds)
def generate_xincref(self, code, cname, **kwds):
self.checked_base_type.generate_xincref(code, "%s.result" % cname, **kwds)
def generate_decref(self, code, cname, **kwds):
self.checked_base_type.generate_decref(code, "%s.result" % cname, **kwds)
def generate_decref_clear(self, code, cname, **kwds):
self.checked_base_type.generate_decref_clear(code, "%s.result" % cname, **kwds)
def generate_xdecref(self, code, cname, **kwds):
self.checked_base_type.generate_xdecref(code, "%s.result" % cname, **kwds)
def generate_xdecref_clear(self, code, cname, **kwds):
self.checked_base_type.generate_xdecref_clear(code, "%s.result" % cname, **kwds)
def generate_gotref(self, code, cname):
self.checked_base_type.generate_gotref(code, "%s.result" % cname)
def generate_xgotref(self, code, cname):
self.checked_base_type.generate_xgotref(code, "%s.result" % cname)
def generate_giveref(self, code, cname):
self.checked_base_type.generate_giveref(code, "%s.result" % cname)
def generate_xgiveref(self, code, cname):
self.checked_base_type.generate_xgiveref(code, "%s.result" % cname)
def generate_decref_set(self, code, cname, rhs_cname):
self.checked_base_type.generate_decref_set(code, "%s.result" % cname, rhs_cname)
def generate_xdecref_set(self, code, cname, rhs_cname):
self.checked_base_type.generate_xdecref_set(code, "%s.result" % cname, rhs_cname)
class MemoryViewSliceType(PyrexType): class MemoryViewSliceType(PyrexType):
is_memoryviewslice = 1 is_memoryviewslice = 1
......
...@@ -532,6 +532,8 @@ def find_spanning_type(type1, type2): ...@@ -532,6 +532,8 @@ def find_spanning_type(type1, type2):
return result_type return result_type
def simply_type(result_type, pos): def simply_type(result_type, pos):
if result_type.is_checked_result:
result_type = result_type.checked_base_type
if result_type.is_reference: if result_type.is_reference:
result_type = result_type.ref_base_type result_type = result_type.ref_base_type
if result_type.is_cv_qualified: if result_type.is_cv_qualified:
......
...@@ -67,3 +67,40 @@ auto __Pyx_pythran_to_python(T &&value) -> decltype(to_python( ...@@ -67,3 +67,40 @@ auto __Pyx_pythran_to_python(T &&value) -> decltype(to_python(
#else #else
#define __PYX_STD_MOVE_IF_SUPPORTED(x) x #define __PYX_STD_MOVE_IF_SUPPORTED(x) x
#endif #endif
/////////////// CheckedResult.proto ///////////////
#include <type_traits>
template <typename T, bool = std::is_void<T>::value>
struct CheckedResult {};
template <typename T>
class CheckedResult<T, false> {
enum Status { Ok, Err };
private:
enum Status status;
public:
T result;
CheckedResult(const T& value) : status(Ok), result(value) {}
CheckedResult() : status(Err) {}
operator T() { return result; }
void set_error() { status = Err; }
bool is_error() { return status == Err; }
};
template <typename T>
class CheckedResult<T, true> {
enum Status { Ok, Err };
public:
CheckedResult() : status(Ok) {}
void set_error() { status = Err; }
bool is_error() { return status == Err; }
private:
enum Status status;
};
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