diff --git a/examples/arithmetic/Cargo.toml b/examples/arithmetic/Cargo.toml index d8df588363..7d43ca41c0 100644 --- a/examples/arithmetic/Cargo.toml +++ b/examples/arithmetic/Cargo.toml @@ -12,6 +12,7 @@ name = "uniffi_arithmetic" [dependencies] uniffi_macros = {path = "../../uniffi_macros"} uniffi = {path = "../../uniffi", features=["builtin-bindgen"]} +thiserror = "1.0" [build-dependencies] uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]} diff --git a/examples/arithmetic/src/arithmetic.idl b/examples/arithmetic/src/arithmetic.idl index c7df6698b4..dcb9762e64 100644 --- a/examples/arithmetic/src/arithmetic.idl +++ b/examples/arithmetic/src/arithmetic.idl @@ -1,11 +1,14 @@ - -enum Overflow { - "WRAPPING", - "SATURATING", +[Error] +enum ArithmeticError { + "IntegerOverflow", }; namespace arithmetic { - u64 add(u64 a, u64 b, optional Overflow overflow = "WRAPPING"); - u64 sub(u64 a, u64 b, optional Overflow overflow = "WRAPPING"); + [Throws=ArithmeticError] + u64 add(u64 a, u64 b); + + [Throws=ArithmeticError] + u64 sub(u64 a, u64 b); + boolean equal(u64 a, u64 b); }; diff --git a/examples/arithmetic/src/lib.rs b/examples/arithmetic/src/lib.rs index 66cb4a163c..9b8766b141 100644 --- a/examples/arithmetic/src/lib.rs +++ b/examples/arithmetic/src/lib.rs @@ -2,29 +2,24 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// Flag to let calling code specify a particular behaviour of integer overflow, -// since rust has such nice support for these. -pub enum Overflow { - WRAPPING, - SATURATING, +#[derive(Debug, thiserror::Error)] +enum ArithmeticError { + #[error("Integer overflow!")] + IntegerOverflow, } -pub fn add(a: u64, b: u64, overflow: Overflow) -> u64 { - match overflow { - Overflow::WRAPPING => a.overflowing_add(b).0, - Overflow::SATURATING => a.saturating_add(b), - } +fn add(a: u64, b: u64) -> Result { + a.checked_add(b).ok_or(ArithmeticError::IntegerOverflow) } -pub fn sub(a: u64, b: u64, overflow: Overflow) -> u64 { - match overflow { - Overflow::WRAPPING => a.overflowing_sub(b).0, - Overflow::SATURATING => a.saturating_sub(b), - } +fn sub(a: u64, b: u64) -> Result { + a.checked_sub(b).ok_or(ArithmeticError::IntegerOverflow) } -pub fn equal(a: u64, b: u64) -> bool { +fn equal(a: u64, b: u64) -> bool { a == b } +type Result = std::result::Result; + include!(concat!(env!("OUT_DIR"), "/arithmetic.uniffi.rs")); diff --git a/examples/arithmetic/tests/bindings/test_arithmetic.kts b/examples/arithmetic/tests/bindings/test_arithmetic.kts index de91ef23a6..6a2fe37961 100644 --- a/examples/arithmetic/tests/bindings/test_arithmetic.kts +++ b/examples/arithmetic/tests/bindings/test_arithmetic.kts @@ -1,6 +1,20 @@ import uniffi.arithmetic.*; -assert(add(2, 3, Overflow.SATURATING) == 5L) +assert(add(2, 4) == 6L) +assert(add(4, 8) == 12L) +try { + sub(0, 2) + throw RuntimeException("Should have thrown a IntegerOverflow exception!") +} catch (e: ArithmeticErrorException) { + // It's okay! +} + +assert(sub(4, 2) == 2L) +assert(sub(8, 4) == 4L) + +assert(equal(2, 2)) assert(equal(4, 4)) -assert(!equal(4, 5)) + +assert(!equal(2, 4)) +assert(!equal(4, 8)) diff --git a/examples/arithmetic/tests/bindings/test_arithmetic.py b/examples/arithmetic/tests/bindings/test_arithmetic.py index b2959a9a1e..690e2e965e 100644 --- a/examples/arithmetic/tests/bindings/test_arithmetic.py +++ b/examples/arithmetic/tests/bindings/test_arithmetic.py @@ -1,6 +1,27 @@ from arithmetic import * -assert add(2, 3, Overflow.SATURATING) == 5 +try: + add(18446744073709551615, 1) + assert(not("Should have thrown a IntegerOverflow exception!")) +except ArithmeticError.IntegerOverflow: + # It's okay! + pass +assert add(2, 4) == 6 +assert add(4, 8) == 12 + +try: + sub(0, 1) + assert(not("Should have thrown a IntegerOverflow exception!")) +except ArithmeticError.IntegerOverflow: + # It's okay! + pass + +assert sub(4, 2) == 2 +assert sub(8, 4) == 4 + +assert equal(2, 2) assert equal(4, 4) -assert not equal(4,5) + +assert not equal(2, 4) +assert not equal(4, 8) diff --git a/examples/arithmetic/tests/bindings/test_arithmetic.swift b/examples/arithmetic/tests/bindings/test_arithmetic.swift index 1fb88ac3c1..2076f032e7 100644 --- a/examples/arithmetic/tests/bindings/test_arithmetic.swift +++ b/examples/arithmetic/tests/bindings/test_arithmetic.swift @@ -1,6 +1,27 @@ import arithmetic -assert(add(a: 2, b: 3, overflow: .saturating) == 5, "addition works") +do { + let _ = try add(a: 18446744073709551615, b: 1) + fatalError("Should have thrown a IntegerOverflow exception!") +} catch ArithmeticError.IntegerOverflow { + // It's okay! +} -assert(equal(a: 4, b: 4), "equality works") -assert(!equal(a: 4, b: 5), "non-equality works") +assert(try! add(a: 2, b: 4) == 6, "add work") +assert(try! add(a: 4, b: 8) == 12, "add work") + +do { + let _ = try sub(a: 0, b: 1) + fatalError("Should have thrown a IntegerOverflow exception!") +} catch ArithmeticError.IntegerOverflow { + // It's okay! +} + +assert(try! sub(a: 4, b: 2) == 2, "sub work") +assert(try! sub(a: 8, b: 4) == 4, "sub work") + +assert(equal(a: 2, b: 2), "equal works") +assert(equal(a: 4, b: 4), "equal works") + +assert(!equal(a: 2, b: 4), "non-equal works") +assert(!equal(a: 4, b: 8), "non-equal works") diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt index 28d21ea145..6d0141e584 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -94,7 +94,7 @@ internal open class {{e.name()}} : RustError() { {% for value in e.values() -%} {{loop.index}} -> return {{e.name()}}Exception.{{value}}(message) as E {% endfor -%} - else -> throw RuntimeException("Invalid error recieved...") + else -> throw RuntimeException("Invalid error received...") } } } diff --git a/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py new file mode 100644 index 0000000000..191881163e --- /dev/null +++ b/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -0,0 +1,44 @@ +class RustError(ctypes.Structure): + _fields_ = [ + ("code", ctypes.c_int32), + ("message", ctypes.c_void_p), + ] + + def free(self): + return _UniFFILib.{{ ci.ffi_string_free().name() }}(self.message) + + def __str__(self): + return "RustError(code={}, message={})".format( + self.code, + str(ctypes.cast(self.message, ctypes.c_char_p).value, "utf-8"), + ) + +{% for e in ci.iter_error_definitions() %} +class {{ e.name()|class_name_py }}: + {%- for value in e.values() %} + class {{ value|class_name_py }}(Exception): + pass + {%- endfor %} + + @staticmethod + def raise_err(code, message): + {%- for value in e.values() %} + if code == {{ loop.index }}: + raise {{ e.name()|class_name_py }}.{{ value|class_name_py }}(message) + {% endfor %} + raise Exception("Unknown error code") +{% endfor %} + +def rust_call_with_error(error_class, fn, *args): + error = RustError() + error.code = 0 + + args_with_error = args + (ctypes.byref(error),) + result = fn(*args_with_error) + if error.code != 0: + message = str(error) + error.free() + + error_class.raise_err(error.code, message) + + return result diff --git a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index df3daf9c11..6aba969e07 100644 --- a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -20,7 +20,7 @@ def loadIndirect(componentName): _UniFFILib = loadIndirect(componentName="{{ ci.namespace() }}") {%- for func in ci.iter_ffi_function_definitions() %} _UniFFILib.{{ func.name() }}.argtypes = ( - {%- call py::arg_list_ffi_decl(func.arguments()) -%} + {%- call py::arg_list_ffi_decl(func) -%} ) _UniFFILib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|type_ffi }}{% when None %}None{% endmatch %} {%- endfor %} \ No newline at end of file diff --git a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 31952e18a7..3f079d0d7e 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -1,8 +1,8 @@ class {{ obj.name()|class_name_py }}(object): # XXX TODO: support for multiple constructors... {%- for cons in obj.constructors() %} - def __init__(self, {% call py::arg_list_decl(cons.arguments()) -%}): - {%- call py::coerce_args_extra_indent(cons.arguments()) %} + def __init__(self, {% call py::arg_list_decl(cons) -%}): + {%- call py::coerce_args_extra_indent(cons) %} self._handle = {% call py::to_ffi_call(cons) %} {%- endfor %} @@ -13,14 +13,14 @@ def __del__(self): {%- match meth.return_type() -%} {%- when Some with (return_type) -%} - def {{ meth.name()|fn_name_py }}(self, {% call py::arg_list_decl(meth.arguments()) %}): - {%- call py::coerce_args_extra_indent(meth.arguments()) %} + def {{ meth.name()|fn_name_py }}(self, {% call py::arg_list_decl(meth) %}): + {%- call py::coerce_args_extra_indent(meth) %} _retval = {% call py::to_ffi_call_with_prefix("self._handle", meth) %} return {{ "_retval"|lift_py(return_type) }} {%- when None -%} - def {{ meth.name()|fn_name_py }}(self, {% call py::arg_list_decl(meth.arguments()) %}): - {%- call py::coerce_args_extra_indent(meth.arguments()) %} + def {{ meth.name()|fn_name_py }}(self, {% call py::arg_list_decl(meth) %}): + {%- call py::coerce_args_extra_indent(meth) %} {% call py::to_ffi_call_with_prefix("self._handle", meth) %} {% endmatch %} {% endfor %} diff --git a/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py index 7cf8b00e01..0111a0a390 100644 --- a/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -1,14 +1,14 @@ {%- match func.return_type() -%} {%- when Some with (return_type) %} -def {{ func.name()|fn_name_py }}({%- call py::arg_list_decl(func.arguments()) -%}): - {%- call py::coerce_args(func.arguments()) %} +def {{ func.name()|fn_name_py }}({%- call py::arg_list_decl(func) -%}): + {%- call py::coerce_args(func) %} _retval = {% call py::to_ffi_call(func) %} return {{ "_retval"|lift_py(return_type) }} {% when None -%} -def {{ func.name()|fn_name_py }}({%- call py::arg_list_decl(func.arguments()) -%}): - {%- call py::coerce_args(func.arguments()) %} +def {{ func.name()|fn_name_py }}({%- call py::arg_list_decl(func) -%}): + {%- call py::coerce_args(func) %} {% call py::to_ffi_call(func) %} {% endmatch %} \ No newline at end of file diff --git a/uniffi_bindgen/src/bindings/python/templates/macros.py b/uniffi_bindgen/src/bindings/python/templates/macros.py index aba27853ab..64d282c840 100644 --- a/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -5,19 +5,27 @@ #} {%- macro to_ffi_call(func) -%} -_UniFFILib.{{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func.arguments()) -%}) +{%- match func.throws() -%} +{%- when Some with (e) -%} +rust_call_with_error({{ e|class_name_py }},_UniFFILib.{{ func.ffi_func().name() }}{% if func.arguments().len() > 0 %},{% endif %}{% call _arg_list_ffi_call(func) -%}) +{%- else -%} +_UniFFILib.{{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}) +{%- endmatch -%} {%- endmacro -%} {%- macro to_ffi_call_with_prefix(prefix, func) -%} -_UniFFILib.{{ func.ffi_func().name() }}( - {{- prefix }}{% if func.arguments().len() > 0 %}, {% call _arg_list_ffi_call(func.arguments()) -%}{% endif -%} -) +{%- match func.throws() -%} +{%- when Some with (e) -%} +rust_call_with_error({{ e|class_name_py }},_UniFFILib.{{ func.ffi_func().name() }},{{- prefix }}{% if func.arguments().len() > 0 %},{% endif %}{% call _arg_list_ffi_call(func) %})) +{%- else -%} +_UniFFILib.{{ func.ffi_func().name() }}({{- prefix }}{% if func.arguments().len() > 0 %},{% endif %}{% call _arg_list_ffi_call(func) %})) +{%- endmatch -%} {%- endmacro -%} -{%- macro _arg_list_ffi_call(args) %} - {%- for arg in args %} +{%- macro _arg_list_ffi_call(func) %} + {%- for arg in func.arguments() %} {{- arg.name()|lower_py(arg.type_()) }} - {%- if !loop.last %}, {% endif %} + {%- if !loop.last %},{% endif %} {%- endfor %} {%- endmacro -%} @@ -26,10 +34,10 @@ // Note the var_name_py and type_py filters. -#} -{% macro arg_list_decl(args) %} - {%- for arg in args -%} +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} {{ arg.name()|var_name_py }} - {%- if !loop.last %}, {% endif -%} + {%- if !loop.last %},{% endif -%} {%- endfor %} {%- endmacro %} @@ -37,20 +45,21 @@ // Arglist as used in the _UniFFILib function declations. // Note unfiltered name but type_ffi filters. -#} -{%- macro arg_list_ffi_decl(args) %} - {%- for arg in args -%} - {{ arg.type_()|type_ffi }}, {##} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() -%} + {{ arg.type_()|type_ffi }},{##} {%- endfor %} + {%- if func.has_out_err() -%}ctypes.POINTER(RustError),{%- endif -%} {%- endmacro -%} -{%- macro coerce_args(args) %} - {%- for arg in args %} +{%- macro coerce_args(func) %} + {%- for arg in func.arguments() %} {{ arg.name()|coerce_py(arg.type_()) -}} {% endfor -%} {%- endmacro -%} -{%- macro coerce_args_extra_indent(args) %} - {%- for arg in args %} +{%- macro coerce_args_extra_indent(func) %} + {%- for arg in func.arguments() %} {{ arg.name()|coerce_py(arg.type_()) }} {%- endfor %} {%- endmacro -%} diff --git a/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/uniffi_bindgen/src/bindings/python/templates/wrapper.py index c33335a3b7..9a8cb466ad 100644 --- a/uniffi_bindgen/src/bindings/python/templates/wrapper.py +++ b/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -23,6 +23,9 @@ {% include "RustBufferHelper.py" %} +# Error definitions +{% include "ErrorTemplate.py" %} + {% include "NamespaceLibraryTemplate.py" %} # Public interface members begin here. @@ -56,6 +59,9 @@ {%- for obj in ci.iter_object_definitions() %} "{{ obj.name()|class_name_py }}", {%- endfor %} + {%- for e in ci.iter_error_definitions() %} + "{{ e.name()|class_name_py }}", + {%- endfor %} ] -{% import "macros.py" as py %} \ No newline at end of file +{% import "macros.py" as py %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift index a2aca67f0c..811faf68a2 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -1,6 +1,6 @@ {# // In here we define conversions between a native reference to Swift errors -// We use the RustError protocol to define the requirments. Any implementers of the protocol +// We use the RustError protocol to define the requirements. Any implementers of the protocol // Can be generated from a NativeRustError. #}