diff --git a/.cargo/config.toml b/.cargo/config.toml index 35049cbc..4045f1a0 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [alias] -xtask = "run --package xtask --" +xtask = "--quiet run --package xtask --" diff --git a/cpp/hermes-extension/CMakeLists.txt b/cpp/hermes-extension/CMakeLists.txt index e337fb81..e3e7cb33 100644 --- a/cpp/hermes-extension/CMakeLists.txt +++ b/cpp/hermes-extension/CMakeLists.txt @@ -46,5 +46,4 @@ link_directories("${HERMES_BUILD_DIR}/API/hermes") link_directories("${HERMES_BUILD_DIR}/jsi") link_libraries(jsi) -add_definitions(-DUNIFFI_ENABLE_TEST_HOOKS) add_library(${HERMES_EXTENSION_NAME} SHARED ${HERMES_EXTENSION_CPP}) diff --git a/cpp/hermes-rust-extension/CMakeLists.txt b/cpp/hermes-rust-extension/CMakeLists.txt index dbfa9404..a42764ce 100644 --- a/cpp/hermes-rust-extension/CMakeLists.txt +++ b/cpp/hermes-rust-extension/CMakeLists.txt @@ -56,6 +56,5 @@ link_directories("${HERMES_BUILD_DIR}/jsi") link_libraries(jsi) link_directories("${RUST_TARGET_DIR}") -add_definitions(-DUNIFFI_ENABLE_TEST_HOOKS) add_library(${HERMES_EXTENSION_NAME} SHARED ${HERMES_EXTENSION_CPP}) target_link_libraries(${HERMES_EXTENSION_NAME} ${RUST_LIB_NAME}) diff --git a/crates/ubrn_bindgen/src/bindings/mod.rs b/crates/ubrn_bindgen/src/bindings/mod.rs index 7507b53a..61c9efea 100644 --- a/crates/ubrn_bindgen/src/bindings/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/mod.rs @@ -6,10 +6,12 @@ pub mod metadata; pub(crate) mod react_native; +pub(crate) mod type_map; -use std::str::FromStr; +use std::{fs, str::FromStr}; use anyhow::Result; +use askama::Template; use camino::{Utf8Path, Utf8PathBuf}; use clap::{command, Args}; use ubrn_common::mk_dir; @@ -150,4 +152,17 @@ impl BindingsArgs { Ok(configs) } + + pub fn render_entrypoint(&self, path: &Utf8Path, modules: &Vec) -> Result<()> { + let index = EntrypointCpp { modules }; + let string = index.render()?; + fs::write(path, string)?; + Ok(()) + } +} + +#[derive(Template)] +#[template(path = "entrypoint.cpp", escape = "none")] +struct EntrypointCpp<'a> { + modules: &'a Vec, } diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/entrypoint.cpp b/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/entrypoint.cpp new file mode 100644 index 00000000..47fc6297 --- /dev/null +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/entrypoint.cpp @@ -0,0 +1,21 @@ +// This file was autogenerated by some hot garbage in the `uniffi-bindgen-react-native` crate. +// Trust me, you don't want to mess with it! + +// This template is only used when running `cargo xtask run`, i.e. when running `./scripts/run-tests.sh`. +// It is an implementation of the `registerNatives` function which is used by the test-runner to dynamically +// load the native modules without re-compiling the test-runner. +// +// Files generated with this template appear in the fixtures/{fixture}/generated/cpp directory, always called +// `Entrypoint.cpp`, with a captilized `E` to ensure it does not collide with namespaces called "entrypoint". + +#include "registerNatives.h" + +{%- for m in modules %} +#include "{{ m.hpp_filename() }}"; +{%- endfor %} + +extern "C" void registerNatives(jsi::Runtime &rt, std::shared_ptr callInvoker) { + {%- for m in modules %} + {{ m.cpp_module() }}::registerModule(rt, callInvoker); + {%- endfor %} +} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.cpp b/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.cpp index 92cf5a42..3fe3472b 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.cpp +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.cpp @@ -1,7 +1,7 @@ -{%- let namespace = ci.namespace() %} -{%- let module_name = module.cpp_module() %} // This file was autogenerated by some hot garbage in the `uniffi-bindgen-react-native` crate. // Trust me, you don't want to mess with it! +{%- let namespace = ci.namespace() %} +{%- let module_name = module.cpp_module() %} #include "{{ module.hpp_filename() }}" #include "UniffiJsiTypes.h" @@ -14,14 +14,6 @@ namespace react = facebook::react; namespace jsi = facebook::jsi; -#ifdef UNIFFI_ENABLE_TEST_HOOKS -// Initialization into the Hermes Runtime -#include "registerNatives.h" -extern "C" void registerNatives(jsi::Runtime &rt, std::shared_ptr callInvoker) { - {{ module_name }}::registerModule(rt, callInvoker); -} -#endif - // Calling into Rust. extern "C" { {%- for definition in ci.ffi_definitions() %} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.hpp b/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.hpp index 128ae249..ad12fe50 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.hpp +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_cpp/templates/wrapper.hpp @@ -1,7 +1,7 @@ -{%- let namespace = ci.namespace() %} -{%- let module_name = module.cpp_module() %} // This file was autogenerated by some hot garbage in the `uniffi-bindgen-react-native` crate. // Trust me, you don't want to mess with it! +{%- let namespace = ci.namespace() %} +{%- let module_name = module.cpp_module() %} #pragma once #include #include diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/external.rs b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/external.rs deleted file mode 100644 index 4b76ef91..00000000 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/external.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/ - */ -use super::oracle::{CodeOracle, CodeType}; -use uniffi_bindgen::ComponentInterface; - -#[derive(Debug)] -pub struct ExternalCodeType { - name: String, -} - -impl ExternalCodeType { - pub fn new(name: String) -> Self { - Self { name } - } -} - -impl CodeType for ExternalCodeType { - fn type_label(&self, ci: &ComponentInterface) -> String { - CodeOracle.class_name(ci, &self.name) - } - - fn canonical_name(&self) -> String { - format!("Type{}", self.name) - } -} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/filters.rs b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/filters.rs index c8b5c149..4a7a5d0b 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/filters.rs +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/filters.rs @@ -3,7 +3,10 @@ * 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::oracle::{AsCodeType, CodeOracle}; +use super::{ + oracle::{AsCodeType, CodeOracle}, + TypeRenderer, +}; pub(crate) use uniffi_bindgen::backend::filters::*; use uniffi_bindgen::{ backend::{Literal, Type}, @@ -12,47 +15,72 @@ use uniffi_bindgen::{ }; pub(super) fn type_name( - as_ct: &impl AsCodeType, - ci: &ComponentInterface, + as_type: &impl AsType, + types: &TypeRenderer, ) -> Result { - Ok(as_ct.as_codetype().type_label(ci)) + let type_ = types.as_type(as_type); + Ok(type_.as_codetype().type_label(types.ci)) } pub(super) fn decl_type_name( - as_ct: &impl AsCodeType, - ci: &ComponentInterface, + as_type: &impl AsType, + types: &TypeRenderer, ) -> Result { - Ok(as_ct.as_codetype().decl_type_label(ci)) -} - -pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result { - Ok(as_ct.as_codetype().canonical_name()) + let type_ = types.as_type(as_type); + Ok(type_.as_codetype().decl_type_label(types.ci)) } -pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { - Ok(as_ct.as_codetype().ffi_converter_name()) +pub(super) fn ffi_converter_name( + as_type: &impl AsType, + types: &TypeRenderer, +) -> Result { + let type_ = types.as_type(as_type); + Ok(type_.as_codetype().ffi_converter_name()) } -pub(super) fn ffi_error_converter_name(as_type: &impl AsType) -> Result { +pub(super) fn ffi_error_converter_name( + as_type: &impl AsType, + types: &TypeRenderer, +) -> Result { // special handling for types used as errors. - let mut name = ffi_converter_name(as_type)?; - if matches!(&as_type.as_type(), Type::Object { .. }) { + let type_ = types.as_type(as_type); + let mut name = type_.as_codetype().ffi_converter_name(); + if matches!(type_, Type::Object { .. }) { name.push_str("__as_error") } + if let Type::External { namespace, .. } = as_type.as_type() { + types.import_converter(name.clone(), &namespace); + } Ok(name) } -pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result { +pub(super) fn lower_error_fn( + as_type: &impl AsType, + types: &TypeRenderer, +) -> Result { Ok(format!( "{ct}.lower.bind({ct})", - ct = as_ct.as_codetype().ffi_converter_name() + ct = ffi_error_converter_name(as_type, types)? + )) +} + +pub(super) fn lift_error_fn( + as_type: &impl AsType, + types: &TypeRenderer, +) -> Result { + Ok(format!( + "{ct}.lift.bind({ct})", + ct = ffi_error_converter_name(as_type, types)? )) } -pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result { +pub(super) fn lift_fn( + as_type: &impl AsType, + types: &TypeRenderer, +) -> Result { Ok(format!( "{ct}.lift.bind({ct})", - ct = as_ct.as_codetype().ffi_converter_name() + ct = ffi_converter_name(as_type, types)? )) } diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/mod.rs b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/mod.rs index f7b6e39d..8203e037 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/mod.rs @@ -10,7 +10,6 @@ mod callback_interface; mod compounds; mod custom; mod enum_; -mod external; mod miscellany; mod object; mod primitives; @@ -18,20 +17,20 @@ mod record; use anyhow::{Context, Result}; use askama::Template; -use filters::ffi_converter_name; +use filters::{ffi_converter_name, type_name}; use heck::ToUpperCamelCase; use oracle::CodeOracle; use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeMap, BTreeSet, HashSet}; -use uniffi_bindgen::interface::{Callable, FfiDefinition, FfiType, Type, UniffiTrait}; +use uniffi_bindgen::interface::{AsType, Callable, FfiDefinition, FfiType, Type, UniffiTrait}; use uniffi_bindgen::ComponentInterface; -use uniffi_meta::{AsType, ExternalKind}; use crate::bindings::metadata::ModuleMetadata; use crate::bindings::react_native::{ ComponentInterfaceExt, FfiCallbackFunctionExt, FfiFunctionExt, FfiStructExt, ObjectExt, }; +use crate::bindings::type_map::TypeMap; #[derive(Default)] pub(crate) struct TsBindings { @@ -45,11 +44,12 @@ pub(crate) fn generate_bindings( ci: &ComponentInterface, config: &Config, module: &ModuleMetadata, + type_map: &TypeMap, ) -> Result { let codegen = CodegenWrapper::new(ci, config, module) .render() .context("generating codegen bindings failed")?; - let frontend = FrontendWrapper::new(ci, config, module) + let frontend = FrontendWrapper::new(ci, config, module, type_map) .render() .context("generating frontend javascript failed")?; @@ -85,8 +85,13 @@ struct FrontendWrapper<'a> { } impl<'a> FrontendWrapper<'a> { - pub fn new(ci: &'a ComponentInterface, config: &'a Config, module: &'a ModuleMetadata) -> Self { - let type_renderer = TypeRenderer::new(ci, config, module); + pub fn new( + ci: &'a ComponentInterface, + config: &'a Config, + module: &'a ModuleMetadata, + type_map: &'a TypeMap, + ) -> Self { + let type_renderer = TypeRenderer::new(ci, config, module, type_map); let type_helper_code = type_renderer.render().unwrap(); let type_imports = type_renderer.imports.into_inner(); let exported_converters = type_renderer.exported_converters.into_inner(); @@ -122,10 +127,18 @@ pub struct TypeRenderer<'a> { // Track imports added with the `add_import()` macro imported_converters: RefCell>>, + + // The universe of types outside of this module. For tracking external types. + type_map: &'a TypeMap, } impl<'a> TypeRenderer<'a> { - fn new(ci: &'a ComponentInterface, config: &'a Config, module: &'a ModuleMetadata) -> Self { + fn new( + ci: &'a ComponentInterface, + config: &'a Config, + module: &'a ModuleMetadata, + type_map: &'a TypeMap, + ) -> Self { Self { ci, config, @@ -133,6 +146,7 @@ impl<'a> TypeRenderer<'a> { imports: RefCell::new(Default::default()), exported_converters: RefCell::new(Default::default()), imported_converters: RefCell::new(Default::default()), + type_map, } } @@ -186,39 +200,33 @@ impl<'a> TypeRenderer<'a> { fn import_external_type(&self, external: &impl AsType) -> &str { match external.as_type() { - Type::External { - namespace, - name, - kind, - .. - } => { - match kind { - ExternalKind::DataClass => { - self.import_ext_type(&name, &namespace); - } - ExternalKind::Interface => { - self.import_ext(&name, &namespace); - } - ExternalKind::Trait => { - self.import_ext(&name, &namespace); - } - } - let converters = format!("uniffi{}Module", namespace.to_upper_camel_case()); - let src = format!("./{namespace}"); - let ffi_converter_name = ffi_converter_name(external) + Type::External { namespace, .. } => { + let type_ = self.as_type(external); + let name = type_name(&type_, self).expect("External types should have type names"); + match &type_ { + Type::CallbackInterface { .. } + | Type::Object { .. } + | Type::Custom { .. } + | Type::Enum { .. } => self.import_ext(&name, &namespace), + Type::Record { .. } => self.import_ext_type(&name, &namespace), + _ => unreachable!(), + }; + let ffi_converter_name = ffi_converter_name(&type_, self) .expect("FfiConverter for External type will always exist"); - self.import_converter(&ffi_converter_name, &src, &converters); - "" + self.import_converter(ffi_converter_name, &namespace) } _ => unreachable!(), } } - fn import_converter(&self, what: &str, src: &str, converters: &str) -> &str { + fn import_converter(&self, ffi_converter_name: String, namespace: &str) -> &str { + let converters = format!("uniffi{}Module", namespace.to_upper_camel_case()); + let src = format!("./{namespace}"); + let mut map = self.imported_converters.borrow_mut(); - let key = (src.to_owned(), converters.to_owned()); + let key = (src, converters); let set = map.entry(key).or_default(); - set.insert(what.to_owned()); + set.insert(ffi_converter_name); "" } @@ -231,10 +239,15 @@ impl<'a> TypeRenderer<'a> { fn initialization_fns(&self) -> Vec { self.ci .iter_sorted_types() + .filter(|t| !matches!(t, Type::External { .. })) .map(|t| CodeOracle.find(&t)) .filter_map(|ct| ct.initialization_fn()) .collect() } + + pub(crate) fn as_type(&self, as_type: &impl AsType) -> Type { + self.type_map.as_type(as_type) + } } #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/oracle.rs b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/oracle.rs index d5561873..c8dd9aa2 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/oracle.rs +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/oracle.rs @@ -181,7 +181,9 @@ impl AsCodeType for T { key_type, value_type, } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), - Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::External { .. } => unreachable!( + "External types should have been elimintated by going through the TypeRenderer::as_type() method" + ), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), } } diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CallbackInterfaceImpl.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CallbackInterfaceImpl.ts index 16ded3a8..f9a6e33e 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CallbackInterfaceImpl.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CallbackInterfaceImpl.ts @@ -27,7 +27,7 @@ const {{ trait_impl }}: { vtable: {{ vtable|ffi_type_name }}; register: () => vo const jsCallback = {{ ffi_converter_name }}.lift(uniffiHandle); return {% call ts::await(meth) %}jsCallback.{{ meth.name()|fn_name }}( {%- for arg in meth.arguments() %} - {{ arg|ffi_converter_name }}.lift({{ arg.name()|var_name }}){% if !loop.last %}, {% endif %} + {{ arg|ffi_converter_name(self) }}.lift({{ arg.name()|var_name }}){% if !loop.last %}, {% endif %} {%- endfor %} ) } @@ -35,7 +35,7 @@ const {{ trait_impl }}: { vtable: {{ vtable|ffi_type_name }}; register: () => vo {% match meth.return_type() %} {%- when Some(t) %} - const uniffiWriteReturn = (obj: any) => { uniffiOutReturn.pointee = {{ t|lower_fn }}(obj) }; + const uniffiWriteReturn = (obj: any) => { uniffiOutReturn.pointee = {{ t|ffi_converter_name(self) }}.lower(obj) }; {%- when None %} const uniffiWriteReturn = (obj: any) => {}; {%- endmatch %} @@ -55,8 +55,8 @@ const {{ trait_impl }}: { vtable: {{ vtable|ffi_type_name }}; register: () => vo /*callStatus:*/ uniffiCallStatus, /*makeCall:*/ uniffiMakeCall, /*writeReturn:*/ uniffiWriteReturn, - /*isErrorType:*/ {{ error_type|decl_type_name(ci) }}.instanceOf, - /*lowerError:*/ {{ error_type|lower_fn }}, + /*isErrorType:*/ {{ error_type|decl_type_name(self) }}.instanceOf, + /*lowerError:*/ {{ error_type|lower_error_fn(self) }}, /*lowerString:*/ FfiConverterString.lower ) {%- endmatch %} @@ -68,7 +68,7 @@ const {{ trait_impl }}: { vtable: {{ vtable|ffi_type_name }}; register: () => vo /* {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }} */{ {%- match meth.return_type() %} {%- when Some(return_type) %} - returnValue: {{ return_type|ffi_converter_name }}.lower(returnValue), + returnValue: {{ return_type|ffi_converter_name(self) }}.lower(returnValue), {%- when None %} {%- endmatch %} callStatus: uniffiCreateCallStatus() @@ -104,8 +104,8 @@ const {{ trait_impl }}: { vtable: {{ vtable|ffi_type_name }}; register: () => vo /*makeCall:*/ uniffiMakeCall, /*handleSuccess:*/ uniffiHandleSuccess, /*handleError:*/ uniffiHandleError, - /*isErrorType:*/ {{ error_type|decl_type_name(ci) }}.instanceOf, - /*lowerError:*/ {{ error_type|lower_fn }}, + /*isErrorType:*/ {{ error_type|decl_type_name(self) }}.instanceOf, + /*lowerError:*/ {{ error_type|lower_error_fn(self) }}, /*lowerString:*/ FfiConverterString.lower ) {%- endmatch %} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CustomTypeTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CustomTypeTemplate.ts index b3d3011d..1335844f 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CustomTypeTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/CustomTypeTemplate.ts @@ -6,9 +6,9 @@ * 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. */ -export type {{ type_name }} = {{ builtin|type_name(ci) }}; -// FfiConverter for {{ type_name }}, a type alias for {{ builtin|type_name(ci) }}. -const {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }}; +export type {{ type_name }} = {{ builtin|type_name(self) }}; +// FfiConverter for {{ type_name }}, a type alias for {{ builtin|type_name(self) }}. +const {{ ffi_converter_name }} = {{ builtin|ffi_converter_name(self) }}; {%- when Some with (config) %} @@ -32,7 +32,7 @@ export type {{ type_name }} = {{ concrete_type_name }}; const {{ ffi_converter_name }} = (() => { type TsType = {{ type_name }}; type FfiType = {{ ffi_type_name }}; - const intermediateConverter = {{ builtin|ffi_converter_name }}; + const intermediateConverter = {{ builtin|ffi_converter_name(self) }}; class FFIConverter implements FfiConverter { lift(value: FfiType): TsType { const intermediate = intermediateConverter.lift(value); diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ErrorTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ErrorTemplate.ts index dd6e01ba..7a10c4a2 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ErrorTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ErrorTemplate.ts @@ -54,7 +54,7 @@ const {{ ffi_converter_name }} = (() => { {%- if flat %}FfiConverterString.read(from) {%- else %} {%- for field in variant.fields() %} - {{ field|ffi_converter_name }}.read(from) + {{ field|ffi_converter_name(self) }}.read(from) {%- if !loop.last %}, {% endif %} {%- endfor %} {%- endif %} @@ -72,7 +72,7 @@ const {{ ffi_converter_name }} = (() => { {%- for variant in e.variants() %} case {{ loop.index }}: {%- for field in variant.fields() %} - {{ field|ffi_converter_name }}.write(obj.{{ field.name()|var_name }} as {{ field|type_name(ci) }}, into); + {{ field|ffi_converter_name(self) }}.write(obj.{{ field.name()|var_name }} as {{ field|type_name(self) }}, into); {%- endfor -%} break; {%- endfor %} @@ -91,7 +91,7 @@ const {{ ffi_converter_name }} = (() => { case {{ loop.index }}: return (intConverter.allocationSize({{ loop.index }}) {%- for field in variant.fields() %} + {# space #} - {{ field|ffi_converter_name }}.allocationSize(obj.{{ field.name()|var_name }} as {{ field|type_name(ci) }}) + {{ field|ffi_converter_name(self) }}.allocationSize(obj.{{ field.name()|var_name }} as {{ field|type_name(self) }}) {%- endfor -%} ); {%- endfor %} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/MapTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/MapTemplate.ts index 13e5f5b5..c96db731 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/MapTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/MapTemplate.ts @@ -1,5 +1,5 @@ {{- self.import_infra("FfiConverterMap", "ffi-converters") }} -{%- let key_ffi_converter = key_type|ffi_converter_name %} -{%- let value_ffi_converter = value_type|ffi_converter_name %} +{%- let key_ffi_converter = key_type|ffi_converter_name(self) %} +{%- let value_ffi_converter = value_type|ffi_converter_name(self) %} // FfiConverter for {{ type_name }} const {{ ffi_converter_name }} = new FfiConverterMap({{ key_ffi_converter }}, {{ value_ffi_converter }}); diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ObjectTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ObjectTemplate.ts index e5b5dfa2..d42a22dd 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ObjectTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/ObjectTemplate.ts @@ -6,8 +6,8 @@ {{- self.import_infra_type("UniffiRustArcPtr", "rust-call") }} {%- let obj = ci|get_object_definition(name) %} -{%- let protocol_name = obj|type_name(ci) %} -{%- let impl_class_name = obj|decl_type_name(ci) %} +{%- let protocol_name = obj|type_name(self) %} +{%- let impl_class_name = obj|decl_type_name(self) %} {%- let obj_factory = format!("uniffiType{}ObjectFactory", impl_class_name) %} {%- let methods = obj.methods() %} @@ -196,7 +196,7 @@ const {{ ffi_converter_name }} = new FfiConverterObjectWithCallbacks({{ obj_fact {#- Objects as error #} {%- if is_error %} -{%- let ffi_error_converter_name = type_|ffi_error_converter_name %} +{%- let ffi_error_converter_name = type_|ffi_error_converter_name(self) %} {{- self.import_infra("FfiConverterObjectAsError", "objects") }} // FfiConverter for {{ type_name }} as an error. const {{ ffi_error_converter_name }} = new FfiConverterObjectAsError("{{ decl_type_name }}", {{ ffi_converter_name }}); diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/OptionalTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/OptionalTemplate.ts index 1225801e..9d226b8d 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/OptionalTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/OptionalTemplate.ts @@ -1,4 +1,4 @@ {{- self.import_infra("FfiConverterOptional", "ffi-converters") }} -{%- let item_ffi_converter = inner_type|ffi_converter_name %} +{%- let item_ffi_converter = inner_type|ffi_converter_name(self) %} // FfiConverter for {{ type_name }} const {{ ffi_converter_name }} = new FfiConverterOptional({{ item_ffi_converter }}); diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/RecordTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/RecordTemplate.ts index fd5e455e..1f6a44e0 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/RecordTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/RecordTemplate.ts @@ -6,7 +6,7 @@ export type {{ type_name }} = { {%- for field in rec.fields() %} {%- call ts::docstring(field, 4) %} - {{ field.name()|var_name }}: {{ field|type_name(ci) }} + {{ field.name()|var_name }}: {{ field|type_name(self) }} {%- if !loop.last %},{% endif %} {%- endfor %} } @@ -55,20 +55,20 @@ const {{ ffi_converter_name }} = (() => { read(from: RustBuffer): TypeName { return { {%- for field in rec.fields() %} - {{ field.name()|arg_name }}: {{ field|ffi_converter_name }}.read(from) + {{ field.name()|arg_name }}: {{ field|ffi_converter_name(self) }}.read(from) {%- if !loop.last %}, {% endif %} {%- endfor %} }; } write(value: TypeName, into: RustBuffer): void { {%- for field in rec.fields() %} - {{ field|ffi_converter_name }}.write(value.{{ field.name()|var_name }}, into); + {{ field|ffi_converter_name(self) }}.write(value.{{ field.name()|var_name }}, into); {%- endfor %} } allocationSize(value: TypeName): number { {%- if rec.has_fields() %} return {% for field in rec.fields() -%} - {{ field|ffi_converter_name }}.allocationSize(value.{{ field.name()|var_name }}) + {{ field|ffi_converter_name(self) }}.allocationSize(value.{{ field.name()|var_name }}) {%- if !loop.last %} + {% else %};{% endif %} {% endfor %} {%- else %} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/SequenceTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/SequenceTemplate.ts index 0ae20545..4e92290f 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/SequenceTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/SequenceTemplate.ts @@ -1,4 +1,4 @@ {{- self.import_infra("FfiConverterArray", "ffi-converters") }} -{%- let item_ffi_converter = inner_type|ffi_converter_name %} +{%- let item_ffi_converter = inner_type|ffi_converter_name(self) %} // FfiConverter for {{ type_name }} const {{ ffi_converter_name }} = new FfiConverterArray({{ item_ffi_converter }}); diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/TaggedEnumTemplate.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/TaggedEnumTemplate.ts index 08a11760..5e0440a5 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/TaggedEnumTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/TaggedEnumTemplate.ts @@ -21,12 +21,12 @@ export const {{ decl_type_name }} = (() => { type {{ variant_data }} = {# space #} {%- if !is_tuple %}{ {%- for field in variant.fields() %} - {{- field.name()|var_name }}: {{ field|type_name(ci) }} + {{- field.name()|var_name }}: {{ field|type_name(self) }} {%- if !loop.last %}; {% endif -%} {%- endfor %}} {%- else %}[ {%- for field in variant.fields() %} - {{- field|type_name(ci) }} + {{- field|type_name(self) }} {%- if !loop.last %}, {% endif -%} {%- endfor %}] {%- endif %}; @@ -124,12 +124,12 @@ const {{ ffi_converter_name }} = (() => { {%- if !variant.fields().is_empty() %} {%- if !variant.has_nameless_fields() %}{ {%- for field in variant.fields() %} - {{- field.name()|var_name }}: {{ field|ffi_converter_name }}.read(from) + {{- field.name()|var_name }}: {{ field|ffi_converter_name(self) }}.read(from) {%- if !loop.last -%}, {% endif %} {%- endfor %} } {%- else %} {%- for field in variant.fields() %} - {{- field|ffi_converter_name }}.read(from) + {{- field|ffi_converter_name(self) }}.read(from) {%- if !loop.last -%}, {% endif %} {%- endfor %} {%- endif %} @@ -146,7 +146,7 @@ const {{ ffi_converter_name }} = (() => { {%- if !variant.fields().is_empty() %} const inner = value.inner; {%- for field in variant.fields() %} - {{ field|ffi_converter_name }}.write({% call ts::field_name("inner", field, loop.index0) %}, into); + {{ field|ffi_converter_name(self) }}.write({% call ts::field_name("inner", field, loop.index0) %}, into); {%- endfor %} {%- endif %} return; @@ -165,7 +165,7 @@ const {{ ffi_converter_name }} = (() => { const inner = value.inner; let size = ordinalConverter.allocationSize({{ loop.index }}); {%- for field in variant.fields() %} - size += {{ field|ffi_converter_name }}.allocationSize({% call ts::field_name("inner", field, loop.index0) %}); + size += {{ field|ffi_converter_name(self) }}.allocationSize({% call ts::field_name("inner", field, loop.index0) %}); {%- endfor %} return size; {%- else %} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/Types.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/Types.ts index 484dd062..6127886b 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/Types.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/Types.ts @@ -7,10 +7,9 @@ {%- endfor %} {%- for type_ in ci.iter_sorted_types() %} -{%- let type_name = type_|type_name(ci) %} -{%- let decl_type_name = type_|decl_type_name(ci) %} -{%- let ffi_converter_name = type_|ffi_converter_name %} -{%- let canonical_type_name = type_|canonical_name %} +{%- let type_name = type_|type_name(self) %} +{%- let decl_type_name = type_|decl_type_name(self) %} +{%- let ffi_converter_name = type_|ffi_converter_name(self) %} {%- let contains_object_references = ci.item_contains_object_references(type_) %} {# diff --git a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/macros.ts b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/macros.ts index 16be40f1..60e9abad 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/macros.ts +++ b/crates/ubrn_bindgen/src/bindings/react_native/gen_typescript/templates/macros.ts @@ -32,9 +32,8 @@ import { decl_type_name } from "./EnumTemplate" {%- match func.throws_type() -%} {%- when Some with (e) -%} {{- self.import_infra("rustCallWithError", "rust-call") }} - {%- let error_converter = e|ffi_error_converter_name %} rustCallWithError( - /*liftError:*/ {{ error_converter }}.lift.bind({{ error_converter }}), + /*liftError:*/ {{ e|lift_error_fn(self) }}, /*caller:*/ (callStatus) => { {%- else -%} rustCall( @@ -83,7 +82,7 @@ import { decl_type_name } from "./EnumTemplate" {%- macro raw_return_type(callable) %} {%- match callable.return_type() %} - {%- when Some with (return_type) %}{{ return_type|type_name(ci) }} + {%- when Some with (return_type) %}{{ return_type|type_name(self) }} {%- when None %}void {%- endmatch %} {%- endmacro %} @@ -107,7 +106,7 @@ import { decl_type_name } from "./EnumTemplate" {%- else %} {%- match callable.return_type() -%} {%- when Some with (return_type) %} - return {{ return_type|ffi_converter_name }}.lift({% call to_ffi_method_call(obj_factory, callable) %}); + return {{ return_type|ffi_converter_name(self) }}.lift({% call to_ffi_method_call(obj_factory, callable) %}); {%- when None %} {%- call to_ffi_method_call(obj_factory, callable) %}; {%- endmatch %} @@ -124,7 +123,7 @@ import { decl_type_name } from "./EnumTemplate" {{ obj_factory }}.clonePointer(this){% if !callable.arguments().is_empty() %},{% endif %} {% endif %} {%- for arg in callable.arguments() -%} - {{ arg|ffi_converter_name }}.lower({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {{ arg|ffi_converter_name(self) }}.lower({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} {%- endfor %} ); }, @@ -134,15 +133,14 @@ import { decl_type_name } from "./EnumTemplate" /*freeFunc:*/ nativeModule().{{ callable.ffi_rust_future_free(ci) }}, {%- match callable.return_type() %} {%- when Some(return_type) %} - /*liftFunc:*/ {{ return_type|lift_fn }}, + /*liftFunc:*/ {{ return_type|lift_fn(self) }}, {%- when None %} /*liftFunc:*/ (_v) => {}, {%- endmatch %} /*liftString:*/ FfiConverterString.lift, {%- match callable.throws_type() %} {%- when Some with (e) %} - {%- let error_converter = e|ffi_error_converter_name %} - /*errorHandler:*/ {{ error_converter }}.lift.bind({{ error_converter }}) + /*errorHandler:*/ {{ e|lift_error_fn(self) }} {%- else %} {% endmatch %} ) @@ -150,7 +148,7 @@ import { decl_type_name } from "./EnumTemplate" {%- macro arg_list_lowered(func) %} {%- for arg in func.arguments() %} - {{ arg|ffi_converter_name }}.lower({{ arg.name()|var_name }}), + {{ arg|ffi_converter_name(self) }}.lower({{ arg.name()|var_name }}), {%- endfor %} {%- endmacro -%} @@ -161,7 +159,7 @@ import { decl_type_name } from "./EnumTemplate" {% macro arg_list_decl(func) %} {%- for arg in func.arguments() -%} - {{ arg.name()|var_name }}: {{ arg|type_name(ci) -}} + {{ arg.name()|var_name }}: {{ arg|type_name(self) -}} {%- match arg.default_value() %} {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} {%- else %} @@ -179,9 +177,9 @@ import { decl_type_name } from "./EnumTemplate" {%- call docstring(field, 8) %} {%- if has_nameless_fields -%} v{{ loop.index0 }}: {# space #} - {{- field|type_name(ci) }} + {{- field|type_name(self) }} {%- else %} - {{- field.name()|var_name }}: {{ field|type_name(ci) -}} + {{- field.name()|var_name }}: {{ field|type_name(self) -}} {%- match field.default_value() %} {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }} {%- else %} @@ -213,7 +211,7 @@ import { decl_type_name } from "./EnumTemplate" {% macro arg_list_protocol(func) %} {%- for arg in func.arguments() -%} - {{ arg.name()|var_name }}: {{ arg|type_name(ci) -}} + {{ arg.name()|var_name }}: {{ arg|type_name(self) -}} {%- if !loop.last %}, {% endif -%} {%- endfor %} {%- endmacro %} diff --git a/crates/ubrn_bindgen/src/bindings/react_native/mod.rs b/crates/ubrn_bindgen/src/bindings/react_native/mod.rs index 83e32881..e7bc4ae7 100644 --- a/crates/ubrn_bindgen/src/bindings/react_native/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/react_native/mod.rs @@ -27,7 +27,7 @@ use uniffi_toml::ReactNativeConfig; use self::{gen_cpp::CppBindings, gen_typescript::TsBindings}; -use super::OutputArgs; +use super::{type_map::TypeMap, OutputArgs}; use crate::bindings::metadata::ModuleMetadata; pub(crate) struct ReactNativeBindingGenerator { @@ -70,12 +70,16 @@ impl BindingGenerator for ReactNativeBindingGenerator { settings: &GenerationSettings, components: &[Component], ) -> Result<()> { + let mut type_map = TypeMap::default(); + for component in components { + type_map.insert_ci(&component.ci); + } for component in components { let ci = &component.ci; let module: ModuleMetadata = component.into(); let config = &component.config; let TsBindings { codegen, frontend } = - gen_typescript::generate_bindings(ci, &config.typescript, &module)?; + gen_typescript::generate_bindings(ci, &config.typescript, &module, &type_map)?; let out_dir = &self.output.ts_dir.canonicalize_utf8()?; let codegen_path = out_dir.join(module.ts_ffi_filename()); diff --git a/crates/ubrn_bindgen/src/bindings/type_map.rs b/crates/ubrn_bindgen/src/bindings/type_map.rs new file mode 100644 index 00000000..0e8d9f49 --- /dev/null +++ b/crates/ubrn_bindgen/src/bindings/type_map.rs @@ -0,0 +1,71 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ + */ +use std::collections::HashMap; + +use uniffi_bindgen::{interface::Type, ComponentInterface}; +use uniffi_meta::AsType; + +#[derive(Default, Debug)] +struct CiTypeMap { + types: HashMap, +} + +#[derive(Default, Debug)] +pub(crate) struct TypeMap { + modules: HashMap, +} + +impl TypeMap { + pub(crate) fn insert_type(&mut self, type_: Type) { + let (module_path, name) = match &type_ { + Type::CallbackInterface { module_path, name } + | Type::Enum { module_path, name } + | Type::Object { + module_path, name, .. + } + | Type::Record { module_path, name } + | Type::Custom { + module_path, name, .. + } => (module_path, name), + _ => return, + }; + let module = self.modules.entry(module_path.clone()).or_default(); + module.types.insert(name.clone(), type_); + } + + pub(crate) fn insert_ci(&mut self, ci: &ComponentInterface) { + for type_ in ci.iter_types() { + self.insert_type(type_.clone()); + } + } + + pub(crate) fn as_type(&self, as_type: &impl AsType) -> Type { + let t = as_type.as_type(); + match t { + Type::External { + module_path, name, .. + } => { + let module = self.modules.get(&module_path).expect("module not found"); + let type_ = module.types.get(&name).expect("type not found"); + type_.clone() + } + Type::Optional { inner_type } => Type::Optional { + inner_type: Box::new(self.as_type(&inner_type)), + }, + Type::Sequence { inner_type } => Type::Sequence { + inner_type: Box::new(self.as_type(&inner_type)), + }, + Type::Map { + key_type, + value_type, + } => Type::Map { + key_type: Box::new(self.as_type(&key_type)), + value_type: Box::new(self.as_type(&value_type)), + }, + _ => t, + } + } +} diff --git a/fixtures/custom-types-example/tests/bindings/test_custom_types_example.ts b/fixtures/custom-types-example/tests/bindings/test_custom_types_example.ts index c2dc2a70..9c50ec4f 100644 --- a/fixtures/custom-types-example/tests/bindings/test_custom_types_example.ts +++ b/fixtures/custom-types-example/tests/bindings/test_custom_types_example.ts @@ -11,7 +11,8 @@ import { MyEnum_Tags, unwapEnumWrapper, } from "../../generated/custom_types"; -import { Url, secondsToDate } from "../../src/converters"; +import { URL } from "@/hermes"; +import { secondsToDate } from "@/converters"; import { test } from "@/asserts"; /// These tests are worth looking at inconjunction with the uniffi.toml file @@ -23,9 +24,9 @@ test("Rust url::Url --> via String --> Typescript Url", (t) => { // Url has an imported classe const urlString = "http://example.com/"; t.assertEqual(demo.url.toString(), urlString); - t.assertEqual(demo.url, new Url(urlString)); + t.assertEqual(demo.url, new URL(urlString)); - demo.url = new Url("http://new.example.com/"); + demo.url = new URL("http://new.example.com/"); t.assertEqual(demo, getCustomTypesDemo(demo)); }); diff --git a/fixtures/custom-types-example/uniffi.toml b/fixtures/custom-types-example/uniffi.toml index 12562380..4befa30b 100644 --- a/fixtures/custom-types-example/uniffi.toml +++ b/fixtures/custom-types-example/uniffi.toml @@ -1,9 +1,10 @@ [bindings.typescript.customTypes.Url] # Modules that need to be imported -imports = [ [ "Url", "../src/converters" ] ] +imports = [ [ "URL", "@/hermes" ] ] +typeName = "URL" # Expressions to convert between strings and URLs. # The `{}` is substituted for the value. -intoCustom = "new Url({})" +intoCustom = "new URL({})" fromCustom = "{}.toString()" [bindings.typescript.customTypes.TimeIntervalMs] @@ -18,8 +19,8 @@ fromCustom = "BigInt({}.getTime())" typeName = "Date" # Modules that need to be imported imports = [ - ["dateToSeconds", "../src/converters"], - ["secondsToDate", "../src/converters"], + ["dateToSeconds", "@/converters"], + ["secondsToDate", "@/converters"], ] # Functions to convert between f64 and TimeIntervalSecDbl as a Date intoCustom = "secondsToDate({})" @@ -30,8 +31,8 @@ fromCustom = "dateToSeconds({})" typeName = "Date" # Modules that need to be imported imports = [ - ["dateToSeconds", "../src/converters"], - ["secondsToDate", "../src/converters"], + ["dateToSeconds", "@/converters"], + ["secondsToDate", "@/converters"], ] # Functions to convert between f64 and TimeIntervalSecFlt, as a Date intoCustom = "secondsToDate({})" diff --git a/fixtures/ext-types/Cargo.toml b/fixtures/ext-types/Cargo.toml new file mode 100644 index 00000000..e009ed21 --- /dev/null +++ b/fixtures/ext-types/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "uniffi-fixture-ext-types" +edition = "2021" +version = "0.22.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[package.metadata.uniffi.testing] +external-crates = [ + "uniffi-fixture-ext-types-custom-types", + "uniffi-fixture-ext-types-lib-one", + "uniffi-fixture-ext-types-external-crate", + "uniffi-fixture-ext-types-sub-lib", + "uniffi-example-custom-types", +] + +[lib] +crate-type = ["lib", "cdylib"] +name = "uniffi_ext_types_lib" + +[dependencies] +anyhow = "1" +bytes = "1.3" +# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly +uniffi = { workspace = true } + +uniffi-fixture-ext-types-external-crate = {path = "subcrates/external-crate"} +uniffi-fixture-ext-types-lib-one = {path = "subcrates/uniffi-one"} +uniffi-fixture-ext-types-custom-types = {path = "subcrates/custom-types"} +uniffi-fixture-ext-types-sub-lib = {path = "subcrates/sub-lib"} + +# Reuse one of our examples. +uniffi-example-custom-types = {path = "../custom-types-example"} + +url = "2.2" + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/fixtures/ext-types/build.rs b/fixtures/ext-types/build.rs new file mode 100644 index 00000000..daf32bd4 --- /dev/null +++ b/fixtures/ext-types/build.rs @@ -0,0 +1,9 @@ +/* + * 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/ + */ + +fn main() { + uniffi::generate_scaffolding("src/ext-types-lib.udl").unwrap(); +} diff --git a/fixtures/ext-types/src/ext-types-lib.udl b/fixtures/ext-types/src/ext-types-lib.udl new file mode 100644 index 00000000..828a4998 --- /dev/null +++ b/fixtures/ext-types/src/ext-types-lib.udl @@ -0,0 +1,95 @@ +namespace imported_types_lib { + CombinedType get_combined_type(optional CombinedType? value); + + Url get_url(Url url); + sequence get_urls(sequence urls); + Url? get_maybe_url(Url? url); + sequence get_maybe_urls(sequence urls); + + UniffiOneType get_uniffi_one_type(UniffiOneType t); + sequence get_uniffi_one_types(sequence ts); + UniffiOneType? get_maybe_uniffi_one_type(UniffiOneType? t); + sequence get_maybe_uniffi_one_types(sequence ts); + + UniffiOneEnum get_uniffi_one_enum(UniffiOneEnum e); + sequence get_uniffi_one_enums(sequence es); + UniffiOneEnum? get_maybe_uniffi_one_enum(UniffiOneEnum? e); + sequence get_maybe_uniffi_one_enums(sequence es); + + UniffiOneInterface get_uniffi_one_interface(); + ExternalCrateInterface get_external_crate_interface(string val); + UniffiOneProcMacroType get_uniffi_one_proc_macro_type(UniffiOneProcMacroType t); + UniffiOneUDLTrait? get_uniffi_one_udl_trait(UniffiOneUDLTrait? t); +}; + +// A type defined in a .udl file in the `uniffi-one` crate (ie, in +// `../../uniffi-one/src/uniffi-one.udl`) +[External="uniffi_one"] +typedef extern UniffiOneType; + +// An enum in the same crate +[External="uniffi_one"] +typedef extern UniffiOneEnum; + +// An interface in the same crate +[ExternalInterface="uniffi_one"] +typedef extern UniffiOneInterface; + +// An UDL defined trait +[ExternalTrait="uniffi_one"] +typedef extern UniffiOneUDLTrait; + +// A type defined via procmacros in an external crate +[ExternalExport="uniffi_one"] +typedef extern UniffiOneProcMacroType; + +// A Custom (ie, "wrapped") type defined externally in `../../custom-types/src/lib.rs`, +// but because it's in a UDL it's still "external" from our POV, so same as the `.udl` type above. +[External="ext_types_custom"] +typedef extern Guid; + +// And re-use the `custom-types` example - this exposes `Url` and `Handle` +[External="custom_types"] +typedef extern Url; + +[External="custom_types"] +typedef extern Handle; + +// Here are some different kinds of "external" types - the types are described +// in this UDL, but the types themselves are defined in a different crate. +dictionary ExternalCrateDictionary { + string sval; +}; + +interface ExternalCrateInterface { + string value(); +}; + +[NonExhaustive] +enum ExternalCrateNonExhaustiveEnum { + "One", + "Two", +}; + +// And a new type here to tie them all together. +dictionary CombinedType { + UniffiOneEnum uoe; + UniffiOneType uot; + sequence uots; + UniffiOneType? maybe_uot; + + Guid guid; + sequence guids; + Guid? maybe_guid; + + Url url; + sequence urls; + Url? maybe_url; + + Handle handle; + sequence handles; + Handle? maybe_handle; + + ExternalCrateDictionary ecd; + ExternalCrateNonExhaustiveEnum ecnee; +}; diff --git a/fixtures/ext-types/src/lib.rs b/fixtures/ext-types/src/lib.rs new file mode 100644 index 00000000..dfe688ba --- /dev/null +++ b/fixtures/ext-types/src/lib.rs @@ -0,0 +1,211 @@ +/* + * 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 custom_types::Handle; +use ext_types_custom::{ANestedGuid, Guid, Ouid}; +use ext_types_external_crate::{ + ExternalCrateDictionary, ExternalCrateInterface, ExternalCrateNonExhaustiveEnum, +}; +use std::sync::Arc; +use uniffi_one::{ + UniffiOneEnum, UniffiOneInterface, UniffiOneProcMacroType, UniffiOneTrait, UniffiOneType, + UniffiOneUDLTrait, +}; +use uniffi_sublib::SubLibType; +use url::Url; + +// #1988 +uniffi::ffi_converter_forward!( + ext_types_custom::Ouid, + ext_types_custom::UniFfiTag, + crate::UniFfiTag +); +uniffi::ffi_converter_forward!( + ext_types_custom::ANestedGuid, + ext_types_custom::UniFfiTag, + crate::UniFfiTag +); + +pub struct CombinedType { + pub uoe: UniffiOneEnum, + pub uot: UniffiOneType, + pub uots: Vec, + pub maybe_uot: Option, + + pub guid: Guid, + pub guids: Vec, + pub maybe_guid: Option, + + pub url: Url, + pub urls: Vec, + pub maybe_url: Option, + + pub handle: Handle, + pub handles: Vec, + pub maybe_handle: Option, + + pub ecd: ExternalCrateDictionary, + pub ecnee: ExternalCrateNonExhaustiveEnum, +} + +fn get_combined_type(existing: Option) -> CombinedType { + existing.unwrap_or_else(|| CombinedType { + uoe: UniffiOneEnum::One, + uot: UniffiOneType { + sval: "hello".to_string(), + }, + uots: vec![ + UniffiOneType { + sval: "first of many".to_string(), + }, + UniffiOneType { + sval: "second of many".to_string(), + }, + ], + maybe_uot: None, + + guid: Guid("a-guid".into()), + guids: vec![Guid("b-guid".into()), Guid("c-guid".into())], + maybe_guid: None, + + url: Url::parse("http://example.com/").unwrap(), + urls: vec![], + maybe_url: None, + + handle: Handle(123), + handles: vec![Handle(1), Handle(2), Handle(3)], + maybe_handle: Some(Handle(4)), + + ecd: ExternalCrateDictionary { sval: "ecd".into() }, + ecnee: ExternalCrateNonExhaustiveEnum::One, + }) +} + +// Not part of CombinedType as (a) object refs prevent equality testing and +// (b) it's not currently possible to refer to external traits in UDL. +#[derive(Default, uniffi::Record)] +pub struct ObjectsType { + pub maybe_trait: Option>, + // XXX - can't refer to UniffiOneInterface here - #1854 + //pub maybe_interface: Option>, + // Use this in the meantime so the tests can still refer to it. + pub maybe_interface: Option, + pub sub: SubLibType, +} + +#[uniffi::export] +fn get_objects_type(value: Option) -> ObjectsType { + value.unwrap_or_default() +} + +// A Custom type +fn get_url(url: Url) -> Url { + url +} + +fn get_urls(urls: Vec) -> Vec { + urls +} + +fn get_maybe_url(url: Option) -> Option { + url +} + +fn get_maybe_urls(urls: Vec>) -> Vec> { + urls +} + +// XXX - #1854 +// fn get_imported_guid(guid: Guid) -> Guid { + +#[uniffi::export] +fn get_imported_ouid(ouid: Ouid) -> Ouid { + ouid +} + +// external custom types wrapping external custom types. +#[uniffi::export] +fn get_imported_nested_guid(guid: Option) -> ANestedGuid { + guid.unwrap_or_else(|| ANestedGuid(Guid("nested".to_string()))) +} + +#[uniffi::export] +fn get_imported_nested_ouid(guid: Option) -> ANestedGuid { + guid.unwrap_or_else(|| ANestedGuid(Guid("nested".to_string()))) +} + +// A local custom type wrapping an external imported UDL type +// XXX - #1854 +// pub struct NestedExternalGuid(pub Guid); +// ... +// fn get_nested_external_guid(nguid: Option) -> NestedExternalGuid { + +// A local custom type wrapping an external imported procmacro type +pub struct NestedExternalOuid(pub Ouid); +uniffi::custom_newtype!(NestedExternalOuid, Ouid); + +#[uniffi::export] +fn get_nested_external_ouid(ouid: Option) -> NestedExternalOuid { + ouid.unwrap_or_else(|| NestedExternalOuid(Ouid("nested-external-ouid".to_string()))) +} + +// A struct +fn get_uniffi_one_type(t: UniffiOneType) -> UniffiOneType { + t +} + +fn get_uniffi_one_types(ts: Vec) -> Vec { + ts +} + +fn get_maybe_uniffi_one_type(t: Option) -> Option { + t +} + +fn get_maybe_uniffi_one_types(ts: Vec>) -> Vec> { + ts +} + +// An enum +fn get_uniffi_one_enum(e: UniffiOneEnum) -> UniffiOneEnum { + e +} + +fn get_uniffi_one_enums(es: Vec) -> Vec { + es +} + +fn get_maybe_uniffi_one_enum(e: Option) -> Option { + e +} + +fn get_maybe_uniffi_one_enums(es: Vec>) -> Vec> { + es +} + +fn get_uniffi_one_interface() -> Arc { + Arc::new(UniffiOneInterface::new()) +} + +#[uniffi::export] +fn get_uniffi_one_trait(t: Option>) -> Option> { + t +} + +fn get_uniffi_one_proc_macro_type(t: UniffiOneProcMacroType) -> UniffiOneProcMacroType { + t +} + +fn get_external_crate_interface(val: String) -> Arc { + Arc::new(ExternalCrateInterface::new(val)) +} + +fn get_uniffi_one_udl_trait( + t: Option>, +) -> Option> { + t +} + +uniffi::include_scaffolding!("ext-types-lib"); diff --git a/fixtures/ext-types/subcrates/custom-types/Cargo.toml b/fixtures/ext-types/subcrates/custom-types/Cargo.toml new file mode 100644 index 00000000..c05526d0 --- /dev/null +++ b/fixtures/ext-types/subcrates/custom-types/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "uniffi-fixture-ext-types-custom-types" +edition = "2021" +version = "0.22.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +name = "ext_types_custom" + +[dependencies] +anyhow = "1" +bytes = "1.3" +thiserror = "1.0" +uniffi = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/fixtures/ext-types/subcrates/custom-types/build.rs b/fixtures/ext-types/subcrates/custom-types/build.rs new file mode 100644 index 00000000..841d1ca7 --- /dev/null +++ b/fixtures/ext-types/subcrates/custom-types/build.rs @@ -0,0 +1,9 @@ +/* + * 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/ + */ + +fn main() { + uniffi::generate_scaffolding("src/custom_types.udl").unwrap(); +} diff --git a/fixtures/ext-types/subcrates/custom-types/src/custom_types.udl b/fixtures/ext-types/subcrates/custom-types/src/custom_types.udl new file mode 100644 index 00000000..c088229a --- /dev/null +++ b/fixtures/ext-types/subcrates/custom-types/src/custom_types.udl @@ -0,0 +1,35 @@ +[Custom] +typedef string Guid; + +// Wrapping another custom type. +[Custom] +typedef Guid ANestedGuid; + +[Error] +enum GuidError { + "TooShort" +}; + +dictionary GuidHelper { + Guid guid; + sequence guids; + Guid? maybe_guid; +}; + +callback interface GuidCallback { + Guid run(Guid arg); +}; + +namespace ext_types_custom { + // Note this intentionally does not throw an error - uniffi will panic if + // a Guid can't be converted. + Guid get_guid(optional Guid? value); + + // Uniffi will handle failure converting a string to a Guid correctly if + // the conversion returns `Err(GuidError)`, or panic otherwise. + [Throws=GuidError] + Guid try_get_guid(optional Guid? value); + + GuidHelper get_guid_helper(optional GuidHelper? values); + Guid run_callback(GuidCallback callback); +}; diff --git a/fixtures/ext-types/subcrates/custom-types/src/lib.rs b/fixtures/ext-types/subcrates/custom-types/src/lib.rs new file mode 100644 index 00000000..bc8e091a --- /dev/null +++ b/fixtures/ext-types/subcrates/custom-types/src/lib.rs @@ -0,0 +1,196 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ + */ +use std::collections::HashMap; + +// A trivial guid, declared as `[Custom]` in the UDL. +pub struct Guid(pub String); + +// Ditto, using a proc-macro. +pub struct Ouid(pub String); +uniffi::custom_newtype!(Ouid, String); + +// This error is represented in the UDL. +#[derive(Debug, thiserror::Error)] +pub enum GuidError { + #[error("The Guid is too short")] + TooShort, +} + +// This error is not represented in the UDL - it's only to be used internally (although +// for test purposes, we do allow this to leak out below.) +#[derive(Debug, thiserror::Error)] +pub enum InternalError { + #[error("Something unexpected went wrong")] + Unexpected, +} + +pub fn get_guid(guid: Option) -> Guid { + // This function doesn't return a Result, so all conversion errors are panics + match guid { + Some(guid) => { + assert!( + !guid.0.is_empty(), + "our UniffiCustomTypeConverter already checked!" + ); + guid + } + None => Guid("NewGuid".to_string()), + } +} + +fn try_get_guid(guid: Option) -> std::result::Result { + // This function itself always returns Ok - but it's declared as a Result + // because the UniffiCustomTypeConverter might return the Err as part of + // turning the string into the Guid. + Ok(match guid { + Some(guid) => { + assert!( + !guid.0.is_empty(), + "our UniffiCustomTypeConverter failed to check for an empty GUID" + ); + guid + } + None => Guid("NewGuid".to_string()), + }) +} + +#[uniffi::export] +pub fn get_ouid(ouid: Option) -> Ouid { + ouid.unwrap_or_else(|| Ouid("Ouid".to_string())) +} + +pub struct GuidHelper { + pub guid: Guid, + pub guids: Vec, + pub maybe_guid: Option, +} + +fn get_guid_helper(vals: Option) -> GuidHelper { + match vals { + None => GuidHelper { + guid: Guid("first-guid".to_string()), + guids: vec![ + Guid("second-guid".to_string()), + Guid("third-guid".to_string()), + ], + maybe_guid: None, + }, + Some(vals) => vals, + } +} + +pub trait GuidCallback { + fn run(&self, arg: Guid) -> Guid; +} + +pub fn run_callback(callback: Box) -> Guid { + callback.run(Guid("callback-test-payload".into())) +} + +impl UniffiCustomTypeConverter for Guid { + type Builtin = String; + + // This is a "fixture" rather than an "example", so we are free to do things that don't really + // make sense for real apps. + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if val.is_empty() { + Err(GuidError::TooShort.into()) + } else if val == "unexpected" { + Err(InternalError::Unexpected.into()) + } else if val == "panic" { + panic!("guid value caused a panic!"); + } else { + Ok(Guid(val)) + } + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.0 + } +} + +pub struct ANestedGuid(pub Guid); + +impl UniffiCustomTypeConverter for ANestedGuid { + type Builtin = Guid; + + // This is a "fixture" rather than an "example", so we are free to do things that don't really + // make sense for real apps. + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(ANestedGuid(val)) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.0 + } +} + +#[uniffi::export] +fn get_nested_guid(nguid: Option) -> ANestedGuid { + nguid.unwrap_or_else(|| ANestedGuid(Guid("ANestedGuid".to_string()))) +} + +// Dependent types might come in the "wrong" order - #2067 triggered +// a bug here because of the alphabetical order of these types. +pub struct ANestedOuid(pub Ouid); +uniffi::custom_newtype!(ANestedOuid, Ouid); + +#[uniffi::export] +fn get_nested_ouid(nouid: Option) -> ANestedOuid { + nouid.unwrap_or_else(|| ANestedOuid(Ouid("ANestedOuid".to_string()))) +} + +// Dependent types that are nested deeper inside of other types +// need to also be taken into account when ordering aliases. +#[derive(PartialEq, Eq, Hash)] +pub struct StringWrapper(pub String); +uniffi::custom_newtype!(StringWrapper, String); + +pub struct IntWrapper(pub u32); +uniffi::custom_newtype!(IntWrapper, u32); + +pub struct MapUsingStringWrapper(pub HashMap); +uniffi::custom_newtype!(MapUsingStringWrapper, HashMap); + +#[uniffi::export] +fn get_map_using_string_wrapper(maybe_map: Option) -> MapUsingStringWrapper { + maybe_map.unwrap_or_else(|| MapUsingStringWrapper(HashMap::new())) +} + +// And custom types around other objects. +#[derive(uniffi::Object)] +pub struct InnerObject; + +#[uniffi::export] +impl InnerObject { + #[uniffi::constructor] + fn new() -> Self { + Self + } +} + +pub struct NestedObject(pub std::sync::Arc); +uniffi::custom_newtype!(NestedObject, std::sync::Arc); + +#[uniffi::export] +pub fn get_nested_object(n: NestedObject) -> NestedObject { + n +} + +#[derive(uniffi::Record)] +pub struct InnerRecord { + i: i32, +} + +pub struct NestedRecord(pub InnerRecord); +uniffi::custom_newtype!(NestedRecord, InnerRecord); + +#[uniffi::export] +pub fn get_nested_record(n: NestedRecord) -> NestedRecord { + n +} + +uniffi::include_scaffolding!("custom_types"); diff --git a/fixtures/ext-types/subcrates/custom-types/tests/bindings/test_guid.py b/fixtures/ext-types/subcrates/custom-types/tests/bindings/test_guid.py new file mode 100644 index 00000000..0a4b5171 --- /dev/null +++ b/fixtures/ext-types/subcrates/custom-types/tests/bindings/test_guid.py @@ -0,0 +1,62 @@ +# 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/. */ + +import unittest +from ext_types_custom import * + +class TestCallback(GuidCallback): + def run(self, guid): + self.saw_guid = guid + return guid + +class TestGuid(unittest.TestCase): + def test_get_guid(self): + self.assertEqual(get_guid(None), "NewGuid") + self.assertEqual(get_guid("SomeGuid"), "SomeGuid") + self.assertEqual(get_ouid(None), "Ouid") + + def test_guid_helper(self): + helper = get_guid_helper(None) + self.assertEqual(helper.guid, "first-guid") + self.assertEqual(helper.guids, ["second-guid", "third-guid"]) + self.assertEqual(helper.maybe_guid, None) + + def test_get_guid_errors(self): + # This is testing `get_guid` which never returns a result, so everything + # is InternalError representing a panic. + # The fixture hard-codes some Guid strings to return specific errors. + with self.assertRaisesRegex(InternalError, "Failed to convert arg 'value': The Guid is too short"): + get_guid("") + + with self.assertRaisesRegex(InternalError, "Failed to convert arg 'value': Something unexpected went wrong"): + get_guid("unexpected") + + with self.assertRaisesRegex(InternalError, "guid value caused a panic!"): + get_guid("panic") + + def test_try_get_guid_errors(self): + # This is testing `try_get_guid()` which says it returns a result, so we + # will get a mix of "expected" errors and panics. + with self.assertRaises(GuidError.TooShort): + try_get_guid("") + + with self.assertRaisesRegex(InternalError, "Failed to convert arg 'value': Something unexpected went wrong"): + try_get_guid("unexpected") + + with self.assertRaisesRegex(InternalError, "guid value caused a panic!"): + try_get_guid("panic") + + def test_guid_callback(self): + # Test that we can passing a guid from run_callback() to TestCallback.run() then back out + + test_callback = TestCallback() + guid = run_callback(test_callback) + self.assertEqual(guid, "callback-test-payload") + self.assertEqual(test_callback.saw_guid, "callback-test-payload") + + def test_custom(self): + get_nested_object(InnerObject()) + +if __name__=='__main__': + unittest.main() diff --git a/fixtures/ext-types/subcrates/custom-types/tests/test_generated_bindings.rs b/fixtures/ext-types/subcrates/custom-types/tests/test_generated_bindings.rs new file mode 100644 index 00000000..77cd93c0 --- /dev/null +++ b/fixtures/ext-types/subcrates/custom-types/tests/test_generated_bindings.rs @@ -0,0 +1,6 @@ +/* + * 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/ + */ +uniffi::build_foreign_language_testcases!("tests/bindings/test_guid.py",); diff --git a/fixtures/ext-types/subcrates/external-crate/Cargo.toml b/fixtures/ext-types/subcrates/external-crate/Cargo.toml new file mode 100644 index 00000000..a1ac4b57 --- /dev/null +++ b/fixtures/ext-types/subcrates/external-crate/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "uniffi-fixture-ext-types-external-crate" +edition = "2021" +version = "0.22.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +name = "ext_types_external_crate" + +[dependencies] +# NOTE: This crate should never depend on UniFFI - see README.md for more. + +[build-dependencies] + +[dev-dependencies] diff --git a/fixtures/ext-types/subcrates/external-crate/README.md b/fixtures/ext-types/subcrates/external-crate/README.md new file mode 100644 index 00000000..e3bffe09 --- /dev/null +++ b/fixtures/ext-types/subcrates/external-crate/README.md @@ -0,0 +1,2 @@ +This is a crate which does not itself depend on UniFFI - however, the +types in this crate are referenced externally by crates which do. diff --git a/fixtures/ext-types/subcrates/external-crate/src/lib.rs b/fixtures/ext-types/subcrates/external-crate/src/lib.rs new file mode 100644 index 00000000..c2bdf718 --- /dev/null +++ b/fixtures/ext-types/subcrates/external-crate/src/lib.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/ + */ +// This crate does not use UniFFI, but it exposes some types which are used by crates which do. +// This type is referenced as an "external" type as a dictionary. +pub struct ExternalCrateDictionary { + pub sval: String, +} + +pub struct ExternalCrateInterface { + pub sval: String, +} + +#[non_exhaustive] +pub enum ExternalCrateNonExhaustiveEnum { + One, + Two, +} + +// This type is referenced as an "external" type as an interface. +impl ExternalCrateInterface { + pub fn new(sval: String) -> Self { + ExternalCrateInterface { sval } + } + + pub fn value(&self) -> String { + self.sval.clone() + } +} diff --git a/fixtures/ext-types/subcrates/sub-lib/Cargo.toml b/fixtures/ext-types/subcrates/sub-lib/Cargo.toml new file mode 100644 index 00000000..6e6aed39 --- /dev/null +++ b/fixtures/ext-types/subcrates/sub-lib/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "uniffi-fixture-ext-types-sub-lib" +edition = "2021" +version = "0.22.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[package.metadata.uniffi.testing] +external-crates = [ + "uniffi-fixture-ext-types-lib-one", +] + +[lib] +crate-type = ["lib", "cdylib"] +name = "uniffi_sublib" + +[dependencies] +anyhow = "1" +uniffi = { workspace = true } +uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"} + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/fixtures/ext-types/subcrates/sub-lib/README.md b/fixtures/ext-types/subcrates/sub-lib/README.md new file mode 100644 index 00000000..48fd1db9 --- /dev/null +++ b/fixtures/ext-types/subcrates/sub-lib/README.md @@ -0,0 +1,3 @@ +This is a "sub library" - ie, a crate which consumes types from +other external crates and *also* exports its own composite types and trait +implementations to be consumed by the "main" library. diff --git a/fixtures/ext-types/subcrates/sub-lib/src/lib.rs b/fixtures/ext-types/subcrates/sub-lib/src/lib.rs new file mode 100644 index 00000000..a0f80279 --- /dev/null +++ b/fixtures/ext-types/subcrates/sub-lib/src/lib.rs @@ -0,0 +1,37 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ + */ +use std::sync::Arc; +use uniffi_one::{UniffiOneEnum, UniffiOneInterface, UniffiOneTrait}; + +uniffi::use_udl_object!(uniffi_one, UniffiOneInterface); +uniffi::use_udl_enum!(uniffi_one, UniffiOneEnum); + +#[derive(Default, uniffi::Record)] +pub struct SubLibType { + pub maybe_enum: Option, + pub maybe_trait: Option>, + pub maybe_interface: Option>, +} + +#[uniffi::export] +fn get_sub_type(existing: Option) -> SubLibType { + existing.unwrap_or_default() +} + +struct OneImpl; + +impl UniffiOneTrait for OneImpl { + fn hello(&self) -> String { + "sub-lib trait impl says hello".to_string() + } +} + +#[uniffi::export] +fn get_trait_impl() -> Arc { + Arc::new(OneImpl {}) +} + +uniffi::setup_scaffolding!("imported_types_sublib"); diff --git a/fixtures/ext-types/subcrates/sub-lib/uniffi.toml b/fixtures/ext-types/subcrates/sub-lib/uniffi.toml new file mode 100644 index 00000000..94337b22 --- /dev/null +++ b/fixtures/ext-types/subcrates/sub-lib/uniffi.toml @@ -0,0 +1,3 @@ +[bindings.python.external_packages] +# This fixture does not create a Python package, so we want all modules to be top-level modules. +uniffi_one_ns = "" diff --git a/fixtures/ext-types/subcrates/uniffi-one/Cargo.toml b/fixtures/ext-types/subcrates/uniffi-one/Cargo.toml new file mode 100644 index 00000000..dc6e936e --- /dev/null +++ b/fixtures/ext-types/subcrates/uniffi-one/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "uniffi-fixture-ext-types-lib-one" +edition = "2021" +version = "0.22.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +# Note the crate name is different from the namespace used by this crate. +name = "uniffi_one" + +[dependencies] +anyhow = "1" +bytes = "1.3" +uniffi = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } diff --git a/fixtures/ext-types/subcrates/uniffi-one/build.rs b/fixtures/ext-types/subcrates/uniffi-one/build.rs new file mode 100644 index 00000000..35062047 --- /dev/null +++ b/fixtures/ext-types/subcrates/uniffi-one/build.rs @@ -0,0 +1,9 @@ +/* + * 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/ + */ + +fn main() { + uniffi::generate_scaffolding("src/uniffi-one.udl").unwrap(); +} diff --git a/fixtures/ext-types/subcrates/uniffi-one/src/lib.rs b/fixtures/ext-types/subcrates/uniffi-one/src/lib.rs new file mode 100644 index 00000000..ccdda9f9 --- /dev/null +++ b/fixtures/ext-types/subcrates/uniffi-one/src/lib.rs @@ -0,0 +1,57 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ + */ +use std::sync::atomic::{AtomicI32, Ordering}; + +pub struct UniffiOneType { + pub sval: String, +} + +pub enum UniffiOneEnum { + One, + Two, +} + +#[derive(uniffi::Record)] +pub struct UniffiOneProcMacroType { + pub sval: String, +} + +#[derive(Default)] +pub struct UniffiOneInterface { + current: AtomicI32, +} + +impl UniffiOneInterface { + pub fn new() -> Self { + Self::default() + } + + pub fn increment(&self) -> i32 { + self.current.fetch_add(1, Ordering::Relaxed) + 1 + } +} + +#[uniffi::export] +fn get_my_proc_macro_type(t: UniffiOneProcMacroType) -> UniffiOneProcMacroType { + t +} + +#[uniffi::export] +async fn get_uniffi_one_async() -> UniffiOneEnum { + UniffiOneEnum::One +} + +#[uniffi::export(with_foreign)] +pub trait UniffiOneTrait: Send + Sync { + fn hello(&self) -> String; +} + +// Note `UDL` vs `Udl` is important here to test foreign binding name fixups. +pub trait UniffiOneUDLTrait: Send + Sync { + fn hello(&self) -> String; +} + +uniffi::include_scaffolding!("uniffi-one"); diff --git a/fixtures/ext-types/subcrates/uniffi-one/src/uniffi-one.udl b/fixtures/ext-types/subcrates/uniffi-one/src/uniffi-one.udl new file mode 100644 index 00000000..a0245922 --- /dev/null +++ b/fixtures/ext-types/subcrates/uniffi-one/src/uniffi-one.udl @@ -0,0 +1,21 @@ +namespace uniffi_one_ns {}; + +dictionary UniffiOneType { + string sval; +}; + +enum UniffiOneEnum { + "One", + "Two", +}; + +interface UniffiOneInterface { + constructor(); + + i32 increment(); +}; + +[Trait, WithForeign] +interface UniffiOneUDLTrait { + string hello(); +}; diff --git a/fixtures/ext-types/tests/bindings/test_ext_types.ts b/fixtures/ext-types/tests/bindings/test_ext_types.ts new file mode 100644 index 00000000..294d4ea2 --- /dev/null +++ b/fixtures/ext-types/tests/bindings/test_ext_types.ts @@ -0,0 +1,165 @@ +/* + * 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/ + */ + +import { test } from "@/asserts"; + +import module1 from "../../generated/custom_types"; +import module2, { + getGuid, + getNestedOuid, + getOuid, +} from "../../generated/ext_types_custom"; +import module3, { + CombinedType, + ExternalCrateInterfaceInterface, + getCombinedType, + getExternalCrateInterface, + getImportedNestedGuid, + getImportedOuid, + getMaybeUniffiOneEnum, + getMaybeUniffiOneEnums, + getMaybeUniffiOneType, + getMaybeUrl, + getMaybeUrls, + getNestedExternalOuid, + getObjectsType, + getUniffiOneEnum, + getUniffiOneEnums, + getUniffiOneProcMacroType, + getUniffiOneTrait, + getUniffiOneType, + getUniffiOneTypes, + getUrl, + getUrls, + ObjectsType, +} from "../../generated/imported_types_lib"; +import module4, { + getSubType, + getTraitImpl, + SubLibType, +} from "../../generated/imported_types_sublib"; +import module5, { + getMyProcMacroType, + UniffiOneEnum, + UniffiOneProcMacroType, + UniffiOneTrait, + UniffiOneType, +} from "../../generated/uniffi_one_ns"; + +import { URL, console } from "@/hermes"; + +module1.initialize(); +module2.initialize(); +module3.initialize(); +module4.initialize(); +module5.initialize(); + +// import imported_types_lib +// import Foundation + +test("combinedType from lib", (t) => { + const ct: CombinedType = getCombinedType(undefined); + t.assertEqual(ct.uot.sval, "hello"); + t.assertEqual(ct.guid, "a-guid"); + t.assertEqual(ct.url, new URL("http://example.com/")); + t.assertEqual(ct.ecd.sval, "ecd"); + + const ct2 = getCombinedType(ct); + t.assertEqual(ct, ct2); +}); + +test("Getting a UniffiOneTrait from uniffi-one-ns, from getTraitImpl function in sublib", (t) => { + const ti: UniffiOneTrait = getTraitImpl(); + t.assertEqual(ti.hello(), "sub-lib trait impl says hello"); + const sub = SubLibType.create({ + maybeEnum: undefined, + maybeTrait: ti, + maybeInterface: undefined, + }); + t.assertNotNull(getSubType(sub).maybeTrait); +}); + +test("UniffiOneTrait from uniffi-one-ns, ObjectsType record from lib, SubLibType record from sublib passing sublib object", (t) => { + const ti = getTraitImpl(); + const sub = SubLibType.create({ + maybeEnum: undefined, + maybeTrait: ti, + maybeInterface: undefined, + }); + + const ob = ObjectsType.create({ + maybeTrait: ti, + maybeInterface: undefined, + sub, + }); + + t.assertNull(getObjectsType(undefined).maybeInterface); + t.assertNotNull(getObjectsType(ob).maybeTrait); + t.assertNull(getUniffiOneTrait(undefined)); +}); + +test("getUrl from lib using custom-type from custom-types-example", (t) => { + const url = new URL("http://example.com/"); + t.assertEqual(getUrl(url), url); + t.assertEqual(getMaybeUrl(url), url); + t.assertNull(getMaybeUrl(undefined)); + t.assertEqual(getUrls([url]), [url]); + t.assertEqual(getMaybeUrls([url, undefined]), [url, undefined]); +}); + +test("Calling in to custom-types-example and lib", (t) => { + t.assertEqual(getGuid("guid"), "guid"); + t.assertEqual(getOuid("ouid"), "ouid"); + t.assertEqual(getImportedOuid("ouid"), "ouid"); + t.assertEqual(getNestedOuid("ouid"), "ouid"); + t.assertEqual(getImportedNestedGuid(undefined), "nested"); + t.assertEqual(getNestedExternalOuid(undefined), "nested-external-ouid"); +}); + +test("UniffiOneType record from uniffi-one, roundtrip function from lib", (t) => { + t.assertEqual( + getUniffiOneType(UniffiOneType.create({ sval: "hello" })).sval, + "hello", + ); + t.assertEqual( + getMaybeUniffiOneType(UniffiOneType.create({ sval: "hello" }))?.sval, + "hello", + ); + t.assertNull(getMaybeUniffiOneType(undefined)); + t.assertEqual(getUniffiOneTypes([UniffiOneType.create({ sval: "hello" })]), [ + UniffiOneType.create({ sval: "hello" }), + ]); + t.assertEqual( + getMyProcMacroType(UniffiOneProcMacroType.create({ sval: "proc-macros" })) + .sval, + "proc-macros", + ); +}); + +test("UniffiOneEnum enum from uniffi-one, roundtrip function from lib", (t) => { + t.assertEqual(getUniffiOneEnum(UniffiOneEnum.One), UniffiOneEnum.One); + t.assertEqual(getMaybeUniffiOneEnum(UniffiOneEnum.One), UniffiOneEnum.One); + t.assertNull(getMaybeUniffiOneEnum(undefined)); + t.assertEqual(getUniffiOneEnums([UniffiOneEnum.One, UniffiOneEnum.Two]), [ + UniffiOneEnum.One, + UniffiOneEnum.Two, + ]); + t.assertEqual(getMaybeUniffiOneEnums([UniffiOneEnum.One, undefined]), [ + UniffiOneEnum.One, + undefined, + ]); +}); + +test("ExternalCrateInterface interface from uniffi-one-ns, roundtrip function from lib", (t) => { + const foo: ExternalCrateInterfaceInterface = getExternalCrateInterface("foo"); + t.assertEqual(getExternalCrateInterface("foo").value(), "foo"); +}); + +test("proc-macro fron uniffi-one-ns roundtripping via functions in lib", (t) => { + const t1 = UniffiOneProcMacroType.create({ sval: "hello" }); + t.assertEqual(getUniffiOneProcMacroType(t1), t1); + t.assertEqual(getMyProcMacroType(t1), t1); +}); diff --git a/fixtures/ext-types/tests/test_generated_bindings.rs b/fixtures/ext-types/tests/test_generated_bindings.rs new file mode 100644 index 00000000..24ffd28d --- /dev/null +++ b/fixtures/ext-types/tests/test_generated_bindings.rs @@ -0,0 +1,5 @@ +/* + * 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/ + */ diff --git a/fixtures/ext-types/uniffi.toml b/fixtures/ext-types/uniffi.toml new file mode 100644 index 00000000..5b9c6d20 --- /dev/null +++ b/fixtures/ext-types/uniffi.toml @@ -0,0 +1,6 @@ +[bindings.python.external_packages] +# This fixture does not create a Python package, so we want all modules to be top-level modules. +custom_types = "" +ext_types_custom = "" +uniffi_one_ns = "" +imported_types_sublib = "" diff --git a/typescript/src/handle-map.ts b/typescript/src/handle-map.ts index 4e5631d4..a19005a0 100644 --- a/typescript/src/handle-map.ts +++ b/typescript/src/handle-map.ts @@ -34,6 +34,10 @@ export class UniffiHandleMap { return obj; } + has(handle: UniffiHandle): boolean { + return this.map.has(handle); + } + get size(): number { return this.map.size; } diff --git a/typescript/src/objects.ts b/typescript/src/objects.ts index a2d92622..d5fc086e 100644 --- a/typescript/src/objects.ts +++ b/typescript/src/objects.ts @@ -113,11 +113,19 @@ export class FfiConverterObjectWithCallbacks extends FfiConverterObject { } lift(value: UnsafeMutableRawPointer): T { - return this.handleMap.get(value); + if (this.handleMap.has(value)) { + return this.handleMap.get(value); + } else { + return super.lift(value); + } } - drop(handle: UniffiHandle): T { - return this.handleMap.remove(handle); + drop(handle: UniffiHandle): T | undefined { + if (this.handleMap.has(handle)) { + return this.handleMap.remove(handle); + } else { + return undefined; + } } } diff --git a/fixtures/custom-types-example/src/converters.ts b/typescript/testing/converters.ts similarity index 83% rename from fixtures/custom-types-example/src/converters.ts rename to typescript/testing/converters.ts index 8f40e3c9..5413da96 100644 --- a/fixtures/custom-types-example/src/converters.ts +++ b/typescript/testing/converters.ts @@ -3,17 +3,12 @@ * 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/ */ + /** * This is used in both the generated code and the test. * To get it into the generated typescript, it should be part of a * custom_type in the {@link ../uniffi.toml | uniffi.toml file}. */ -export class Url { - constructor(private urlString: string) {} - toString(): string { - return this.urlString; - } -} export function dateToSeconds(date: Date): number { return date.getTime() / 1000.0; diff --git a/typescript/testing/hermes.ts b/typescript/testing/hermes.ts index 9025510e..d8b0d53b 100644 --- a/typescript/testing/hermes.ts +++ b/typescript/testing/hermes.ts @@ -55,3 +55,10 @@ function replacer(key: string, value: any): any { return value; } + +export class URL { + constructor(private urlString: string) {} + toString(): string { + return this.urlString; + } +} diff --git a/xtask/src/run/cpp_bindings.rs b/xtask/src/run/cpp_bindings.rs index 8b792ee6..a1134f9a 100644 --- a/xtask/src/run/cpp_bindings.rs +++ b/xtask/src/run/cpp_bindings.rs @@ -38,7 +38,7 @@ impl CppBindingArg { .filter_map(|f| f.canonicalize_utf8().ok()) .map(|p| p.as_str().to_string()) .collect::>() - .join(" ") + .join(";") } pub(crate) fn compile_with_crate( diff --git a/xtask/src/run/generate_bindings.rs b/xtask/src/run/generate_bindings.rs index 9d6ffe09..b5e15931 100644 --- a/xtask/src/run/generate_bindings.rs +++ b/xtask/src/run/generate_bindings.rs @@ -41,9 +41,12 @@ impl GenerateBindingsArg { let bindings = BindingsArgs::new(source, output); let modules = bindings.run()?; let cpp_dir = bindings.cpp_dir(); + let index = cpp_dir.join("Entrypoint.cpp"); + bindings.render_entrypoint(&index, &modules)?; Ok(modules .iter() .map(|m| cpp_dir.join(m.cpp_filename())) + .chain(vec![index]) .collect()) } }