diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/uniffi_bindgen/src/scaffolding | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/scaffolding')
12 files changed, 879 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs new file mode 100644 index 0000000000..df170f240f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs @@ -0,0 +1,152 @@ +/* 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 anyhow::Result; +use askama::Template; +use std::borrow::Borrow; + +use super::interface::*; +use heck::ToSnakeCase; + +#[derive(Template)] +#[template(syntax = "rs", escape = "none", path = "scaffolding_template.rs")] +pub struct RustScaffolding<'a> { + ci: &'a ComponentInterface, + uniffi_version: &'static str, +} +impl<'a> RustScaffolding<'a> { + pub fn new(ci: &'a ComponentInterface) -> Self { + Self { + ci, + uniffi_version: crate::BINDGEN_VERSION, + } + } +} +mod filters { + use super::*; + + pub fn type_rs(type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Int8 => "i8".into(), + Type::UInt8 => "u8".into(), + Type::Int16 => "i16".into(), + Type::UInt16 => "u16".into(), + Type::Int32 => "i32".into(), + Type::UInt32 => "u32".into(), + Type::Int64 => "i64".into(), + Type::UInt64 => "u64".into(), + Type::Float32 => "f32".into(), + Type::Float64 => "f64".into(), + Type::Boolean => "bool".into(), + Type::String => "String".into(), + Type::Timestamp => "std::time::SystemTime".into(), + Type::Duration => "std::time::Duration".into(), + Type::Enum(name) | Type::Record(name) | Type::Error(name) => format!("r#{}", name), + Type::Object(name) => format!("std::sync::Arc<r#{}>", name), + Type::CallbackInterface(name) => format!("Box<dyn r#{}>", name), + Type::Optional(t) => format!("std::option::Option<{}>", type_rs(t)?), + Type::Sequence(t) => format!("std::vec::Vec<{}>", type_rs(t)?), + Type::Map(k, v) => format!( + "std::collections::HashMap<{}, {}>", + type_rs(k)?, + type_rs(v)? + ), + Type::Custom { name, .. } => format!("r#{}", name), + Type::External { .. } => panic!("External types coming to a uniffi near you soon!"), + Type::Unresolved { .. } => { + unreachable!("UDL scaffolding code never contains unresolved types") + } + }) + } + + pub fn type_ffi(type_: &FFIType) -> Result<String, askama::Error> { + Ok(match type_ { + FFIType::Int8 => "i8".into(), + FFIType::UInt8 => "u8".into(), + FFIType::Int16 => "i16".into(), + FFIType::UInt16 => "u16".into(), + FFIType::Int32 => "i32".into(), + FFIType::UInt32 => "u32".into(), + FFIType::Int64 => "i64".into(), + FFIType::UInt64 => "u64".into(), + FFIType::Float32 => "f32".into(), + FFIType::Float64 => "f64".into(), + FFIType::RustArcPtr(_) => "*const std::os::raw::c_void".into(), + FFIType::RustBuffer => "uniffi::RustBuffer".into(), + FFIType::ForeignBytes => "uniffi::ForeignBytes".into(), + FFIType::ForeignCallback => "uniffi::ForeignCallback".into(), + }) + } + + /// Get the name of the FfiConverter implementation for this type + /// + /// - For primitives / standard types this is the type itself. + /// - For user-defined types, this is a unique generated name. We then generate a unit-struct + /// in the scaffolding code that implements FfiConverter. + pub fn ffi_converter_name(type_: &Type) -> askama::Result<String> { + Ok(match type_ { + // Timestamp/Duraration are handled by standard types + Type::Timestamp => "std::time::SystemTime".into(), + Type::Duration => "std::time::Duration".into(), + // Object is handled by Arc<T> + Type::Object(name) => format!("std::sync::Arc<r#{}>", name), + // Other user-defined types are handled by a unit-struct that we generate. The + // FfiConverter implementation for this can be found in one of the scaffolding template code. + // + // We generate a unit-struct to sidestep Rust's orphan rules (ADR-0006). + // + // CallbackInterface is handled by special case code on both the scaffolding and + // bindings side. It's not a unit-struct, but the same name generation code works. + Type::Enum(_) | Type::Record(_) | Type::Error(_) | Type::CallbackInterface(_) => { + format!("FfiConverter{}", type_.canonical_name()) + } + // Wrapper types are implemented by generics that wrap the FfiConverter implementation of the + // inner type. + Type::Optional(inner) => { + format!("std::option::Option<{}>", ffi_converter_name(inner)?) + } + Type::Sequence(inner) => format!("std::vec::Vec<{}>", ffi_converter_name(inner)?), + Type::Map(k, v) => format!( + "std::collections::HashMap<{}, {}>", + ffi_converter_name(k)?, + ffi_converter_name(v)? + ), + // External and Wrapped bytes have FfiConverters with a predictable name based on the type name. + Type::Custom { name, .. } | Type::External { name, .. } => { + format!("FfiConverterType{}", name) + } + // Primitive types / strings are implemented by their rust type + Type::Int8 => "i8".into(), + Type::UInt8 => "u8".into(), + Type::Int16 => "i16".into(), + Type::UInt16 => "u16".into(), + Type::Int32 => "i32".into(), + Type::UInt32 => "u32".into(), + Type::Int64 => "i64".into(), + Type::UInt64 => "u64".into(), + Type::Float32 => "f32".into(), + Type::Float64 => "f64".into(), + Type::String => "String".into(), + Type::Boolean => "bool".into(), + Type::Unresolved { .. } => { + unreachable!("UDL scaffolding code never contains unresolved types") + } + }) + } + + // Map a type to Rust code that specifies the FfiConverter implementation. + // + // This outputs something like `<TheFfiConverterStruct as FfiConverter>` + pub fn ffi_converter(type_: &Type) -> Result<String, askama::Error> { + Ok(format!( + "<{} as uniffi::FfiConverter>", + ffi_converter_name(type_)? + )) + } + + // Turns a `crate-name` into the `crate_name` the .rs code needs to specify. + pub fn crate_name_rs(nm: &str) -> Result<String, askama::Error> { + Ok(nm.to_string().to_snake_case()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs new file mode 100644 index 0000000000..166da0d994 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs @@ -0,0 +1,186 @@ +{# +// For each Callback Interface definition, we assume that there is a corresponding trait defined in Rust client code. +// If the UDL callback interface and Rust trait's methods don't match, the Rust compiler will complain. +// We generate: +// * an init function to accept that `ForeignCallback` from the foreign language, and stores it. +// * a holder for a `ForeignCallback`, of type `uniffi::ForeignCallbackInternals`. +// * a proxy `struct` which implements the `trait` that the Callback Interface corresponds to. This +// is the object that client code interacts with. +// - for each method, arguments will be packed into a `RustBuffer` and sent over the `ForeignCallback` to be +// unpacked and called. The return value is packed into another `RustBuffer` and sent back to Rust. +// - a `Drop` `impl`, which tells the foreign language to forget about the real callback object. +#} +{% let trait_name = cbi.name() -%} +{% let trait_impl = cbi.type_().borrow()|ffi_converter_name -%} +{% let foreign_callback_internals = format!("foreign_callback_{}_internals", trait_name)|upper -%} + +// Register a foreign callback for getting across the FFI. +#[doc(hidden)] +static {{ foreign_callback_internals }}: uniffi::ForeignCallbackInternals = uniffi::ForeignCallbackInternals::new(); + +#[doc(hidden)] +#[no_mangle] +pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::ForeignCallback, _: &mut uniffi::RustCallStatus) { + {{ foreign_callback_internals }}.set_callback(callback); + // The call status should be initialized to CALL_SUCCESS, so no need to modify it. +} + +// Make an implementation which will shell out to the foreign language. +#[doc(hidden)] +#[derive(Debug)] +struct {{ trait_impl }} { + handle: u64 +} + +impl Drop for {{ trait_impl }} { + fn drop(&mut self) { + let callback = {{ foreign_callback_internals }}.get_callback().unwrap(); + let mut rbuf = uniffi::RustBuffer::new(); + unsafe { callback(self.handle, uniffi::IDX_CALLBACK_FREE, Default::default(), &mut rbuf) }; + } +} + +uniffi::deps::static_assertions::assert_impl_all!({{ trait_impl }}: Send); + +impl r#{{ trait_name }} for {{ trait_impl }} { + {%- for meth in cbi.methods() %} + + {#- Method declaration #} + fn r#{{ meth.name() -}} + ({% call rs::arg_list_decl_with_prefix("&self", meth) %}) + {%- match (meth.return_type(), meth.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type.borrow()|type_rs }} + {%- when (Some(return_type), Some(err)) %} -> ::std::result::Result<{{ return_type.borrow()|type_rs }}, {{ err|type_rs }}> + {%- when (None, Some(err)) %} -> ::std::result::Result<(), {{ err|type_rs }}> + {% else -%} + {%- endmatch -%} { + {#- Method body #} + uniffi::deps::log::debug!("{{ cbi.name() }}.{{ meth.name() }}"); + + {#- Packing args into a RustBuffer #} + {% if meth.arguments().len() == 0 -%} + let args_buf = Vec::new(); + {% else -%} + let mut args_buf = Vec::new(); + {% endif -%} + {%- for arg in meth.arguments() %} + {{ arg.type_().borrow()|ffi_converter }}::write(r#{{ arg.name() }}, &mut args_buf); + {%- endfor -%} + let args_rbuf = uniffi::RustBuffer::from_vec(args_buf); + + {#- Calling into foreign code. #} + let callback = {{ foreign_callback_internals }}.get_callback().unwrap(); + + unsafe { + // SAFETY: + // * We're passing in a pointer to an empty buffer. + // * Nothing allocated, so nothing to drop. + // * We expect the callback to write into that a valid allocated instance of a + // RustBuffer. + let mut ret_rbuf = uniffi::RustBuffer::new(); + let ret = callback(self.handle, {{ loop.index }}, args_rbuf, &mut ret_rbuf); + #[allow(clippy::let_and_return, clippy::let_unit_value)] + match ret { + 1 => { + // 1 indicates success with the return value written to the RustBuffer for + // non-void calls. + let result = { + {% match meth.return_type() -%} + {%- when Some(return_type) -%} + let vec = ret_rbuf.destroy_into_vec(); + let mut ret_buf = vec.as_slice(); + {{ return_type|ffi_converter }}::try_read(&mut ret_buf).unwrap() + {%- else %} + uniffi::RustBuffer::destroy(ret_rbuf); + {%- endmatch %} + }; + {%- if meth.throws() %} + Ok(result) + {%- else %} + result + {%- endif %} + } + -2 => { + // -2 indicates an error written to the RustBuffer + {% match meth.throws_type() -%} + {% when Some(error_type) -%} + let vec = ret_rbuf.destroy_into_vec(); + let mut ret_buf = vec.as_slice(); + Err({{ error_type|ffi_converter }}::try_read(&mut ret_buf).unwrap()) + {%- else -%} + panic!("Callback return -2, but throws_type() is None"); + {%- endmatch %} + } + // 0 is a deprecated method to indicates success for void returns + 0 => { + eprintln!("UniFFI: Callback interface returned 0. Please update the bindings code to return 1 for all successfull calls"); + {% match (meth.return_type(), meth.throws()) %} + {% when (Some(_), _) %} + panic!("Callback returned 0 when we were expecting a return value"); + {% when (None, false) %} + {% when (None, true) %} + Ok(()) + {%- endmatch %} + } + // -1 indicates an unexpected error + {% match meth.throws_type() %} + {%- when Some(error_type) -%} + -1 => { + let reason = if !ret_rbuf.is_empty() { + match {{ Type::String.borrow()|ffi_converter }}::try_lift(ret_rbuf) { + Ok(s) => s, + Err(e) => { + println!("{{ trait_name }} Error reading ret_buf: {e}"); + String::from("[Error reading reason]") + } + } + } else { + uniffi::RustBuffer::destroy(ret_rbuf); + String::from("[Unknown Reason]") + }; + let e: {{ error_type|type_rs }} = uniffi::UnexpectedUniFFICallbackError::from_reason(reason).into(); + Err(e) + } + {%- else %} + -1 => panic!("Callback failed"), + {%- endmatch %} + // Other values should never be returned + _ => panic!("Callback failed with unexpected return code"), + } + } + } + {%- endfor %} +} + +unsafe impl uniffi::FfiConverter for {{ trait_impl }} { + // This RustType allows for rust code that inputs this type as a Box<dyn CallbackInterfaceTrait> param + type RustType = Box<dyn r#{{ trait_name }}>; + type FfiType = u64; + + // Lower and write are tricky to implement because we have a dyn trait as our type. There's + // probably a way to, but this carries lots of thread safety risks, down to impedence + // mismatches between Rust and foreign languages, and our uncertainty around implementations of + // concurrent handlemaps. + // + // The use case for them is also quite exotic: it's passing a foreign callback back to the foreign + // language. + // + // Until we have some certainty, and use cases, we shouldn't use them. + fn lower(_obj: Self::RustType) -> Self::FfiType { + panic!("Lowering CallbackInterface not supported") + } + + fn write(_obj: Self::RustType, _buf: &mut std::vec::Vec<u8>) { + panic!("Writing CallbackInterface not supported") + } + + fn try_lift(v: Self::FfiType) -> uniffi::deps::anyhow::Result<Self::RustType> { + Ok(Box::new(Self { handle: v })) + } + + fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<Self::RustType> { + use uniffi::deps::bytes::Buf; + uniffi::check_remaining(buf, 8)?; + <Self as uniffi::FfiConverter>::try_lift(buf.get_u64()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs new file mode 100644 index 0000000000..0d83a99b90 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -0,0 +1,45 @@ +{# +// For each enum declared in the UDL, we assume the caller has provided a corresponding +// rust `enum`. We provide the traits for sending it across the FFI, which will fail to +// compile if the provided struct has a different shape to the one declared in the UDL. +// +// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's +// public so other crates can refer to it via an `[External='crate'] typedef` +#} + +#[doc(hidden)] +pub struct {{ e.type_().borrow()|ffi_converter_name }}; + +#[doc(hidden)] +impl uniffi::RustBufferFfiConverter for {{ e.type_().borrow()|ffi_converter_name }} { + type RustType = r#{{ e.name() }}; + + fn write(obj: Self::RustType, buf: &mut std::vec::Vec<u8>) { + use uniffi::deps::bytes::BufMut; + match obj { + {%- for variant in e.variants() %} + r#{{ e.name() }}::r#{{ variant.name() }} { {% for field in variant.fields() %}r#{{ field.name() }}, {%- endfor %} } => { + buf.put_i32({{ loop.index }}); + {% for field in variant.fields() -%} + {{ field.type_().borrow()|ffi_converter }}::write(r#{{ field.name() }}, buf); + {%- endfor %} + }, + {%- endfor %} + }; + } + + fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> { + use uniffi::deps::bytes::Buf; + uniffi::check_remaining(buf, 4)?; + Ok(match buf.get_i32() { + {%- for variant in e.variants() %} + {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { + {% for field in variant.fields() %} + r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?, + {%- endfor %} + }{% endif %}, + {%- endfor %} + v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), + }) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs new file mode 100644 index 0000000000..7ca1d104fe --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -0,0 +1,95 @@ +{# +// For each error declared in the UDL, we assume the caller has provided a corresponding +// rust `enum`. We provide the traits for sending it across the FFI, which will fail to +// compile if the provided struct has a different shape to the one declared in the UDL. +// +// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's +// public so other crates can refer to it via an `[External='crate'] typedef` +#} + +#[doc(hidden)] +pub struct {{ e.type_().borrow()|ffi_converter_name }}; + +#[doc(hidden)] +impl uniffi::RustBufferFfiConverter for {{ e.type_().borrow()|ffi_converter_name }} { + type RustType = r#{{ e.name() }}; + + {% if e.is_flat() %} + + // For "flat" error enums, we stringify the error on the Rust side and surface that + // as the error message in the foreign language. + + + fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec<u8>) { + use uniffi::deps::bytes::BufMut; + let msg = obj.to_string(); + match obj { + {%- for variant in e.variants() %} + r#{{ e.name() }}::r#{{ variant.name() }}{..} => { + buf.put_i32({{ loop.index }}); + <String as uniffi::FfiConverter>::write(msg, buf); + }, + {%- endfor %} + }; + } + + {%- if ci.should_generate_error_read(e) %} + fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> { + use uniffi::deps::bytes::Buf; + uniffi::check_remaining(buf, 4)?; + Ok(match buf.get_i32() { + {%- for variant in e.variants() %} + {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{ }, + {%- endfor %} + v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), + }) + } + {%- else %} + fn try_read(_buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> { + panic!("try_read not supported for flat errors"); + } + {%- endif %} + + {% else %} + + // For rich structured enums, we map individual fields on the Rust side over to + // corresponding fields on the foreign-language side. + // + // If a variant doesn't have fields defined in the UDL, it's currently still possible that + // the Rust enum has fields and they're just not listed. In that case we use the `Variant{..}` + // syntax to match the variant while ignoring its fields. + + fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec<u8>) { + use uniffi::deps::bytes::BufMut; + match obj { + {%- for variant in e.variants() %} + r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { {% for field in variant.fields() %}r#{{ field.name() }}, {%- endfor %} }{% else %}{..}{% endif %} => { + buf.put_i32({{ loop.index }}); + {% for field in variant.fields() -%} + {{ field.type_().borrow()|ffi_converter }}::write(r#{{ field.name() }}, buf); + {%- endfor %} + }, + {%- endfor %} + }; + } + + fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> { + // Note: no need to call should_generate_error_read here, since it is always true for + // non-flat errors + use uniffi::deps::bytes::Buf; + uniffi::check_remaining(buf, 4)?; + Ok(match buf.get_i32() { + {%- for variant in e.variants() %} + {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { + {% for field in variant.fields() %} + r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?, + {%- endfor %} + }{% endif %}, + {%- endfor %} + v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v), + }) + } + {% endif %} +} + +impl uniffi::FfiError for {{ e.type_().borrow()|ffi_converter_name }} { } diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs new file mode 100644 index 0000000000..078c4e2462 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs @@ -0,0 +1,48 @@ +// Support for external types. + +// Types with an external `FfiConverter`... +{% for (name, crate_name) in ci.iter_external_types() %} +// `{{ name }}` is defined in `{{ crate_name }}` +use {{ crate_name|crate_name_rs }}::FfiConverterType{{ name }}; +{% endfor %} + +// For custom scaffolding types we need to generate an FfiConverterType based on the +// UniffiCustomTypeConverter implementation that the library supplies +{% for (name, builtin) in ci.iter_custom_types() %} +{% if loop.first %} + +// A trait that's in our crate for our external wrapped types to implement. +trait UniffiCustomTypeConverter { + type Builtin; + + fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> where Self: Sized; + fn from_custom(obj: Self) -> Self::Builtin; +} + +{%- endif -%} + +// Type `{{ name }}` wraps a `{{ builtin.canonical_name() }}` +#[doc(hidden)] +pub struct FfiConverterType{{ name }}; + +unsafe impl uniffi::FfiConverter for FfiConverterType{{ name }} { + type RustType = r#{{ name }}; + type FfiType = {{ FFIType::from(builtin).borrow()|type_ffi }}; + + fn lower(obj: {{ name }} ) -> Self::FfiType { + <{{ builtin|type_rs }} as uniffi::FfiConverter>::lower(<{{ name }} as UniffiCustomTypeConverter>::from_custom(obj)) + } + + fn try_lift(v: Self::FfiType) -> uniffi::Result<{{ name }}> { + <r#{{ name }} as UniffiCustomTypeConverter>::into_custom(<{{ builtin|type_rs }} as uniffi::FfiConverter>::try_lift(v)?) + } + + fn write(obj: {{ name }}, buf: &mut Vec<u8>) { + <{{ builtin|type_rs }} as uniffi::FfiConverter>::write(<{{ name }} as UniffiCustomTypeConverter>::from_custom(obj), buf); + } + + fn try_read(buf: &mut &[u8]) -> uniffi::Result<r#{{ name }}> { + <{{ name }} as UniffiCustomTypeConverter>::into_custom(<{{ builtin|type_rs }} as uniffi::FfiConverter>::try_read(buf)?) + } +} +{%- endfor -%} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs new file mode 100644 index 0000000000..3834ad994e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -0,0 +1,73 @@ +// For each Object definition, we assume the caller has provided an appropriately-shaped `struct T` +// with an `impl` for each method on the object. We create an `Arc<T>` for "safely" handing out +// references to these structs to foreign language code, and we provide a `pub extern "C"` function +// corresponding to each method. +// +// (Note that "safely" is in "scare quotes" - that's because we use functions on an `Arc` that +// that are inherently unsafe, but the code we generate is safe in practice.) +// +// If the caller's implementation of the struct does not match with the methods or types specified +// in the UDL, then the rust compiler will complain with a (hopefully at least somewhat helpful!) +// error message when processing this generated code. + +{% if obj.uses_deprecated_threadsafe_attribute() %} +// We want to mark this as `deprecated` - long story short, the only way to +// sanely do this using `#[deprecated(..)]` is to generate a function with that +// attribute, then generate call to that function in the object constructors. +#[deprecated( + since = "0.11.0", + note = "The `[Threadsafe]` attribute on interfaces is now the default and its use is deprecated - you should upgrade \ + `{{ obj.name() }}` to remove the `[ThreadSafe]` attribute. \ + See https://github.com/mozilla/uniffi-rs/#thread-safety for more details" +)] +#[allow(non_snake_case)] +fn uniffi_note_threadsafe_deprecation_{{ obj.name() }}() {} +{% endif %} + + +// All Object structs must be `Sync + Send`. The generated scaffolding will fail to compile +// if they are not, but unfortunately it fails with an unactionably obscure error message. +// By asserting the requirement explicitly, we help Rust produce a more scrutable error message +// and thus help the user debug why the requirement isn't being met. +uniffi::deps::static_assertions::assert_impl_all!(r#{{ obj.name() }}: Sync, Send); + +{% let ffi_free = obj.ffi_object_free() -%} +#[doc(hidden)] +#[no_mangle] +pub extern "C" fn {{ ffi_free.name() }}(ptr: *const std::os::raw::c_void, call_status: &mut uniffi::RustCallStatus) { + uniffi::call_with_output(call_status, || { + assert!(!ptr.is_null()); + {#- turn it into an Arc and explicitly drop it. #} + drop(unsafe { std::sync::Arc::from_raw(ptr as *const r#{{ obj.name() }}) }) + }) +} + +{%- for cons in obj.constructors() %} + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn r#{{ cons.ffi_func().name() }}( + {%- call rs::arg_list_ffi_decl(cons.ffi_func()) %}) -> *const std::os::raw::c_void /* *const {{ obj.name() }} */ { + uniffi::deps::log::debug!("{{ cons.ffi_func().name() }}"); + {% if obj.uses_deprecated_threadsafe_attribute() %} + uniffi_note_threadsafe_deprecation_{{ obj.name() }}(); + {% endif %} + + // If the constructor does not have the same signature as declared in the UDL, then + // this attempt to call it will fail with a (somewhat) helpful compiler error. + {% call rs::to_rs_constructor_call(obj, cons) %} + } +{%- endfor %} + +{%- for meth in obj.methods() %} + #[doc(hidden)] + #[no_mangle] + #[allow(clippy::let_unit_value)] // Sometimes we generate code that binds `_retval` to `()`. + pub extern "C" fn r#{{ meth.ffi_func().name() }}( + {%- call rs::arg_list_ffi_decl(meth.ffi_func()) %} + ) {% call rs::return_signature(meth) %} { + uniffi::deps::log::debug!("{{ meth.ffi_func().name() }}"); + // If the method does not have the same signature as declared in the UDL, then + // this attempt to call it will fail with a (somewhat) helpful compiler error. + {% call rs::to_rs_method_call(obj, meth) %} + } +{% endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs new file mode 100644 index 0000000000..6ec85ed036 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -0,0 +1,33 @@ +{# +// For each record declared in the UDL, we assume the caller has provided a corresponding +// rust `struct` with the declared fields. We provide the traits for sending it across the FFI. +// If the caller's struct does not match the shape and types declared in the UDL then the rust +// compiler will complain with a type error. +// +// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's +// public so other crates can refer to it via an `[External='crate'] typedef` +#} + +#[doc(hidden)] +pub struct {{ rec.type_().borrow()|ffi_converter_name }}; + +#[doc(hidden)] +impl uniffi::RustBufferFfiConverter for {{ rec.type_().borrow()|ffi_converter_name }} { + type RustType = r#{{ rec.name() }}; + + fn write(obj: r#{{ rec.name() }}, buf: &mut std::vec::Vec<u8>) { + // If the provided struct doesn't match the fields declared in the UDL, then + // the generated code here will fail to compile with somewhat helpful error. + {%- for field in rec.fields() %} + {{ field.type_().borrow()|ffi_converter }}::write(obj.r#{{ field.name() }}, buf); + {%- endfor %} + } + + fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ rec.name() }}> { + Ok(r#{{ rec.name() }} { + {%- for field in rec.fields() %} + r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?, + {%- endfor %} + }) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs new file mode 100644 index 0000000000..668f1a2e63 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs @@ -0,0 +1,27 @@ +// Code to re-export the UniFFI scaffolding functions. +// +// Rust won't always re-export the functions from dependencies +// ([rust-lang#50007](https://github.com/rust-lang/rust/issues/50007)) +// +// A workaround for this is to have the dependent crate reference a function from its dependency in +// an extern "C" function. This is clearly hacky and brittle, but at least we have some unittests +// that check if this works (fixtures/reexport-scaffolding-macro). +// +// The main way we use this macro is for that contain multiple UniFFI components (libxul, +// megazord). The combined library has a cargo dependency for each component and calls +// uniffi_reexport_scaffolding!() for each one. + +#[doc(hidden)] +pub fn uniffi_reexport_hack() { +} + +#[macro_export] +macro_rules! uniffi_reexport_scaffolding { + () => { + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn {{ ci.namespace() }}_uniffi_reexport_hack() { + $crate::uniffi_reexport_hack() + } + }; +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs new file mode 100644 index 0000000000..39d3f32dcc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs @@ -0,0 +1,27 @@ +// Everybody gets basic buffer support, since it's needed for passing complex types over the FFI. +// +// See `uniffi/src/ffi/rustbuffer.rs` for documentation on these functions + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub extern "C" fn {{ ci.ffi_rustbuffer_alloc().name() }}(size: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn {{ ci.ffi_rustbuffer_from_bytes().name() }}(bytes: uniffi::ForeignBytes, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + uniffi::ffi::uniffi_rustbuffer_from_bytes(bytes, call_status) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn {{ ci.ffi_rustbuffer_free().name() }}(buf: uniffi::RustBuffer, call_status: &mut uniffi::RustCallStatus) { + uniffi::ffi::uniffi_rustbuffer_free(buf, call_status) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn {{ ci.ffi_rustbuffer_reserve().name() }}(buf: uniffi::RustBuffer, additional: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status) +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs new file mode 100644 index 0000000000..0c6d5a47de --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs @@ -0,0 +1,17 @@ +{# +// For each top-level function declared in the UDL, we assume the caller has provided a corresponding +// rust function of the same name. We provide a `pub extern "C"` wrapper that does type conversions to +// send data across the FFI, which will fail to compile if the provided function does not match what's +// specified in the UDL. +#} +#[doc(hidden)] +#[no_mangle] +#[allow(clippy::let_unit_value)] // Sometimes we generate code that binds `_retval` to `()`. +pub extern "C" fn r#{{ func.ffi_func().name() }}( + {% call rs::arg_list_ffi_decl(func.ffi_func()) %} +) {% call rs::return_signature(func) %} { + // If the provided function does not match the signature specified in the UDL + // then this attempt to call it will not compile, and will give guidance as to why. + uniffi::deps::log::debug!("{{ func.ffi_func().name() }}"); + {% call rs::to_rs_function_call(func) %} +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs new file mode 100644 index 0000000000..22ee543b3e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs @@ -0,0 +1,118 @@ +{# +// Template to receive calls into rust. +#} + +{%- macro to_rs_call(func) -%} +r#{{ func.name() }}({% call _arg_list_rs_call(func) -%}) +{%- endmacro -%} + +{%- macro _arg_list_rs_call(func) %} + {%- for arg in func.full_arguments() %} + match {{- arg.type_().borrow()|ffi_converter }}::try_lift(r#{{ arg.name() }}) { + {%- if arg.by_ref() %} + Ok(ref val) => val, + {% else %} + Ok(val) => val, + {% endif %} + + {# If this function returns an error, we attempt to downcast errors doing arg + conversions to this error. If the downcast fails or the function doesn't + return an error, we just panic. + #} + {%- match func.throws_type() -%} + {%- when Some with (e) %} + Err(err) => return Err(uniffi::lower_anyhow_error_or_panic::<{{ e|ffi_converter_name }}>(err, "{{ arg.name() }}")), + {%- else %} + Err(err) => panic!("Failed to convert arg '{}': {}", "{{ arg.name() }}", err), + {%- endmatch %} + } + {%- if !loop.last %}, {% endif %} + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in the _UniFFILib function declations. +// Note unfiltered name but type_ffi filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + r#{{- arg.name() }}: {{ arg.type_().borrow()|type_ffi -}}, + {%- endfor %} + call_status: &mut uniffi::RustCallStatus +{%- endmacro -%} + +{%- macro arg_list_decl_with_prefix(prefix, meth) %} + {{- prefix -}} + {%- if meth.arguments().len() > 0 %}, {# whitespace #} + {%- for arg in meth.arguments() %} + r#{{- arg.name() }}: {{ arg.type_().borrow()|type_rs -}}{% if loop.last %}{% else %},{% endif %} + {%- endfor %} + {%- endif %} +{%- endmacro -%} + +{% macro return_signature(func) %}{% match func.ffi_func().return_type() %}{% when Some with (return_type) %} -> {% call return_type_func(func) %}{%- else -%}{%- endmatch -%}{%- endmacro -%} + +{% macro return_type_func(func) %}{% match func.ffi_func().return_type() %}{% when Some with (return_type) %}{{ return_type|type_ffi }}{%- else -%}(){%- endmatch -%}{%- endmacro -%} + +{% macro ret(func) %}{% match func.return_type() %}{% when Some with (return_type) %}{{ return_type|ffi_converter }}::lower(_retval){% else %}_retval{% endmatch %}{% endmacro %} + +{% macro construct(obj, cons) %} + r#{{- obj.name() }}::{% call to_rs_call(cons) -%} +{% endmacro %} + +{% macro to_rs_constructor_call(obj, cons) %} +{% match cons.throws_type() %} +{% when Some with (e) %} + uniffi::call_with_result(call_status, || { + let _new = {% call construct(obj, cons) %}.map_err(Into::into).map_err({{ e|ffi_converter }}::lower)?; + let _arc = std::sync::Arc::new(_new); + Ok({{ obj.type_().borrow()|ffi_converter }}::lower(_arc)) + }) +{% else %} + uniffi::call_with_output(call_status, || { + let _new = {% call construct(obj, cons) %}; + let _arc = std::sync::Arc::new(_new); + {{ obj.type_().borrow()|ffi_converter }}::lower(_arc) + }) +{% endmatch %} +{% endmacro %} + +{% macro to_rs_method_call(obj, meth) -%} +{% match meth.throws_type() -%} +{% when Some with (e) -%} +uniffi::call_with_result(call_status, || { + let _retval = r#{{ obj.name() }}::{% call to_rs_call(meth) %}.map_err(Into::into).map_err({{ e|ffi_converter }}::lower)?; + Ok({% call ret(meth) %}) +}) +{% else %} +uniffi::call_with_output(call_status, || { + {% match meth.return_type() -%} + {% when Some with (return_type) -%} + let retval = r#{{ obj.name() }}::{% call to_rs_call(meth) %}; + {{ return_type|ffi_converter }}::lower(retval) + {% else -%} + r#{{ obj.name() }}::{% call to_rs_call(meth) %} + {% endmatch -%} +}) +{% endmatch -%} +{% endmacro -%} + +{% macro to_rs_function_call(func) %} +{% match func.throws_type() %} +{% when Some with (e) %} +uniffi::call_with_result(call_status, || { + let _retval = {% call to_rs_call(func) %}.map_err(Into::into).map_err({{ e|ffi_converter }}::lower)?; + Ok({% call ret(func) %}) +}) +{% else %} +uniffi::call_with_output(call_status, || { + {% match func.return_type() -%} + {% when Some with (return_type) -%} + {{ return_type|ffi_converter }}::lower({% call to_rs_call(func) %}) + {% else -%} + {% if func.full_arguments().is_empty() %}#[allow(clippy::redundant_closure)]{% endif %} + {% call to_rs_call(func) %} + {% endmatch -%} +}) +{% endmatch %} +{% endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs new file mode 100644 index 0000000000..11b6507e29 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs @@ -0,0 +1,58 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +{% import "macros.rs" as rs %} + +// Check for compatibility between `uniffi` and `uniffi_bindgen` versions. +// Note that we have an error message on the same line as the assertion. +// This is important, because if the assertion fails, the compiler only +// seems to show that single line as context for the user. +uniffi::assert_compatible_version!("{{ uniffi_version }}"); // Please check that you depend on version {{ uniffi_version }} of the `uniffi` crate. + +{% for ty in ci.iter_types() %} +{%- match ty %} +{%- when Type::Map with (k, v) -%} +{# Next comment MUST be after the line to be in the compiler output #} +uniffi::deps::static_assertions::assert_impl_all!({{ k|type_rs }}: ::std::cmp::Eq, ::std::hash::Hash); // record<{{ k|type_rs }}, {{ v|type_rs }}> +{%- else %} +{%- endmatch %} +{% endfor %} + +{% include "RustBuffer.rs" %} + +// Error definitions, corresponding to `error` in the UDL. +{% for e in ci.error_definitions() %} +{% include "ErrorTemplate.rs" %} +{% endfor %} + +// Enum defitions, corresponding to `enum` in UDL. +{% for e in ci.enum_definitions() %} +{% include "EnumTemplate.rs" %} +{% endfor %} + +// Record definitions, implemented as method-less structs, corresponding to `dictionary` objects. +{% for rec in ci.record_definitions() %} +{% include "RecordTemplate.rs" %} +{% endfor %} + +// Top level functions, corresponding to UDL `namespace` functions. +{%- for func in ci.function_definitions() %} +{% include "TopLevelFunctionTemplate.rs" %} +{% endfor -%} + +// Object definitions, corresponding to UDL `interface` definitions. +{% for obj in ci.object_definitions() %} +{% include "ObjectTemplate.rs" %} +{% endfor %} + +// Callback Interface definitions, corresponding to UDL `callback interface` definitions. +{% for cbi in ci.callback_interface_definitions() %} +{% include "CallbackInterfaceTemplate.rs" %} +{% endfor %} + +// External and Wrapped types +{% include "ExternalTypesTemplate.rs" %} + +// The `reexport_uniffi_scaffolding` macro +{% include "ReexportUniFFIScaffolding.rs" %} + +{%- import "macros.rs" as rs -%} |