diff options
Diffstat (limited to '')
45 files changed, 850 insertions, 486 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs index b91bcbe18f..16adeca9a5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs @@ -3,7 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; -use crate::backend::{Literal, Type}; +use crate::{ + backend::{Literal, Type}, + bindings::python::gen_python::AsCodeType, +}; #[derive(Debug)] pub struct OptionalCodeType { @@ -33,8 +36,9 @@ impl CodeType for OptionalCodeType { fn literal(&self, literal: &Literal) -> String { match literal { - Literal::Null => "None".into(), - _ => super::PythonCodeOracle.find(&self.inner).literal(literal), + Literal::None => "None".into(), + Literal::Some { inner } => super::PythonCodeOracle.find(&self.inner).literal(inner), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } } @@ -88,7 +92,11 @@ impl MapCodeType { impl CodeType for MapCodeType { fn type_label(&self) -> String { - "dict".to_string() + format!( + "dict[{}, {}]", + self.key.as_codetype().type_label(), + self.value.as_codetype().type_label() + ) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs deleted file mode 100644 index be3ba1d791..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -use super::CodeType; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { - "asyncio.BaseEventLoop".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs index 0d19c4bb3c..0a46251d6d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs @@ -17,7 +17,7 @@ impl ExternalCodeType { impl CodeType for ExternalCodeType { fn type_label(&self) -> String { - self.name.clone() + super::PythonCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 8178fcc102..6a10a38e7f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -2,8 +2,9 @@ * 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/. */ -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -13,20 +14,43 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; use crate::backend::TemplateExpression; +use crate::bindings::python; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; mod primitives; mod record; +pub struct PythonBindingGenerator; + +impl BindingGenerator for PythonBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + python::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Python requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} + /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -114,6 +138,8 @@ pub struct Config { cdylib_name: Option<String>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, + #[serde(default)] + external_packages: HashMap<String, String>, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -133,6 +159,16 @@ impl Config { "uniffi".into() } } + + /// Get the package name for a given external namespace. + pub fn module_for_namespace(&self, ns: &str) -> String { + let ns = ns.to_string().to_snake_case(); + match self.external_packages.get(&ns) { + None => format!(".{ns}"), + Some(value) if value.is_empty() => ns, + Some(value) => format!("{value}.{ns}"), + } + } } impl BindingsConfig for Config { @@ -326,7 +362,19 @@ impl PythonCodeOracle { fixup_keyword(nm.to_string().to_shouty_snake_case()) } - fn ffi_type_label(ffi_type: &FfiType) -> String { + /// Get the idiomatic Python rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("UNIFFI_{}", nm.to_shouty_snake_case()) + } + + /// Get the idiomatic Python rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's use + // UpperCamelCase and reserve shouting for global variables + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "ctypes.c_int8".to_string(), FfiType::UInt8 => "ctypes.c_uint8".to_string(), @@ -338,19 +386,64 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), + FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), None => "_UniffiRustBuffer".to_string(), }, + FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(), FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), - FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), // Pointer to an `asyncio.EventLoop` instance - FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), - FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), - FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), - FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), - FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), + FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), + FfiType::VoidPointer => "ctypes.c_void_p".to_string(), + } + } + + /// Default values for FFI types + /// + /// Used to set a default return value when returning an error + fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 + | FfiType::Int8 + | FfiType::UInt16 + | FfiType::Int16 + | FfiType::UInt32 + | FfiType::Int32 + | FfiType::UInt64 + | FfiType::Int64 => "0".to_owned(), + FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(), + FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { + Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"), + None => "_UniffiRustBuffer.default()".to_owned(), + }, + _ => unimplemented!("FFI return type: {t:?}"), + }, + // When we need to use a value for void returns, we use a `u8` placeholder + None => "0".to_owned(), + } + } + + /// Get the name of the protocol and class name for an object. + /// + /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the protocol name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, obj: &Object) -> (String, String) { + let class_name = self.class_name(obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Protocol"), class_name) } } } @@ -392,7 +485,6 @@ impl<T: AsType> AsCodeType for T { Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -429,6 +521,10 @@ pub mod filters { Ok(format!("{}.lift", ffi_converter_name(as_ct)?)) } + pub(super) fn check_lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.check_lower", ffi_converter_name(as_ct)?)) + } + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { Ok(format!("{}.lower", ffi_converter_name(as_ct)?)) } @@ -448,8 +544,18 @@ pub mod filters { Ok(as_ct.as_codetype().literal(literal)) } + // Get the idiomatic Python rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + Ok(Type::UInt64.as_codetype().literal(&literal)) + } + pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> { - Ok(PythonCodeOracle::ffi_type_label(type_)) + Ok(PythonCodeOracle.ffi_type_label(type_)) + } + + pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref())) } /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). @@ -471,4 +577,51 @@ pub mod filters { pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> { Ok(PythonCodeOracle.enum_variant_name(nm)) } + + /// Get the idiomatic Python rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_callback_name(nm)) + } + + /// Get the idiomatic Python rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_struct_name(nm)) + } + + /// Get the idiomatic Python rendering of an individual enum variant. + pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { + Ok(PythonCodeOracle.object_names(obj)) + } + + /// Get the idiomatic Python rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let docstring = textwrap::dedent(docstring); + // Escape triple quotes to avoid syntax error + let escaped = docstring.replace(r#"""""#, r#"\"\"\""#); + + let wrapped = format!("\"\"\"\n{escaped}\n\"\"\""); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_docstring_escape() { + let docstring = r#""""This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: """ +And a even longer quote: """"""#; + + let expected = r#"""" +\"\"\"This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: \"\"\" +And a even longer quote: \"\"\""" +""""#; + + assert_eq!(super::filters::docstring(docstring, &0).unwrap(), expected); + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py index 82aa534b46..26daa9ba5c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py @@ -3,13 +3,37 @@ _UNIFFI_RUST_FUTURE_POLL_READY = 0 _UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 # Stores futures for _uniffi_continuation_callback -_UniffiContinuationPointerManager = _UniffiPointerManager() +_UniffiContinuationHandleMap = _UniffiHandleMap() + +UNIFFI_GLOBAL_EVENT_LOOP = None + +""" +Set the event loop to use for async functions + +This is needed if some async functions run outside of the eventloop, for example: + - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the + Rust code spawning its own thread. + - The Rust code calls an async callback method from a sync callback function, using something + like `pollster` to block on the async call. + +In this case, we need an event loop to run the Python async function, but there's no eventloop set +for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case. +""" +def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop): + global UNIFFI_GLOBAL_EVENT_LOOP + UNIFFI_GLOBAL_EVENT_LOOP = eventloop + +def _uniffi_get_event_loop(): + if UNIFFI_GLOBAL_EVENT_LOOP is not None: + return UNIFFI_GLOBAL_EVENT_LOOP + else: + return asyncio.get_running_loop() # Continuation callback for async functions # lift the return value or error and resolve the future, causing the async function to resume. -@_UNIFFI_FUTURE_CONTINUATION_T +@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK def _uniffi_continuation_callback(future_ptr, poll_code): - (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr) + (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr) eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) def _uniffi_set_future_result(future, poll_code): @@ -18,14 +42,15 @@ def _uniffi_set_future_result(future, poll_code): async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): try: - eventloop = asyncio.get_running_loop() + eventloop = _uniffi_get_event_loop() # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value while True: future = eventloop.create_future() ffi_poll( rust_future, - _UniffiContinuationPointerManager.new_pointer((eventloop, future)), + _uniffi_continuation_callback, + _UniffiContinuationHandleMap.insert((eventloop, future)), ) poll_code = await future if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: @@ -37,4 +62,53 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, finally: ffi_free(rust_future) -_UniffiLib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(_uniffi_continuation_callback) +{%- if ci.has_async_callback_interface_definition() %} +def uniffi_trait_interface_call_async(make_call, handle_success, handle_error): + async def make_call_and_call_callback(): + try: + handle_success(await make_call()) + except Exception as e: + print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) + traceback.print_exc(file=sys.stderr) + handle_error( + _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(repr(e)), + ) + eventloop = _uniffi_get_event_loop() + task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) + handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) + return UniffiForeignFuture(handle, uniffi_foreign_future_free) + +def uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, error_type, lower_error): + async def make_call_and_call_callback(): + try: + try: + handle_success(await make_call()) + except error_type as e: + handle_error( + _UniffiRustCallStatus.CALL_ERROR, + lower_error(e), + ) + except Exception as e: + print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) + traceback.print_exc(file=sys.stderr) + handle_error( + _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(repr(e)), + ) + eventloop = _uniffi_get_event_loop() + task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) + handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) + return UniffiForeignFuture(handle, uniffi_foreign_future_free) + +UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap() + +@UNIFFI_FOREIGN_FUTURE_FREE +def uniffi_foreign_future_free(handle): + (eventloop, task) = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle) + eventloop.call_soon(uniffi_foreign_future_do_free, task) + +def uniffi_foreign_future_do_free(task): + if not task.done(): + task.cancel() +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py index 6775e9e132..3f8c5d1d4d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py @@ -1,16 +1,20 @@ -class _UniffiConverterBool(_UniffiConverterPrimitive): +class _UniffiConverterBool: @classmethod - def check(cls, value): + def check_lower(cls, value): return not not value @classmethod + def lower(cls, value): + return 1 if value else 0 + + @staticmethod + def lift(value): + return value != 0 + + @classmethod def read(cls, buf): return cls.lift(buf.read_u8()) @classmethod - def write_unchecked(cls, value, buf): + def write(cls, value, buf): buf.write_u8(value) - - @staticmethod - def lift(value): - return value != 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py index 196b5b29fa..4d09531322 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py @@ -7,10 +7,13 @@ class _UniffiConverterBytes(_UniffiConverterRustBuffer): return buf.read(size) @staticmethod - def write(value, buf): + def check_lower(value): try: memoryview(value) except TypeError: raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) + + @staticmethod + def write(value, buf): buf.write_i32(len(value)) buf.write(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py new file mode 100644 index 0000000000..676f01177a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py @@ -0,0 +1,98 @@ +{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} +{%- let trait_impl=format!("UniffiTraitImpl{}", name) %} + +# Put all the bits inside a class to keep the top-level namespace clean +class {{ trait_impl }}: + # For each method, generate a callback function to pass to Rust + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + + @{{ ffi_callback.name()|ffi_callback_name }} + def {{ meth.name()|fn_name }}( + {%- for arg in ffi_callback.arguments() %} + {{ arg.name()|var_name }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() %} + uniffi_call_status_ptr, + {%- endif %} + ): + uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle) + def make_call(): + args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %}) + method = uniffi_obj.{{ meth.name()|fn_name }} + return method(*args) + + {% if !meth.is_async() %} + {%- match meth.return_type() %} + {%- when Some(return_type) %} + def write_return_value(v): + uniffi_out_return[0] = {{ return_type|lower_fn }}(v) + {%- when None %} + write_return_value = lambda v: None + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + _uniffi_trait_interface_call( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + ) + {%- when Some(error) %} + _uniffi_trait_interface_call_with_error( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + {{ error|type_name }}, + {{ error|lower_fn }}, + ) + {%- endmatch %} + {%- else %} + def handle_success(return_value): + uniffi_future_callback( + uniffi_callback_data, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lower_fn }}(return_value), + {%- when None %} + {%- endmatch %} + _UniffiRustCallStatus.default() + ) + ) + + def handle_error(status_code, rust_buffer): + uniffi_future_callback( + uniffi_callback_data, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ meth.return_type().map(FfiType::from)|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + _UniffiRustCallStatus(status_code, rust_buffer), + ) + ) + + {%- match meth.throws_type() %} + {%- when None %} + uniffi_out_return[0] = uniffi_trait_interface_call_async(make_call, handle_success, handle_error) + {%- when Some(error) %} + uniffi_out_return[0] = uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }}) + {%- endmatch %} + {%- endif %} + {%- endfor %} + + @{{ "CallbackInterfaceFree"|ffi_callback_name }} + def uniffi_free(uniffi_handle): + {{ ffi_converter_name }}._handle_map.remove(uniffi_handle) + + # Generate the FFI VTable. This has a field for each callback interface method. + uniffi_vtable = {{ vtable|ffi_type_name }}( + {%- for (_, meth) in vtable_methods.iter() %} + {{ meth.name()|fn_name }}, + {%- endfor %} + uniffi_free + ) + # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever, + # or else bad things will happen when Rust tries to access it. + _UniffiLib.{{ ffi_init_callback.name() }}(ctypes.byref(uniffi_vtable)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py index 0fe2ab8dc0..d802c90fde 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py @@ -1,42 +1,3 @@ -import threading - -class ConcurrentHandleMap: - """ - A map where inserting, getting and removing data is synchronized with a lock. - """ - - def __init__(self): - # type Handle = int - self._left_map = {} # type: Dict[Handle, Any] - self._right_map = {} # type: Dict[Any, Handle] - - self._lock = threading.Lock() - self._current_handle = 0 - self._stride = 1 - - - def insert(self, obj): - with self._lock: - if obj in self._right_map: - return self._right_map[obj] - else: - handle = self._current_handle - self._current_handle += self._stride - self._left_map[handle] = obj - self._right_map[obj] = handle - return handle - - def get(self, handle): - with self._lock: - return self._left_map.get(handle) - - def remove(self, handle): - with self._lock: - if handle in self._left_map: - obj = self._left_map.pop(handle) - del self._right_map[obj] - return obj - # Magic number for the Rust proxy to call using the same mechanism as every other method, # to free the callback once it's dropped by Rust. IDX_CALLBACK_FREE = 0 @@ -45,22 +6,12 @@ _UNIFFI_CALLBACK_SUCCESS = 0 _UNIFFI_CALLBACK_ERROR = 1 _UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -class _UniffiConverterCallbackInterface: - _handle_map = ConcurrentHandleMap() - - def __init__(self, cb): - self._foreign_callback = cb - - def drop(self, handle): - self.__class__._handle_map.remove(handle) +class UniffiCallbackInterfaceFfiConverter: + _handle_map = _UniffiHandleMap() @classmethod def lift(cls, handle): - obj = cls._handle_map.get(handle) - if not obj: - raise InternalError("The object in the handle map has been dropped already") - - return obj + return cls._handle_map.get(handle) @classmethod def read(cls, buf): @@ -68,6 +19,10 @@ class _UniffiConverterCallbackInterface: cls.lift(handle) @classmethod + def check_lower(cls, cb): + pass + + @classmethod def lower(cls, cb): handle = cls._handle_map.insert(cb) return handle diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py index e0e926757a..a41e58e635 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -1,105 +1,13 @@ -{%- let cbi = ci|get_callback_interface_definition(id) %} -{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} +{%- let protocol_name = type_name.clone() %} +{%- let protocol_docstring = cbi.docstring() %} +{%- let vtable = cbi.vtable() %} +{%- let methods = cbi.methods() %} +{%- let vtable_methods = cbi.vtable_methods() %} -{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} - -# Declaration and _UniffiConverters for {{ type_name }} Callback Interface - -class {{ type_name }}: - {% for meth in cbi.methods() -%} - def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): - raise NotImplementedError - - {% endfor %} - -def py_{{ foreign_callback }}(handle, method, args_data, args_len, buf_ptr): - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name %} - def {{ method_name }}(python_callback, args_stream, buf_ptr): - {#- Unpacking args from the _UniffiRustBuffer #} - def makeCall(): - {#- Calling the concrete callback object #} - {%- if meth.arguments().len() != 0 -%} - return python_callback.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {{ arg|read_fn }}(args_stream) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - {%- else %} - return python_callback.{{ meth.name()|fn_name }}() - {%- endif %} - - def makeCallAndHandleReturn(): - {%- match meth.return_type() %} - {%- when Some(return_type) %} - rval = makeCall() - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ return_type|write_fn }}(rval, builder) - buf_ptr[0] = builder.finalize() - {%- when None %} - makeCall() - {%- endmatch %} - return _UNIFFI_CALLBACK_SUCCESS - - {%- match meth.throws_type() %} - {%- when None %} - return makeCallAndHandleReturn() - {%- when Some(err) %} - try: - return makeCallAndHandleReturn() - except {{ err|type_name }} as e: - # Catch errors declared in the UDL file - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ err|write_fn }}(e, builder) - buf_ptr[0] = builder.finalize() - return _UNIFFI_CALLBACK_ERROR - {%- endmatch %} - - {% endfor %} - - cb = {{ ffi_converter_name }}.lift(handle) - if not cb: - raise InternalError("No callback in handlemap; this is a uniffi bug") - - if method == IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.drop(handle) - # Successfull return - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_SUCCESS - - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - if method == {{ loop.index }}: - # Call the method and handle any errors - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details - try: - return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr) - except BaseException as e: - # Catch unexpected errors - try: - # Try to serialize the exception into a String - buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e)) - except: - # If that fails, just give up - pass - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - {% endfor %} - - # This should never happen, because an out of bounds method index won't - # ever be used. Once we can catch errors, we should return an InternalException. - # https://github.com/mozilla/uniffi-rs/issues/351 - - # An unexpected error happened. - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - -# We need to keep this function reference alive: -# if they get GC'd while in use then UniFFI internals could attempt to call a function -# that is in freed memory. -# That would be...uh...bad. Yeah, that's the word. Bad. -{{ foreign_callback }} = _UNIFFI_FOREIGN_CALLBACK_T(py_{{ foreign_callback }}) -_rust_call(lambda err: _UniffiLib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)) +{% include "Protocol.py" %} +{% include "CallbackInterfaceImpl.py" %} # The _UniffiConverter which transforms the Callbacks in to Handles to pass to Rust. -{{ ffi_converter_name }} = _UniffiConverterCallbackInterface({{ foreign_callback }}) +{{ ffi_converter_name }} = UniffiCallbackInterfaceFfiConverter() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py index 5be6155b84..f75a85dc27 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py @@ -18,6 +18,10 @@ class _UniffiConverterType{{ name }}: return {{ builtin|ffi_converter_name }}.lift(value) @staticmethod + def check_lower(value): + return {{ builtin|ffi_converter_name }}.check_lower(value) + + @staticmethod def lower(value): return {{ builtin|ffi_converter_name }}.lower(value) @@ -52,6 +56,11 @@ class _UniffiConverterType{{ name }}: return {{ config.into_custom.render("builtin_value") }} @staticmethod + def check_lower(value): + builtin_value = {{ config.from_custom.render("value") }} + return {{ builtin|check_lower_fn }}(builtin_value) + + @staticmethod def lower(value): builtin_value = {{ config.from_custom.render("value") }} return {{ builtin|lower_fn }}(builtin_value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py index 10974e009d..ecb035b7f4 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py @@ -12,10 +12,14 @@ class _UniffiConverterDuration(_UniffiConverterRustBuffer): return datetime.timedelta(seconds=seconds, microseconds=microseconds) @staticmethod - def write(value, buf): + def check_lower(value): seconds = value.seconds + value.days * 24 * 3600 - nanoseconds = value.microseconds * 1000 if seconds < 0: raise ValueError("Invalid duration, must be non-negative") + + @staticmethod + def write(value, buf): + seconds = value.seconds + value.days * 24 * 3600 + nanoseconds = value.microseconds * 1000 buf.write_i64(seconds) buf.write_u32(nanoseconds) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py index 84d089baf9..d07dd1c44a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py @@ -7,31 +7,59 @@ {% if e.is_flat() %} class {{ type_name }}(enum.Enum): - {% for variant in e.variants() -%} - {{ variant.name()|enum_variant_py }} = {{ loop.index }} + {%- call py::docstring(e, 4) %} + {%- for variant in e.variants() %} + {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }} + {%- call py::docstring(variant, 4) %} {% endfor %} {% else %} class {{ type_name }}: + {%- call py::docstring(e, 4) %} def __init__(self): raise RuntimeError("{{ type_name }} cannot be instantiated directly") # Each enum variant is a nested class of the enum itself. {% for variant in e.variants() -%} class {{ variant.name()|enum_variant_py }}: - {% for field in variant.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(variant, 8) %} + + {%- if variant.has_nameless_fields() %} + def __init__(self, *values): + if len(values) != {{ variant.fields().len() }}: + raise TypeError(f"Expected a tuple of len {{ variant.fields().len() }}, found len {len(values)}") + {%- for field in variant.fields() %} + if not isinstance(values[{{ loop.index0 }}], {{ field|type_name }}): + raise TypeError(f"unexpected type for tuple element {{ loop.index0 }} - expected '{{ field|type_name }}', got '{type(values[{{ loop.index0 }}])}'") + {%- endfor %} + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}" + + def __eq__(self, other): + if not other.is_{{ variant.name()|var_name }}(): + return False + return self._values == other._values + + {%- else -%} + {%- for field in variant.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 8) %} {%- endfor %} @typing.no_type_check def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}): - {% if variant.has_fields() %} + {%- if variant.has_fields() %} {%- for field in variant.fields() %} self.{{ field.name()|var_name }} = {{ field.name()|var_name }} {%- endfor %} - {% else %} + {%- else %} pass - {% endif %} + {%- endif %} def __str__(self): return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) @@ -44,6 +72,7 @@ class {{ type_name }}: return False {%- endfor %} return True + {% endif %} {% endfor %} # For each variant, we have an `is_NAME` method for easily checking @@ -81,6 +110,30 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): {%- endfor %} raise InternalError("Raw enum value doesn't match any cases") + @staticmethod + def check_lower(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + {%- if e.is_flat() %} + if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + {%- else %} + if value.is_{{ variant.name()|var_name }}(): + {%- endif %} + {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() %} + {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}]) + {%- else %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endif %} + {%- endfor %} + return + {%- endfor %} + raise ValueError(value) + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} {%- if e.is_flat() %} @@ -90,7 +143,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): if value.is_{{ variant.name()|var_name }}(): buf.write_i32({{ loop.index }}) {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() %} + {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf) + {%- else %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endif %} {%- endfor %} {%- endif %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py index 26a1e6452a..0911ff559a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -5,6 +5,7 @@ # __dict__. All of this happens in dummy class to avoid polluting the module # namespace. class {{ type_name }}(Exception): + {%- call py::docstring(e, 4) %} pass _UniffiTemp{{ type_name }} = {{ type_name }} @@ -14,10 +15,14 @@ class {{ type_name }}: # type: ignore {%- let variant_type_name = variant.name()|class_name -%} {%- if e.is_flat() %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __repr__(self): return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self))) {%- else %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}): {%- if variant.has_fields() %} super().__init__(", ".join([ @@ -60,6 +65,20 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): raise InternalError("Raw enum value doesn't match any cases") @staticmethod + def check_lower(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + {%- for field in variant.fields() %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + return + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py index 71e05e8b06..6c0cee85ef 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py @@ -1,9 +1,9 @@ -{%- let ns = namespace|fn_name %} +{%- let module = python_config.module_for_namespace(namespace) -%} # External type {{name}} is in namespace "{{namespace}}", crate {{module_path}} {%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %} -{{ self.add_import_of(ns, ffi_converter_name) }} -{{ self.add_import_of(ns, name) }} {#- import the type alias itself -#} +{{ self.add_import_of(module, ffi_converter_name) }} +{{ self.add_import_of(module, name|class_name) }} {#- import the type alias itself -#} {%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %} -{{ self.add_import_of_as(ns, "_UniffiRustBuffer", rustbuffer_local_name) }} +{{ self.add_import_of_as(module, "_UniffiRustBuffer", rustbuffer_local_name) }} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py index a52107a638..49a1a7286e 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py @@ -4,5 +4,5 @@ class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): return buf.read_float() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_float(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py index 772f5080e9..e2084c7b13 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py @@ -4,5 +4,5 @@ class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): return buf.read_double() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_double(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py deleted file mode 100644 index 6a6932fed0..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py +++ /dev/null @@ -1,63 +0,0 @@ -# FFI code for the ForeignExecutor type - -{{ self.add_import("asyncio") }} - -_UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0 -_UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED = 1 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2 - -class {{ ffi_converter_name }}: - _pointer_manager = _UniffiPointerManager() - - @classmethod - def lower(cls, eventloop): - if not isinstance(eventloop, asyncio.BaseEventLoop): - raise TypeError("_uniffi_executor_callback: Expected EventLoop instance") - return cls._pointer_manager.new_pointer(eventloop) - - @classmethod - def write(cls, eventloop, buf): - buf.write_c_size_t(cls.lower(eventloop)) - - @classmethod - def read(cls, buf): - return cls.lift(buf.read_c_size_t()) - - @classmethod - def lift(cls, value): - return cls._pointer_manager.lookup(value) - -@_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T -def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data): - if task_ptr is None: - {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address) - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - else: - eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address) - if eventloop.is_closed(): - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED - - callback = _UNIFFI_RUST_TASK(task_ptr) - # FIXME: there's no easy way to get a callback when an eventloop is closed. This means that - # if eventloop is called before the `call_soon_threadsafe()` calls are invoked, the call - # will never happen and we will probably leak a resource. - if delay == 0: - # This can be called from any thread, so make sure to use `call_soon_threadsafe' - eventloop.call_soon_threadsafe(callback, task_data, - _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) - else: - # For delayed tasks, we use `call_soon_threadsafe()` + `call_later()` to make the - # operation threadsafe - eventloop.call_soon_threadsafe(eventloop.call_later, delay / 1000.0, callback, - task_data, _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - -# Register the callback with the scaffolding -{%- match ci.ffi_foreign_executor_callback_set() %} -{%- when Some with (fn) %} -_UniffiLib.{{ fn.name() }}(_uniffi_executor_callback) -{%- when None %} -{#- No foreign executor, we don't set anything #} -{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py new file mode 100644 index 0000000000..f7c13cf745 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py @@ -0,0 +1,33 @@ +class _UniffiHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._map = {} # type: Dict[Handle, Any] + self._lock = threading.Lock() + self._counter = itertools.count() + + def insert(self, obj): + with self._lock: + handle = next(self._counter) + self._map[handle] = obj + return handle + + def get(self, handle): + try: + with self._lock: + return self._map[handle] + except KeyError: + raise InternalError("UniffiHandleMap.get: Invalid handle") + + def remove(self, handle): + try: + with self._lock: + return self._map.pop(handle) + except KeyError: + raise InternalError("UniffiHandleMap.remove: Invalid handle") + + def __len__(self): + return len(self._map) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py index dca962f176..5d4bcbba89 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -16,15 +16,19 @@ class _UniffiRustCallStatus(ctypes.Structure): # These match the values from the uniffi::rustcalls module CALL_SUCCESS = 0 CALL_ERROR = 1 - CALL_PANIC = 2 + CALL_UNEXPECTED_ERROR = 2 + + @staticmethod + def default(): + return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default()) def __str__(self): if self.code == _UniffiRustCallStatus.CALL_SUCCESS: return "_UniffiRustCallStatus(CALL_SUCCESS)" elif self.code == _UniffiRustCallStatus.CALL_ERROR: return "_UniffiRustCallStatus(CALL_ERROR)" - elif self.code == _UniffiRustCallStatus.CALL_PANIC: - return "_UniffiRustCallStatus(CALL_PANIC)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" else: return "_UniffiRustCallStatus(<invalid code>)" @@ -37,7 +41,7 @@ def _rust_call_with_error(error_ffi_converter, fn, *args): # # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. - call_status = _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer(0, 0, None)) + call_status = _UniffiRustCallStatus.default() args_with_error = args + (ctypes.byref(call_status),) result = fn(*args_with_error) @@ -53,7 +57,7 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") else: raise error_ffi_converter.lift(call_status.error_buf) - elif call_status.code == _UniffiRustCallStatus.CALL_PANIC: + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer # with the message. But if that code panics, then it just sends back # an empty buffer. @@ -66,10 +70,20 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( call_status.code)) -# A function pointer for a callback as defined by UniFFI. -# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int` -_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer)) - -# UniFFI future continuation -_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8) +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py index 99f19dc1c0..befa563384 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt16(_UniffiConverterPrimitiveInt): return buf.read_i16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py index 3b142c8749..70644f6717 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt32(_UniffiConverterPrimitiveInt): return buf.read_i32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py index 6e94379cbf..232f127bd6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt64(_UniffiConverterPrimitiveInt): return buf.read_i64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py index 732530e3cb..c1de1625e7 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt8(_UniffiConverterPrimitiveInt): return buf.read_i8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py index 387227ed09..a09ca28a30 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py @@ -3,6 +3,12 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, items): + for (key, value) in items.items(): + {{ key_ffi_converter }}.check_lower(key) + {{ value_ffi_converter }}.check_lower(value) + + @classmethod def write(cls, items, buf): buf.write_i32(len(items)) for (key, value) in items.items(): diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index fac6cd5564..1929f9aad6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -1,22 +1,6 @@ # Define some ctypes FFI types that we use in the library """ -ctypes type for the foreign executor callback. This is a built-in interface for scheduling -tasks - -Args: - executor: opaque c_size_t value representing the eventloop - delay: delay in ms - task: function pointer to the task callback - task_data: void pointer to the task callback data - -Normally we should call task(task_data) after the detail. -However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should -decrease the EventLoop refcount. -""" -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p) - -""" Function pointer for a Rust task, which a callback function that takes a opaque pointer """ _UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) @@ -25,7 +9,7 @@ def _uniffi_future_callback_t(return_type): """ Factory function to create callback function types for async functions """ - return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus) + return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus) def _uniffi_load_indirect(): """ @@ -72,12 +56,37 @@ def _uniffi_check_api_checksums(lib): # This is an implementation detail which will be called internally by the public API. _UniffiLib = _uniffi_load_indirect() -{%- for func in ci.iter_ffi_function_definitions() %} + +{%- for def in ci.ffi_definitions() %} +{%- match def %} +{%- when FfiDefinition::CallbackFunction(callback) %} +{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE( + {%- match callback.return_type() %} + {%- when Some(return_type) %}{{ return_type|ffi_type_name }}, + {%- when None %}None, + {%- endmatch %} + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + ctypes.POINTER(_UniffiRustCallStatus), + {%- endif %} +) +{%- when FfiDefinition::Struct(ffi_struct) %} +class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure): + _fields_ = [ + {%- for field in ffi_struct.fields() %} + ("{{ field.name()|var_name }}", {{ field.type_().borrow()|ffi_type_name }}), + {%- endfor %} + ] +{%- when FfiDefinition::Function(func) %} _UniffiLib.{{ func.name() }}.argtypes = ( {%- call py::arg_list_ffi_decl(func) -%} ) _UniffiLib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %} +{%- endmatch %} {%- endfor %} + {# Ensure to call the contract verification only after we defined all functions. -#} _uniffi_check_contract_api_version(_UniffiLib) _uniffi_check_api_checksums(_UniffiLib) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 7e98f7c46f..18dca4743a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -1,14 +1,33 @@ {%- let obj = ci|get_object_definition(name) %} - -class {{ type_name }}: +{%- let (protocol_name, impl_name) = obj|object_names %} +{%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} + +{% include "Protocol.py" %} + +{% if ci.is_name_used_as_error(name) %} +class {{ impl_name }}(Exception): +{%- else %} +class {{ impl_name }}: +{%- endif %} + {%- call py::docstring(obj, 4) %} _pointer: ctypes.c_void_p {%- match obj.primary_constructor() %} {%- when Some with (cons) %} +{%- if cons.is_async() %} + def __init__(self, *args, **kw): + raise ValueError("async constructors not supported.") +{%- else %} def __init__(self, {% call py::arg_list_decl(cons) -%}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} self._pointer = {% call py::to_ffi_call(cons) %} +{%- endif %} {%- when None %} + {# no __init__ means simple construction without a pointer works, which can confuse #} + def __init__(self, *args, **kwargs): + raise ValueError("This class has no default constructor") {%- endmatch %} def __del__(self): @@ -17,6 +36,9 @@ class {{ type_name }}: if pointer is not None: _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer) + def _uniffi_clone_pointer(self): + return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._pointer) + # Used by alternative constructors or any methods which return this type. @classmethod def _make_instance_(cls, pointer): @@ -29,17 +51,32 @@ class {{ type_name }}: {%- for cons in obj.alternate_constructors() %} @classmethod +{%- if cons.is_async() %} + async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} + {%- call py::setup_args_extra_indent(cons) %} + + return await _uniffi_rust_call_async( + _UniffiLib.{{ cons.ffi_func().name() }}({% call py::arg_list_lowered(cons) %}), + _UniffiLib.{{ cons.ffi_rust_future_poll(ci) }}, + _UniffiLib.{{ cons.ffi_rust_future_complete(ci) }}, + _UniffiLib.{{ cons.ffi_rust_future_free(ci) }}, + {{ ffi_converter_name }}.lift, + {% call py::error_ffi_converter(cons) %} + ) +{%- else %} def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. pointer = {% call py::to_ffi_call(cons) %} return cls._make_instance_(pointer) +{%- endif %} {% endfor %} {%- for meth in obj.methods() -%} {%- call py::method_decl(meth.name()|fn_name, meth) %} -{% endfor %} - +{%- endfor %} {%- for tm in obj.uniffi_traits() -%} {%- match tm %} {%- when UniffiTrait::Debug { fmt } %} @@ -51,37 +88,84 @@ class {{ type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %}) + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", eq) %}) def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %}) + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", ne) %}) {%- when UniffiTrait::Hash { hash } %} {%- call py::method_decl("__hash__", hash) %} -{% endmatch %} -{% endfor %} - - -class {{ ffi_converter_name }}: +{%- endmatch %} +{%- endfor %} + +{%- if obj.has_callback_interface() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{% include "CallbackInterfaceImpl.py" %} +{%- endif %} + +{# Objects as error #} +{%- if ci.is_name_used_as_error(name) %} +{# Due to some mismatches in the ffi converter mechanisms, errors are forced to be a RustBuffer #} +class {{ ffi_converter_name }}__as_error(_UniffiConverterRustBuffer): @classmethod def read(cls, buf): - ptr = buf.read_u64() - if ptr == 0: - raise InternalError("Raw pointer value was null") - return cls.lift(ptr) + raise NotImplementedError() @classmethod def write(cls, value, buf): - if not isinstance(value, {{ type_name }}): - raise TypeError("Expected {{ type_name }} instance, {} found".format(type(value).__name__)) - buf.write_u64(cls.lower(value)) + raise NotImplementedError() @staticmethod def lift(value): - return {{ type_name }}._make_instance_(value) + # Errors are always a rust buffer holding a pointer - which is a "read" + with value.consume_with_stream() as stream: + return {{ ffi_converter_name }}.read(stream) @staticmethod def lower(value): - return value._pointer + raise NotImplementedError() + +{%- endif %} + +class {{ ffi_converter_name }}: + {%- if obj.has_callback_interface() %} + _handle_map = _UniffiHandleMap() + {%- endif %} + + @staticmethod + def lift(value: int): + return {{ impl_name }}._make_instance_(value) + + @staticmethod + def check_lower(value: {{ type_name }}): + {%- if obj.has_callback_interface() %} + pass + {%- else %} + if not isinstance(value, {{ impl_name }}): + raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) + {%- endif %} + + @staticmethod + def lower(value: {{ protocol_name }}): + {%- if obj.has_callback_interface() %} + return {{ ffi_converter_name }}._handle_map.insert(value) + {%- else %} + if not isinstance(value, {{ impl_name }}): + raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) + return value._uniffi_clone_pointer() + {%- endif %} + + @classmethod + def read(cls, buf: _UniffiRustBuffer): + ptr = buf.read_u64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value: {{ protocol_name }}, buf: _UniffiRustBuffer): + buf.write_u64(cls.lower(value)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py index e406c51d49..4c07ae3e34 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py @@ -2,6 +2,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, value): + if value is not None: + {{ inner_ffi_converter }}.check_lower(value) + + @classmethod def write(cls, value, buf): if value is None: buf.write_u8(0) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py deleted file mode 100644 index 23aa28eab4..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py +++ /dev/null @@ -1,68 +0,0 @@ -class _UniffiPointerManagerCPython: - """ - Manage giving out pointers to Python objects on CPython - - This class is used to generate opaque pointers that reference Python objects to pass to Rust. - It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative. - """ - - def new_pointer(self, obj): - """ - Get a pointer for an object as a ctypes.c_size_t instance - - Each call to new_pointer() must be balanced with exactly one call to release_pointer() - - This returns a ctypes.c_size_t. This is always the same size as a pointer and can be - interchanged with pointers for FFI function arguments and return values. - """ - # IncRef the object since we're going to pass a pointer to Rust - ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj)) - # id() is the object address on CPython - # (https://docs.python.org/3/library/functions.html#id) - return id(obj) - - def release_pointer(self, address): - py_obj = ctypes.cast(address, ctypes.py_object) - obj = py_obj.value - ctypes.pythonapi.Py_DecRef(py_obj) - return obj - - def lookup(self, address): - return ctypes.cast(address, ctypes.py_object).value - -class _UniffiPointerManagerGeneral: - """ - Manage giving out pointers to Python objects on non-CPython platforms - - This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on - CPython and is slightly slower. - - Instead of using real pointers, it maps integer values to objects and returns the keys as - c_size_t values. - """ - - def __init__(self): - self._map = {} - self._lock = threading.Lock() - self._current_handle = 0 - - def new_pointer(self, obj): - with self._lock: - handle = self._current_handle - self._current_handle += 1 - self._map[handle] = obj - return handle - - def release_pointer(self, handle): - with self._lock: - return self._map.pop(handle) - - def lookup(self, handle): - with self._lock: - return self._map[handle] - -# Pick an pointer manager implementation based on the platform -if platform.python_implementation() == 'CPython': - _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore -else: - _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py new file mode 100644 index 0000000000..3b7e93596a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py @@ -0,0 +1,9 @@ +class {{ protocol_name }}(typing.Protocol): + {%- call py::docstring_value(protocol_docstring, 4) %} + {%- for meth in methods.iter() %} + def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): + {%- call py::docstring(meth, 8) %} + raise NotImplementedError + {%- else %} + pass + {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py index 99a30e120f..0b5634eb52 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -1,11 +1,14 @@ {%- let rec = ci|get_record_definition(name) %} class {{ type_name }}: - {% for field in rec.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(rec, 4) %} + {%- for field in rec.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 4) %} {%- endfor %} + {%- if rec.has_fields() %} @typing.no_type_check - def __init__(self, {% for field in rec.fields() %} + def __init__(self, *, {% for field in rec.fields() %} {{- field.name()|var_name }}: "{{- field|type_name }}" {%- if field.default_value().is_some() %} = _DEFAULT{% endif %} {%- if !loop.last %}, {% endif %} @@ -22,6 +25,7 @@ class {{ type_name }}: self.{{ field_name }} = {{ field_name }} {%- endmatch %} {%- endfor %} + {%- endif %} def __str__(self): return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) @@ -43,7 +47,21 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): ) @staticmethod + def check_lower(value): + {%- if rec.fields().is_empty() %} + pass + {%- else %} + {%- for field in rec.fields() %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): + {%- if rec.has_fields() %} {%- for field in rec.fields() %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) {%- endfor %} + {%- else %} + pass + {%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py index daabd5b4b9..4db74fb157 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py @@ -1,28 +1,16 @@ # Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. class _UniffiConverterPrimitive: @classmethod - def check(cls, value): - return value - - @classmethod def lift(cls, value): return value @classmethod def lower(cls, value): - return cls.lowerUnchecked(cls.check(value)) - - @classmethod - def lowerUnchecked(cls, value): return value - @classmethod - def write(cls, value, buf): - cls.write_unchecked(cls.check(value), buf) - class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): @classmethod - def check(cls, value): + def check_lower(cls, value): try: value = value.__index__() except Exception: @@ -31,18 +19,16 @@ class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) if not cls.VALUE_MIN <= value < cls.VALUE_MAX: raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) - return super().check(value) class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): @classmethod - def check(cls, value): + def check_lower(cls, value): try: value = value.__float__() except Exception: raise TypeError("must be real number, not {}".format(type(value).__name__)) if not isinstance(value, float): raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) - return super().check(value) # Helper class for wrapper types that will always go through a _UniffiRustBuffer. # Classes should inherit from this and implement the `read` and `write` static methods. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py index c317a632fc..44e0ba1001 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py @@ -1,12 +1,16 @@ class _UniffiRustBuffer(ctypes.Structure): _fields_ = [ - ("capacity", ctypes.c_int32), - ("len", ctypes.c_int32), + ("capacity", ctypes.c_uint64), + ("len", ctypes.c_uint64), ("data", ctypes.POINTER(ctypes.c_char)), ] @staticmethod + def default(): + return _UniffiRustBuffer(0, 0, None) + + @staticmethod def alloc(size): return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}, size) @@ -137,9 +141,6 @@ class _UniffiRustBufferStream: def read_double(self): return self._unpack_from(8, ">d") - def read_c_size_t(self): - return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N") - class _UniffiRustBufferBuilder: """ Helper for structured writing of bytes into a _UniffiRustBuffer. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py index 3c9f5a4596..3c30d9f9f5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py @@ -2,6 +2,11 @@ class {{ ffi_converter_name}}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, value): + for item in value: + {{ inner_ffi_converter }}.check_lower(item) + + @classmethod def write(cls, value, buf): items = len(value) buf.write_i32(items) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py index 40890b6abc..d574edd09f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py @@ -1,6 +1,6 @@ class _UniffiConverterString: @staticmethod - def check(value): + def check_lower(value): if not isinstance(value, str): raise TypeError("argument must be str, not {}".format(type(value).__name__)) return value @@ -15,7 +15,6 @@ class _UniffiConverterString: @staticmethod def write(value, buf): - value = _UniffiConverterString.check(value) utf8_bytes = value.encode("utf-8") buf.write_i32(len(utf8_bytes)) buf.write(utf8_bytes) @@ -27,7 +26,6 @@ class _UniffiConverterString: @staticmethod def lower(value): - value = _UniffiConverterString.check(value) with _UniffiRustBuffer.alloc_with_builder() as builder: builder.write(value.encode("utf-8")) return builder.finalize() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py index 8402f6095d..76d7f8bcdb 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py @@ -18,6 +18,10 @@ class _UniffiConverterTimestamp(_UniffiConverterRustBuffer): return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds) @staticmethod + def check_lower(value): + pass + + @staticmethod def write(value, buf): if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc): sign = 1 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py index f258b60a1c..230b9e853f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -1,7 +1,15 @@ {%- if func.is_async() %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): - return _uniffi_rust_call_async( +{%- match func.return_type() -%} +{%- when Some with (return_type) %} +async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": +{% when None %} +async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: +{% endmatch %} + + {%- call py::docstring(func, 4) %} + {%- call py::setup_args(func) %} + return await _uniffi_rust_call_async( _UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}), _UniffiLib.{{func.ffi_rust_future_poll(ci) }}, _UniffiLib.{{func.ffi_rust_future_complete(ci) }}, @@ -13,13 +21,7 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when None %} lambda val: None, {% endmatch %} - # Error FFI converter - {%- match func.throws_type() %} - {%- when Some(e) %} - {{ e|ffi_converter_name }}, - {%- when None %} - None, - {%- endmatch %} + {% call py::error_ffi_converter(func) %} ) {%- else %} @@ -27,11 +29,13 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when Some with (return_type) %} def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %}) {% when None %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): +def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} {% call py::to_ffi_call(func) %} {% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py index 5e05314c37..4aaed253e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py @@ -85,7 +85,7 @@ {%- when Type::Map { key_type, value_type } %} {%- include "MapTemplate.py" %} -{%- when Type::CallbackInterface { name: id, module_path } %} +{%- when Type::CallbackInterface { name, module_path } %} {%- include "CallbackInterfaceTemplate.py" %} {%- when Type::Custom { name, module_path, builtin } %} @@ -94,9 +94,6 @@ {%- when Type::External { name, module_path, namespace, kind, tagged } %} {%- include "ExternalTemplate.py" %} -{%- when Type::ForeignExecutor %} -{%- include "ForeignExecutorTemplate.py" %} - {%- else %} {%- endmatch %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py index 081c6731ce..039bf76162 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt): return buf.read_u16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py index b80e75177d..1650bf9b60 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt): return buf.read_u32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py index 4b87e58547..f354545e26 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt64(_UniffiConverterPrimitiveInt): return buf.read_u64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py index 33026706f2..cee130b4d9 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt8(_UniffiConverterPrimitiveInt): return buf.read_u8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py index ef3b1bb94d..6818a8c107 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -5,27 +5,29 @@ #} {%- macro to_ffi_call(func) -%} - {%- match func.throws_type() -%} - {%- when Some with (e) -%} -_rust_call_with_error({{ e|ffi_converter_name }}, - {%- else -%} -_rust_call( - {%- endmatch -%} - _UniffiLib.{{ func.ffi_func().name() }}, - {%- call arg_list_lowered(func) -%} -) +{%- call _to_ffi_call_with_prefix_arg("", func) %} {%- endmacro -%} {%- macro to_ffi_call_with_prefix(prefix, func) -%} - {%- match func.throws_type() -%} - {%- when Some with (e) -%} -_rust_call_with_error( - {{ e|ffi_converter_name }}, - {%- else -%} +{%- call _to_ffi_call_with_prefix_arg(format!("{},", prefix), func) %} +{%- endmacro -%} + +{%- macro _to_ffi_call_with_prefix_arg(prefix, func) -%} +{%- match func.throws_type() -%} +{%- when Some with (e) -%} +{%- match e -%} +{%- when Type::Enum { name, module_path } -%} +_rust_call_with_error({{ e|ffi_converter_name }}, +{%- when Type::Object { name, module_path, imp } -%} +_rust_call_with_error({{ e|ffi_converter_name }}__as_error, +{%- else %} +# unsupported error type! +{%- endmatch %} +{%- else -%} _rust_call( - {%- endmatch -%} +{%- endmatch -%} _UniffiLib.{{ func.ffi_func().name() }}, - {{- prefix }}, + {{- prefix }} {%- call arg_list_lowered(func) -%} ) {%- endmacro -%} @@ -37,6 +39,19 @@ _rust_call( {%- endfor %} {%- endmacro -%} +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{{ "" }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} + {#- // Arglist as used in Python declarations of methods, functions and constructors. // Note the var_name and type_name filters. @@ -76,6 +91,7 @@ _rust_call( if {{ arg.name()|var_name }} is _DEFAULT: {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} + {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) {% endfor -%} {%- endmacro -%} @@ -91,6 +107,7 @@ _rust_call( if {{ arg.name()|var_name }} is _DEFAULT: {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} + {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) {% endfor -%} {%- endmacro -%} @@ -100,11 +117,18 @@ _rust_call( {%- macro method_decl(py_method_name, meth) %} {% if meth.is_async() %} - def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): +{%- match meth.return_type() %} +{%- when Some with (return_type) %} + async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": +{%- when None %} + async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None: +{% endmatch %} + + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - return _uniffi_rust_call_async( + return await _uniffi_rust_call_async( _UniffiLib.{{ meth.ffi_func().name() }}( - self._pointer, {% call arg_list_lowered(meth) %} + self._uniffi_clone_pointer(), {% call arg_list_lowered(meth) %} ), _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, @@ -116,13 +140,7 @@ _rust_call( {%- when None %} lambda val: None, {% endmatch %} - # Error FFI converter - {%- match meth.throws_type() %} - {%- when Some(e) %} - {{ e|ffi_converter_name }}, - {%- when None %} - None, - {%- endmatch %} + {% call error_ffi_converter(meth) %} ) {%- else -%} @@ -131,17 +149,36 @@ _rust_call( {%- when Some with (return_type) %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} return {{ return_type|lift_fn }}( - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %} ) {%- when None %} - def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None: + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %} {% endmatch %} {% endif %} {% endmacro %} + +{%- macro error_ffi_converter(func) %} + # Error FFI converter +{% match func.throws_type() %} +{%- when Some(e) %} +{%- match e -%} +{%- when Type::Enum { name, module_path } -%} + {{ e|ffi_converter_name }}, +{%- when Type::Object { name, module_path, imp } -%} + {{ e|ffi_converter_name }}__as_error, +{%- else %} + # unsupported error type! +{%- endmatch %} +{%- when None %} + None, +{%- endmatch %} +{% endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py index 24c3290ff7..1ccd6821c0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -1,3 +1,5 @@ +{%- call py::docstring_value(ci.namespace_docstring(), 0) %} + # This file was autogenerated by some hot garbage in the `uniffi` crate. # Trust me, you don't want to mess with it! @@ -13,6 +15,7 @@ # compile the rust component. The easiest way to ensure this is to bundle the Python # helpers directly inline like we're doing here. +from __future__ import annotations import os import sys import ctypes @@ -20,6 +23,9 @@ import enum import struct import contextlib import datetime +import threading +import itertools +import traceback import typing {%- if ci.has_async_fns() %} import asyncio @@ -34,20 +40,20 @@ _DEFAULT = object() {% include "RustBufferTemplate.py" %} {% include "Helpers.py" %} -{% include "PointerManager.py" %} +{% include "HandleMap.py" %} {% include "RustBufferHelper.py" %} # Contains loading, initialization code, and the FFI Function declarations. {% include "NamespaceLibraryTemplate.py" %} +# Public interface members begin here. +{{ type_helper_code }} + # Async support {%- if ci.has_async_fns() %} {%- include "Async.py" %} {%- endif %} -# Public interface members begin here. -{{ type_helper_code }} - {%- for func in ci.function_definitions() %} {%- include "TopLevelFunctionTemplate.py" %} {%- endfor %} @@ -69,6 +75,9 @@ __all__ = [ {%- for c in ci.callback_interface_definitions() %} "{{ c.name()|class_name }}", {%- endfor %} + {%- if ci.has_async_fns() %} + "uniffi_set_event_loop", + {%- endif %} ] {% import "macros.py" as py %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs index 0fcf09996f..0c23140b33 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs @@ -2,10 +2,8 @@ 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/. */ -use crate::{ - bindings::{RunScriptOptions, TargetLanguage}, - library_mode::generate_bindings, -}; +use crate::bindings::TargetLanguage; +use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{Context, Result}; use camino::Utf8Path; use std::env; @@ -34,14 +32,18 @@ pub fn run_script( args: Vec<String>, _options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; let test_helper = UniFFITestHelper::new(crate_name)?; let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; generate_bindings( &cdylib_path, None, - &[TargetLanguage::Python], + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Python], + try_format_code: false, + }, + None, &out_dir, false, )?; |