From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../bindings/swift/gen_swift/callback_interface.rs | 26 + .../src/bindings/swift/gen_swift/compounds.rs | 108 ++++ .../src/bindings/swift/gen_swift/custom.rs | 26 + .../src/bindings/swift/gen_swift/enum_.rs | 35 ++ .../src/bindings/swift/gen_swift/executor.rs | 23 + .../src/bindings/swift/gen_swift/external.rs | 36 ++ .../src/bindings/swift/gen_swift/miscellany.rs | 31 + .../src/bindings/swift/gen_swift/mod.rs | 688 +++++++++++++++++++++ .../src/bindings/swift/gen_swift/object.rs | 26 + .../src/bindings/swift/gen_swift/primitives.rs | 91 +++ .../src/bindings/swift/gen_swift/record.rs | 26 + .../rust/uniffi_bindgen/src/bindings/swift/mod.rs | 97 +++ .../src/bindings/swift/templates/Async.swift | 62 ++ .../bindings/swift/templates/BooleanHelper.swift | 20 + .../swift/templates/BridgingHeaderTemplate.h | 79 +++ .../swift/templates/CallbackInterfaceRuntime.swift | 64 ++ .../templates/CallbackInterfaceTemplate.swift | 150 +++++ .../src/bindings/swift/templates/CustomType.swift | 86 +++ .../src/bindings/swift/templates/DataHelper.swift | 14 + .../bindings/swift/templates/DurationHelper.swift | 24 + .../bindings/swift/templates/EnumTemplate.swift | 59 ++ .../bindings/swift/templates/ErrorTemplate.swift | 86 +++ .../bindings/swift/templates/Float32Helper.swift | 12 + .../bindings/swift/templates/Float64Helper.swift | 12 + .../swift/templates/ForeignExecutorTemplate.swift | 69 +++ .../src/bindings/swift/templates/Helpers.swift | 101 +++ .../src/bindings/swift/templates/Int16Helper.swift | 12 + .../src/bindings/swift/templates/Int32Helper.swift | 12 + .../src/bindings/swift/templates/Int64Helper.swift | 12 + .../src/bindings/swift/templates/Int8Helper.swift | 12 + .../src/bindings/swift/templates/MapTemplate.swift | 22 + .../swift/templates/ModuleMapTemplate.modulemap | 6 + .../bindings/swift/templates/ObjectTemplate.swift | 138 +++++ .../swift/templates/OptionalTemplate.swift | 20 + .../bindings/swift/templates/RecordTemplate.swift | 62 ++ .../swift/templates/RustBufferTemplate.swift | 183 ++++++ .../swift/templates/SequenceTemplate.swift | 21 + .../bindings/swift/templates/StringHelper.swift | 37 ++ .../bindings/swift/templates/TimestampHelper.swift | 34 + .../swift/templates/TopLevelFunctionTemplate.swift | 48 ++ .../src/bindings/swift/templates/Types.swift | 98 +++ .../bindings/swift/templates/UInt16Helper.swift | 12 + .../bindings/swift/templates/UInt32Helper.swift | 12 + .../bindings/swift/templates/UInt64Helper.swift | 12 + .../src/bindings/swift/templates/UInt8Helper.swift | 12 + .../src/bindings/swift/templates/macros.swift | 89 +++ .../src/bindings/swift/templates/wrapper.swift | 68 ++ .../rust/uniffi_bindgen/src/bindings/swift/test.rs | 202 ++++++ 48 files changed, 3175 insertions(+) create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift create mode 100644 third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs (limited to 'third_party/rust/uniffi_bindgen/src/bindings/swift') 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..5d8b37e0af --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("CallbackInterface{}", self.type_label()) + } +} 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..8e6dddf3f9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; +use crate::backend::{Literal, Type}; + +#[derive(Debug)] +pub struct OptionalCodeType { + inner: Type, +} + +impl OptionalCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } +} + +impl CodeType for OptionalCodeType { + fn type_label(&self) -> String { + format!("{}?", super::SwiftCodeOracle.find(&self.inner).type_label()) + } + + fn canonical_name(&self) -> String { + format!( + "Option{}", + super::SwiftCodeOracle.find(&self.inner).canonical_name() + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::Null => "nil".into(), + _ => super::SwiftCodeOracle.find(&self.inner).literal(literal), + } + } +} + +#[derive(Debug)] +pub struct SequenceCodeType { + inner: Type, +} + +impl SequenceCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } +} + +impl CodeType for SequenceCodeType { + fn type_label(&self) -> String { + format!( + "[{}]", + super::SwiftCodeOracle.find(&self.inner).type_label() + ) + } + + fn canonical_name(&self) -> String { + format!( + "Sequence{}", + super::SwiftCodeOracle.find(&self.inner).canonical_name() + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::EmptySequence => "[]".into(), + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +pub struct MapCodeType { + key: Type, + value: Type, +} + +impl MapCodeType { + pub fn new(key: Type, value: Type) -> Self { + Self { key, value } + } +} + +impl CodeType for MapCodeType { + fn type_label(&self) -> String { + format!( + "[{}: {}]", + super::SwiftCodeOracle.find(&self.key).type_label(), + super::SwiftCodeOracle.find(&self.value).type_label() + ) + } + + fn canonical_name(&self) -> String { + format!( + "Dictionary{}{}", + super::SwiftCodeOracle.find(&self.key).canonical_name(), + super::SwiftCodeOracle.find(&self.value).canonical_name() + ) + } + + fn literal(&self, 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..f4591b6eb6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + CustomCodeType { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> 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..14377ed9de --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.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 super::CodeType; +use crate::backend::Literal; + +#[derive(Debug)] +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, literal: &Literal) -> String { + if let Literal::Enum(v, _) = literal { + format!(".{}", super::SwiftCodeOracle.enum_variant_name(v)) + } else { + unreachable!(); + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs new file mode 100644 index 0000000000..b488b004cf --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ForeignExecutorCodeType; + +impl CodeType for ForeignExecutorCodeType { + fn type_label(&self) -> String { + // On Swift, we define a struct to represent a ForeignExecutor + "UniFfiForeignExecutor".into() + } + + fn canonical_name(&self) -> String { + "ForeignExecutor".into() + } + + fn initialization_fn(&self) -> Option { + Some("uniffiInitForeignExecutor".into()) + } +} 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..0b6728ba84 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + ExternalCodeType { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } + + // lower and lift need to call public function which were generated for + // the original types. + fn lower(&self) -> String { + format!("{}_lower", self.ffi_converter_name()) + } + + fn lift(&self) -> String { + format!("{}_lift", self.ffi_converter_name()) + } +} 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..c45091c80a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct TimestampCodeType; + +impl CodeType for TimestampCodeType { + fn type_label(&self) -> String { + "Date".into() + } + + fn canonical_name(&self) -> String { + "Timestamp".into() + } +} + +#[derive(Debug)] +pub struct DurationCodeType; + +impl CodeType for DurationCodeType { + fn type_label(&self) -> String { + "TimeInterval".into() + } + + fn canonical_name(&self) -> 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..12db4afc66 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -0,0 +1,688 @@ +/* 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 once_cell::sync::Lazy; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; + +use anyhow::{Context, Result}; +use askama::Template; +use heck::{ToLowerCamelCase, ToUpperCamelCase}; +use serde::{Deserialize, Serialize}; + +use super::Bindings; +use crate::backend::TemplateExpression; +use crate::interface::*; +use crate::BindingsConfig; + +mod callback_interface; +mod compounds; +mod custom; +mod enum_; +mod executor; +mod external; +mod miscellany; +mod object; +mod primitives; +mod record; + +/// A trait tor the implementation. +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String { + self.type_label() + } + + fn literal(&self, _literal: &Literal) -> String { + unimplemented!("Unimplemented for {}", self.type_label()) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + // XXX - the below should be removed and replace with the ffi_converter_name reference in the template. + /// An expression for lowering a value into something we can pass over the FFI. + fn lower(&self) -> String { + format!("{}.lower", self.ffi_converter_name()) + } + + /// An expression for writing a value into a byte buffer. + fn write(&self) -> String { + format!("{}.write", self.ffi_converter_name()) + } + + /// An expression for lifting a value from something we received over the FFI. + fn lift(&self) -> String { + format!("{}.lift", self.ffi_converter_name()) + } + + /// An expression for reading a value from a byte buffer. + fn read(&self) -> String { + format!("{}.read", self.ffi_converter_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option { + None + } +} + +/// From +static KEYWORDS: Lazy> = Lazy::new(|| { + [ + // Keywords used in declarations: + "associatedtype", + "class", + "deinit", + "enum", + "extension", + "fileprivate", + "func", + "import", + "init", + "inout", + "internal", + "let", + "open", + "operator", + "private", + "precedencegroup", + "protocol", + "public", + "rethrows", + "static", + "struct", + "subscript", + "typealias", + "var", + // Keywords used in statements: + "break", + "case", + "catch", + "continue", + "default", + "defer", + "do", + "else", + "fallthrough", + "for", + "guard", + "if", + "in", + "repeat", + "return", + "throw", + "switch", + "where", + "while", + // Keywords used in expressions and types: + "Any", + "as", + "await", + "catch", + "false", + "is", + "nil", + "rethrows", + "self", + "Self", + "super", + "throw", + "throws", + "true", + "try", + ] + .iter() + .map(ToString::to_string) + .collect::>() +}); + +/// Quote a name for use in a context where keywords must be quoted +pub fn quote_general_keyword(nm: String) -> String { + if KEYWORDS.contains(&nm) { + format!("`{nm}`") + } else { + nm + } +} + +/// Per subset of keywords which need quoting in arg context. +static ARG_KEYWORDS: Lazy> = Lazy::new(|| { + ["inout", "var", "let"] + .iter() + .map(ToString::to_string) + .collect::>() +}); + +/// Quote a name for use in arg context where fewer keywords must be quoted +pub fn quote_arg_keyword(nm: String) -> String { + if ARG_KEYWORDS.contains(&nm) { + format!("`{nm}`") + } else { + nm + } +} + +/// 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, + module_name: Option, + ffi_module_name: Option, + ffi_module_filename: Option, + generate_module_map: Option, + omit_argument_labels: Option, + #[serde(default)] + custom_types: HashMap, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CustomTypeConfig { + imports: Option>, + type_name: Option, + 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 BindingsConfig for Config { + fn update_from_ci(&mut self, ci: &ComponentInterface) { + self.module_name + .get_or_insert_with(|| ci.namespace().into()); + self.cdylib_name + .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); + } + + fn update_from_cdylib_name(&mut self, cdylib_name: &str) { + self.cdylib_name + .get_or_insert_with(|| cdylib_name.to_string()); + } + + fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} +} + +/// Generate UniFFI component bindings for Swift, as strings in memory. +/// +pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { + 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 = "swift", 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>, + // Track imports added with the `add_import()` macro + imports: RefCell>, +} + +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.swift` 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, + has_async_fns: bool, +} +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, + has_async_fns: ci.has_async_fns(), + } + } + + pub fn imports(&self) -> Vec { + self.type_imports.iter().cloned().collect() + } + + pub fn initialization_fns(&self) -> Vec { + self.ci + .iter_types() + .map(|t| SwiftCodeOracle.find(t)) + .filter_map(|ct| ct.initialization_fn()) + .chain( + self.has_async_fns + .then(|| "uniffiInitContinuationCallback".into()), + ) + .collect() + } +} + +#[derive(Clone)] +pub struct SwiftCodeOracle; + +impl SwiftCodeOracle { + // Map `Type` instances to a `Box` 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_: Type) -> Box { + 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::Bytes => Box::new(primitives::BytesCodeType), + + Type::Timestamp => Box::new(miscellany::TimestampCodeType), + Type::Duration => Box::new(miscellany::DurationCodeType), + + Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), + Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), + Type::CallbackInterface { name, .. } => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) + } + Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), + Type::Optional { inner_type } => { + Box::new(compounds::OptionalCodeType::new(*inner_type)) + } + Type::Sequence { inner_type } => { + Box::new(compounds::SequenceCodeType::new(*inner_type)) + } + Type::Map { + key_type, + value_type, + } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), + } + } + + fn find(&self, type_: &Type) -> Box { + 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 { + nm.to_string().to_lower_camel_case() + } + + /// Get the idiomatic Swift rendering of a variable name. + fn var_name(&self, nm: &str) -> String { + 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 { + nm.to_string().to_lower_camel_case() + } + + fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String { + 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(_) => "UnsafeMutableRawPointer".into(), + FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::ForeignCallback => "ForeignCallback".into(), + FfiType::ForeignExecutorHandle => "Int".into(), + FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), + FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), + FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { + "UnsafeMutableRawPointer".into() + } + } + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::ForeignCallback + | FfiType::ForeignExecutorCallback + | FfiType::RustFutureHandle + | FfiType::RustFutureContinuationCallback + | FfiType::RustFutureContinuationData => { + format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) + } + _ => self.ffi_type_label_raw(ffi_type), + } + } + + fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String { + self.ffi_type_label_raw(ffi_type) + } +} + +pub mod filters { + use super::*; + pub use crate::backend::filters::*; + + fn oracle() -> &'static SwiftCodeOracle { + &SwiftCodeOracle + } + + pub fn type_name(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).type_label()) + } + + pub fn canonical_name(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).canonical_name()) + } + + pub fn ffi_converter_name(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).ffi_converter_name()) + } + + pub fn lower_fn(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).lower()) + } + + pub fn write_fn(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).write()) + } + + pub fn lift_fn(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).lift()) + } + + pub fn read_fn(as_type: &impl AsType) -> Result { + Ok(oracle().find(&as_type.as_type()).read()) + } + + pub fn literal_swift( + literal: &Literal, + as_type: &impl AsType, + ) -> Result { + Ok(oracle().find(&as_type.as_type()).literal(literal)) + } + + /// Get the Swift type for an FFIType + pub fn ffi_type_name(ffi_type: &FfiType) -> Result { + Ok(oracle().ffi_type_label(ffi_type)) + } + + pub fn ffi_canonical_name(ffi_type: &FfiType) -> Result { + Ok(oracle().ffi_canonical_name(ffi_type)) + } + + /// Like `ffi_type_name`, but used in `BridgingHeaderTemplate.h` which uses a slightly different + /// names. + pub fn header_ffi_type_name(ffi_type: &FfiType) -> Result { + Ok(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".into(), + FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), + FfiType::ForeignExecutorHandle => "size_t".into(), + FfiType::RustFutureContinuationCallback => { + "UniFfiRustFutureContinuation _Nonnull".into() + } + FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { + "void* _Nonnull".into() + } + }) + } + + /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc). + pub fn class_name(nm: &str) -> Result { + Ok(oracle().class_name(nm)) + } + + /// Get the idiomatic Swift rendering of a function name. + pub fn fn_name(nm: &str) -> Result { + Ok(quote_general_keyword(oracle().fn_name(nm))) + } + + /// Get the idiomatic Swift rendering of a variable name. + pub fn var_name(nm: &str) -> Result { + Ok(quote_general_keyword(oracle().var_name(nm))) + } + + /// Get the idiomatic Swift rendering of an arguments name. + /// This is the same as the var name but quoting is not required. + pub fn arg_name(nm: &str) -> Result { + Ok(quote_arg_keyword(oracle().var_name(nm))) + } + + /// Get the idiomatic Swift rendering of an individual enum variant, quoted if it is a keyword (for use in e.g. declarations) + pub fn enum_variant_swift_quoted(nm: &str) -> Result { + Ok(quote_general_keyword(oracle().enum_variant_name(nm))) + } + + /// Get the idiomatic Swift rendering of an individual enum variant, for contexts (for use in non-declaration contexts where quoting is not needed) + pub fn enum_variant_swift(nm: &str) -> Result { + Ok(oracle().enum_variant_name(nm)) + } + + pub fn error_handler(result: &ResultType) -> Result { + Ok(match &result.throws_type { + Some(t) => format!("{}.lift", ffi_converter_name(t)?), + None => "nil".into(), + }) + } + + /// Name of the callback function to handle an async result + pub fn future_callback(result: &ResultType) -> Result { + Ok(format!( + "uniffiFutureCallbackHandler{}{}", + match &result.return_type { + Some(t) => SwiftCodeOracle.find(t).canonical_name(), + None => "Void".into(), + }, + match &result.throws_type { + Some(t) => SwiftCodeOracle.find(t).canonical_name(), + None => "".into(), + } + )) + } +} 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..ea140c998d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> 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..86424658a3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; +use crate::backend::Literal; +use crate::interface::{Radix, Type}; +use paste::paste; + +fn render_literal(literal: &Literal) -> String { + fn typed_number(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})", + super::SwiftCodeOracle.find(type_).type_label() + ) + } + _ => 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( + 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( + type_, + match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::Float(string, type_) => typed_number(type_, string.clone()), + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $class_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self) -> String { + $class_name.into() + } + + fn literal(&self, literal: &Literal) -> String { + render_literal(&literal) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "Bool"); +impl_code_type_for_primitive!(StringCodeType, "String"); +impl_code_type_for_primitive!(BytesCodeType, "Data"); +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..401109011f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> 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..bf17f38a4e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs @@ -0,0 +1,97 @@ +/* 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::process::Command; + +use anyhow::Result; +use camino::Utf8Path; +use fs_err as fs; + +pub mod gen_swift; +pub use gen_swift::{generate_bindings, Config}; +mod test; + +use super::super::interface::ComponentInterface; +pub use test::{run_script, 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, +} + +/// 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())); + fs::write(&source_file, library)?; + + let header_file = out_dir.join(config.header_filename()); + fs::write(header_file, header)?; + + if let Some(modulemap) = modulemap { + let modulemap_file = out_dir.join(config.modulemap_filename()); + fs::write(modulemap_file, 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: {e:?}", + source_file.file_name().unwrap(), + ); + } + } + + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift new file mode 100644 index 0000000000..695208861d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -0,0 +1,62 @@ +private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 +private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 + +fileprivate func uniffiRustCallAsync( + rustFutureFunc: () -> UnsafeMutableRawPointer, + pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (), + completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, + freeFunc: (UnsafeMutableRawPointer) -> (), + liftFunc: (F) throws -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) async throws -> T { + // Make sure to call uniffiEnsureInitialized() since future creation doesn't have a + // RustCallStatus param, so doesn't use makeRustCall() + uniffiEnsureInitialized() + let rustFuture = rustFutureFunc() + defer { + freeFunc(rustFuture) + } + var pollResult: Int8; + repeat { + pollResult = await withUnsafeContinuation { + pollFunc(rustFuture, ContinuationHolder($0).toOpaque()) + } + } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY + + return try liftFunc(makeRustCall( + { completeFunc(rustFuture, $0) }, + errorHandler: errorHandler + )) +} + +// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { + ContinuationHolder.fromOpaque(ptr).resume(pollResult) +} + +// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across +// the FFI +fileprivate class ContinuationHolder { + let continuation: UnsafeContinuation + + init(_ continuation: UnsafeContinuation) { + self.continuation = continuation + } + + func resume(_ pollResult: Int8) { + self.continuation.resume(returning: pollResult) + } + + func toOpaque() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { + return Unmanaged.fromOpaque(ptr).takeRetainedValue() + } +} + +fileprivate func uniffiInitContinuationCallback() { + {{ ci.ffi_rust_future_continuation_callback_set().name() }}(uniffiFutureContinuationCallback) +} 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..87698e359f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -0,0 +1,79 @@ +// 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 +#include +#include + +// 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, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); + +// Task defined in Rust that Swift executes +typedef void (*UniFfiRustTaskCallback)(const void * _Nullable, int8_t); + +// Callback to execute Rust tasks using a Swift Task +// +// Args: +// executor: ForeignExecutor lowered into a size_t value +// delay: Delay in MS +// task: UniFfiRustTaskCallback to call +// task_data: data to pass the task callback +typedef int8_t (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); + +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 + +// Continuation callback for UniFFI Futures +typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); + +// Scaffolding functions +{%- for func in ci.iter_ffi_function_definitions() %} +{% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( + {%- if func.arguments().len() > 0 %} + {%- for arg in func.arguments() %} + {{- arg.type_().borrow()|header_ffi_type_name }} {{ arg.name() -}}{% if !loop.last || func.has_rust_call_status_arg() %}, {% endif %} + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{% endif %} + {%- else %} + {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %} + {% endif %} +); +{%- 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..9ae62d1667 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -0,0 +1,64 @@ +fileprivate extension NSLock { + func withLock(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate typealias UniFFICallbackHandle = UInt64 +fileprivate class UniFFICallbackHandleMap { + 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 +// Callback return codes +private let UNIFFI_CALLBACK_SUCCESS: Int32 = 0 +private let UNIFFI_CALLBACK_ERROR: Int32 = 1 +private let UNIFFI_CALLBACK_UNEXPECTED_ERROR: Int32 = 2 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..aec8ded930 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -0,0 +1,150 @@ +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- 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, argsData: UnsafePointer, argsLen: Int32, out_buf: UnsafeMutablePointer) -> Int32 in + {% for meth in cbi.methods() -%} + {%- let method_name = format!("invoke_{}", meth.name())|fn_name %} + + func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer) throws -> Int32 { + {%- if meth.arguments().len() > 0 %} + var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen))) + {%- endif %} + + {%- match meth.return_type() %} + {%- when Some(return_type) %} + func makeCall() throws -> Int32 { + let result = {% 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 -%} + ) + var writer = [UInt8]() + {{ return_type|write_fn }}(result, into: &writer) + out_buf.pointee = RustBuffer(bytes: writer) + return UNIFFI_CALLBACK_SUCCESS + } + {%- when None %} + func makeCall() throws -> Int32 { + try 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 -%} + ) + return UNIFFI_CALLBACK_SUCCESS + } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + return try makeCall() + {%- when Some(error_type) %} + do { + return try makeCall() + } catch let error as {{ error_type|type_name }} { + out_buf.pointee = {{ error_type|lower_fn }}(error) + return UNIFFI_CALLBACK_ERROR + } + {%- endmatch %} + } + {%- endfor %} + + + switch method { + case IDX_CALLBACK_FREE: + {{ ffi_converter_name }}.drop(handle: handle) + // Sucessful return + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + return UNIFFI_CALLBACK_SUCCESS + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + case {{ loop.index }}: + 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 UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + do { + return try {{ method_name }}(cb, argsData, argsLen, out_buf) + } catch let error { + out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + return UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + {% endfor %} + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + return UNIFFI_CALLBACK_UNEXPECTED_ERROR + } +} + +// FfiConverter protocol for callback interfaces +fileprivate struct {{ ffi_converter_name }} { + private static let initCallbackOnce: () = { + // Swift ensures this initializer code will once run once, even when accessed by multiple threads. + try! rustCall { (err: UnsafeMutablePointer) in + {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err) + } + }() + + private static func ensureCallbackinitialized() { + _ = initCallbackOnce + } + + 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..504cadfc1d --- /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|ffi_type_name %} +{%- 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) + } +} + +{%- endmatch %} + +{# +We always write these public functions just incase the type is used as +an external type by another crate. +#} +public func FfiConverterType{{ name }}_lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + return try FfiConverterType{{ name }}.lift(value) +} + +public func FfiConverterType{{ name }}_lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + return FfiConverterType{{ name }}.lower(value) +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift new file mode 100644 index 0000000000..7db240bf9c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift @@ -0,0 +1,14 @@ +fileprivate struct FfiConverterData: FfiConverterRustBuffer { + typealias SwiftType = Data + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Data { + let len: Int32 = try readInt(&buf) + return Data(try readBytes(&buf, count: Int(len))) + } + + public static func write(_ value: Data, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + writeBytes(&buf, value) + } +} 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..99f45290cc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -0,0 +1,59 @@ +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum {{ type_name }} { + {% for variant in e.variants() %} + case {{ variant.name()|enum_variant_swift_quoted }}{% 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_quoted }}{% if variant.has_fields() %}( + {%- for field in variant.fields() %} + {{ field.name()|arg_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_quoted }}({% 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_quoted }}: + 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..786091395b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -0,0 +1,86 @@ +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 %} + + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { + return try {{ ffi_converter_name }}.lift(error) + } +} + + +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 .{{ variant.name()|class_name }}(_ /* message is ignored*/): + writeInt(&buf, Int32({{ loop.index }})) + {%- 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/ForeignExecutorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift new file mode 100644 index 0000000000..167e4c7546 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift @@ -0,0 +1,69 @@ +private let UNIFFI_RUST_TASK_CALLBACK_SUCCESS: Int8 = 0 +private let UNIFFI_RUST_TASK_CALLBACK_CANCELLED: Int8 = 1 +private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS: Int8 = 0 +private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED: Int8 = 1 +private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR: Int8 = 2 + +// Encapsulates an executor that can run Rust tasks +// +// On Swift, `Task.detached` can handle this we just need to know what priority to send it. +public struct UniFfiForeignExecutor { + var priority: TaskPriority + + public init(priority: TaskPriority) { + self.priority = priority + } + + public init() { + self.priority = Task.currentPriority + } +} + +fileprivate struct FfiConverterForeignExecutor: FfiConverter { + typealias SwiftType = UniFfiForeignExecutor + // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8. + // let's use `Int`, which is equivalent to `size_t` + typealias FfiType = Int + + public static func lift(_ value: FfiType) throws -> SwiftType { + UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value))) + } + public static func lower(_ value: SwiftType) -> FfiType { + numericCast(value.priority.rawValue) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + fatalError("FfiConverterForeignExecutor.read not implemented yet") + } + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + fatalError("FfiConverterForeignExecutor.read not implemented yet") + } +} + + +fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) -> Int8 { + if let rustTask = rustTask { + let executor = try! FfiConverterForeignExecutor.lift(executorHandle) + Task.detached(priority: executor.priority) { + if delayMs != 0 { + let nanoseconds: UInt64 = numericCast(delayMs * 1000000) + try! await Task.sleep(nanoseconds: nanoseconds) + } + rustTask(taskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) + } + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } else { + // When rustTask is null, we should drop the foreign executor. + // However, since its just a value type, we don't need to do anything here. + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } +} + +fileprivate func uniffiInitForeignExecutor() { + {%- match ci.ffi_foreign_executor_callback_set() %} + {%- when Some with (fn) %} + {{ fn.name() }}(uniffiForeignExecutorCallback) + {%- when None %} + {#- No foreign executor, we don't set anything #} + {% endmatch %} +} 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..a34b128e23 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -0,0 +1,101 @@ +// 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 let CALL_CANCELLED: Int8 = 3 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: nil) +} + +private func rustCallWithError( + _ errorHandler: @escaping (RustBuffer) throws -> Error, + _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall( + _ callback: (UnsafeMutablePointer) -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) throws -> T { + uniffiEnsureInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> Error)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + 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") + } + + case CALL_CANCELLED: + throw CancellationError() + + 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.. {{ 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() -%} + {%- if meth.is_async() %} + + public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { + return {% call swift::try(meth) %} await uniffiRustCallAsync( + rustFutureFunc: { + {{ meth.ffi_func().name() }}( + self.pointer + {%- for arg in meth.arguments() -%} + , + {{ arg|lower_fn }}({{ arg.name()|var_name }}) + {%- endfor %} + ) + }, + pollFunc: {{ meth.ffi_rust_future_poll(ci) }}, + completeFunc: {{ meth.ffi_rust_future_complete(ci) }}, + freeFunc: {{ meth.ffi_rust_future_free(ci) }}, + {%- match meth.return_type() %} + {%- when Some(return_type) %} + liftFunc: {{ return_type|lift_fn }}, + {%- when None %} + liftFunc: { $0 }, + {%- endmatch %} + {%- match meth.throws_type() %} + {%- when Some with (e) %} + errorHandler: {{ e|ffi_converter_name }}.lift + {%- else %} + errorHandler: nil + {% endmatch %} + ) + } + + {% else -%} + + {%- 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 -%} + {%- endif -%} + {% 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 + } +} + +{# +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(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(pointer) +} + +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + return {{ ffi_converter_name }}.lower(value) +} 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..44de9dd358 --- /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) %} +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()|arg_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) -> 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) { + 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(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset...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 { + 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(_ 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(_ 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(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..a2c6311931 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift @@ -0,0 +1,48 @@ +{%- if func.is_async() %} + +public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { + return {% call swift::try(func) %} await uniffiRustCallAsync( + rustFutureFunc: { + {{ func.ffi_func().name() }}( + {%- for arg in func.arguments() %} + {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) + }, + pollFunc: {{ func.ffi_rust_future_poll(ci) }}, + completeFunc: {{ func.ffi_rust_future_complete(ci) }}, + freeFunc: {{ func.ffi_rust_future_free(ci) }}, + {%- match func.return_type() %} + {%- when Some(return_type) %} + liftFunc: {{ return_type|lift_fn }}, + {%- when None %} + liftFunc: { $0 }, + {%- endmatch %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + errorHandler: {{ e|ffi_converter_name }}.lift + {%- else %} + errorHandler: nil + {% endmatch %} + ) +} + +{% else %} + +{%- 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 %} +{%- endif %} 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..aba34f4b0b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -0,0 +1,98 @@ +{%- 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::Bytes %} +{%- include "DataHelper.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, module_path } %} +{%- include "CallbackInterfaceTemplate.swift" %} + +{%- when Type::ForeignExecutor %} +{%- include "ForeignExecutorTemplate.swift" %} + +{%- when Type::Custom { name, module_path, builtin } %} +{%- include "CustomType.swift" %} + +{%- when Type::Enum { name, module_path } %} +{%- let e = ci.get_enum_definition(name).unwrap() %} +{%- if ci.is_name_used_as_error(name) %} +{%- include "ErrorTemplate.swift" %} +{%- else %} +{%- include "EnumTemplate.swift" %} +{% endif %} + +{%- when Type::Object{ name, module_path, imp } %} +{%- include "ObjectTemplate.swift" %} + +{%- when Type::Record { name, module_path } %} +{%- 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..0a125e6f61 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -0,0 +1,89 @@ +{# +// 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_lowered` +#} + +{%- macro to_ffi_call(func) -%} + {%- call try(func) -%} + {%- match func.throws_type() -%} + {%- when Some with (e) -%} + rustCallWithError({{ e|ffi_converter_name }}.lift) { + {%- else -%} + rustCall() { + {%- endmatch %} + {{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} $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 }}.lift) { + {%- else %} + rustCall() { + {% endmatch %} + {{ func.ffi_func().name() }}( + {{- prefix }}, {% call arg_list_lowered(func) -%} $0 + ) +} +{%- endmacro %} + +{%- macro arg_list_lowered(func) %} + {%- for arg in func.arguments() %} + {{ arg|lower_fn }}({{ arg.name()|var_name }}), + {%- 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 %} + + +{%- macro async(func) %} +{%- if func.is_async() %}async{% endif %} +{%- 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..c34d348efb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift @@ -0,0 +1,68 @@ +// 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 }} + +{%- if ci.has_async_fns() %} +{% include "Async.swift" %} +{%- endif %} + +{%- for func in ci.function_definitions() %} +{%- include "TopLevelFunctionTemplate.swift" %} +{%- endfor %} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variables to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private var initializationResult: InitializationResult { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = {{ ci.uniffi_contract_version() }} + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = {{ ci.ffi_uniffi_contract_version().name() }}() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + + {%- for (name, expected_checksum) in ci.iter_checksums() %} + if ({{ name }}() != {{ expected_checksum }}) { + return InitializationResult.apiChecksumMismatch + } + {%- endfor %} + + {% for fn in self.initialization_fns() -%} + {{ fn }}() + {% endfor -%} + + return InitializationResult.ok +} + +private func uniffiEnsureInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} 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..c3b2f15277 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs @@ -0,0 +1,202 @@ +/* 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::{ + bindings::{RunScriptOptions, TargetLanguage}, + library_mode::generate_bindings, +}; +use anyhow::{bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +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, Stdio}; +use uniffi_testing::UniFFITestHelper; + +/// Run Swift tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + run_script( + tmp_dir, + fixture_name, + script_file, + vec![], + &RunScriptOptions::default(), + ) +} + +/// Run a Swift script +/// +/// This function will set things up so that the script can import the UniFFI bindings for a crate +pub fn run_script( + tmp_dir: &str, + crate_name: &str, + script_file: &str, + args: Vec, + options: &RunScriptOptions, +) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let test_helper = UniFFITestHelper::new(crate_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + let generated_sources = GeneratedSources::new(crate_name, &cdylib_path, &out_dir)?; + + // Compile the generated sources together to create a single swift module + compile_swift_module( + &out_dir, + &generated_sources.main_module, + &generated_sources.generated_swift_files, + &generated_sources.module_map, + options, + )?; + + // Run the test script against compiled bindings + let mut command = create_command("swift", options); + 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) + .args(args); + 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 compile_swift_module>( + out_dir: &Utf8Path, + module_name: &str, + sources: impl IntoIterator, + module_map: &Utf8Path, + options: &RunScriptOptions, +) -> Result<()> { + let output_filename = format!("{DLL_PREFIX}testmod_{module_name}{DLL_SUFFIX}"); + let mut command = create_command("swiftc", options); + 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 { + main_module: String, + generated_swift_files: Vec, + module_map: Utf8PathBuf, +} + +impl GeneratedSources { + fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result { + let sources = + generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?; + let main_source = sources + .iter() + .find(|s| s.package.name == crate_name) + .unwrap(); + let main_module = main_source.config.bindings.swift.module_name(); + let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; + let module_map = match modulemap_glob.len() { + 0 => bail!("No modulemap files found in {out_dir}"), + // Normally we only generate 1 module map and can return it directly + 1 => modulemap_glob.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, + "{}", + modulemap_glob + .into_iter() + .map(|path| Ok(read_to_string(path)?)) + .collect::>>()? + .join("\n") + )?; + path + } + }; + + Ok(GeneratedSources { + main_module, + generated_swift_files: glob(&out_dir.join("*.swift"))?, + module_map, + }) + } +} + +fn create_command(program: &str, options: &RunScriptOptions) -> Command { + let mut command = Command::new(program); + if !options.show_compiler_messages { + // This prevents most compiler messages, but not remarks + command.arg("-suppress-warnings"); + // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument, + // maybe we can eventually move to that + command.stderr(Stdio::null()); + } + command +} + +// Wraps glob to use Utf8Paths and flattens errors +fn glob(globspec: &Utf8Path) -> Result> { + glob::glob(globspec.as_str())? + .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?)) + .collect() +} + +fn calc_library_args(out_dir: &Utf8Path) -> Result> { + 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() +} -- cgit v1.2.3