Skip to content

Commit

Permalink
Initialize VTables for external types
Browse files Browse the repository at this point in the history
  • Loading branch information
bendk committed Dec 27, 2024
1 parent 1c002fa commit 76767ee
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 17 deletions.
5 changes: 5 additions & 0 deletions fixtures/ext-types/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ fn get_uniffi_one_trait(t: Option<Arc<dyn UniffiOneTrait>>) -> Option<Arc<dyn Un
t
}

#[uniffi::export]
fn invoke_uniffi_one_trait(t: Arc<dyn UniffiOneTrait>) -> String {
t.hello()
}

fn get_uniffi_one_proc_macro_type(t: UniffiOneProcMacroType) -> UniffiOneProcMacroType {
t
}
Expand Down
10 changes: 10 additions & 0 deletions fixtures/ext-types/lib/tests/bindings/test_imported_types.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import uniffi.imported_types_sublib.*
import uniffi.uniffi_one_ns.*
import uniffi.ext_types_custom.*

// First step: implement a trait from an external crate in Kotlin and pass it to a function from this
// crate. This tests #2343 -- the codegen for this module needs to initialize the vtable from
// uniffi_one.
class KtUniffiOneImpl: UniffiOneTrait {
override fun hello(): String {
return "Hello from Kotlin"
}
}
assert(invokeUniffiOneTrait(KtUniffiOneImpl()) == "Hello from Kotlin")

val ct = getCombinedType(null)
assert(ct.uot.sval == "hello")
assert(ct.guid == "a-guid")
Expand Down
16 changes: 16 additions & 0 deletions fixtures/ext-types/lib/tests/bindings/test_imported_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@
from uniffi_one_ns import *
from ext_types_custom import *

def test_external_callback_interface_impl():
"""
Implement a trait from an external crate in Python and pass it to a function from this
crate. This tests #2343 -- the codegen for this module needs to initialize the vtable from
uniffi_one.
This is written as a plain function rather than a unittest, because it
needs to run first before there's any chance of a `uniffi_one` funciton
being called.
"""
class PyUniffiOneImpl(UniffiOneTrait):
def hello(self):
return "Hello from Python"
assert(invoke_uniffi_one_trait(PyUniffiOneImpl()) == "Hello from Python")

class TestIt(unittest.TestCase):
def test_it(self):
ct = get_combined_type(None)
Expand Down Expand Up @@ -83,4 +98,5 @@ def test_misc_external_types(self):
self.assertEqual(get_nested_external_ouid(None), "nested-external-ouid")

if __name__=='__main__':
test_external_callback_interface_impl()
unittest.main()
10 changes: 10 additions & 0 deletions fixtures/ext-types/lib/tests/bindings/test_imported_types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
import imported_types_lib
import Foundation

// First step: implement a trait from an external crate in Swift and pass it to a function from this
// crate. This tests #2343 -- the codegen for this module needs to initialize the vtable from
// uniffi_one.
final class SwiftUniffiOneImpl: UniffiOneTrait {
func hello() -> String {
"Hello from Swift"
}
}
assert(invokeUniffiOneTrait(t: SwiftUniffiOneImpl()) == "Hello from Swift")

let ct = getCombinedType(value: nil)
assert(ct.uot.sval == "hello")
assert(ct.guid == "a-guid")
Expand Down
54 changes: 45 additions & 9 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ impl Config {
})
.unwrap_or(KotlinVersion::new(0, 0, 0))
}

// Get the package name for an external type
fn external_package_name(&self, module_path: &str, namespace: Option<&str>) -> String {
// config overrides are keyed by the crate name, default fallback is the namespace.
let crate_name = module_path.split("::").next().unwrap();
match self.external_packages.get(crate_name) {
Some(name) => name.clone(),
// If the module path is not in `external_packages`, we need to fall back to a default
// with the namespace, which we hopefully have. This is quite fragile, but it's
// unreachable in library mode - all deps get an entry in `external_packages` with the
// correct namespace.
None => format!("uniffi.{}", namespace.unwrap_or(module_path)),
}
}
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -218,13 +232,8 @@ impl<'a> TypeRenderer<'a> {

// Get the package name for an external type
fn external_type_package_name(&self, module_path: &str, namespace: &str) -> String {
// config overrides are keyed by the crate name, default fallback is the namespace.
let crate_name = module_path.split("::").next().unwrap();
match self.config.external_packages.get(crate_name) {
Some(name) => name.clone(),
// unreachable in library mode - all deps are in our config with correct namespace.
None => format!("uniffi.{namespace}"),
}
self.config
.external_package_name(module_path, Some(namespace))
}

// The following methods are used by the `Types.kt` macros.
Expand Down Expand Up @@ -287,11 +296,38 @@ impl<'a> KotlinWrapper<'a> {
}

pub fn initialization_fns(&self) -> Vec<String> {
self.ci
let init_fns = self
.ci
.iter_types()
.map(|t| KotlinCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
.collect()
.map(|fn_name| format!("{fn_name}(lib)"));

// Also call global initialization function for any external type we use.
// For example, we need to make sure that all callback interface vtables are registered
// (#2343).
let extern_module_init_fns = self
.ci
.iter_types()
.filter_map(|ty| {
let module_path = ty.module_path()?;
if module_path == self.ci.crate_name() {
return None;
}
let namespace = if let Type::External { namespace, .. } = ty {
Some(namespace.as_str())
} else {
None
};
Some((module_path, namespace))
})
.map(|(module_path, namespace)| {
let package_name = self.config.external_package_name(module_path, namespace);
format!("{package_name}.uniffiEnsureInitialized()")
})
.collect::<HashSet<_>>();

init_fns.chain(extern_module_init_fns).collect()
}

pub fn imports(&self) -> Vec<ImportRequirement> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ internal interface UniffiLib : Library {
// No need to check the contract version and checksums, since
// we already did that with `IntegrityCheckingUniffiLib` above.
{% for fn in self.initialization_fns() -%}
{{ fn }}(lib)
{{ fn }}
{% endfor -%}
}
{%- endif -%}
Expand Down Expand Up @@ -163,3 +163,10 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) {
}
{%- endfor %}
}

/**
* @suppress
*/
public fun uniffiEnsureInitialized() {
UniffiLib.INSTANCE
}
30 changes: 27 additions & 3 deletions uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ pub struct SwiftWrapper<'a> {
ci: &'a ComponentInterface,
type_helper_code: String,
type_imports: BTreeSet<String>,
ensure_init_fn_name: String,
}
impl<'a> SwiftWrapper<'a> {
pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
Expand All @@ -437,6 +438,10 @@ impl<'a> SwiftWrapper<'a> {
ci,
type_helper_code,
type_imports,
ensure_init_fn_name: format!(
"uniffiEnsure{}Initialized",
ci.crate_name().to_upper_camel_case()
),
}
}

Expand All @@ -445,11 +450,30 @@ impl<'a> SwiftWrapper<'a> {
}

pub fn initialization_fns(&self) -> Vec<String> {
self.ci
let init_fns = self
.ci
.iter_types()
.map(|t| SwiftCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
.collect()
.filter_map(|ct| ct.initialization_fn());

// Also call global initialization function for any external type we use.
// For example, we need to make sure that all callback interface vtables are registered
// (#2343).
let extern_module_init_fns = self
.ci
.iter_types()
.filter_map(|ty| ty.module_path())
.filter(|module_path| *module_path != self.ci.crate_name())
.map(|module_path| {
format!(
"uniffiEnsure{}Initialized",
module_path.to_upper_camel_case()
)
})
// Collect into a hash set to de-dup
.collect::<HashSet<_>>();

init_fns.chain(extern_module_init_fns).collect()
}
}

Expand Down
4 changes: 2 additions & 2 deletions uniffi_bindgen/src/bindings/swift/templates/Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ fileprivate func uniffiRustCallAsync<F, T>(
liftFunc: (F) throws -> T,
errorHandler: ((RustBuffer) throws -> Swift.Error)?
) async throws -> T {
// Make sure to call uniffiEnsureInitialized() since future creation doesn't have a
// Make sure to call the ensure init function since future creation doesn't have a
// RustCallStatus param, so doesn't use makeRustCall()
uniffiEnsureInitialized()
{{ ensure_init_fn_name }}()
let rustFuture = rustFutureFunc()
defer {
freeFunc(rustFuture)
Expand Down
2 changes: 1 addition & 1 deletion uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private func makeRustCall<T, E: Swift.Error>(
_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T,
errorHandler: ((RustBuffer) throws -> E)?
) throws -> T {
uniffiEnsureInitialized()
{{ ensure_init_fn_name }}()
var callStatus = RustCallStatus.init()
let returnedVal = callback(&callStatus)
try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler)
Expand Down
4 changes: 3 additions & 1 deletion uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ private let initializationResult: InitializationResult = {
return InitializationResult.ok
}()

private func uniffiEnsureInitialized() {
// Make the ensure init function public so that other modules which have external type references to
// our types can call it.
public func {{ ensure_init_fn_name }}() {
switch initializationResult {
case .ok:
break
Expand Down
4 changes: 4 additions & 0 deletions uniffi_bindgen/src/interface/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ impl CallbackInterface {
&self.name
}

pub fn module_path(&self) -> &str {
&self.module_path
}

pub fn methods(&self) -> Vec<&Method> {
self.methods.iter().collect()
}
Expand Down

0 comments on commit 76767ee

Please sign in to comment.