summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Behnel <stefan_ml@behnel.de>2018-09-23 08:56:16 +0200
committerStefan Behnel <stefan_ml@behnel.de>2018-09-23 08:56:16 +0200
commit36f9d0b8d01b694d14b4516c880dab786728567d (patch)
tree435b352d74c87922f9c6334e7e63422d4c63904b
parent107fc454595180121f4a42eecc28a48b617d445f (diff)
parent525b8e2ec134cc478d6ab6cafe10a1f3a60df7e5 (diff)
downloadcython-36f9d0b8d01b694d14b4516c880dab786728567d.tar.gz
Merge branch 'master' into release
-rw-r--r--CHANGES.rst8
-rw-r--r--Cython/Compiler/ExprNodes.py40
-rw-r--r--Cython/Compiler/Nodes.py6
-rw-r--r--Cython/Compiler/Parsing.py3
-rw-r--r--docs/src/userguide/wrapping_CPlusPlus.rst17
-rw-r--r--tests/run/cpp_exceptions.pyx14
-rw-r--r--tests/run/cpp_exceptions_helper.h8
7 files changed, 80 insertions, 16 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 58d3eba5a..6e25b7712 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -21,11 +21,15 @@ Features added
* Memoryviews are supported in PEP-484/526 style type declarations.
(Github issue #2529)
+* ``@cython.nogil`` is supported as a C-function decorator in Python code.
+ (Github issue #2557)
+
* Raising exceptions from nogil code will automatically acquire the GIL, instead
of requiring an explicit ``with gil`` block.
-* ``@cython.nogil`` is supported as a C-function decorator in Python code.
- (Github issue #2557)
+* C++ functions can now be declared as potentially raising both C++ and Python
+ exceptions, so that Cython can handle both correctly.
+ (Github issue #2615)
* ``cython.inline()`` supports a direct ``language_level`` keyword argument that
was previously only available via a directive.
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index b3e5b31e1..9c8cab8a3 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -187,22 +187,44 @@ def infer_sequence_item_type(env, seq_node, index_node=None, seq_type=None):
return item_types.pop()
return None
+# Returns a block of code to translate the exception,
+# plus a boolean indicating whether to check for Python exceptions.
def get_exception_handler(exception_value):
if exception_value is None:
- return "__Pyx_CppExn2PyErr();"
+ return "__Pyx_CppExn2PyErr();", False
+ elif (exception_value.type == PyrexTypes.c_char_type
+ and exception_value.value == '*'):
+ return "__Pyx_CppExn2PyErr();", True
elif exception_value.type.is_pyobject:
- return 'try { throw; } catch(const std::exception& exn) { PyErr_SetString(%s, exn.what()); } catch(...) { PyErr_SetNone(%s); }' % (
- exception_value.entry.cname,
- exception_value.entry.cname)
+ return (
+ 'try { throw; } catch(const std::exception& exn) {'
+ 'PyErr_SetString(%s, exn.what());'
+ '} catch(...) { PyErr_SetNone(%s); }' % (
+ exception_value.entry.cname,
+ exception_value.entry.cname),
+ False)
else:
- return '%s(); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError , "Error converting c++ exception.");' % exception_value.entry.cname
+ return (
+ '%s(); if (!PyErr_Occurred())'
+ 'PyErr_SetString(PyExc_RuntimeError, '
+ '"Error converting c++ exception.");' % (
+ exception_value.entry.cname),
+ False)
+
+def maybe_check_py_error(code, check_py_exception, pos, nogil):
+ if check_py_exception:
+ if nogil:
+ code.putln(code.error_goto_if("__Pyx_ErrOccurredWithGIL()", pos))
+ else:
+ code.putln(code.error_goto_if("PyErr_Occurred()", pos))
def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil):
- raise_py_exception = get_exception_handler(exception_value)
+ raise_py_exception, check_py_exception = get_exception_handler(exception_value)
code.putln("try {")
code.putln("%s" % inside)
if py_result:
code.putln(code.error_goto_if_null(py_result, pos))
+ maybe_check_py_error(code, check_py_exception, pos, nogil)
code.putln("} catch(...) {")
if nogil:
code.put_ensure_gil(declare_gilstate=True)
@@ -216,12 +238,14 @@ def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil
# both have an exception declaration.
def translate_double_cpp_exception(code, pos, lhs_type, lhs_code, rhs_code,
lhs_exc_val, assign_exc_val, nogil):
- handle_lhs_exc = get_exception_handler(lhs_exc_val)
- handle_assignment_exc = get_exception_handler(assign_exc_val)
+ handle_lhs_exc, lhc_check_py_exc = get_exception_handler(lhs_exc_val)
+ handle_assignment_exc, assignment_check_py_exc = get_exception_handler(assign_exc_val)
code.putln("try {")
code.putln(lhs_type.declaration_code("__pyx_local_lvalue = %s;" % lhs_code))
+ maybe_check_py_error(code, lhc_check_py_exc, pos, nogil)
code.putln("try {")
code.putln("__pyx_local_lvalue = %s;" % rhs_code)
+ maybe_check_py_error(code, assignment_check_py_exc, pos, nogil)
# Catch any exception from the overloaded assignment.
code.putln("} catch(...) {")
if nogil:
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index bb1ec44e4..e06bfdc9b 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -741,9 +741,11 @@ class CFuncDeclaratorNode(CDeclaratorNode):
and not exc_val_type.is_pyobject
and not (exc_val_type.is_cfunction
and not exc_val_type.return_type.is_pyobject
- and not exc_val_type.args)):
+ and not exc_val_type.args)
+ and not (exc_val_type == PyrexTypes.c_char_type
+ and self.exception_value.value == '*')):
error(self.exception_value.pos,
- "Exception value must be a Python exception or cdef function with no arguments.")
+ "Exception value must be a Python exception or cdef function with no arguments or *.")
exc_val = self.exception_value
else:
self.exception_value = self.exception_value.coerce_to(
diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py
index dde1c3b8f..4200ee494 100644
--- a/Cython/Compiler/Parsing.py
+++ b/Cython/Compiler/Parsing.py
@@ -2937,6 +2937,9 @@ def p_exception_value_clause(s):
name = s.systring
s.next()
exc_val = p_name(s, name)
+ elif s.sy == '*':
+ exc_val = ExprNodes.CharNode(s.position(), value=u'*')
+ s.next()
else:
if s.sy == '?':
exc_check = 1
diff --git a/docs/src/userguide/wrapping_CPlusPlus.rst b/docs/src/userguide/wrapping_CPlusPlus.rst
index 94c5df5f1..e12bf38be 100644
--- a/docs/src/userguide/wrapping_CPlusPlus.rst
+++ b/docs/src/userguide/wrapping_CPlusPlus.rst
@@ -111,7 +111,12 @@ of the :func:`cythonize` function.
Declare a var with the wrapped C++ class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Now, we use cdef to declare a var of the class with the C++ ``new`` statement:
+We'll create a ``.pyx`` file named ``rect.pyx`` to build our wrapper. We're
+using a name other than ``Rectangle``, but if you prefer giving the same name
+to the wrapper as the C++ class, see the section on
+:ref:`resolving naming conflicts <resolve-conflicts>`.
+
+Within, we use cdef to declare a var of the class with the C++ ``new`` statement:
.. literalinclude:: ../../examples/userguide/wrapping_CPlusPlus/cython_usage.pyx
@@ -164,9 +169,6 @@ instance.
.. literalinclude:: ../../examples/userguide/wrapping_CPlusPlus/rect_ptr.pyx
-If you prefer giving the same name to the wrapper as the C++ class, see the
-section on :ref:`resolving naming conflicts <resolve-conflicts>`.
-
Compilation and Importing
=========================
@@ -440,6 +442,13 @@ called, which allows one to do custom C++ to Python error "translations." If
raise_py_error does not actually raise an exception a RuntimeError will be
raised.
+There is also the special form::
+
+ cdef int raise_py_or_cpp() except +*
+
+for those functions that may raise either a Python or a C++ exception.
+
+
Static member method
--------------------
diff --git a/tests/run/cpp_exceptions.pyx b/tests/run/cpp_exceptions.pyx
index 419eb7bd5..38f27ba54 100644
--- a/tests/run/cpp_exceptions.pyx
+++ b/tests/run/cpp_exceptions.pyx
@@ -22,6 +22,7 @@ cdef extern from "cpp_exceptions_helper.h":
cdef void raise_underflow() except +
cdef raise_or_throw(bint py) except +
+ cdef int raise_or_throw_int(bint py) except +*
cdef cppclass Foo:
int bar_raw "bar"(bint fire) except +
@@ -113,6 +114,19 @@ def test_func_that_can_raise_or_throw(bint py):
"""
raise_or_throw(py)
+def test_func_that_can_raise_or_throw_c_return(bint py):
+ """
+ >>> test_func_that_can_raise_or_throw_c_return(0)
+ Traceback (most recent call last):
+ ...
+ RuntimeError: oopsie
+ >>> test_func_that_can_raise_or_throw_c_return(1)
+ Traceback (most recent call last):
+ ...
+ ValueError: oopsie
+ """
+ raise_or_throw_int(py)
+
def test_int_raw(bint fire):
"""
>>> test_int_raw(False)
diff --git a/tests/run/cpp_exceptions_helper.h b/tests/run/cpp_exceptions_helper.h
index d1cbffb6f..a5147fe31 100644
--- a/tests/run/cpp_exceptions_helper.h
+++ b/tests/run/cpp_exceptions_helper.h
@@ -70,3 +70,11 @@ PyObject *raise_or_throw(int py) {
PyErr_SetString(PyExc_ValueError, "oopsie");
return NULL;
}
+
+int raise_or_throw_int(int py) {
+ if (!py) {
+ throw std::runtime_error("oopsie");
+ }
+ PyErr_SetString(PyExc_ValueError, "oopsie");
+ return -1;
+}