diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/swift')
45 files changed, 2668 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs new file mode 100644 index 0000000000..828a8823ee --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs @@ -0,0 +1,25 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, oracle: &dyn CodeOracle) -> String { + format!("CallbackInterface{}", self.type_label(oracle)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs new file mode 100644 index 0000000000..f91be9ce54 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -0,0 +1,98 @@ +/* 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 crate::backend::{CodeOracle, CodeType, Literal, TypeIdentifier}; + +pub struct OptionalCodeType { + inner: TypeIdentifier, +} + +impl OptionalCodeType { + pub fn new(inner: TypeIdentifier) -> Self { + Self { inner } + } +} + +impl CodeType for OptionalCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + format!("{}?", oracle.find(&self.inner).type_label(oracle)) + } + + fn canonical_name(&self, oracle: &dyn CodeOracle) -> String { + format!("Option{}", oracle.find(&self.inner).canonical_name(oracle)) + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + match literal { + Literal::Null => "nil".into(), + _ => oracle.find(&self.inner).literal(oracle, literal), + } + } +} + +pub struct SequenceCodeType { + inner: TypeIdentifier, +} + +impl SequenceCodeType { + pub fn new(inner: TypeIdentifier) -> Self { + Self { inner } + } +} + +impl CodeType for SequenceCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + format!("[{}]", oracle.find(&self.inner).type_label(oracle)) + } + + fn canonical_name(&self, oracle: &dyn CodeOracle) -> String { + format!( + "Sequence{}", + oracle.find(&self.inner).canonical_name(oracle) + ) + } + + fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String { + match literal { + Literal::EmptySequence => "[]".into(), + _ => unreachable!(), + } + } +} + +pub struct MapCodeType { + key: TypeIdentifier, + value: TypeIdentifier, +} + +impl MapCodeType { + pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self { + Self { key, value } + } +} + +impl CodeType for MapCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + format!( + "[{}: {}]", + oracle.find(&self.key).type_label(oracle), + oracle.find(&self.value).type_label(oracle) + ) + } + + fn canonical_name(&self, oracle: &dyn CodeOracle) -> String { + format!( + "Dictionary{}{}", + oracle.find(&self.key).canonical_name(oracle), + oracle.find(&self.value).canonical_name(oracle) + ) + } + + fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String { + match literal { + Literal::EmptyMap => "[:]".into(), + _ => unreachable!(), + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs new file mode 100644 index 0000000000..bf1806e5e2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs @@ -0,0 +1,25 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + CustomCodeType { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + self.name.clone() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs new file mode 100644 index 0000000000..04d8422f6a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs @@ -0,0 +1,33 @@ +/* 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 crate::backend::{CodeOracle, CodeType, Literal}; + +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + if let Literal::Enum(v, _) = literal { + format!(".{}", oracle.enum_variant_name(v)) + } else { + unreachable!(); + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs new file mode 100644 index 0000000000..4219f24c39 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs @@ -0,0 +1,25 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct ErrorCodeType { + id: String, +} + +impl ErrorCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ErrorCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs new file mode 100644 index 0000000000..c9a925cdaa --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -0,0 +1,35 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + ExternalCodeType { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + self.name.clone() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.name) + } + + // lower and lift need to call public function which were generated for + // the original types. + fn lower(&self, oracle: &dyn CodeOracle) -> String { + format!("{}_lower", self.ffi_converter_name(oracle)) + } + + fn lift(&self, oracle: &dyn CodeOracle) -> String { + format!("{}_lift", self.ffi_converter_name(oracle)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs new file mode 100644 index 0000000000..4f57734934 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs @@ -0,0 +1,29 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct TimestampCodeType; + +impl CodeType for TimestampCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + "Date".into() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + "Timestamp".into() + } +} + +pub struct DurationCodeType; + +impl CodeType for DurationCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + "TimeInterval".into() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + "Duration".into() + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs new file mode 100644 index 0000000000..abbe0b3539 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -0,0 +1,476 @@ +/* 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 std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeSet, HashMap, HashSet}; + +use anyhow::{Context, Result}; +use askama::Template; +use heck::{ToLowerCamelCase, ToUpperCamelCase}; +use serde::{Deserialize, Serialize}; + +use super::Bindings; +use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier}; +use crate::interface::*; +use crate::MergeWith; + +mod callback_interface; +mod compounds; +mod custom; +mod enum_; +mod error; +mod external; +mod miscellany; +mod object; +mod primitives; +mod record; + +/// Config options for the caller to customize the generated Swift. +/// +/// Note that this can only be used to control details of the Swift *that do not affect the underlying component*, +/// since the details of the underlying component are entirely determined by the `ComponentInterface`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + cdylib_name: Option<String>, + module_name: Option<String>, + ffi_module_name: Option<String>, + ffi_module_filename: Option<String>, + generate_module_map: Option<bool>, + omit_argument_labels: Option<bool>, + #[serde(default)] + custom_types: HashMap<String, CustomTypeConfig>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CustomTypeConfig { + imports: Option<Vec<String>>, + type_name: Option<String>, + into_custom: TemplateExpression, + from_custom: TemplateExpression, +} + +impl Config { + /// The name of the Swift module containing the high-level foreign-language bindings. + pub fn module_name(&self) -> String { + match self.module_name.as_ref() { + Some(name) => name.clone(), + None => "uniffi".into(), + } + } + + /// The name of the lower-level C module containing the FFI declarations. + pub fn ffi_module_name(&self) -> String { + match self.ffi_module_name.as_ref() { + Some(name) => name.clone(), + None => format!("{}FFI", self.module_name()), + } + } + + /// The filename stem for the lower-level C module containing the FFI declarations. + pub fn ffi_module_filename(&self) -> String { + match self.ffi_module_filename.as_ref() { + Some(name) => name.clone(), + None => self.ffi_module_name(), + } + } + + /// The name of the `.modulemap` file for the lower-level C module with FFI declarations. + pub fn modulemap_filename(&self) -> String { + format!("{}.modulemap", self.ffi_module_filename()) + } + + /// The name of the `.h` file for the lower-level C module with FFI declarations. + pub fn header_filename(&self) -> String { + format!("{}.h", self.ffi_module_filename()) + } + + /// The name of the compiled Rust library containing the FFI implementation. + pub fn cdylib_name(&self) -> String { + if let Some(cdylib_name) = &self.cdylib_name { + cdylib_name.clone() + } else { + "uniffi".into() + } + } + + /// Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations. + pub fn generate_module_map(&self) -> bool { + self.generate_module_map.unwrap_or(true) + } + + /// Whether to omit argument labels in Swift function definitions. + pub fn omit_argument_labels(&self) -> bool { + self.omit_argument_labels.unwrap_or(false) + } +} + +impl From<&ComponentInterface> for Config { + fn from(ci: &ComponentInterface) -> Self { + Config { + module_name: Some(ci.namespace().into()), + cdylib_name: Some(format!("uniffi_{}", ci.namespace())), + ..Default::default() + } + } +} + +impl MergeWith for Config { + fn merge_with(&self, other: &Self) -> Self { + Config { + module_name: self.module_name.merge_with(&other.module_name), + ffi_module_name: self.ffi_module_name.merge_with(&other.ffi_module_name), + cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name), + ffi_module_filename: self + .ffi_module_filename + .merge_with(&other.ffi_module_filename), + generate_module_map: self + .generate_module_map + .merge_with(&other.generate_module_map), + omit_argument_labels: self + .omit_argument_labels + .merge_with(&other.omit_argument_labels), + custom_types: self.custom_types.merge_with(&other.custom_types), + } + } +} + +/// Generate UniFFI component bindings for Swift, as strings in memory. +/// +pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bindings> { + let header = BridgingHeader::new(config, ci) + .render() + .context("failed to render Swift bridging header")?; + let library = SwiftWrapper::new(config.clone(), ci) + .render() + .context("failed to render Swift library")?; + let modulemap = if config.generate_module_map() { + Some( + ModuleMap::new(config, ci) + .render() + .context("failed to render Swift modulemap")?, + ) + } else { + None + }; + Ok(Bindings { + library, + header, + modulemap, + }) +} + +/// Renders Swift helper code for all types +/// +/// This template is a bit different than others in that it stores internal state from the render +/// process. Make sure to only call `render()` once. +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "Types.swift")] +pub struct TypeRenderer<'a> { + config: &'a Config, + ci: &'a ComponentInterface, + // Track included modules for the `include_once()` macro + include_once_names: RefCell<HashSet<String>>, + // Track imports added with the `add_import()` macro + imports: RefCell<BTreeSet<String>>, +} + +impl<'a> TypeRenderer<'a> { + fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { + Self { + config, + ci, + include_once_names: RefCell::new(HashSet::new()), + imports: RefCell::new(BTreeSet::new()), + } + } + + // The following methods are used by the `Types.kt` macros. + + // Helper for the including a template, but only once. + // + // The first time this is called with a name it will return true, indicating that we should + // include the template. Subsequent calls will return false. + fn include_once_check(&self, name: &str) -> bool { + self.include_once_names + .borrow_mut() + .insert(name.to_string()) + } + + // Helper to add an import statement + // + // Call this inside your template to cause an import statement to be added at the top of the + // file. Imports will be sorted and de-deuped. + // + // Returns an empty string so that it can be used inside an askama `{{ }}` block. + fn add_import(&self, name: &str) -> &str { + self.imports.borrow_mut().insert(name.to_owned()); + "" + } +} + +/// Template for generating the `.h` file that defines the low-level C FFI. +/// +/// This file defines only the low-level structs and functions that are exposed +/// by the compiled Rust code. It gets wrapped into a higher-level API by the +/// code from [`SwiftWrapper`]. +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "BridgingHeaderTemplate.h")] +pub struct BridgingHeader<'config, 'ci> { + _config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> BridgingHeader<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Self { + _config: config, + ci, + } + } +} + +/// Template for generating the `.modulemap` file that exposes the low-level C FFI. +/// +/// This file defines how the low-level C FFI from [`BridgingHeader`] gets exposed +/// as a Swift module that can be called by other Swift code. In our case, its only +/// job is to define the *name* of the Swift module that will contain the FFI functions +/// so that it can be imported by the higher-level code in from [`SwiftWrapper`]. +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "ModuleMapTemplate.modulemap")] +pub struct ModuleMap<'config, 'ci> { + config: &'config Config, + _ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> ModuleMap<'config, 'ci> { + pub fn new(config: &'config Config, _ci: &'ci ComponentInterface) -> Self { + Self { config, _ci } + } +} + +#[derive(Template)] +#[template(syntax = "swift", escape = "none", path = "wrapper.swift")] +pub struct SwiftWrapper<'a> { + config: Config, + ci: &'a ComponentInterface, + type_helper_code: String, + type_imports: BTreeSet<String>, +} +impl<'a> SwiftWrapper<'a> { + pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { + let type_renderer = TypeRenderer::new(&config, ci); + let type_helper_code = type_renderer.render().unwrap(); + let type_imports = type_renderer.imports.into_inner(); + Self { + config, + ci, + type_helper_code, + type_imports, + } + } + + pub fn imports(&self) -> Vec<String> { + self.type_imports.iter().cloned().collect() + } + + pub fn initialization_fns(&self) -> Vec<String> { + self.ci + .iter_types() + .into_iter() + .filter_map(|t| t.initialization_fn(&SwiftCodeOracle)) + .collect() + } +} + +#[derive(Clone)] +pub struct SwiftCodeOracle; + +impl SwiftCodeOracle { + // Map `Type` instances to a `Box<dyn CodeType>` for that type. + // + // There is a companion match in `templates/Types.swift` which performs a similar function for the + // template code. + // + // - When adding additional types here, make sure to also add a match arm to the `Types.swift` template. + // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> { + match type_ { + Type::UInt8 => Box::new(primitives::UInt8CodeType), + Type::Int8 => Box::new(primitives::Int8CodeType), + Type::UInt16 => Box::new(primitives::UInt16CodeType), + Type::Int16 => Box::new(primitives::Int16CodeType), + Type::UInt32 => Box::new(primitives::UInt32CodeType), + Type::Int32 => Box::new(primitives::Int32CodeType), + Type::UInt64 => Box::new(primitives::UInt64CodeType), + Type::Int64 => Box::new(primitives::Int64CodeType), + Type::Float32 => Box::new(primitives::Float32CodeType), + Type::Float64 => Box::new(primitives::Float64CodeType), + Type::Boolean => Box::new(primitives::BooleanCodeType), + Type::String => Box::new(primitives::StringCodeType), + + Type::Timestamp => Box::new(miscellany::TimestampCodeType), + Type::Duration => Box::new(miscellany::DurationCodeType), + + Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)), + Type::Object(id) => Box::new(object::ObjectCodeType::new(id)), + Type::Record(id) => Box::new(record::RecordCodeType::new(id)), + Type::Error(id) => Box::new(error::ErrorCodeType::new(id)), + Type::CallbackInterface(id) => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(id)) + } + + Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)), + Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)), + Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), + Type::Unresolved { name } => { + unreachable!("Type `{name}` must be resolved before calling create_code_type") + } + } + } +} + +impl CodeOracle for SwiftCodeOracle { + fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> { + self.create_code_type(type_.clone()) + } + + /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc). + fn class_name(&self, nm: &str) -> String { + nm.to_string().to_upper_camel_case() + } + + /// Get the idiomatic Swift rendering of a function name. + fn fn_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Swift rendering of a variable name. + fn var_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Swift rendering of an individual enum variant. + fn enum_variant_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Swift rendering of an exception name. + fn error_name(&self, nm: &str) -> String { + format!("`{}`", self.class_name(nm)) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::Int8 => "int8_t".into(), + FfiType::UInt8 => "uint8_t".into(), + FfiType::Int16 => "int16_t".into(), + FfiType::UInt16 => "uint16_t".into(), + FfiType::Int32 => "int32_t".into(), + FfiType::UInt32 => "uint32_t".into(), + FfiType::Int64 => "int64_t".into(), + FfiType::UInt64 => "uint64_t".into(), + FfiType::Float32 => "float".into(), + FfiType::Float64 => "double".into(), + FfiType::RustArcPtr(_) => "void*_Nonnull".into(), + FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::ForeignCallback => "ForeignCallback _Nonnull".to_string(), + } + } +} + +pub mod filters { + use super::*; + + fn oracle() -> &'static SwiftCodeOracle { + &SwiftCodeOracle + } + + pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> { + let oracle = oracle(); + Ok(codetype.type_label(oracle)) + } + + pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> { + let oracle = oracle(); + Ok(codetype.canonical_name(oracle)) + } + + pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> { + Ok(codetype.ffi_converter_name(oracle())) + } + + pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> { + Ok(codetype.lower(oracle())) + } + + pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> { + Ok(codetype.write(oracle())) + } + + pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> { + Ok(codetype.lift(oracle())) + } + + pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> { + Ok(codetype.read(oracle())) + } + + pub fn literal_swift( + literal: &Literal, + codetype: &impl CodeType, + ) -> Result<String, askama::Error> { + let oracle = oracle(); + Ok(codetype.literal(oracle, literal)) + } + + /// Get the Swift syntax for representing a given low-level `FfiType`. + pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> { + Ok(oracle().ffi_type_label(type_)) + } + + /// Get the type that a type is lowered into. This is subtly different than `type_ffi`, see + /// #1106 for details + pub fn type_ffi_lowered(ffi_type: &FfiType) -> Result<String, askama::Error> { + Ok(match ffi_type { + FfiType::Int8 => "Int8".into(), + FfiType::UInt8 => "UInt8".into(), + FfiType::Int16 => "Int16".into(), + FfiType::UInt16 => "UInt16".into(), + FfiType::Int32 => "Int32".into(), + FfiType::UInt32 => "UInt32".into(), + FfiType::Int64 => "Int64".into(), + FfiType::UInt64 => "UInt64".into(), + FfiType::Float32 => "float".into(), + FfiType::Float64 => "double".into(), + FfiType::RustArcPtr(_) => "void*_Nonnull".into(), + FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::ForeignCallback => "ForeignCallback _Nonnull".to_string(), + }) + } + + /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc). + pub fn class_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().class_name(nm)) + } + + /// Get the idiomatic Swift rendering of a function name. + pub fn fn_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().fn_name(nm)) + } + + /// Get the idiomatic Swift rendering of a variable name. + pub fn var_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().var_name(nm)) + } + + /// Get the idiomatic Swift rendering of an individual enum variant. + pub fn enum_variant_swift(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().enum_variant_name(nm)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs new file mode 100644 index 0000000000..d227f8e7cd --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs @@ -0,0 +1,25 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs new file mode 100644 index 0000000000..9989e0a421 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -0,0 +1,87 @@ +/* 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 crate::backend::{CodeOracle, CodeType, Literal}; +use crate::interface::{types::Type, Radix}; +use paste::paste; + +fn render_literal(oracle: &dyn CodeOracle, literal: &Literal) -> String { + fn typed_number(oracle: &dyn CodeOracle, type_: &Type, num_str: String) -> String { + match type_ { + // special case Int32. + Type::Int32 => num_str, + // otherwise use constructor e.g. UInt8(x) + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 + | Type::Float32 + | Type::Float64 => + // XXX we should pass in the codetype itself. + { + format!("{}({num_str})", oracle.find(type_).type_label(oracle)) + } + _ => panic!("Unexpected literal: {num_str} is not a number"), + } + } + + match literal { + Literal::Boolean(v) => format!("{v}"), + Literal::String(s) => format!("\"{s}\""), + Literal::Int(i, radix, type_) => typed_number( + oracle, + type_, + match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::UInt(i, radix, type_) => typed_number( + oracle, + type_, + match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::Float(string, type_) => typed_number(oracle, type_, string.clone()), + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $class_name:literal) => { + paste! { + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + $class_name.into() + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + render_literal(oracle, &literal) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "Bool"); +impl_code_type_for_primitive!(StringCodeType, "String"); +impl_code_type_for_primitive!(Int8CodeType, "Int8"); +impl_code_type_for_primitive!(Int16CodeType, "Int16"); +impl_code_type_for_primitive!(Int32CodeType, "Int32"); +impl_code_type_for_primitive!(Int64CodeType, "Int64"); +impl_code_type_for_primitive!(UInt8CodeType, "UInt8"); +impl_code_type_for_primitive!(UInt16CodeType, "UInt16"); +impl_code_type_for_primitive!(UInt32CodeType, "UInt32"); +impl_code_type_for_primitive!(UInt64CodeType, "UInt64"); +impl_code_type_for_primitive!(Float32CodeType, "Float"); +impl_code_type_for_primitive!(Float64CodeType, "Double"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs new file mode 100644 index 0000000000..0943bcaada --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs @@ -0,0 +1,25 @@ +/* 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 crate::backend::{CodeOracle, CodeType}; + +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs new file mode 100644 index 0000000000..5fd1c404cb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs @@ -0,0 +1,99 @@ +/* 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/. */ + +//! # Swift bindings backend for UniFFI +//! +//! This module generates Swift bindings from a [`ComponentInterface`] definition, +//! using Swift's builtin support for loading C header files. +//! +//! Conceptually, the generated bindings are split into two Swift modules, one for the low-level +//! C FFI layer and one for the higher-level Swift bindings. For a UniFFI component named "example" +//! we generate: +//! +//! * A C header file `exampleFFI.h` declaring the low-level structs and functions for calling +//! into Rust, along with a corresponding `exampleFFI.modulemap` to expose them to Swift. +//! +//! * A Swift source file `example.swift` that imports the `exampleFFI` module and wraps it +//! to provide the higher-level Swift API. +//! +//! Most of the concepts in a [`ComponentInterface`] have an obvious counterpart in Swift, +//! with the details documented in inline comments where appropriate. +//! +//! To handle lifting/lowering/serializing types across the FFI boundary, the Swift code +//! defines a `protocol ViaFfi` that is analogous to the `uniffi::ViaFfi` Rust trait. +//! Each type that can traverse the FFI conforms to the `ViaFfi` protocol, which specifies: +//! +//! * The corresponding low-level type. +//! * How to lift from and lower into into that type. +//! * How to read from and write into a byte buffer. +//! + +use std::{io::Write, process::Command}; + +use anyhow::Result; +use camino::Utf8Path; +use fs_err::File; + +pub mod gen_swift; +pub use gen_swift::{generate_bindings, Config}; +mod test; + +use super::super::interface::ComponentInterface; +pub use test::run_test; + +/// The Swift bindings generated from a [`ComponentInterface`]. +/// +pub struct Bindings { + /// The contents of the generated `.swift` file, as a string. + library: String, + /// The contents of the generated `.h` file, as a string. + header: String, + /// The contents of the generated `.modulemap` file, as a string. + modulemap: Option<String>, +} + +/// Write UniFFI component bindings for Swift as files on disk. +/// +/// Unlike other target languages, binding to Rust code from Swift involves more than just +/// generating a `.swift` file. We also need to produce a `.h` file with the C-level API +/// declarations, and a `.modulemap` file to tell Swift how to use it. +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<()> { + let Bindings { + header, + library, + modulemap, + } = generate_bindings(config, ci)?; + + let source_file = out_dir.join(format!("{}.swift", config.module_name())); + let mut l = File::create(&source_file)?; + write!(l, "{library}")?; + + let mut h = File::create(out_dir.join(config.header_filename()))?; + write!(h, "{header}")?; + + if let Some(modulemap) = modulemap { + let mut m = File::create(out_dir.join(config.modulemap_filename()))?; + write!(m, "{modulemap}")?; + } + + if try_format_code { + if let Err(e) = Command::new("swiftformat") + .arg(source_file.as_str()) + .output() + { + println!( + "Warning: Unable to auto-format {} using swiftformat: {:?}", + source_file.file_name().unwrap(), + e + ) + } + } + + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift new file mode 100644 index 0000000000..465e519628 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift @@ -0,0 +1,20 @@ +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h new file mode 100644 index 0000000000..50b95d96e8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -0,0 +1,55 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, RustBuffer, RustBuffer *_Nonnull); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +{% for func in ci.iter_ffi_function_definitions() -%} + {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( + {% call swift::arg_list_ffi_decl(func) %} + ); +{% endfor -%} + +{% import "macros.swift" as swift %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift new file mode 100644 index 0000000000..9ab9bdbe48 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -0,0 +1,60 @@ +fileprivate extension NSLock { + func withLock<T>(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate typealias UniFFICallbackHandle = UInt64 +fileprivate class UniFFICallbackHandleMap<T> { + private var leftMap: [UniFFICallbackHandle: T] = [:] + private var counter: [UniFFICallbackHandle: UInt64] = [:] + private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:] + + private let lock = NSLock() + private var currentHandle: UniFFICallbackHandle = 0 + private let stride: UniFFICallbackHandle = 1 + + func insert(obj: T) -> UniFFICallbackHandle { + lock.withLock { + let id = ObjectIdentifier(obj as AnyObject) + let handle = rightMap[id] ?? { + currentHandle += stride + let handle = currentHandle + leftMap[handle] = obj + rightMap[id] = handle + return handle + }() + counter[handle] = (counter[handle] ?? 0) + 1 + return handle + } + } + + func get(handle: UniFFICallbackHandle) -> T? { + lock.withLock { + leftMap[handle] + } + } + + func delete(handle: UniFFICallbackHandle) { + remove(handle: handle) + } + + @discardableResult + func remove(handle: UniFFICallbackHandle) -> T? { + lock.withLock { + defer { counter[handle] = (counter[handle] ?? 1) - 1 } + guard counter[handle] == 1 else { return leftMap[handle] } + let obj = leftMap.removeValue(forKey: handle) + if let obj = obj { + rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) + } + 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. +private let IDX_CALLBACK_FREE: Int32 = 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift new file mode 100644 index 0000000000..7842511a83 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -0,0 +1,154 @@ +{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %} +{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} +{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} + +// Declaration and FfiConverters for {{ type_name }} Callback Interface + +public protocol {{ type_name }} : AnyObject { + {% for meth in cbi.methods() -%} + func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %} -> {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + +// The ForeignCallback that is passed to Rust. +fileprivate let {{ foreign_callback }} : ForeignCallback = + { (handle: UniFFICallbackHandle, method: Int32, args: RustBuffer, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in + {% for meth in cbi.methods() -%} + {%- let method_name = format!("invoke_{}", meth.name())|fn_name -%} + + func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + {#- Unpacking args from the RustBuffer #} + {%- if meth.arguments().len() != 0 -%} + {#- Calling the concrete callback object #} + + var reader = createReader(data: Data(rustBuffer: args)) + {% if meth.return_type().is_some() %}let result = {% endif -%} + {% if meth.throws() %}try {% endif -%} + swiftCallbackInterface.{{ meth.name()|fn_name }}( + {% for arg in meth.arguments() -%} + {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ) + {% else %} + {% if meth.return_type().is_some() %}let result = {% endif -%} + {% if meth.throws() %}try {% endif -%} + swiftCallbackInterface.{{ meth.name()|fn_name }}() + {% endif -%} + + {#- Packing up the return value into a RustBuffer #} + {%- match meth.return_type() -%} + {%- when Some with (return_type) -%} + var writer = [UInt8]() + {{ return_type|write_fn }}(result, into: &writer) + return RustBuffer(bytes: writer) + {%- else -%} + return RustBuffer() + {% endmatch -%} + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + {% endfor %} + + let cb: {{ cbi|type_name }} + do { + cb = try {{ ffi_converter_name }}.lift(handle) + } catch { + out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle") + return -1 + } + + switch method { + case IDX_CALLBACK_FREE: + {{ ffi_converter_name }}.drop(handle: handle) + // No return value. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return 0 + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + case {{ loop.index }}: + do { + out_buf.pointee = try {{ method_name }}(cb, args) + // Value written to out buffer. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return 1 + {%- match meth.throws_type() %} + {%- when Some(error_type) %} + } catch let error as {{ error_type|type_name }} { + out_buf.pointee = {{ error_type|lower_fn }}(error) + return -2 + {%- else %} + {%- endmatch %} + } catch let error { + out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + return -1 + } + {% 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 InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + return -1 + } + } + +// FfiConverter protocol for callback interfaces +fileprivate struct {{ ffi_converter_name }} { + // Initialize our callback method with the scaffolding code + private static var callbackInitialized = false + private static func initCallback() { + try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in + {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err) + } + } + private static func ensureCallbackinitialized() { + if !callbackInitialized { + initCallback() + callbackInitialized = true + } + } + + static func drop(handle: UniFFICallbackHandle) { + handleMap.remove(handle: handle) + } + + private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() +} + +extension {{ ffi_converter_name }} : FfiConverter { + typealias SwiftType = {{ type_name }} + // We can use Handle as the FfiType because it's a typealias to UInt64 + typealias FfiType = UniFFICallbackHandle + + public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { + ensureCallbackinitialized(); + guard let callback = handleMap.get(handle: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return callback + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + ensureCallbackinitialized(); + let handle: UniFFICallbackHandle = try readInt(&buf) + return try lift(handle) + } + + public static func lower(_ v: SwiftType) -> UniFFICallbackHandle { + ensureCallbackinitialized(); + return handleMap.insert(obj: v) + } + + public static func write(_ v: SwiftType, into buf: inout [UInt8]) { + ensureCallbackinitialized(); + writeInt(&buf, lower(v)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift new file mode 100644 index 0000000000..cb63f0b97b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift @@ -0,0 +1,86 @@ +{%- let ffi_type_name=builtin.ffi_type().borrow()|type_ffi_lowered %} +{%- match config.custom_types.get(name.as_str()) %} +{%- when None %} +{#- No config, just forward all methods to our builtin type #} +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias {{ name }} = {{ builtin|type_name }} +public struct FfiConverterType{{ name }}: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + return try {{ builtin|read_fn }}(from: &buf) + } + + public static func write(_ value: {{ name }}, into buf: inout [UInt8]) { + return {{ builtin|write_fn }}(value, into: &buf) + } + + public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + return try {{ builtin|lift_fn }}(value) + } + + public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + return {{ builtin|lower_fn }}(value) + } +} + +{%- when Some with (config) %} + +{# When the config specifies a different type name, create a typealias for it #} +{%- match config.type_name %} +{%- when Some with (concrete_type_name) %} +/** + * Typealias from the type name used in the UDL file to the custom type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias {{ name }} = {{ concrete_type_name }} +{%- else %} +{%- endmatch %} + +{%- match config.imports %} +{%- when Some(imports) %} +{%- for import_name in imports %} +{{ self.add_import(import_name) }} +{%- endfor %} +{%- else %} +{%- endmatch %} + +public struct FfiConverterType{{ name }}: FfiConverter { + {#- Custom type config supplied, use it to convert the builtin type #} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + let builtinValue = try {{ builtin|read_fn }}(from: &buf) + return {{ config.into_custom.render("builtinValue") }} + } + + public static func write(_ value: {{ name }}, into buf: inout [UInt8]) { + let builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|write_fn }}(builtinValue, into: &buf) + } + + public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + let builtinValue = try {{ builtin|lift_fn }}(value) + return {{ config.into_custom.render("builtinValue") }} + } + + public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + let builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|lower_fn }}(builtinValue) + } +} + +{# +We always write these public functions just incase the type is used as +an external type by another crate. +#} +public func FfiConverterType{{ name }}_lift(_ buf: RustBuffer) throws -> {{ name }} { + return try FfiConverterType{{ name }}.lift(buf) +} + +public func FfiConverterType{{ name }}_lower(_ value: {{ name }}) -> RustBuffer { + return FfiConverterType{{ name }}.lower(value) +} + +{%- endmatch %} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift new file mode 100644 index 0000000000..c2aa49e9d1 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift @@ -0,0 +1,24 @@ +fileprivate struct FfiConverterDuration: FfiConverterRustBuffer { + typealias SwiftType = TimeInterval + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TimeInterval { + let seconds: UInt64 = try readInt(&buf) + let nanoseconds: UInt32 = try readInt(&buf) + return Double(seconds) + (Double(nanoseconds) / 1.0e9) + } + + public static func write(_ value: TimeInterval, into buf: inout [UInt8]) { + if value.rounded(.down) > Double(Int64.max) { + fatalError("Duration overflow, exceeds max bounds supported by Uniffi") + } + + if value < 0 { + fatalError("Invalid duration, must be non-negative") + } + + let seconds = UInt64(value) + let nanoseconds = UInt32((value - Double(seconds)) * 1.0e9) + writeInt(&buf, seconds) + writeInt(&buf, nanoseconds) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift new file mode 100644 index 0000000000..74f0a3effe --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -0,0 +1,60 @@ +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +{%- let e = ci.get_enum_definition(name).unwrap() %} +public enum {{ type_name }} { + {% for variant in e.variants() %} + case {{ variant.name()|enum_variant_swift }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {% endfor %} +} + +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let variant: Int32 = try readInt(&buf) + switch variant { + {% for variant in e.variants() %} + case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift }}{% if variant.has_fields() %}( + {%- for field in variant.fields() %} + {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} + {%- endfor %} + ){%- endif %} + {% endfor %} + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + switch value { + {% for variant in e.variants() %} + {% if variant.has_fields() %} + case let .{{ variant.name()|enum_variant_swift }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): + writeInt(&buf, Int32({{ loop.index }})) + {% for field in variant.fields() -%} + {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) + {% endfor -%} + {% else %} + case .{{ variant.name()|enum_variant_swift }}: + writeInt(&buf, Int32({{ loop.index }})) + {% endif %} + {%- endfor %} + } + } +} + +{# +We always write these public functions just in case the enum is used as +an external type by another crate. +#} +public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(buf) +} + +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer { + return {{ ffi_converter_name }}.lower(value) +} + +{% if !contains_object_references %} +extension {{ type_name }}: Equatable, Hashable {} +{% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift new file mode 100644 index 0000000000..b2c4bfe492 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -0,0 +1,83 @@ +{%- let e = ci.get_error_definition(name).unwrap() %} +public enum {{ type_name }} { + + {% if e.is_flat() %} + {% for variant in e.variants() %} + // Simple error enums only carry a message + case {{ variant.name()|class_name }}(message: String) + {% endfor %} + + {%- else %} + {% for variant in e.variants() %} + case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {% endfor %} + + {%- endif %} +} + +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let variant: Int32 = try readInt(&buf) + switch variant { + + {% if e.is_flat() %} + + {% for variant in e.variants() %} + case {{ loop.index }}: return .{{ variant.name()|class_name }}( + message: try {{ Type::String.borrow()|read_fn }}(from: &buf) + ) + {% endfor %} + + {% else %} + + {% for variant in e.variants() %} + case {{ loop.index }}: return .{{ variant.name()|class_name }}{% if variant.has_fields() -%}( + {% for field in variant.fields() -%} + {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ){% endif -%} + {% endfor %} + + {% endif -%} + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + switch value { + + {% if e.is_flat() %} + + {% for variant in e.variants() %} + case let .{{ variant.name()|class_name }}(message): + writeInt(&buf, Int32({{ loop.index }})) + {{ Type::String.borrow()|write_fn }}(message, into: &buf) + {%- endfor %} + + {% else %} + + {% for variant in e.variants() %} + {% if variant.has_fields() %} + case let .{{ variant.name()|class_name }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): + writeInt(&buf, Int32({{ loop.index }})) + {% for field in variant.fields() -%} + {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) + {% endfor -%} + {% else %} + case .{{ variant.name()|class_name }}: + writeInt(&buf, Int32({{ loop.index }})) + {% endif %} + {%- endfor %} + + {%- endif %} + } + } +} + +{% if !contains_object_references %} +extension {{ type_name }}: Equatable, Hashable {} +{% endif %} +extension {{ type_name }}: Error { } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift new file mode 100644 index 0000000000..fb986beab6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterFloat: FfiConverterPrimitive { + typealias FfiType = Float + typealias SwiftType = Float + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { + return try lift(readFloat(&buf)) + } + + public static func write(_ value: Float, into buf: inout [UInt8]) { + writeFloat(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift new file mode 100644 index 0000000000..74421c045c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterDouble: FfiConverterPrimitive { + typealias FfiType = Double + typealias SwiftType = Double + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double { + return try lift(readDouble(&buf)) + } + + public static func write(_ value: Double, into buf: inout [UInt8]) { + writeDouble(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift new file mode 100644 index 0000000000..18e2397733 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -0,0 +1,84 @@ +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_PANIC: Int8 = 2 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T { + try makeRustCall(callback, errorHandler: { + $0.deallocate() + return UniffiInternalError.unexpectedRustCallError + }) +} + +private func rustCallWithError<T, F: FfiConverter> + (_ errorFfiConverter: F.Type, _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T + where F.SwiftType: Error, F.FfiType == RustBuffer + { + try makeRustCall(callback, errorHandler: { return try errorFfiConverter.lift($0) }) +} + +private func makeRustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T { + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + switch callStatus.code { + case CALL_SUCCESS: + return returnedVal + + case CALL_ERROR: + throw try errorHandler(callStatus.errorBuf) + + case CALL_PANIC: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try {{ Type::String.borrow()|lift_fn }}(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift new file mode 100644 index 0000000000..ac57fc5e58 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt16: FfiConverterPrimitive { + typealias FfiType = Int16 + typealias SwiftType = Int16 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int16, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift new file mode 100644 index 0000000000..0ccfc13e4e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt32: FfiConverterPrimitive { + typealias FfiType = Int32 + typealias SwiftType = Int32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int32, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift new file mode 100644 index 0000000000..d7d4082933 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt64: FfiConverterPrimitive { + typealias FfiType = Int64 + typealias SwiftType = Int64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int64, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift new file mode 100644 index 0000000000..f2387e4340 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt8: FfiConverterPrimitive { + typealias FfiType = Int8 + typealias SwiftType = Int8 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift new file mode 100644 index 0000000000..05713aca26 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift @@ -0,0 +1,22 @@ +fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for (key, value) in value { + {{ key_type|write_fn }}(key, into: &buf) + {{ value_type|write_fn }}(value, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let len: Int32 = try readInt(&buf) + var dict = {{ type_name }}() + dict.reserveCapacity(Int(len)) + for _ in 0..<len { + let key = try {{ key_type|read_fn }}(from: &buf) + let value = try {{ value_type|read_fn }}(from: &buf) + dict[key] = value + } + return dict + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap new file mode 100644 index 0000000000..f5f73ceb1b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap @@ -0,0 +1,6 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +module {{ config.ffi_module_name() }} { + header "{{ config.header_filename() }}" + export * +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift new file mode 100644 index 0000000000..ab0b22b1d8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -0,0 +1,88 @@ +{%- let obj = ci.get_object_definition(name).unwrap() %} +public protocol {{ obj.name() }}Protocol { + {% for meth in obj.methods() -%} + func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %} -> {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + +public class {{ type_name }}: {{ obj.name() }}Protocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + {%- match obj.primary_constructor() %} + {%- when Some with (cons) %} + public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { + self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + } + {%- when None %} + {%- endmatch %} + + deinit { + try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) } + } + + {% for cons in obj.alternate_constructors() %} + public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} { + return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + } + {% endfor %} + + {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #} + {% for meth in obj.methods() -%} + {%- match meth.return_type() -%} + + {%- when Some with (return_type) -%} + public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { + return {% call swift::try(meth) %} {{ return_type|lift_fn }}( + {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + ) + } + + {%- when None -%} + public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { + {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + } + {%- endmatch %} + {% endfor %} +} + + +public struct {{ ffi_converter_name }}: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = {{ type_name }} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + return {{ type_name}}(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + return value.pointer + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift new file mode 100644 index 0000000000..1dac65be63 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift @@ -0,0 +1,20 @@ +fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + {{ inner_type|write_fn }}(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try {{ inner_type|read_fn }}(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift new file mode 100644 index 0000000000..7f2de44052 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -0,0 +1,62 @@ +{%- let rec = ci.get_record_definition(name).unwrap() %} +public struct {{ type_name }} { + {%- for field in rec.fields() %} + public var {{ field.name()|var_name }}: {{ field|type_name }} + {%- endfor %} + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init({% call swift::field_list_decl(rec) %}) { + {%- for field in rec.fields() %} + self.{{ field.name()|var_name }} = {{ field.name()|var_name }} + {%- endfor %} + } +} + +{% if !contains_object_references %} +extension {{ type_name }}: Equatable, Hashable { + public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool { + {%- for field in rec.fields() %} + if lhs.{{ field.name()|var_name }} != rhs.{{ field.name()|var_name }} { + return false + } + {%- endfor %} + return true + } + + public func hash(into hasher: inout Hasher) { + {%- for field in rec.fields() %} + hasher.combine({{ field.name()|var_name }}) + {%- endfor %} + } +} +{% endif %} + +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + return try {{ type_name }}( + {%- for field in rec.fields() %} + {{ field.name()|var_name }}: {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} + {%- endfor %} + ) + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + {%- for field in rec.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, into: &buf) + {%- endfor %} + } +} + +{# +We always write these public functions just in case the struct is used as +an external type by another crate. +#} +public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(buf) +} + +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer { + return {{ ffi_converter_name }}.lower(value) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift new file mode 100644 index 0000000000..2f737b6635 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -0,0 +1,183 @@ +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer { + try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { {{ ci.ffi_rustbuffer_free().name() }}(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer<UInt8>) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt<T: FixedWidthInteger>(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset..<reader.offset + MemoryLayout<T>.size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array<UInt8> { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes<S>(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt<T: FixedWidthInteger>(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous go the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift new file mode 100644 index 0000000000..bf664f6411 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift @@ -0,0 +1,21 @@ +fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + {{ inner_type|write_fn }}(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let len: Int32 = try readInt(&buf) + var seq = {{ type_name }}() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try {{ inner_type|read_fn }}(from: &buf)) + } + return seq + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift new file mode 100644 index 0000000000..b7d3466bdd --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift @@ -0,0 +1,37 @@ +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift new file mode 100644 index 0000000000..3cd472fa0e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift @@ -0,0 +1,34 @@ +fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { + typealias SwiftType = Date + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date { + let seconds: Int64 = try readInt(&buf) + let nanoseconds: UInt32 = try readInt(&buf) + if seconds >= 0 { + let delta = Double(seconds) + (Double(nanoseconds) / 1.0e9) + return Date.init(timeIntervalSince1970: delta) + } else { + let delta = Double(seconds) - (Double(nanoseconds) / 1.0e9) + return Date.init(timeIntervalSince1970: delta) + } + } + + public static func write(_ value: Date, into buf: inout [UInt8]) { + var delta = value.timeIntervalSince1970 + var sign: Int64 = 1 + if delta < 0 { + // The nanoseconds portion of the epoch offset must always be + // positive, to simplify the calculation we will use the absolute + // value of the offset. + sign = -1 + delta = -delta + } + if delta.rounded(.down) > Double(Int64.max) { + fatalError("Timestamp overflow, exceeds max bounds supported by Uniffi") + } + let seconds = Int64(delta) + let nanoseconds = UInt32((delta - Double(seconds)) * 1.0e9) + writeInt(&buf, sign * seconds) + writeInt(&buf, nanoseconds) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift new file mode 100644 index 0000000000..5ce762d2a0 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift @@ -0,0 +1,15 @@ +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} { + return {% call swift::try(func) %} {{ return_type|lift_fn }}( + {% call swift::to_ffi_call(func) %} + ) +} + +{% when None %} + +public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} { + {% call swift::to_ffi_call(func) %} +} +{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift new file mode 100644 index 0000000000..6e37e1b7f3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -0,0 +1,90 @@ +{%- import "macros.swift" as swift %} +{%- for type_ in ci.iter_types() %} +{%- let type_name = type_|type_name %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} +{%- let contains_object_references = ci.item_contains_object_references(type_) %} + +{# + # Map `Type` instances to an include statement for that type. + # + # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the + # Rust code. + # + # - When adding additional types here, make sure to also add a match arm to that function. + # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + #} +{%- match type_ %} + +{%- when Type::Boolean %} +{%- include "BooleanHelper.swift" %} + +{%- when Type::String %} +{%- include "StringHelper.swift" %} + +{%- when Type::Int8 %} +{%- include "Int8Helper.swift" %} + +{%- when Type::Int16 %} +{%- include "Int16Helper.swift" %} + +{%- when Type::Int32 %} +{%- include "Int32Helper.swift" %} + +{%- when Type::Int64 %} +{%- include "Int64Helper.swift" %} + +{%- when Type::UInt8 %} +{%- include "UInt8Helper.swift" %} + +{%- when Type::UInt16 %} +{%- include "UInt16Helper.swift" %} + +{%- when Type::UInt32 %} +{%- include "UInt32Helper.swift" %} + +{%- when Type::UInt64 %} +{%- include "UInt64Helper.swift" %} + +{%- when Type::Float32 %} +{%- include "Float32Helper.swift" %} + +{%- when Type::Float64 %} +{%- include "Float64Helper.swift" %} + +{%- when Type::Timestamp %} +{%- include "TimestampHelper.swift" %} + +{%- when Type::Duration %} +{%- include "DurationHelper.swift" %} + +{%- when Type::CallbackInterface(name) %} +{%- include "CallbackInterfaceTemplate.swift" %} + +{%- when Type::Custom { name, builtin } %} +{%- include "CustomType.swift" %} + +{%- when Type::Enum(name) %} +{%- include "EnumTemplate.swift" %} + +{%- when Type::Error(name) %} +{%- include "ErrorTemplate.swift" %} + +{%- when Type::Object(name) %} +{%- include "ObjectTemplate.swift" %} + +{%- when Type::Record(name) %} +{%- include "RecordTemplate.swift" %} + +{%- when Type::Optional(inner_type) %} +{%- include "OptionalTemplate.swift" %} + +{%- when Type::Sequence(inner_type) %} +{%- include "SequenceTemplate.swift" %} + +{%- when Type::Map(key_type, value_type) %} +{%- include "MapTemplate.swift" %} + +{%- else %} +{%- endmatch %} +{%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift new file mode 100644 index 0000000000..b7fc0942a5 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt16: FfiConverterPrimitive { + typealias FfiType = UInt16 + typealias SwiftType = UInt16 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift new file mode 100644 index 0000000000..e7a64aab93 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt32: FfiConverterPrimitive { + typealias FfiType = UInt32 + typealias SwiftType = UInt32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift new file mode 100644 index 0000000000..eb674a2c53 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt64: FfiConverterPrimitive { + typealias FfiType = UInt64 + typealias SwiftType = UInt64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift new file mode 100644 index 0000000000..4baf613494 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt8: FfiConverterPrimitive { + typealias FfiType = UInt8 + typealias SwiftType = UInt8 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: UInt8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift new file mode 100644 index 0000000000..dcc0a2df74 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -0,0 +1,97 @@ +{# +// Template to call into rust. Used in several places. +// Variable names in `arg_list_decl` should match up with arg lists +// passed to rust via `_arg_list_ffi_call` +#} + +{%- macro to_ffi_call(func) -%} +{% call try(func) %} + {% match func.throws_type() %} + {% when Some with (e) %} + rustCallWithError({{ e|ffi_converter_name }}.self) { + {% else %} + rustCall() { + {% endmatch %} + {{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %}, {% endif %}$0) +} +{%- endmacro -%} + +{%- macro to_ffi_call_with_prefix(prefix, func) -%} +{% call try(func) %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|ffi_converter_name }}.self) { + {%- else %} + rustCall() { + {% endmatch %} + {{ func.ffi_func().name() }}( + {{- prefix }}, {% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %}, {% endif %}$0 + ) +} +{%- endmacro %} + +{%- macro _arg_list_ffi_call(func) %} + {%- for arg in func.arguments() %} + {{ arg|lower_fn }}({{ arg.name()|var_name }}) + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in Swift declarations of methods, functions and constructors. +// Note the var_name and type_name filters. +-#} + +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}} + {%- match arg.default_value() %} + {%- when Some with(literal) %} = {{ literal|literal_swift(arg) }} + {%- else %} + {%- endmatch %} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + +{#- +// Field lists as used in Swift declarations of Records and Enums. +// Note the var_name and type_name filters. +-#} +{% macro field_list_decl(item) %} + {%- for field in item.fields() -%} + {{ field.name()|var_name }}: {{ field|type_name -}} + {%- match field.default_value() %} + {%- when Some with(literal) %} = {{ literal|literal_swift(field) }} + {%- else %} + {%- endmatch -%} + {% if !loop.last %}, {% endif %} + {%- endfor %} +{%- endmacro %} + + +{% macro arg_list_protocol(func) %} + {%- for arg in func.arguments() -%} + {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + + +{#- +// Arglist as used in the _UniFFILib function declarations. +// Note unfiltered name but ffi_type_name filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + {{- arg.type_().borrow()|ffi_type_name }} {{ arg.name() -}}, + {%- endfor %} + RustCallStatus *_Nonnull out_status +{%- endmacro -%} + +{%- macro throws(func) %} +{%- if func.throws() %}throws{% endif %} +{%- endmacro -%} + +{%- macro try(func) %} +{%- if func.throws() %}try{% else %}try!{% endif %} +{%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift new file mode 100644 index 0000000000..ac0557912e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift @@ -0,0 +1,40 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +{%- import "macros.swift" as swift %} +import Foundation +{%- for imported_class in self.imports() %} +import {{ imported_class }} +{%- endfor %} + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport({{ config.ffi_module_name() }}) +import {{ config.ffi_module_name() }} +#endif + +{% include "RustBufferTemplate.swift" %} +{% include "Helpers.swift" %} + +// Public interface members begin here. +{{ type_helper_code }} + +{%- for func in ci.function_definitions() %} +{%- include "TopLevelFunctionTemplate.swift" %} +{%- endfor %} + +/** + * Top level initializers and tear down methods. + * + * This is generated by uniffi. + */ +public enum {{ config.module_name().borrow()|class_name }}Lifecycle { + /** + * Initialize the FFI and Rust library. This should be only called once per application. + */ + func initialize() { + {%- for initialization_fn in self.initialization_fns() %} + {{ initialization_fn }}() + {%- endfor %} + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs new file mode 100644 index 0000000000..e51c4a9c05 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs @@ -0,0 +1,225 @@ +/* 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::{bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use heck::ToSnakeCase; +use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +use std::ffi::OsStr; +use std::fs::{read_to_string, File}; +use std::io::Write; +use std::process::Command; +use uniffi_testing::{CompileSource, UniFFITestHelper}; + +/// Run Swift tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?; + let out_dir = test_helper + .create_out_dir(tmp_dir, &script_path) + .context("create_out_dir()")?; + test_helper + .copy_cdylibs_to_out_dir(&out_dir) + .context("copy_fixture_library_to_out_dir()")?; + let generated_sources = + GeneratedSources::new(&test_helper.cdylib_path()?, &out_dir, &test_helper) + .context("generate_sources()")?; + + // Compile the generated sources together to create a single swift module + compile_swift_module( + &out_dir, + &calc_module_name(&generated_sources.main_source_filename), + &generated_sources.generated_swift_files, + &generated_sources.module_map, + )?; + + // Run the test script against compiled bindings + + let mut command = Command::new("swift"); + command + .current_dir(&out_dir) + .arg("-I") + .arg(&out_dir) + .arg("-L") + .arg(&out_dir) + .args(calc_library_args(&out_dir)?) + .arg("-Xcc") + .arg(format!( + "-fmodule-map-file={}", + generated_sources.module_map + )) + .arg(&script_path); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when running test script")? + .wait() + .context("Failed to wait for `swiftc` when running test script")?; + if !status.success() { + bail!("running `swift` to run test script failed ({:?})", command) + } + Ok(()) +} + +fn calc_module_name(filename: &str) -> String { + filename.strip_suffix(".swift").unwrap().to_snake_case() +} + +fn compile_swift_module<T: AsRef<OsStr>>( + out_dir: &Utf8Path, + module_name: &str, + sources: impl IntoIterator<Item = T>, + module_map: &Utf8Path, +) -> Result<()> { + let output_filename = format!("{DLL_PREFIX}testmod_{module_name}{DLL_SUFFIX}"); + let mut command = Command::new("swiftc"); + command + .current_dir(out_dir) + .arg("-emit-module") + .arg("-module-name") + .arg(module_name) + .arg("-o") + .arg(output_filename) + .arg("-emit-library") + .arg("-Xcc") + .arg(format!("-fmodule-map-file={}", module_map)) + .arg("-I") + .arg(out_dir) + .arg("-L") + .arg(out_dir) + .args(calc_library_args(out_dir)?) + .args(sources); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when compiling bindings")? + .wait() + .context("Failed to wait for `swiftc` when compiling bindings")?; + if !status.success() { + bail!( + "running `swiftc` to compile bindings failed ({:?})", + command + ) + }; + Ok(()) +} + +// Stores sources generated by `uniffi-bindgen-swift` +struct GeneratedSources { + generated_swift_files: Vec<Utf8PathBuf>, + module_map: Utf8PathBuf, + main_source_filename: String, +} + +impl GeneratedSources { + fn new( + library_path: &Utf8Path, + out_dir: &Utf8Path, + test_helper: &UniFFITestHelper, + ) -> Result<Self> { + // Generate the bindings for the main compile source, and use that for the swift module name + let main_compile_source = test_helper.get_main_compile_source()?; + Self::run_generate_bindings(&main_compile_source, library_path, out_dir)?; + let generated_files = glob(&out_dir.join("*.swift"))?; + let main_source_filename = match generated_files.len() { + 0 => bail!( + "No .swift file generated for {}", + main_compile_source.udl_path + ), + 1 => generated_files + .into_iter() + .next() + .unwrap() + .file_name() + .unwrap() + .to_string(), + n => bail!( + "{n} .swift files generated for {}", + main_compile_source.udl_path + ), + }; + + // Generate the bindings for other compile sources (crates used by external types) + for source in test_helper.get_external_compile_sources()? { + crate::generate_bindings( + &source.udl_path, + source.config_path.as_deref(), + vec!["swift"], + Some(out_dir), + Some(library_path), + false, + )?; + } + + let generated_module_maps = glob(&out_dir.join("*.modulemap"))?; + + Ok(GeneratedSources { + main_source_filename, + generated_swift_files: glob(&out_dir.join("*.swift"))?, + module_map: match generated_module_maps.len() { + 0 => bail!("No modulemap files found in {out_dir}"), + // Normally we only generate 1 module map and can return it directly + 1 => generated_module_maps.into_iter().next().unwrap(), + // When we use multiple UDL files in a test, for example the ext-types fixture, + // then we get multiple module maps and need to combine them + _ => { + let path = out_dir.join("combined.modulemap"); + let mut f = File::create(&path)?; + write!( + f, + "{}", + generated_module_maps + .into_iter() + .map(|path| Ok(read_to_string(path)?)) + .collect::<Result<Vec<String>>>()? + .join("\n") + )?; + path + } + }, + }) + } + + fn run_generate_bindings( + source: &CompileSource, + library_path: &Utf8Path, + out_dir: &Utf8Path, + ) -> Result<()> { + crate::generate_bindings( + &source.udl_path, + source.config_path.as_deref(), + vec!["swift"], + Some(out_dir), + Some(library_path), + false, + ) + } +} + +// Wraps glob to use Utf8Paths and flattens errors +fn glob(globspec: &Utf8Path) -> Result<Vec<Utf8PathBuf>> { + glob::glob(globspec.as_str())? + .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?)) + .collect() +} + +fn calc_library_args(out_dir: &Utf8Path) -> Result<Vec<String>> { + let results = glob::glob( + out_dir + .join(format!("{}*{}", DLL_PREFIX, DLL_SUFFIX)) + .as_str(), + )?; + results + .map(|globresult| { + let path = Utf8PathBuf::try_from(globresult.unwrap())?; + Ok(format!( + "-l{}", + path.file_name() + .unwrap() + .strip_prefix(DLL_PREFIX) + .unwrap() + .strip_suffix(DLL_SUFFIX) + .unwrap() + )) + }) + .collect() +} |