Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proc-macro external type support (#1531) #1600

Merged
merged 1 commit into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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