Skip to content

Commit

Permalink
Added suport for using external types in proc-macros
Browse files Browse the repository at this point in the history
Moved the FfiConverter implementation for callback interfaces and
traits from a declarative macro in `uniffi_core` to a proc-macro in
`uniffi_macros`.  This offers better `UniFfiTag` handling.  Now
`FfiConverter` is implemented for all tags for callback
interfaces/traits that are wrapped with the proc-macro attributes. This
matches the behavior for other types and will help fix #1531.

Changed the object FfiConverter to be inside a proc-macro, rather than
using the `Interface` trait.  This more flexibility and avoids
conflicting impls. For example, this allows for both the objects and
trait interface `FfiConverters`s to be implemented on `Arc<T>`.  I also
think it's nicer with less indirection. One drawback is that libraries
can't implement `FfiConverter` on Arc<T> because of the orphan rules. To
get around this, I added the `FfiConverterArc` trait.

Other changes:
- Adding `module_path` to the user type metadata
- Added an proc-macro -> proc-macro external type test by copying the
  ext-types fixture into another one that uses proc macros.
  • Loading branch information
bendk committed Jun 29, 2023
1 parent 2218a19 commit 280fe84
Show file tree
Hide file tree
Showing 35 changed files with 1,252 additions and 373 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
- No more implicit conversion to integers/floats in Ruby ([#1596](https://github.com/mozilla/uniffi-rs/pull/1596))
- Updated Rust dependencies ([#1495](https://github.com/mozilla/uniffi-rs/pull/1495), [#1583](https://github.com/mozilla/uniffi-rs/pull/1583), [#1569](https://github.com/mozilla/uniffi-rs/pull/1569))
- Added type checking to strings/bytes for Python/Ruby ([#1597](https://github.com/mozilla/uniffi-rs/pull/1597#))
- Implemented proc-macro external type support. This allows proc-macros to use types defined in UDL files from other crates, [#1600](https://github.com/mozilla/uniffi-rs/pull/1600)

### Guidance for external bindings

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ members = [
"fixtures/ext-types/guid",
"fixtures/ext-types/uniffi-one",
"fixtures/ext-types/lib",
"fixtures/ext-types/proc-macro-lib",

# we should roll the above and below up somehow that makes sense...
"fixtures/external-types/crate-one",
Expand Down
23 changes: 23 additions & 0 deletions docs/manual/src/proc_macro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,29 @@ impl From<UnexpectedUniFFICallbackError> for MyApiError {
}
```

## Types from dependent crates

When using proc-macros, you can use types from dependent crates in your exported library, as long as
the dependent crate annotates the type with one of the UniFFI derives. However, there are a couple
exceptions:

### Types from UDL-based dependent crates

If the dependent crate uses a UDL file to define their types, then you must invoke one of the
`uniffi::use_udl_*!` macros, for example:

```rust
uniffi::use_udl_record!(dependent_crate, RecordType);
uniffi::use_udl_enum!(dependent_crate, EnumType);
uniffi::use_udl_error!(dependent_crate, ErrorType);
uniffi::use_udl_object!(dependent_crate, ObjectType);
```

### Non-UniFFI types from dependent crates

If the dependent crate doesn't define the type in a UDL file or use one of the UniFFI derive macros,
then it's currently not possible to use them in an proc-macro exported interface. However, we hope
to fix this limitation soon.

## Other limitations

Expand Down
6 changes: 6 additions & 0 deletions fixtures/ext-types/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
This directory contains the tests for external types -- types defined in one crate and used in a
different one.

- `guid` and `uniffi-one` are dependent crates that define types exported by UniFFI
- `lib` is a library crate that depends on `guid` and `uniffi-one`
- `proc-macro-lib` is another library crate, but this one uses proc-macros rather than UDL files
37 changes: 37 additions & 0 deletions fixtures/ext-types/proc-macro-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "uniffi-fixture-ext-types-proc-macro"
edition = "2021"
version = "0.22.0"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
license = "MPL-2.0"
publish = false

[package.metadata.uniffi.testing]
external-crates = [
"uniffi-fixture-ext-types-guid",
"uniffi-fixture-ext-types-lib-one",
"uniffi-example-custom-types",
]

[lib]
crate-type = ["lib", "cdylib"]
name = "uniffi_ext_types_proc_macro_lib"

[dependencies]
anyhow = "1"
bytes = "1.3"
uniffi = {path = "../../../uniffi"}

uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"}
uniffi-fixture-ext-types-guid = {path = "../guid"}

# Reuse one of our examples.
uniffi-example-custom-types = {path = "../../../examples/custom-types"}

url = "2.2"

[build-dependencies]
uniffi = {path = "../../../uniffi", features = ["build"] }

[dev-dependencies]
uniffi = {path = "../../../uniffi", features = ["bindgen-tests"] }
7 changes: 7 additions & 0 deletions fixtures/ext-types/proc-macro-lib/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* 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();
}
1 change: 1 addition & 0 deletions fixtures/ext-types/proc-macro-lib/src/ext-types-lib.udl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
namespace imported_types_lib { };
139 changes: 139 additions & 0 deletions fixtures/ext-types/proc-macro-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use custom_types::Handle;
use ext_types_guid::Guid;
use std::sync::Arc;
use uniffi_one::{UniffiOneEnum, UniffiOneInterface, UniffiOneProcMacroType, UniffiOneType};
use url::Url;

uniffi::use_udl_record!(uniffi_one, UniffiOneType);
uniffi::use_udl_enum!(uniffi_one, UniffiOneEnum);
uniffi::use_udl_object!(uniffi_one, UniffiOneInterface);
uniffi::use_udl_record!(ext_types_guid, Guid);
uniffi::use_udl_record!(custom_types, Url);
uniffi::use_udl_record!(custom_types, Handle);

#[derive(uniffi::Record)]
pub struct CombinedType {
pub uoe: UniffiOneEnum,
pub uot: UniffiOneType,
pub uots: Vec<UniffiOneType>,
pub maybe_uot: Option<UniffiOneType>,

pub guid: Guid,
pub guids: Vec<Guid>,
pub maybe_guid: Option<Guid>,

pub url: Url,
pub urls: Vec<Url>,
pub maybe_url: Option<Url>,

pub handle: Handle,
pub handles: Vec<Handle>,
pub maybe_handle: Option<Handle>,
}

#[uniffi::export]
fn get_combined_type(value: Option<CombinedType>) -> CombinedType {
value.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)),
})
}

// A Custom type
#[uniffi::export]
fn get_url(url: Url) -> Url {
url
}

#[uniffi::export]
fn get_urls(urls: Vec<Url>) -> Vec<Url> {
urls
}

#[uniffi::export]
fn get_maybe_url(url: Option<Url>) -> Option<Url> {
url
}

#[uniffi::export]
fn get_maybe_urls(urls: Vec<Option<Url>>) -> Vec<Option<Url>> {
urls
}

// A struct
#[uniffi::export]
fn get_uniffi_one_type(t: UniffiOneType) -> UniffiOneType {
t
}

// Test using a type defined in a proc-macro in another crate
#[uniffi::export]
fn get_uniffi_one_proc_macro_type(t: UniffiOneProcMacroType) -> UniffiOneProcMacroType {
t
}

#[uniffi::export]
fn get_uniffi_one_types(ts: Vec<UniffiOneType>) -> Vec<UniffiOneType> {
ts
}

#[uniffi::export]
fn get_maybe_uniffi_one_type(t: Option<UniffiOneType>) -> Option<UniffiOneType> {
t
}

#[uniffi::export]
fn get_maybe_uniffi_one_types(ts: Vec<Option<UniffiOneType>>) -> Vec<Option<UniffiOneType>> {
ts
}

// An enum
#[uniffi::export]
fn get_uniffi_one_enum(e: UniffiOneEnum) -> UniffiOneEnum {
e
}

#[uniffi::export]
fn get_uniffi_one_enums(es: Vec<UniffiOneEnum>) -> Vec<UniffiOneEnum> {
es
}

#[uniffi::export]
fn get_maybe_uniffi_one_enum(e: Option<UniffiOneEnum>) -> Option<UniffiOneEnum> {
e
}

#[uniffi::export]
fn get_maybe_uniffi_one_enums(es: Vec<Option<UniffiOneEnum>>) -> Vec<Option<UniffiOneEnum>> {
es
}

#[uniffi::export]
fn get_uniffi_one_interface() -> Arc<UniffiOneInterface> {
Arc::new(UniffiOneInterface::new())
}

uniffi::include_scaffolding!("ext-types-lib");
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* 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 uniffi.imported_types_lib.*
import uniffi.uniffi_one.*

val ct = getCombinedType(null)
assert(ct.uot.sval == "hello")
assert(ct.guid == "a-guid")
assert(ct.url == java.net.URL("http://example.com/"))

val ct2 = getCombinedType(ct)
assert(ct == ct2)

val url = java.net.URL("http://example.com/")
assert(getUrl(url) == url)
assert(getMaybeUrl(url)!! == url)
assert(getMaybeUrl(null) == null)
assert(getUrls(listOf(url)) == listOf(url))
assert(getMaybeUrls(listOf(url, null)) == listOf(url, null))

val uot = UniffiOneType("hello")
assert(getUniffiOneType(uot) == uot)
assert(getMaybeUniffiOneType(uot)!! == uot)
assert(getMaybeUniffiOneType(null) == null)
assert(getUniffiOneTypes(listOf(uot)) == listOf(uot))
assert(getMaybeUniffiOneTypes(listOf(uot, null)) == listOf(uot, null))

val uopmt = UniffiOneProcMacroType("hello from proc-macro world")
assert(getUniffiOneProcMacroType(uopmt) == uopmt)

val uoe = UniffiOneEnum.ONE
assert(getUniffiOneEnum(uoe) == uoe)
assert(getMaybeUniffiOneEnum(uoe)!! == uoe)
assert(getMaybeUniffiOneEnum(null) == null)
assert(getUniffiOneEnums(listOf(uoe)) == listOf(uoe))
assert(getMaybeUniffiOneEnums(listOf(uoe, null)) == listOf(uoe, null))
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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
import urllib
from imported_types_lib import *
from uniffi_one import *

class TestIt(unittest.TestCase):
def test_it(self):
ct = get_combined_type(None)
self.assertEqual(ct.uot.sval, "hello")
self.assertEqual(ct.guid, "a-guid")
self.assertEqual(ct.url.scheme, 'http')
self.assertEqual(ct.url.netloc, 'example.com')
self.assertEqual(ct.url.path, '/')

ct2 = get_combined_type(ct)
self.assertEqual(ct, ct2)

def test_get_url(self):
url = urllib.parse.urlparse("http://example.com/")
self.assertEqual(get_url(url), url)
self.assertEqual(get_urls([url]), [url])
self.assertEqual(get_maybe_url(url), url)
self.assertEqual(get_maybe_url(None), None)
self.assertEqual(get_maybe_urls([url, None]), [url, None])

def test_get_uniffi_one_type(self):
t1 = UniffiOneType("hello")
self.assertEqual(t1, get_uniffi_one_type(t1))
self.assertEqual(t1, get_maybe_uniffi_one_type(t1))
self.assertEqual(None, get_maybe_uniffi_one_type(None))
self.assertEqual([t1], get_uniffi_one_types([t1]))
self.assertEqual([t1, None], get_maybe_uniffi_one_types([t1, None]))

def test_get_uniffi_one_proc_macro_type(self):
t1 = UniffiOneProcMacroType("hello")
self.assertEqual(t1, get_uniffi_one_proc_macro_type(t1))

def test_get_uniffi_one_enum(self):
e = UniffiOneEnum.ONE
self.assertEqual(e, get_uniffi_one_enum(e))
self.assertEqual(e, get_maybe_uniffi_one_enum(e))
self.assertEqual(None, get_maybe_uniffi_one_enum(None))
self.assertEqual([e], get_uniffi_one_enums([e]))
self.assertEqual([e, None], get_maybe_uniffi_one_enums([e, None]))


if __name__=='__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -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/. */

import imported_types_lib
//import uniffi_one
import Foundation

let ct = getCombinedType(value: nil)
assert(ct.uot.sval == "hello")
assert(ct.guid == "a-guid")
assert(ct.url == URL(string: "http://example.com/"))

let ct2 = getCombinedType(value: ct)
assert(ct == ct2)

let url = URL(string: "http://example.com/")!;
assert(getUrl(url: url) == url)
assert(getMaybeUrl(url: url)! == url)
assert(getMaybeUrl(url: nil) == nil)
assert(getUrls(urls: [url]) == [url])
assert(getMaybeUrls(urls: [url, nil]) == [url, nil])

assert(getUniffiOneType(t: UniffiOneType(sval: "hello")).sval == "hello")
assert(getMaybeUniffiOneType(t: UniffiOneType(sval: "hello"))!.sval == "hello")
assert(getMaybeUniffiOneType(t: nil) == nil)
assert(getUniffiOneTypes(ts: [UniffiOneType(sval: "hello")]) == [UniffiOneType(sval: "hello")])
assert(getMaybeUniffiOneTypes(ts: [UniffiOneType(sval: "hello"), nil]) == [UniffiOneType(sval: "hello"), nil])

assert(getUniffiOneProcMacroType(t: UniffiOneProcMacroType(sval: "hello from proc-macro world")).sval == "hello from proc-macro world")

assert(getUniffiOneEnum(e: UniffiOneEnum.one) == UniffiOneEnum.one)
assert(getMaybeUniffiOneEnum(e: UniffiOneEnum.one)! == UniffiOneEnum.one)
assert(getMaybeUniffiOneEnum(e: nil) == nil)
assert(getUniffiOneEnums(es: [UniffiOneEnum.one]) == [UniffiOneEnum.one])
assert(getMaybeUniffiOneEnums(es: [UniffiOneEnum.one, nil]) == [UniffiOneEnum.one, nil])
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
uniffi::build_foreign_language_testcases!(
"tests/bindings/test_imported_types.kts",
"tests/bindings/test_imported_types.py",
"tests/bindings/test_imported_types.swift",
);
5 changes: 5 additions & 0 deletions fixtures/ext-types/uniffi-one/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ pub enum UniffiOneEnum {
Two,
}

#[derive(uniffi::Record)]
pub struct UniffiOneProcMacroType {
pub sval: String,
}

#[derive(Default)]
pub struct UniffiOneInterface {
current: AtomicI32,
Expand Down
Loading

0 comments on commit 280fe84

Please sign in to comment.