summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_macros')
-rw-r--r--third_party/rust/uniffi_macros/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_macros/Cargo.toml10
-rw-r--r--third_party/rust/uniffi_macros/README.md81
-rw-r--r--third_party/rust/uniffi_macros/src/default.rs133
-rw-r--r--third_party/rust/uniffi_macros/src/enum_.rs308
-rw-r--r--third_party/rust/uniffi_macros/src/error.rs101
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs211
-rw-r--r--third_party/rust/uniffi_macros/src/export/attributes.rs306
-rw-r--r--third_party/rust/uniffi_macros/src/export/callback_interface.rs204
-rw-r--r--third_party/rust/uniffi_macros/src/export/item.rs88
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs123
-rw-r--r--third_party/rust/uniffi_macros/src/export/trait_interface.rs183
-rw-r--r--third_party/rust/uniffi_macros/src/export/utrait.rs23
-rw-r--r--third_party/rust/uniffi_macros/src/fnsig.rs248
-rw-r--r--third_party/rust/uniffi_macros/src/lib.rs73
-rw-r--r--third_party/rust/uniffi_macros/src/object.rs76
-rw-r--r--third_party/rust/uniffi_macros/src/record.rs113
-rw-r--r--third_party/rust/uniffi_macros/src/setup_scaffolding.rs57
-rw-r--r--third_party/rust/uniffi_macros/src/util.rs47
19 files changed, 1673 insertions, 714 deletions
diff --git a/third_party/rust/uniffi_macros/.cargo-checksum.json b/third_party/rust/uniffi_macros/.cargo-checksum.json
index 96e44ac74e..9db049289c 100644
--- a/third_party/rust/uniffi_macros/.cargo-checksum.json
+++ b/third_party/rust/uniffi_macros/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"376811e12479a0cced9375a12a2e3186da053b3c7520374bf6f2b54d23282082","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/enum_.rs":"2d3181ef22468deb4e8f48b7b1ed9421134604c2c226d5084c453ebb2cedbfd5","src/error.rs":"0b5beb8a2c8c93c30c56f2f80538bf1f1c8e6a99b2b1c934ad12a4feb75a2fc0","src/export.rs":"6d05417f0b10a9d6df9e96a6ed771c89a5e59e6e52d1ce812025bfe68e9f717e","src/export/attributes.rs":"53a27264882ab0a802a0ee109a2ea3f3456d4c83c85eaa5c0f5912d4486ab843","src/export/callback_interface.rs":"ad2782b7ca930dc067c391394480362be1fbf331d8786be089c0a87415c85a88","src/export/item.rs":"a7b74e6400ec6c5e8fb09d8842ce718b9555d75de13fdf5fecbab2fceeec7cbf","src/export/scaffolding.rs":"8ab2b9b0c5ad5b5477963843b7a58d496344da7de1a2a4b07f30f22275c8f3c9","src/export/utrait.rs":"ce4a3d629aaf0b44b8c5ce6794c5d5b0d7f86f46f0dd6b6ecb134514be330f0d","src/fnsig.rs":"886ceec806b429c7d86fe00d0d84f7b04e21142605f7a61d182f9f616210cd2b","src/lib.rs":"501c736647eff2705c5565f80e554d2e440cceceed95044c9fe147fc309afb48","src/object.rs":"0a14d6b8ccb4faef93a1f61a97ac7d47a80b6581383f4a6e0a4789f53e66e630","src/record.rs":"fbff287bb2d0b7a9eb35ef3161fbd1abbc21f3aa08e88bd4242dd12acbfa3ee9","src/setup_scaffolding.rs":"60b48a56fae16cf01824586b90e6440da3362135d98fc07aaed624c57f806163","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"217ecef0e4dabd158a7597aa3d00d94477993a90b388afbbc0fb39e14f6b013e"},"package":"11cf7a58f101fcedafa5b77ea037999b88748607f0ef3a33eaa0efc5392e92e4"} \ No newline at end of file
+{"files":{"Cargo.toml":"a292239ca3c72852768fdf0e7bc2dd6386af7bf1ab0ef56dff01e1c9e781b2ca","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/default.rs":"77466ac54da69094bcdccc5927d0980b1e9dd0095647ca825830673c48847a53","src/enum_.rs":"afe0a6534d8e7f68047e3f1afad9369d5d5650f4c7555e8d4173f24126c715ba","src/error.rs":"30168378da9a23e6530ffe68647bf6618d07a0aaa236d5009137a922798a0e88","src/export.rs":"42c5e784c1dccc796c8b6ea29c2dc1811e48a531488a3ed0e2a59330778a7e41","src/export/attributes.rs":"c848f8c309c4cf7a168f038834752dc4816b5c853768d7c331ea4cd5ce0841b7","src/export/callback_interface.rs":"794b0665dc7eb02ea854c61c8bb2781e0b4ac1de646d95a8fd7791f770f2e6e3","src/export/item.rs":"4e86875692c2d2993fde12e78dbde2cbffa5675ede143577d5620126401efe05","src/export/scaffolding.rs":"b25167d2213b6d6c5ba653622f26791e8c3e74a5ecce6512ec27009fc8bf68e4","src/export/trait_interface.rs":"f07f9908ee28661de4586d89b693f3d93dae5e5cba8a089eff25f20bbf6b373b","src/export/utrait.rs":"b55533d3eef8262944d3c0d9a3a9cba0615d2d5af8608f0919abc7699989e2a8","src/fnsig.rs":"5e434a1cc87166c5245424bb14e896eb766bf680d4d50d4b8536852f91487d7c","src/lib.rs":"a28bbfd2d1dc835306ff6072f75761bb6b3a158477bba966057776c527fe6d70","src/object.rs":"5419ed64c8120aef811a77c2205f58a7a537bdf34ae04f9c92dd3aaa176eed39","src/record.rs":"29072542cc2f3e027bd7c59b45ba913458f8213d1b2b33bc70d140baa98fcdc8","src/setup_scaffolding.rs":"173fdc916967d54bd6532def16d12e5bb85467813a46a031d3338b77625756bb","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"a2c3693343e78dffb2a7f7b39eeb9b7f298b66688f1766a7c08113cf9431ef4c"},"package":"18331d35003f46f0d04047fbe4227291815b83a937a8c32bc057f990962182c4"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_macros/Cargo.toml b/third_party/rust/uniffi_macros/Cargo.toml
index 9d3908ae8d..5ae193e392 100644
--- a/third_party/rust/uniffi_macros/Cargo.toml
+++ b/third_party/rust/uniffi_macros/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_macros"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (convenience macros)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -47,6 +48,7 @@ version = "1.0"
[dependencies.serde]
version = "1.0.136"
+features = ["derive"]
[dependencies.syn]
version = "2.0"
@@ -59,11 +61,13 @@ features = [
version = "0.5.9"
[dependencies.uniffi_build]
-version = "=0.25.3"
+version = "=0.27.1"
+optional = true
[dependencies.uniffi_meta]
-version = "=0.25.3"
+version = "=0.27.1"
[features]
default = []
nightly = []
+trybuild = ["dep:uniffi_build"]
diff --git a/third_party/rust/uniffi_macros/README.md b/third_party/rust/uniffi_macros/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_macros/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_macros/src/default.rs b/third_party/rust/uniffi_macros/src/default.rs
new file mode 100644
index 0000000000..000c205845
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/default.rs
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::util::kw;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{
+ bracketed, parenthesized,
+ parse::{Nothing, Parse, ParseStream},
+ token::{Bracket, Paren},
+ Lit,
+};
+
+/// Default value
+#[derive(Clone)]
+pub enum DefaultValue {
+ Literal(Lit),
+ None(kw::None),
+ Some {
+ some: kw::Some,
+ paren: Paren,
+ inner: Box<DefaultValue>,
+ },
+ EmptySeq(Bracket),
+}
+
+impl ToTokens for DefaultValue {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match self {
+ DefaultValue::Literal(lit) => lit.to_tokens(tokens),
+ DefaultValue::None(kw) => kw.to_tokens(tokens),
+ DefaultValue::Some { inner, .. } => tokens.extend(quote! { Some(#inner) }),
+ DefaultValue::EmptySeq(_) => tokens.extend(quote! { [] }),
+ }
+ }
+}
+
+impl Parse for DefaultValue {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::None) {
+ let none_kw: kw::None = input.parse()?;
+ Ok(Self::None(none_kw))
+ } else if lookahead.peek(kw::Some) {
+ let some: kw::Some = input.parse()?;
+ let content;
+ let paren = parenthesized!(content in input);
+ Ok(Self::Some {
+ some,
+ paren,
+ inner: content.parse()?,
+ })
+ } else if lookahead.peek(Bracket) {
+ let content;
+ let bracket = bracketed!(content in input);
+ content.parse::<Nothing>()?;
+ Ok(Self::EmptySeq(bracket))
+ } else {
+ Ok(Self::Literal(input.parse()?))
+ }
+ }
+}
+
+impl DefaultValue {
+ fn metadata_calls(&self) -> syn::Result<TokenStream> {
+ match self {
+ DefaultValue::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err(
+ syn::Error::new_spanned(i, "integer literals with suffix not supported here"),
+ ),
+ DefaultValue::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err(
+ syn::Error::new_spanned(f, "float literals with suffix not supported here"),
+ ),
+
+ DefaultValue::Literal(Lit::Str(s)) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_STR)
+ .concat_str(#s)
+ }),
+ DefaultValue::Literal(Lit::Int(i)) => {
+ let digits = i.base10_digits();
+ Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_INT)
+ .concat_str(#digits)
+ })
+ }
+ DefaultValue::Literal(Lit::Float(f)) => {
+ let digits = f.base10_digits();
+ Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_FLOAT)
+ .concat_str(#digits)
+ })
+ }
+ DefaultValue::Literal(Lit::Bool(b)) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_BOOL)
+ .concat_bool(#b)
+ }),
+
+ DefaultValue::Literal(_) => Err(syn::Error::new_spanned(
+ self,
+ "this type of literal is not currently supported as a default",
+ )),
+
+ DefaultValue::EmptySeq(_) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_EMPTY_SEQ)
+ }),
+
+ DefaultValue::None(_) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_NONE)
+ }),
+
+ DefaultValue::Some { inner, .. } => {
+ let inner_calls = inner.metadata_calls()?;
+ Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_SOME)
+ #inner_calls
+ })
+ }
+ }
+ }
+}
+
+pub fn default_value_metadata_calls(default: &Option<DefaultValue>) -> syn::Result<TokenStream> {
+ Ok(match default {
+ Some(default) => {
+ let metadata_calls = default.metadata_calls()?;
+ quote! {
+ .concat_bool(true)
+ #metadata_calls
+ }
+ }
+ None => quote! { .concat_bool(false) },
+ })
+}
diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs
index 32abfa08cc..fd98da3129 100644
--- a/third_party/rust/uniffi_macros/src/enum_.rs
+++ b/third_party/rust/uniffi_macros/src/enum_.rs
@@ -1,13 +1,47 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
-use syn::{Data, DataEnum, DeriveInput, Field, Index};
+use syn::{
+ parse::{Parse, ParseStream},
+ spanned::Spanned,
+ Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, Variant,
+};
use crate::util::{
- create_metadata_items, derive_all_ffi_traits, ident_to_string, mod_path, tagged_impl_header,
- try_metadata_value_from_usize, try_read_field,
+ create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
+ ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
+ try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs,
};
-pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
+fn extract_repr(attrs: &[Attribute]) -> syn::Result<Option<Ident>> {
+ let mut result = None;
+ for attr in attrs {
+ if attr.path().is_ident("repr") {
+ attr.parse_nested_meta(|meta| {
+ result = match meta.path.get_ident() {
+ Some(i) => {
+ let s = i.to_string();
+ match s.as_str() {
+ "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32"
+ | "i64" | "isize" => Some(i.clone()),
+ // while the default repr for an enum is `isize` we don't apply that default here.
+ _ => None,
+ }
+ }
+ _ => None,
+ };
+ Ok(())
+ })?
+ }
+ }
+ Ok(result)
+}
+
+pub fn expand_enum(
+ input: DeriveInput,
+ // Attributes from #[derive_error_for_udl()], if we are in udl mode
+ attr_from_udl_mode: Option<EnumAttr>,
+ udl_mode: bool,
+) -> syn::Result<TokenStream> {
let enum_ = match input.data {
Data::Enum(e) => e,
_ => {
@@ -18,10 +52,17 @@ pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStrea
}
};
let ident = &input.ident;
- let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode);
+ let docstring = extract_docstring(&input.attrs)?;
+ let discr_type = extract_repr(&input.attrs)?;
+ let mut attr: EnumAttr = input.attrs.parse_uniffi_attr_args()?;
+ if let Some(attr_from_udl_mode) = attr_from_udl_mode {
+ attr = attr.merge(attr_from_udl_mode)?;
+ }
+ let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode, &attr);
let meta_static_var = (!udl_mode).then(|| {
- enum_meta_static_var(ident, &enum_).unwrap_or_else(syn::Error::into_compile_error)
+ enum_meta_static_var(ident, docstring, discr_type, &enum_, &attr)
+ .unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
@@ -34,11 +75,13 @@ pub(crate) fn enum_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
+ attr: &EnumAttr,
) -> TokenStream {
enum_or_error_ffi_converter_impl(
ident,
enum_,
udl_mode,
+ attr,
quote! { ::uniffi::metadata::codes::TYPE_ENUM },
)
}
@@ -47,11 +90,13 @@ pub(crate) fn rich_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
+ attr: &EnumAttr,
) -> TokenStream {
enum_or_error_ffi_converter_impl(
ident,
enum_,
udl_mode,
+ attr,
quote! { ::uniffi::metadata::codes::TYPE_ENUM },
)
}
@@ -60,6 +105,7 @@ fn enum_or_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
+ attr: &EnumAttr,
metadata_type_code: TokenStream,
) -> TokenStream {
let name = ident_to_string(ident);
@@ -69,19 +115,50 @@ fn enum_or_error_ffi_converter_impl(
Ok(p) => p,
Err(e) => return e.into_compile_error(),
};
- let write_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
- let v_ident = &v.ident;
- let fields = v.fields.iter().map(|f| &f.ident);
- let idx = Index::from(i + 1);
- let write_fields = v.fields.iter().map(write_field);
+ let mut write_match_arms: Vec<_> = enum_
+ .variants
+ .iter()
+ .enumerate()
+ .map(|(i, v)| {
+ let v_ident = &v.ident;
+ let field_idents = v
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(i, f)| {
+ f.ident
+ .clone()
+ .unwrap_or_else(|| Ident::new(&format!("e{i}"), f.span()))
+ })
+ .collect::<Vec<Ident>>();
+ let idx = Index::from(i + 1);
+ let write_fields =
+ std::iter::zip(v.fields.iter(), field_idents.iter()).map(|(f, ident)| {
+ let ty = &f.ty;
+ quote! {
+ <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf);
+ }
+ });
+ let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
+ let fields = if is_tuple {
+ quote! { ( #(#field_idents),* ) }
+ } else {
+ quote! { { #(#field_idents),* } }
+ };
- quote! {
- Self::#v_ident { #(#fields),* } => {
- ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
- #(#write_fields)*
+ quote! {
+ Self::#v_ident #fields => {
+ ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
+ #(#write_fields)*
+ }
}
- }
- });
+ })
+ .collect();
+ if attr.non_exhaustive.is_some() {
+ write_match_arms.push(quote! {
+ _ => panic!("Unexpected variant in non-exhaustive enum"),
+ })
+ }
let write_impl = quote! {
match obj { #(#write_match_arms)* }
};
@@ -89,10 +166,17 @@ fn enum_or_error_ffi_converter_impl(
let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
let idx = Index::from(i + 1);
let v_ident = &v.ident;
+ let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
let try_read_fields = v.fields.iter().map(try_read_field);
- quote! {
- #idx => Self::#v_ident { #(#try_read_fields)* },
+ if is_tuple {
+ quote! {
+ #idx => Self::#v_ident ( #(#try_read_fields)* ),
+ }
+ } else {
+ quote! {
+ #idx => Self::#v_ident { #(#try_read_fields)* },
+ }
}
});
let error_format_string = format!("Invalid {ident} enum value: {{}}");
@@ -127,69 +211,161 @@ fn enum_or_error_ffi_converter_impl(
}
}
-fn write_field(f: &Field) -> TokenStream {
- let ident = &f.ident;
- let ty = &f.ty;
-
- quote! {
- <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf);
- }
-}
-
-pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result<TokenStream> {
+pub(crate) fn enum_meta_static_var(
+ ident: &Ident,
+ docstring: String,
+ discr_type: Option<Ident>,
+ enum_: &DataEnum,
+ attr: &EnumAttr,
+) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
let module_path = mod_path()?;
+ let non_exhaustive = attr.non_exhaustive.is_some();
let mut metadata_expr = quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
.concat_str(#module_path)
.concat_str(#name)
+ .concat_option_bool(None) // forced_flatness
};
+ metadata_expr.extend(match discr_type {
+ None => quote! { .concat_bool(false) },
+ Some(t) => quote! { .concat_bool(true).concat(<#t as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) }
+ });
metadata_expr.extend(variant_metadata(enum_)?);
+ metadata_expr.extend(quote! {
+ .concat_bool(#non_exhaustive)
+ .concat_long_str(#docstring)
+ });
Ok(create_metadata_items("enum", &name, metadata_expr, None))
}
+fn variant_value(v: &Variant) -> syn::Result<TokenStream> {
+ let Some((_, e)) = &v.discriminant else {
+ return Ok(quote! { .concat_bool(false) });
+ };
+ // Attempting to expose an enum value which we don't understand is a hard-error
+ // rather than silently ignoring it. If we had the ability to emit a warning that
+ // might make more sense.
+
+ // We can't sanely handle most expressions other than literals, but we can handle
+ // negative literals.
+ let mut negate = false;
+ let lit = match e {
+ Expr::Lit(lit) => lit,
+ Expr::Unary(expr_unary) if matches!(expr_unary.op, syn::UnOp::Neg(_)) => {
+ negate = true;
+ match *expr_unary.expr {
+ Expr::Lit(ref lit) => lit,
+ _ => {
+ return Err(syn::Error::new_spanned(
+ e,
+ "UniFFI disciminant values must be a literal",
+ ));
+ }
+ }
+ }
+ _ => {
+ return Err(syn::Error::new_spanned(
+ e,
+ "UniFFI disciminant values must be a literal",
+ ));
+ }
+ };
+ let Lit::Int(ref intlit) = lit.lit else {
+ return Err(syn::Error::new_spanned(
+ v,
+ "UniFFI disciminant values must be a literal integer",
+ ));
+ };
+ if !intlit.suffix().is_empty() {
+ return Err(syn::Error::new_spanned(
+ intlit,
+ "integer literals with suffix not supported by UniFFI here",
+ ));
+ }
+ let digits = if negate {
+ format!("-{}", intlit.base10_digits())
+ } else {
+ intlit.base10_digits().to_string()
+ };
+ Ok(quote! {
+ .concat_bool(true)
+ .concat_value(::uniffi::metadata::codes::LIT_INT)
+ .concat_str(#digits)
+ })
+}
+
pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
let variants_len =
try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
- .chain(
- enum_.variants
+ .chain(enum_.variants.iter().map(|v| {
+ let fields_len = try_metadata_value_from_usize(
+ v.fields.len(),
+ "UniFFI limits enum variants to 256 fields",
+ )?;
+
+ let field_names = v
+ .fields
.iter()
- .map(|v| {
- let fields_len = try_metadata_value_from_usize(
- v.fields.len(),
- "UniFFI limits enum variants to 256 fields",
- )?;
-
- let field_names = v.fields
- .iter()
- .map(|f| {
- f.ident
- .as_ref()
- .ok_or_else(||
- syn::Error::new_spanned(
- v,
- "UniFFI only supports enum variants with named fields (or no fields at all)",
- )
- )
- .map(ident_to_string)
- })
- .collect::<syn::Result<Vec<_>>>()?;
-
- let name = ident_to_string(&v.ident);
- let field_types = v.fields.iter().map(|f| &f.ty);
- Ok(quote! {
- .concat_str(#name)
- .concat_value(#fields_len)
- #(
- .concat_str(#field_names)
- .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
- // field defaults not yet supported for enums
- .concat_bool(false)
- )*
- })
- })
- )
+ .map(|f| f.ident.as_ref().map(ident_to_string).unwrap_or_default())
+ .collect::<Vec<_>>();
+
+ let name = ident_to_string(&v.ident);
+ let value_tokens = variant_value(v)?;
+ let docstring = extract_docstring(&v.attrs)?;
+ let field_types = v.fields.iter().map(|f| &f.ty);
+ let field_docstrings = v
+ .fields
+ .iter()
+ .map(|f| extract_docstring(&f.attrs))
+ .collect::<syn::Result<Vec<_>>>()?;
+
+ Ok(quote! {
+ .concat_str(#name)
+ #value_tokens
+ .concat_value(#fields_len)
+ #(
+ .concat_str(#field_names)
+ .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
+ // field defaults not yet supported for enums
+ .concat_bool(false)
+ .concat_long_str(#field_docstrings)
+ )*
+ .concat_long_str(#docstring)
+ })
+ }))
.collect()
}
+
+#[derive(Default)]
+pub struct EnumAttr {
+ pub non_exhaustive: Option<kw::non_exhaustive>,
+}
+
+// So ErrorAttr can be used with `parse_macro_input!`
+impl Parse for EnumAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for EnumAttr {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::non_exhaustive) {
+ Ok(Self {
+ non_exhaustive: input.parse()?,
+ })
+ } else {
+ Err(lookahead.error())
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/error.rs b/third_party/rust/uniffi_macros/src/error.rs
index a2ee7cf603..804b438003 100644
--- a/third_party/rust/uniffi_macros/src/error.rs
+++ b/third_party/rust/uniffi_macros/src/error.rs
@@ -6,11 +6,11 @@ use syn::{
};
use crate::{
- enum_::{rich_error_ffi_converter_impl, variant_metadata},
+ enum_::{rich_error_ffi_converter_impl, variant_metadata, EnumAttr},
util::{
- chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, ident_to_string, kw,
- mod_path, parse_comma_separated, tagged_impl_header, try_metadata_value_from_usize,
- AttributeSliceExt, UniffiAttributeArgs,
+ chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, extract_docstring,
+ ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
+ try_metadata_value_from_usize, AttributeSliceExt, UniffiAttributeArgs,
},
};
@@ -30,13 +30,14 @@ pub fn expand_error(
}
};
let ident = &input.ident;
+ let docstring = extract_docstring(&input.attrs)?;
let mut attr: ErrorAttr = input.attrs.parse_uniffi_attr_args()?;
if let Some(attr_from_udl_mode) = attr_from_udl_mode {
attr = attr.merge(attr_from_udl_mode)?;
}
- let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode);
+ let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode)?;
let meta_static_var = (!udl_mode).then(|| {
- error_meta_static_var(ident, &enum_, attr.flat.is_some())
+ error_meta_static_var(ident, docstring, &enum_, &attr)
.unwrap_or_else(syn::Error::into_compile_error)
});
@@ -67,23 +68,23 @@ fn error_ffi_converter_impl(
enum_: &DataEnum,
attr: &ErrorAttr,
udl_mode: bool,
-) -> TokenStream {
- if attr.flat.is_some() {
- flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr.with_try_read.is_some())
+) -> syn::Result<TokenStream> {
+ Ok(if attr.flat.is_some() {
+ flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr)
} else {
- rich_error_ffi_converter_impl(ident, enum_, udl_mode)
- }
+ rich_error_ffi_converter_impl(ident, enum_, udl_mode, &attr.clone().try_into()?)
+ })
}
// FfiConverters for "flat errors"
//
-// These are errors where we only lower the to_string() value, rather than any assocated data.
+// These are errors where we only lower the to_string() value, rather than any associated data.
// We lower the to_string() value unconditionally, whether the enum has associated data or not.
fn flat_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
- implement_lift: bool,
+ attr: &ErrorAttr,
) -> TokenStream {
let name = ident_to_string(ident);
let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode);
@@ -95,7 +96,7 @@ fn flat_error_ffi_converter_impl(
};
let lower_impl = {
- let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
+ let mut match_arms: Vec<_> = enum_.variants.iter().enumerate().map(|(i, v)| {
let v_ident = &v.ident;
let idx = Index::from(i + 1);
@@ -105,7 +106,12 @@ fn flat_error_ffi_converter_impl(
<::std::string::String as ::uniffi::Lower<crate::UniFfiTag>>::write(error_msg, buf);
}
}
- });
+ }).collect();
+ if attr.non_exhaustive.is_some() {
+ match_arms.push(quote! {
+ _ => panic!("Unexpected variant in non-exhaustive enum"),
+ })
+ }
quote! {
#[automatically_derived]
@@ -128,7 +134,7 @@ fn flat_error_ffi_converter_impl(
}
};
- let lift_impl = if implement_lift {
+ let lift_impl = if attr.with_try_read.is_some() {
let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
let v_ident = &v.ident;
let idx = Index::from(i + 1);
@@ -192,42 +198,53 @@ fn flat_error_ffi_converter_impl(
pub(crate) fn error_meta_static_var(
ident: &Ident,
+ docstring: String,
enum_: &DataEnum,
- flat: bool,
+ attr: &ErrorAttr,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
let module_path = mod_path()?;
+ let flat = attr.flat.is_some();
+ let non_exhaustive = attr.non_exhaustive.is_some();
let mut metadata_expr = quote! {
- ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ERROR)
- // first our is-flat flag
- .concat_bool(#flat)
- // followed by an enum
+ ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
.concat_str(#module_path)
.concat_str(#name)
+ .concat_option_bool(Some(#flat))
+ .concat_bool(false) // discr_type: None
};
if flat {
metadata_expr.extend(flat_error_variant_metadata(enum_)?)
} else {
metadata_expr.extend(variant_metadata(enum_)?);
}
+ metadata_expr.extend(quote! {
+ .concat_bool(#non_exhaustive)
+ .concat_long_str(#docstring)
+ });
Ok(create_metadata_items("error", &name, metadata_expr, None))
}
pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
let variants_len =
try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
- Ok(std::iter::once(quote! { .concat_value(#variants_len) })
+ std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
.chain(enum_.variants.iter().map(|v| {
let name = ident_to_string(&v.ident);
- quote! { .concat_str(#name) }
+ let docstring = extract_docstring(&v.attrs)?;
+ Ok(quote! {
+ .concat_str(#name)
+ .concat_long_str(#docstring)
+ })
}))
- .collect())
+ .collect()
}
-#[derive(Default)]
+#[derive(Clone, Default)]
pub struct ErrorAttr {
- flat: Option<kw::flat_error>,
- with_try_read: Option<kw::with_try_read>,
+ pub flat: Option<kw::flat_error>,
+ pub with_try_read: Option<kw::with_try_read>,
+ pub non_exhaustive: Option<kw::non_exhaustive>,
}
impl UniffiAttributeArgs for ErrorAttr {
@@ -243,8 +260,13 @@ impl UniffiAttributeArgs for ErrorAttr {
with_try_read: input.parse()?,
..Self::default()
})
+ } else if lookahead.peek(kw::non_exhaustive) {
+ Ok(Self {
+ non_exhaustive: input.parse()?,
+ ..Self::default()
+ })
} else if lookahead.peek(kw::handle_unknown_callback_error) {
- // Not used anymore, but still lallowed
+ // Not used anymore, but still allowed
Ok(Self::default())
} else {
Err(lookahead.error())
@@ -255,6 +277,7 @@ impl UniffiAttributeArgs for ErrorAttr {
Ok(Self {
flat: either_attribute_arg(self.flat, other.flat)?,
with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?,
+ non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
})
}
}
@@ -265,3 +288,25 @@ impl Parse for ErrorAttr {
parse_comma_separated(input)
}
}
+
+impl TryFrom<ErrorAttr> for EnumAttr {
+ type Error = syn::Error;
+
+ fn try_from(error_attr: ErrorAttr) -> Result<Self, Self::Error> {
+ if error_attr.flat.is_some() {
+ Err(syn::Error::new(
+ Span::call_site(),
+ "flat attribute not valid for rich enum errors",
+ ))
+ } else if error_attr.with_try_read.is_some() {
+ Err(syn::Error::new(
+ Span::call_site(),
+ "with_try_read attribute not valid for rich enum errors",
+ ))
+ } else {
+ Ok(EnumAttr {
+ non_exhaustive: error_attr.non_exhaustive,
+ })
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export.rs b/third_party/rust/uniffi_macros/src/export.rs
index bbb16acf90..41657a639e 100644
--- a/third_party/rust/uniffi_macros/src/export.rs
+++ b/third_party/rust/uniffi_macros/src/export.rs
@@ -2,7 +2,7 @@
* 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 proc_macro2::{Ident, Span, TokenStream};
+use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{visit_mut::VisitMut, Item, Type};
@@ -10,6 +10,7 @@ mod attributes;
mod callback_interface;
mod item;
mod scaffolding;
+mod trait_interface;
mod utrait;
use self::{
@@ -18,20 +19,16 @@ use self::{
gen_constructor_scaffolding, gen_ffi_function, gen_fn_scaffolding, gen_method_scaffolding,
},
};
-use crate::{
- object::interface_meta_static_var,
- util::{ident_to_string, mod_path, tagged_impl_header},
-};
-pub use attributes::ExportAttributeArguments;
+use crate::util::{ident_to_string, mod_path};
+pub use attributes::{DefaultMap, ExportFnArgs, ExportedImplFnArgs};
pub use callback_interface::ffi_converter_callback_interface_impl;
-use uniffi_meta::free_fn_symbol_name;
// TODO(jplatte): Ensure no generics, …
// TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible
pub(crate) fn expand_export(
mut item: Item,
- args: ExportAttributeArguments,
+ all_args: proc_macro::TokenStream,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let mod_path = mod_path()?;
@@ -41,11 +38,17 @@ pub(crate) fn expand_export(
// new functions outside of the `impl`).
rewrite_self_type(&mut item);
- let metadata = ExportItem::new(item, &args)?;
+ let metadata = ExportItem::new(item, all_args)?;
match metadata {
- ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args, udl_mode),
- ExportItem::Impl { items, self_ident } => {
+ ExportItem::Function { sig, args } => {
+ gen_fn_scaffolding(sig, &args.async_runtime, udl_mode)
+ }
+ ExportItem::Impl {
+ items,
+ self_ident,
+ args,
+ } => {
if let Some(rt) = &args.async_runtime {
if items
.iter()
@@ -61,8 +64,12 @@ pub(crate) fn expand_export(
let item_tokens: TokenStream = items
.into_iter()
.map(|item| match item {
- ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args, udl_mode),
- ImplItem::Method(sig) => gen_method_scaffolding(sig, &args, udl_mode),
+ ImplItem::Constructor(sig) => {
+ gen_constructor_scaffolding(sig, &args.async_runtime, udl_mode)
+ }
+ ImplItem::Method(sig) => {
+ gen_method_scaffolding(sig, &args.async_runtime, udl_mode)
+ }
})
.collect::<syn::Result<_>>()?;
Ok(quote_spanned! { self_ident.span() => #item_tokens })
@@ -70,111 +77,51 @@ pub(crate) fn expand_export(
ExportItem::Trait {
items,
self_ident,
- callback_interface: false,
- } => {
- if let Some(rt) = args.async_runtime {
- return Err(syn::Error::new_spanned(rt, "not supported for traits"));
- }
-
- let name = ident_to_string(&self_ident);
- let free_fn_ident =
- Ident::new(&free_fn_symbol_name(&mod_path, &name), Span::call_site());
-
- let free_tokens = quote! {
- #[doc(hidden)]
- #[no_mangle]
- pub extern "C" fn #free_fn_ident(
- ptr: *const ::std::ffi::c_void,
- call_status: &mut ::uniffi::RustCallStatus
- ) {
- uniffi::rust_call(call_status, || {
- assert!(!ptr.is_null());
- drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) });
- Ok(())
- });
- }
- };
-
- let impl_tokens: TokenStream = items
- .into_iter()
- .map(|item| match item {
- ImplItem::Method(sig) => {
- if sig.is_async {
- return Err(syn::Error::new(
- sig.span,
- "async trait methods are not supported",
- ));
- }
- gen_method_scaffolding(sig, &args, udl_mode)
- }
- _ => unreachable!("traits have no constructors"),
- })
- .collect::<syn::Result<_>>()?;
-
- let meta_static_var = (!udl_mode).then(|| {
- interface_meta_static_var(&self_ident, true, &mod_path)
- .unwrap_or_else(syn::Error::into_compile_error)
- });
- let ffi_converter_tokens = ffi_converter_trait_impl(&self_ident, false);
-
- Ok(quote_spanned! { self_ident.span() =>
- #meta_static_var
- #free_tokens
- #ffi_converter_tokens
- #impl_tokens
- })
- }
+ with_foreign,
+ callback_interface_only: false,
+ docstring,
+ args,
+ } => trait_interface::gen_trait_scaffolding(
+ &mod_path,
+ args,
+ self_ident,
+ items,
+ udl_mode,
+ with_foreign,
+ docstring,
+ ),
ExportItem::Trait {
items,
self_ident,
- callback_interface: true,
+ callback_interface_only: true,
+ docstring,
+ ..
} => {
let trait_name = ident_to_string(&self_ident);
- let trait_impl_ident = Ident::new(
- &format!("UniFFICallbackHandler{trait_name}"),
- Span::call_site(),
- );
- let internals_ident = Ident::new(
- &format!(
- "UNIFFI_FOREIGN_CALLBACK_INTERNALS_{}",
- trait_name.to_ascii_uppercase()
- ),
- Span::call_site(),
- );
-
- let trait_impl = callback_interface::trait_impl(
- &trait_impl_ident,
- &self_ident,
- &internals_ident,
- &items,
- )
- .unwrap_or_else(|e| e.into_compile_error());
- let metadata_items = callback_interface::metadata_items(&self_ident, &items, &mod_path)
- .unwrap_or_else(|e| vec![e.into_compile_error()]);
-
- let init_ident = Ident::new(
- &uniffi_meta::init_callback_fn_symbol_name(&mod_path, &trait_name),
- Span::call_site(),
- );
+ let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name);
+ let trait_impl = callback_interface::trait_impl(&mod_path, &self_ident, &items)
+ .unwrap_or_else(|e| e.into_compile_error());
+ let metadata_items = (!udl_mode).then(|| {
+ let items =
+ callback_interface::metadata_items(&self_ident, &items, &mod_path, docstring)
+ .unwrap_or_else(|e| vec![e.into_compile_error()]);
+ quote! { #(#items)* }
+ });
+ let ffi_converter_tokens =
+ ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident, udl_mode);
Ok(quote! {
- #[doc(hidden)]
- static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new();
-
- #[doc(hidden)]
- #[no_mangle]
- pub extern "C" fn #init_ident(callback: ::uniffi::ForeignCallback, _: &mut ::uniffi::RustCallStatus) {
- #internals_ident.set_callback(callback);
- }
-
#trait_impl
- #(#metadata_items)*
+ #ffi_converter_tokens
+
+ #metadata_items
})
}
ExportItem::Struct {
self_ident,
uniffi_traits,
+ ..
} => {
assert!(!udl_mode);
utrait::expand_uniffi_trait_export(self_ident, uniffi_traits)
@@ -182,62 +129,6 @@ pub(crate) fn expand_export(
}
}
-pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, udl_mode: bool) -> TokenStream {
- let impl_spec = tagged_impl_header("FfiConverterArc", &quote! { dyn #trait_ident }, udl_mode);
- let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { dyn #trait_ident }, udl_mode);
- let name = ident_to_string(trait_ident);
- let mod_path = match mod_path() {
- Ok(p) => p,
- Err(e) => return e.into_compile_error(),
- };
-
- quote! {
- // All traits must be `Sync + Send`. The generated scaffolding will fail to compile
- // if they are not, but unfortunately it fails with an unactionably obscure error message.
- // By asserting the requirement explicitly, we help Rust produce a more scrutable error message
- // and thus help the user debug why the requirement isn't being met.
- uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: Sync, Send);
-
- unsafe #impl_spec {
- type FfiType = *const ::std::os::raw::c_void;
-
- fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType {
- ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void
- }
-
- fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> {
- let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(v as *mut ::std::sync::Arc<Self>) });
- // Take a clone for our own use.
- Ok(::std::sync::Arc::clone(foreign_arc))
- }
-
- fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) {
- ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
- ::uniffi::deps::bytes::BufMut::put_u64(
- buf,
- <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64,
- );
- }
-
- fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> {
- ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
- ::uniffi::check_remaining(buf, 8)?;
- <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift(
- ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType)
- }
-
- const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE)
- .concat_str(#mod_path)
- .concat_str(#name)
- .concat_bool(true);
- }
-
- unsafe #lift_ref_impl_spec {
- type LiftType = ::std::sync::Arc<dyn #trait_ident>;
- }
- }
-}
-
/// Rewrite Self type alias usage in an impl block to the type itself.
///
/// For example,
diff --git a/third_party/rust/uniffi_macros/src/export/attributes.rs b/third_party/rust/uniffi_macros/src/export/attributes.rs
index c3edcd5920..be7e8902e4 100644
--- a/third_party/rust/uniffi_macros/src/export/attributes.rs
+++ b/third_party/rust/uniffi_macros/src/export/attributes.rs
@@ -1,31 +1,33 @@
-use crate::util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs};
+use std::collections::{HashMap, HashSet};
+
+use crate::{
+ default::DefaultValue,
+ util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs},
+};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
+ parenthesized,
parse::{Parse, ParseStream},
- Attribute, LitStr, Meta, PathArguments, PathSegment, Token,
+ Attribute, Ident, LitStr, Meta, PathArguments, PathSegment, Token,
};
+use uniffi_meta::UniffiTraitDiscriminants;
#[derive(Default)]
-pub struct ExportAttributeArguments {
+pub struct ExportTraitArgs {
pub(crate) async_runtime: Option<AsyncRuntime>,
pub(crate) callback_interface: Option<kw::callback_interface>,
- pub(crate) constructor: Option<kw::constructor>,
- // tried to make this a vec but that got messy quickly...
- pub(crate) trait_debug: Option<kw::Debug>,
- pub(crate) trait_display: Option<kw::Display>,
- pub(crate) trait_hash: Option<kw::Hash>,
- pub(crate) trait_eq: Option<kw::Eq>,
+ pub(crate) with_foreign: Option<kw::with_foreign>,
}
-impl Parse for ExportAttributeArguments {
+impl Parse for ExportTraitArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
parse_comma_separated(input)
}
}
-impl UniffiAttributeArgs for ExportAttributeArguments {
+impl UniffiAttributeArgs for ExportTraitArgs {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::async_runtime) {
@@ -40,52 +42,175 @@ impl UniffiAttributeArgs for ExportAttributeArguments {
callback_interface: input.parse()?,
..Self::default()
})
- } else if lookahead.peek(kw::constructor) {
+ } else if lookahead.peek(kw::with_foreign) {
Ok(Self {
- constructor: input.parse()?,
+ with_foreign: input.parse()?,
..Self::default()
})
- } else if lookahead.peek(kw::Debug) {
+ } else {
+ Ok(Self::default())
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ let merged = Self {
+ async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
+ callback_interface: either_attribute_arg(
+ self.callback_interface,
+ other.callback_interface,
+ )?,
+ with_foreign: either_attribute_arg(self.with_foreign, other.with_foreign)?,
+ };
+ if merged.callback_interface.is_some() && merged.with_foreign.is_some() {
+ return Err(syn::Error::new(
+ merged.callback_interface.unwrap().span,
+ "`callback_interface` and `with_foreign` are mutually exclusive",
+ ));
+ }
+ Ok(merged)
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct ExportFnArgs {
+ pub(crate) async_runtime: Option<AsyncRuntime>,
+ pub(crate) name: Option<String>,
+ pub(crate) defaults: DefaultMap,
+}
+
+impl Parse for ExportFnArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportFnArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::async_runtime) {
+ let _: kw::async_runtime = input.parse()?;
+ let _: Token![=] = input.parse()?;
Ok(Self {
- trait_debug: input.parse()?,
+ async_runtime: Some(input.parse()?),
..Self::default()
})
- } else if lookahead.peek(kw::Display) {
+ } else if lookahead.peek(kw::name) {
+ let _: kw::name = input.parse()?;
+ let _: Token![=] = input.parse()?;
+ let name = Some(input.parse::<LitStr>()?.value());
Ok(Self {
- trait_display: input.parse()?,
+ name,
..Self::default()
})
- } else if lookahead.peek(kw::Hash) {
+ } else if lookahead.peek(kw::default) {
Ok(Self {
- trait_hash: input.parse()?,
+ defaults: DefaultMap::parse(input)?,
..Self::default()
})
- } else if lookahead.peek(kw::Eq) {
+ } else {
+ Err(syn::Error::new(
+ input.span(),
+ format!("uniffi::export attribute `{input}` is not supported here."),
+ ))
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
+ name: either_attribute_arg(self.name, other.name)?,
+ defaults: self.defaults.merge(other.defaults),
+ })
+ }
+}
+
+#[derive(Default)]
+pub struct ExportImplArgs {
+ pub(crate) async_runtime: Option<AsyncRuntime>,
+}
+
+impl Parse for ExportImplArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportImplArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::async_runtime) {
+ let _: kw::async_runtime = input.parse()?;
+ let _: Token![=] = input.parse()?;
Ok(Self {
- trait_eq: input.parse()?,
- ..Self::default()
+ async_runtime: Some(input.parse()?),
})
} else {
- Ok(Self::default())
+ Err(syn::Error::new(
+ input.span(),
+ format!("uniffi::export attribute `{input}` is not supported here."),
+ ))
}
}
fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
- callback_interface: either_attribute_arg(
- self.callback_interface,
- other.callback_interface,
- )?,
- constructor: either_attribute_arg(self.constructor, other.constructor)?,
- trait_debug: either_attribute_arg(self.trait_debug, other.trait_debug)?,
- trait_display: either_attribute_arg(self.trait_display, other.trait_display)?,
- trait_hash: either_attribute_arg(self.trait_hash, other.trait_hash)?,
- trait_eq: either_attribute_arg(self.trait_eq, other.trait_eq)?,
})
}
}
+#[derive(Default)]
+pub struct ExportStructArgs {
+ pub(crate) traits: HashSet<UniffiTraitDiscriminants>,
+}
+
+impl Parse for ExportStructArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportStructArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::Debug) {
+ input.parse::<Option<kw::Debug>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Debug]),
+ })
+ } else if lookahead.peek(kw::Display) {
+ input.parse::<Option<kw::Display>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Display]),
+ })
+ } else if lookahead.peek(kw::Hash) {
+ input.parse::<Option<kw::Hash>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Hash]),
+ })
+ } else if lookahead.peek(kw::Eq) {
+ input.parse::<Option<kw::Eq>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Eq]),
+ })
+ } else {
+ Err(syn::Error::new(
+ input.span(),
+ format!(
+ "uniffi::export struct attributes must be builtin trait names; `{input}` is invalid"
+ ),
+ ))
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ let mut traits = self.traits;
+ traits.extend(other.traits);
+ Ok(Self { traits })
+ }
+}
+
+#[derive(Clone)]
pub(crate) enum AsyncRuntime {
Tokio(LitStr),
}
@@ -111,9 +236,57 @@ impl ToTokens for AsyncRuntime {
}
}
+/// Arguments for function inside an impl block
+///
+/// This stores the parsed arguments for `uniffi::constructor` and `uniffi::method`
+#[derive(Clone, Default)]
+pub struct ExportedImplFnArgs {
+ pub(crate) name: Option<String>,
+ pub(crate) defaults: DefaultMap,
+}
+
+impl Parse for ExportedImplFnArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportedImplFnArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::name) {
+ let _: kw::name = input.parse()?;
+ let _: Token![=] = input.parse()?;
+ let name = Some(input.parse::<LitStr>()?.value());
+ Ok(Self {
+ name,
+ ..Self::default()
+ })
+ } else if lookahead.peek(kw::default) {
+ Ok(Self {
+ defaults: DefaultMap::parse(input)?,
+ ..Self::default()
+ })
+ } else {
+ Err(syn::Error::new(
+ input.span(),
+ format!("uniffi::constructor/method attribute `{input}` is not supported here."),
+ ))
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ name: either_attribute_arg(self.name, other.name)?,
+ defaults: self.defaults.merge(other.defaults),
+ })
+ }
+}
+
#[derive(Default)]
pub(super) struct ExportedImplFnAttributes {
pub constructor: bool,
+ pub args: ExportedImplFnArgs,
}
impl ExportedImplFnAttributes {
@@ -130,12 +303,11 @@ impl ExportedImplFnAttributes {
}
ensure_no_path_args(fst)?;
- if let Meta::List(_) | Meta::NameValue(_) = &attr.meta {
- return Err(syn::Error::new_spanned(
- &attr.meta,
- "attribute arguments are not currently recognized in this position",
- ));
- }
+ let args = match &attr.meta {
+ Meta::List(_) => attr.parse_args::<ExportedImplFnArgs>()?,
+ _ => Default::default(),
+ };
+ this.args = args;
if segs.len() != 2 {
return Err(syn::Error::new_spanned(
@@ -156,6 +328,14 @@ impl ExportedImplFnAttributes {
}
this.constructor = true;
}
+ "method" => {
+ if this.constructor {
+ return Err(syn::Error::new_spanned(
+ attr,
+ "confused constructor/method attributes",
+ ));
+ }
+ }
_ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")),
}
}
@@ -171,3 +351,53 @@ fn ensure_no_path_args(seg: &PathSegment) -> syn::Result<()> {
Err(syn::Error::new_spanned(&seg.arguments, "unexpected syntax"))
}
}
+
+/// Maps arguments to defaults for functions
+#[derive(Clone, Default)]
+pub struct DefaultMap {
+ map: HashMap<Ident, DefaultValue>,
+}
+
+impl DefaultMap {
+ pub fn merge(self, other: Self) -> Self {
+ let mut map = self.map;
+ map.extend(other.map);
+ Self { map }
+ }
+
+ pub fn remove(&mut self, ident: &Ident) -> Option<DefaultValue> {
+ self.map.remove(ident)
+ }
+
+ pub fn idents(&self) -> Vec<&Ident> {
+ self.map.keys().collect()
+ }
+}
+
+impl Parse for DefaultMap {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _: kw::default = input.parse()?;
+ let content;
+ let _ = parenthesized!(content in input);
+ let pairs = content.parse_terminated(DefaultPair::parse, Token![,])?;
+ Ok(Self {
+ map: pairs.into_iter().map(|p| (p.name, p.value)).collect(),
+ })
+ }
+}
+
+pub struct DefaultPair {
+ pub name: Ident,
+ pub eq_token: Token![=],
+ pub value: DefaultValue,
+}
+
+impl Parse for DefaultPair {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ Ok(Self {
+ name: input.parse()?,
+ eq_token: input.parse()?,
+ value: input.parse()?,
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/callback_interface.rs b/third_party/rust/uniffi_macros/src/export/callback_interface.rs
index 2f2561bbc2..fe145384ec 100644
--- a/third_party/rust/uniffi_macros/src/export/callback_interface.rs
+++ b/third_party/rust/uniffi_macros/src/export/callback_interface.rs
@@ -5,69 +5,137 @@
use crate::{
export::ImplItem,
fnsig::{FnKind, FnSignature, ReceiverArg},
- util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header},
+ util::{
+ create_metadata_items, derive_ffi_traits, ident_to_string, mod_path, tagged_impl_header,
+ },
};
use proc_macro2::{Span, TokenStream};
-use quote::quote;
+use quote::{format_ident, quote};
use std::iter;
use syn::Ident;
+/// Generate a trait impl that calls foreign callbacks
+///
+/// This generates:
+/// * A `repr(C)` VTable struct where each field is the FFI function for the trait method.
+/// * A FFI function for foreign code to set their VTable for the interface
+/// * An implementation of the trait using that VTable
pub(super) fn trait_impl(
- ident: &Ident,
+ mod_path: &str,
trait_ident: &Ident,
- internals_ident: &Ident,
items: &[ImplItem],
) -> syn::Result<TokenStream> {
- let trait_impl_methods = items
+ let trait_name = ident_to_string(trait_ident);
+ let trait_impl_ident = trait_impl_ident(&trait_name);
+ let vtable_type = format_ident!("UniFfiTraitVtable{trait_name}");
+ let vtable_cell = format_ident!("UNIFFI_TRAIT_CELL_{}", trait_name.to_uppercase());
+ let init_ident = Ident::new(
+ &uniffi_meta::init_callback_vtable_fn_symbol_name(mod_path, &trait_name),
+ Span::call_site(),
+ );
+ let methods = items
.iter()
.map(|item| match item {
- ImplItem::Method(sig) => gen_method_impl(sig, internals_ident),
- _ => unreachable!("traits have no constructors"),
+ ImplItem::Constructor(sig) => Err(syn::Error::new(
+ sig.span,
+ "Constructors not allowed in trait interfaces",
+ )),
+ ImplItem::Method(sig) => Ok(sig),
})
- .collect::<syn::Result<TokenStream>>()?;
- let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, false);
+ .collect::<syn::Result<Vec<_>>>()?;
+
+ let vtable_fields = methods.iter()
+ .map(|sig| {
+ let ident = &sig.ident;
+ let param_names = sig.scaffolding_param_names();
+ let param_types = sig.scaffolding_param_types();
+ let lift_return = sig.lift_return_impl();
+ if !sig.is_async {
+ quote! {
+ #ident: extern "C" fn(
+ uniffi_handle: u64,
+ #(#param_names: #param_types,)*
+ uniffi_out_return: &mut #lift_return::ReturnType,
+ uniffi_out_call_status: &mut ::uniffi::RustCallStatus,
+ ),
+ }
+ } else {
+ quote! {
+ #ident: extern "C" fn(
+ uniffi_handle: u64,
+ #(#param_names: #param_types,)*
+ uniffi_future_callback: ::uniffi::ForeignFutureCallback<#lift_return::ReturnType>,
+ uniffi_callback_data: u64,
+ uniffi_out_return: &mut ::uniffi::ForeignFuture,
+ ),
+ }
+ }
+ });
+
+ let trait_impl_methods = methods
+ .iter()
+ .map(|sig| gen_method_impl(sig, &vtable_cell))
+ .collect::<syn::Result<Vec<_>>>()?;
+ let has_async_method = methods.iter().any(|m| m.is_async);
+ let impl_attributes = has_async_method.then(|| quote! { #[::async_trait::async_trait] });
Ok(quote! {
- #[doc(hidden)]
+ struct #vtable_type {
+ #(#vtable_fields)*
+ uniffi_free: extern "C" fn(handle: u64),
+ }
+
+ static #vtable_cell: ::uniffi::UniffiForeignPointerCell::<#vtable_type> = ::uniffi::UniffiForeignPointerCell::<#vtable_type>::new();
+
+ #[no_mangle]
+ extern "C" fn #init_ident(vtable: ::std::ptr::NonNull<#vtable_type>) {
+ #vtable_cell.set(vtable);
+ }
+
#[derive(Debug)]
- struct #ident {
+ struct #trait_impl_ident {
handle: u64,
}
- impl #ident {
+ impl #trait_impl_ident {
fn new(handle: u64) -> Self {
Self { handle }
}
}
- impl ::std::ops::Drop for #ident {
- fn drop(&mut self) {
- #internals_ident.invoke_callback::<(), crate::UniFfiTag>(
- self.handle, uniffi::IDX_CALLBACK_FREE, Default::default()
- )
- }
- }
-
- ::uniffi::deps::static_assertions::assert_impl_all!(#ident: Send);
+ ::uniffi::deps::static_assertions::assert_impl_all!(#trait_impl_ident: ::core::marker::Send);
- impl #trait_ident for #ident {
- #trait_impl_methods
+ #impl_attributes
+ impl #trait_ident for #trait_impl_ident {
+ #(#trait_impl_methods)*
}
- #ffi_converter_tokens
+ impl ::std::ops::Drop for #trait_impl_ident {
+ fn drop(&mut self) {
+ let vtable = #vtable_cell.get();
+ (vtable.uniffi_free)(self.handle);
+ }
+ }
})
}
+pub fn trait_impl_ident(trait_name: &str) -> Ident {
+ Ident::new(
+ &format!("UniFFICallbackHandler{trait_name}"),
+ Span::call_site(),
+ )
+}
+
pub fn ffi_converter_callback_interface_impl(
trait_ident: &Ident,
trait_impl_ident: &Ident,
udl_mode: bool,
) -> TokenStream {
- let name = ident_to_string(trait_ident);
+ let trait_name = ident_to_string(trait_ident);
let dyn_trait = quote! { dyn #trait_ident };
let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> };
let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, udl_mode);
- let lift_ref_impl_spec = tagged_impl_header("LiftRef", &dyn_trait, udl_mode);
+ let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, udl_mode, &["LiftRef", "LiftReturn"]);
let mod_path = match mod_path() {
Ok(p) => p,
Err(e) => return e.into_compile_error(),
@@ -93,66 +161,85 @@ pub fn ffi_converter_callback_interface_impl(
::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE,
)
.concat_str(#mod_path)
- .concat_str(#name);
+ .concat_str(#trait_name);
}
- unsafe #lift_ref_impl_spec {
- type LiftType = #box_dyn_trait;
- }
+ #derive_ffi_traits
}
}
-fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result<TokenStream> {
+/// Generate a single method for [trait_impl]. This implements a trait method by invoking a
+/// foreign-supplied callback.
+fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result<TokenStream> {
let FnSignature {
ident,
+ is_async,
return_ty,
kind,
receiver,
+ name,
+ span,
..
} = sig;
- let index = match kind {
- // Note: the callback index is 1-based, since 0 is reserved for the free function
- FnKind::TraitMethod { index, .. } => index + 1,
- k => {
- return Err(syn::Error::new(
- sig.span,
- format!(
- "Internal UniFFI error: Unexpected function kind for callback interface {k:?}"
- ),
- ));
- }
- };
+
+ if !matches!(kind, FnKind::TraitMethod { .. }) {
+ return Err(syn::Error::new(
+ *span,
+ format!(
+ "Internal UniFFI error: Unexpected function kind for callback interface {name}: {kind:?}",
+ ),
+ ));
+ }
let self_param = match receiver {
+ Some(ReceiverArg::Ref) => quote! { &self },
+ Some(ReceiverArg::Arc) => quote! { self: Arc<Self> },
None => {
return Err(syn::Error::new(
- sig.span,
+ *span,
"callback interface methods must take &self as their first argument",
));
}
- Some(ReceiverArg::Ref) => quote! { &self },
- Some(ReceiverArg::Arc) => quote! { self: Arc<Self> },
};
+
let params = sig.params();
- let buf_ident = Ident::new("uniffi_args_buf", Span::call_site());
- let write_exprs = sig.write_exprs(&buf_ident);
+ let lower_exprs = sig.args.iter().map(|a| {
+ let lower_impl = a.lower_impl();
+ let ident = &a.ident;
+ quote! { #lower_impl::lower(#ident) }
+ });
- Ok(quote! {
- fn #ident(#self_param, #(#params),*) -> #return_ty {
- #[allow(unused_mut)]
- let mut #buf_ident = ::std::vec::Vec::new();
- #(#write_exprs;)*
- let uniffi_args_rbuf = uniffi::RustBuffer::from_vec(#buf_ident);
+ let lift_return = sig.lift_return_impl();
- #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf)
- }
- })
+ if !is_async {
+ Ok(quote! {
+ fn #ident(#self_param, #(#params),*) -> #return_ty {
+ let vtable = #vtable_cell.get();
+ let mut uniffi_call_status = ::uniffi::RustCallStatus::new();
+ let mut uniffi_return_value: #lift_return::ReturnType = ::uniffi::FfiDefault::ffi_default();
+ (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut uniffi_return_value, &mut uniffi_call_status);
+ #lift_return::lift_foreign_return(uniffi_return_value, uniffi_call_status)
+ }
+ })
+ } else {
+ Ok(quote! {
+ async fn #ident(#self_param, #(#params),*) -> #return_ty {
+ let vtable = #vtable_cell.get();
+ ::uniffi::foreign_async_call::<_, #return_ty, crate::UniFfiTag>(move |uniffi_future_callback, uniffi_future_callback_data| {
+ let mut uniffi_foreign_future: ::uniffi::ForeignFuture = ::uniffi::FfiDefault::ffi_default();
+ (vtable.#ident)(self.handle, #(#lower_exprs,)* uniffi_future_callback, uniffi_future_callback_data, &mut uniffi_foreign_future);
+ uniffi_foreign_future
+ }).await
+ }
+ })
+ }
}
pub(super) fn metadata_items(
self_ident: &Ident,
items: &[ImplItem],
module_path: &str,
+ docstring: String,
) -> syn::Result<Vec<TokenStream>> {
let trait_name = ident_to_string(self_ident);
let callback_interface_items = create_metadata_items(
@@ -162,13 +249,14 @@ pub(super) fn metadata_items(
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE)
.concat_str(#module_path)
.concat_str(#trait_name)
+ .concat_long_str(#docstring)
},
None,
);
iter::once(Ok(callback_interface_items))
.chain(items.iter().map(|item| match item {
- ImplItem::Method(sig) => sig.metadata_items(),
+ ImplItem::Method(sig) => sig.metadata_items_for_callback_interface(),
_ => unreachable!("traits have no constructors"),
}))
.collect()
diff --git a/third_party/rust/uniffi_macros/src/export/item.rs b/third_party/rust/uniffi_macros/src/export/item.rs
index 98c7d0ebe2..da3c9455c8 100644
--- a/third_party/rust/uniffi_macros/src/export/item.rs
+++ b/third_party/rust/uniffi_macros/src/export/item.rs
@@ -3,24 +3,34 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::fnsig::FnSignature;
+use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
-use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes};
+use super::attributes::{
+ ExportFnArgs, ExportImplArgs, ExportStructArgs, ExportTraitArgs, ExportedImplFnArgs,
+ ExportedImplFnAttributes,
+};
+use crate::util::extract_docstring;
use uniffi_meta::UniffiTraitDiscriminants;
pub(super) enum ExportItem {
Function {
sig: FnSignature,
+ args: ExportFnArgs,
},
Impl {
self_ident: Ident,
items: Vec<ImplItem>,
+ args: ExportImplArgs,
},
Trait {
self_ident: Ident,
items: Vec<ImplItem>,
- callback_interface: bool,
+ with_foreign: bool,
+ callback_interface_only: bool,
+ docstring: String,
+ args: ExportTraitArgs,
},
Struct {
self_ident: Ident,
@@ -29,15 +39,17 @@ pub(super) enum ExportItem {
}
impl ExportItem {
- pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result<Self> {
+ pub fn new(item: syn::Item, attr_args: TokenStream) -> syn::Result<Self> {
match item {
syn::Item::Fn(item) => {
- let sig = FnSignature::new_function(item.sig)?;
- Ok(Self::Function { sig })
+ let args: ExportFnArgs = syn::parse(attr_args)?;
+ let docstring = extract_docstring(&item.attrs)?;
+ let sig = FnSignature::new_function(item.sig, args.clone(), docstring)?;
+ Ok(Self::Function { sig, args })
}
- syn::Item::Impl(item) => Self::from_impl(item, args.constructor.is_some()),
- syn::Item::Trait(item) => Self::from_trait(item, args.callback_interface.is_some()),
- syn::Item::Struct(item) => Self::from_struct(item, args),
+ syn::Item::Impl(item) => Self::from_impl(item, attr_args),
+ syn::Item::Trait(item) => Self::from_trait(item, attr_args),
+ syn::Item::Struct(item) => Self::from_struct(item, attr_args),
// FIXME: Support const / static?
_ => Err(syn::Error::new(
Span::call_site(),
@@ -47,7 +59,8 @@ impl ExportItem {
}
}
- pub fn from_impl(item: syn::ItemImpl, force_constructor: bool) -> syn::Result<Self> {
+ pub fn from_impl(item: syn::ItemImpl, attr_args: TokenStream) -> syn::Result<Self> {
+ let args: ExportImplArgs = syn::parse(attr_args)?;
if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
return Err(syn::Error::new_spanned(
&item.generics,
@@ -88,14 +101,22 @@ impl ExportItem {
}
};
+ let docstring = extract_docstring(&impl_fn.attrs)?;
let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?;
- let item = if force_constructor || attrs.constructor {
+ let item = if attrs.constructor {
ImplItem::Constructor(FnSignature::new_constructor(
self_ident.clone(),
impl_fn.sig,
+ attrs.args,
+ docstring,
)?)
} else {
- ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?)
+ ImplItem::Method(FnSignature::new_method(
+ self_ident.clone(),
+ impl_fn.sig,
+ attrs.args,
+ docstring,
+ )?)
};
Ok(item)
@@ -105,10 +126,15 @@ impl ExportItem {
Ok(Self::Impl {
items,
self_ident: self_ident.to_owned(),
+ args,
})
}
- fn from_trait(item: syn::ItemTrait, callback_interface: bool) -> syn::Result<Self> {
+ fn from_trait(item: syn::ItemTrait, attr_args: TokenStream) -> syn::Result<Self> {
+ let args: ExportTraitArgs = syn::parse(attr_args)?;
+ let with_foreign = args.callback_interface.is_some() || args.with_foreign.is_some();
+ let callback_interface_only = args.callback_interface.is_some();
+
if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
return Err(syn::Error::new_spanned(
&item.generics,
@@ -117,6 +143,7 @@ impl ExportItem {
}
let self_ident = item.ident.to_owned();
+ let docstring = extract_docstring(&item.attrs)?;
let items = item
.items
.into_iter()
@@ -132,6 +159,7 @@ impl ExportItem {
}
};
+ let docstring = extract_docstring(&tim.attrs)?;
let attrs = ExportedImplFnAttributes::new(&tim.attrs)?;
let item = if attrs.constructor {
return Err(syn::Error::new_spanned(
@@ -142,7 +170,9 @@ impl ExportItem {
ImplItem::Method(FnSignature::new_trait_method(
self_ident.clone(),
tim.sig,
+ ExportedImplFnArgs::default(),
i as u32,
+ docstring,
)?)
};
@@ -153,28 +183,26 @@ impl ExportItem {
Ok(Self::Trait {
items,
self_ident,
- callback_interface,
+ with_foreign,
+ callback_interface_only,
+ docstring,
+ args,
})
}
- fn from_struct(item: syn::ItemStruct, args: &ExportAttributeArguments) -> syn::Result<Self> {
- let mut uniffi_traits = Vec::new();
- if args.trait_debug.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Debug);
- }
- if args.trait_display.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Display);
- }
- if args.trait_hash.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Hash);
- }
- if args.trait_eq.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Eq);
+ fn from_struct(item: syn::ItemStruct, attr_args: TokenStream) -> syn::Result<Self> {
+ let args: ExportStructArgs = syn::parse(attr_args)?;
+ let uniffi_traits: Vec<UniffiTraitDiscriminants> = args.traits.into_iter().collect();
+ if uniffi_traits.is_empty() {
+ Err(syn::Error::new(Span::call_site(),
+ "uniffi::export on a struct must supply a builtin trait name. Did you mean `#[derive(uniffi::Object)]`?"
+ ))
+ } else {
+ Ok(Self::Struct {
+ self_ident: item.ident,
+ uniffi_traits,
+ })
}
- Ok(Self::Struct {
- self_ident: item.ident,
- uniffi_traits,
- })
}
}
diff --git a/third_party/rust/uniffi_macros/src/export/scaffolding.rs b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
index f120ccc880..fa7b61deca 100644
--- a/third_party/rust/uniffi_macros/src/export/scaffolding.rs
+++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
@@ -6,12 +6,12 @@ use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::iter;
-use super::attributes::{AsyncRuntime, ExportAttributeArguments};
-use crate::fnsig::{FnKind, FnSignature, NamedArg};
+use super::attributes::AsyncRuntime;
+use crate::fnsig::{FnKind, FnSignature};
pub(super) fn gen_fn_scaffolding(
sig: FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
if sig.receiver.is_some() {
@@ -21,7 +21,7 @@ pub(super) fn gen_fn_scaffolding(
));
}
if !sig.is_async {
- if let Some(async_runtime) = &arguments.async_runtime {
+ if let Some(async_runtime) = ar {
return Err(syn::Error::new_spanned(
async_runtime,
"this attribute is only allowed on async functions",
@@ -32,7 +32,7 @@ pub(super) fn gen_fn_scaffolding(
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
- let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?;
+ let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
Ok(quote! {
#scaffolding_func
#metadata_items
@@ -41,7 +41,7 @@ pub(super) fn gen_fn_scaffolding(
pub(super) fn gen_constructor_scaffolding(
sig: FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
if sig.receiver.is_some() {
@@ -50,14 +50,11 @@ pub(super) fn gen_constructor_scaffolding(
"constructors must not have a self parameter",
));
}
- if sig.is_async {
- return Err(syn::Error::new(sig.span, "constructors can't be async"));
- }
let metadata_items = (!udl_mode).then(|| {
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
- let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?;
+ let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
Ok(quote! {
#scaffolding_func
#metadata_items
@@ -66,7 +63,7 @@ pub(super) fn gen_constructor_scaffolding(
pub(super) fn gen_method_scaffolding(
sig: FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let scaffolding_func = if sig.receiver.is_none() {
@@ -75,7 +72,7 @@ pub(super) fn gen_method_scaffolding(
"associated functions are not currently supported",
));
} else {
- gen_ffi_function(&sig, arguments, udl_mode)?
+ gen_ffi_function(&sig, ar, udl_mode)?
};
let metadata_items = (!udl_mode).then(|| {
@@ -90,31 +87,37 @@ pub(super) fn gen_method_scaffolding(
// Pieces of code for the scaffolding function
struct ScaffoldingBits {
- /// Parameters for the scaffolding function
- params: Vec<TokenStream>,
+ /// Parameter names for the scaffolding function
+ param_names: Vec<TokenStream>,
+ /// Parameter types for the scaffolding function
+ param_types: Vec<TokenStream>,
/// Lift closure. See `FnSignature::lift_closure` for an explanation of this.
lift_closure: TokenStream,
/// Expression to call the Rust function after a successful lift.
rust_fn_call: TokenStream,
+ /// Convert the result of `rust_fn_call`, stored in a variable named `uniffi_result` into its final value.
+ /// This is used to do things like error conversion / Arc wrapping
+ convert_result: TokenStream,
}
impl ScaffoldingBits {
fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self {
let ident = &sig.ident;
- let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect();
let call_params = sig.rust_call_params(false);
let rust_fn_call = quote! { #ident(#call_params) };
// UDL mode adds an extra conversion (#1749)
- let rust_fn_call = if udl_mode && sig.looks_like_result {
- quote! { #rust_fn_call.map_err(::std::convert::Into::into) }
+ let convert_result = if udl_mode && sig.looks_like_result {
+ quote! { uniffi_result.map_err(::std::convert::Into::into) }
} else {
- rust_fn_call
+ quote! { uniffi_result }
};
Self {
- params,
+ param_names: sig.scaffolding_param_names().collect(),
+ param_types: sig.scaffolding_param_types().collect(),
lift_closure: sig.lift_closure(None),
rust_fn_call,
+ convert_result,
}
}
@@ -125,20 +128,32 @@ impl ScaffoldingBits {
udl_mode: bool,
) -> Self {
let ident = &sig.ident;
- let ffi_converter = if is_trait {
+ let lift_impl = if is_trait {
quote! {
- <::std::sync::Arc<dyn #self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>>
+ <::std::sync::Arc<dyn #self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
}
} else {
quote! {
- <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>>
+ <::std::sync::Arc<#self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
}
};
- let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #ffi_converter::FfiType })
- .chain(sig.scaffolding_params())
- .collect();
+ let try_lift_self = if is_trait {
+ // For trait interfaces we need to special case this. Trait interfaces normally lift
+ // foreign trait impl pointers. However, for a method call, we want to lift a Rust
+ // pointer.
+ quote! {
+ {
+ let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>) };
+ // Take a clone for our own use.
+ Ok(*boxed_foreign_arc)
+ }
+ }
+ } else {
+ quote! { #lift_impl::try_lift(uniffi_self_lowered) }
+ };
+
let lift_closure = sig.lift_closure(Some(quote! {
- match #ffi_converter::try_lift(uniffi_self_lowered) {
+ match #try_lift_self {
Ok(v) => v,
Err(e) => return Err(("self", e))
}
@@ -146,38 +161,45 @@ impl ScaffoldingBits {
let call_params = sig.rust_call_params(true);
let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) };
// UDL mode adds an extra conversion (#1749)
- let rust_fn_call = if udl_mode && sig.looks_like_result {
- quote! { #rust_fn_call.map_err(::std::convert::Into::into) }
+ let convert_result = if udl_mode && sig.looks_like_result {
+ quote! { uniffi_result .map_err(::std::convert::Into::into) }
} else {
- rust_fn_call
+ quote! { uniffi_result }
};
Self {
- params,
+ param_names: iter::once(quote! { uniffi_self_lowered })
+ .chain(sig.scaffolding_param_names())
+ .collect(),
+ param_types: iter::once(quote! { #lift_impl::FfiType })
+ .chain(sig.scaffolding_param_types())
+ .collect(),
lift_closure,
rust_fn_call,
+ convert_result,
}
}
fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self {
let ident = &sig.ident;
- let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect();
let call_params = sig.rust_call_params(false);
let rust_fn_call = quote! { #self_ident::#ident(#call_params) };
// UDL mode adds extra conversions (#1749)
- let rust_fn_call = match (udl_mode, sig.looks_like_result) {
+ let convert_result = match (udl_mode, sig.looks_like_result) {
// For UDL
- (true, false) => quote! { ::std::sync::Arc::new(#rust_fn_call) },
+ (true, false) => quote! { ::std::sync::Arc::new(uniffi_result) },
(true, true) => {
- quote! { #rust_fn_call.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) }
+ quote! { uniffi_result.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) }
}
- (false, _) => rust_fn_call,
+ (false, _) => quote! { uniffi_result },
};
Self {
- params,
+ param_names: sig.scaffolding_param_names().collect(),
+ param_types: sig.scaffolding_param_types().collect(),
lift_closure: sig.lift_closure(None),
rust_fn_call,
+ convert_result,
}
}
}
@@ -188,13 +210,15 @@ impl ScaffoldingBits {
/// `rust_fn` is the Rust function to call.
pub(super) fn gen_ffi_function(
sig: &FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let ScaffoldingBits {
- params,
+ param_names,
+ param_types,
lift_closure,
rust_fn_call,
+ convert_result,
} = match &sig.kind {
FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode),
FnKind::Method { self_ident } => {
@@ -216,14 +240,15 @@ pub(super) fn gen_ffi_function(
let ffi_ident = sig.scaffolding_fn_ident()?;
let name = &sig.name;
- let return_impl = &sig.return_impl();
+ let return_ty = &sig.return_ty;
+ let return_impl = &sig.lower_return_impl();
Ok(if !sig.is_async {
quote! {
#[doc(hidden)]
#[no_mangle]
#vis extern "C" fn #ffi_ident(
- #(#params,)*
+ #(#param_names: #param_types,)*
call_status: &mut ::uniffi::RustCallStatus,
) -> #return_impl::ReturnType {
::uniffi::deps::log::debug!(#name);
@@ -231,7 +256,10 @@ pub(super) fn gen_ffi_function(
::uniffi::rust_call(call_status, || {
#return_impl::lower_return(
match uniffi_lift_args() {
- Ok(uniffi_args) => #rust_fn_call,
+ Ok(uniffi_args) => {
+ let uniffi_result = #rust_fn_call;
+ #convert_result
+ }
Err((arg_name, anyhow_error)) => {
#return_impl::handle_failed_lift(arg_name, anyhow_error)
},
@@ -242,25 +270,28 @@ pub(super) fn gen_ffi_function(
}
} else {
let mut future_expr = rust_fn_call;
- if matches!(arguments.async_runtime, Some(AsyncRuntime::Tokio(_))) {
+ if matches!(ar, Some(AsyncRuntime::Tokio(_))) {
future_expr = quote! { ::uniffi::deps::async_compat::Compat::new(#future_expr) }
}
quote! {
#[doc(hidden)]
#[no_mangle]
- pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle {
+ pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle {
::uniffi::deps::log::debug!(#name);
let uniffi_lift_args = #lift_closure;
match uniffi_lift_args() {
Ok(uniffi_args) => {
- ::uniffi::rust_future_new(
- async move { #future_expr.await },
+ ::uniffi::rust_future_new::<_, #return_ty, _>(
+ async move {
+ let uniffi_result = #future_expr.await;
+ #convert_result
+ },
crate::UniFfiTag
)
},
Err((arg_name, anyhow_error)) => {
- ::uniffi::rust_future_new(
+ ::uniffi::rust_future_new::<_, #return_ty, _>(
async move {
#return_impl::handle_failed_lift(arg_name, anyhow_error)
},
diff --git a/third_party/rust/uniffi_macros/src/export/trait_interface.rs b/third_party/rust/uniffi_macros/src/export/trait_interface.rs
new file mode 100644
index 0000000000..83587ae386
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/trait_interface.rs
@@ -0,0 +1,183 @@
+/* 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 proc_macro2::{Ident, Span, TokenStream};
+use quote::{quote, quote_spanned};
+
+use uniffi_meta::ObjectImpl;
+
+use crate::{
+ export::{
+ attributes::ExportTraitArgs, callback_interface, gen_method_scaffolding, item::ImplItem,
+ },
+ object::interface_meta_static_var,
+ util::{ident_to_string, tagged_impl_header},
+};
+
+pub(super) fn gen_trait_scaffolding(
+ mod_path: &str,
+ args: ExportTraitArgs,
+ self_ident: Ident,
+ items: Vec<ImplItem>,
+ udl_mode: bool,
+ with_foreign: bool,
+ docstring: String,
+) -> syn::Result<TokenStream> {
+ if let Some(rt) = args.async_runtime {
+ return Err(syn::Error::new_spanned(rt, "not supported for traits"));
+ }
+ let trait_name = ident_to_string(&self_ident);
+ let trait_impl = with_foreign.then(|| {
+ callback_interface::trait_impl(mod_path, &self_ident, &items)
+ .unwrap_or_else(|e| e.into_compile_error())
+ });
+
+ let clone_fn_ident = Ident::new(
+ &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name),
+ Span::call_site(),
+ );
+ let free_fn_ident = Ident::new(
+ &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name),
+ Span::call_site(),
+ );
+
+ let helper_fn_tokens = quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ /// Clone a pointer to this object type
+ ///
+ /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were
+ /// passed to the free function.
+ pub unsafe extern "C" fn #clone_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) -> *const ::std::ffi::c_void {
+ uniffi::rust_call(call_status, || {
+ let ptr = ptr as *mut std::sync::Arc<dyn #self_ident>;
+ let arc = unsafe { ::std::sync::Arc::clone(&*ptr) };
+ Ok(::std::boxed::Box::into_raw(::std::boxed::Box::new(arc)) as *const ::std::ffi::c_void)
+ })
+ }
+
+ #[doc(hidden)]
+ #[no_mangle]
+ /// Free a pointer to this object type
+ ///
+ /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were
+ /// passed to the free function.
+ ///
+ /// Note: clippy doesn't complain about this being unsafe, but it definitely is since it
+ /// calls `Box::from_raw`.
+ pub unsafe extern "C" fn #free_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) {
+ uniffi::rust_call(call_status, || {
+ assert!(!ptr.is_null());
+ drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) });
+ Ok(())
+ });
+ }
+ };
+
+ let impl_tokens: TokenStream = items
+ .into_iter()
+ .map(|item| match item {
+ ImplItem::Method(sig) => gen_method_scaffolding(sig, &None, udl_mode),
+ _ => unreachable!("traits have no constructors"),
+ })
+ .collect::<syn::Result<_>>()?;
+
+ let meta_static_var = (!udl_mode).then(|| {
+ let imp = if with_foreign {
+ ObjectImpl::CallbackTrait
+ } else {
+ ObjectImpl::Trait
+ };
+ interface_meta_static_var(&self_ident, imp, mod_path, docstring)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ });
+ let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign);
+
+ Ok(quote_spanned! { self_ident.span() =>
+ #meta_static_var
+ #helper_fn_tokens
+ #trait_impl
+ #impl_tokens
+ #ffi_converter_tokens
+ })
+}
+
+pub(crate) fn ffi_converter(
+ mod_path: &str,
+ trait_ident: &Ident,
+ udl_mode: bool,
+ with_foreign: bool,
+) -> TokenStream {
+ let impl_spec = tagged_impl_header("FfiConverterArc", &quote! { dyn #trait_ident }, udl_mode);
+ let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { dyn #trait_ident }, udl_mode);
+ let trait_name = ident_to_string(trait_ident);
+ let try_lift = if with_foreign {
+ let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name);
+ quote! {
+ fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> {
+ Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64)))
+ }
+ }
+ } else {
+ quote! {
+ fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> {
+ unsafe {
+ Ok(*::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc<Self>))
+ }
+ }
+ }
+ };
+ let metadata_code = if with_foreign {
+ quote! { ::uniffi::metadata::codes::TYPE_CALLBACK_TRAIT_INTERFACE }
+ } else {
+ quote! { ::uniffi::metadata::codes::TYPE_TRAIT_INTERFACE }
+ };
+
+ quote! {
+ // All traits must be `Sync + Send`. The generated scaffolding will fail to compile
+ // if they are not, but unfortunately it fails with an unactionably obscure error message.
+ // By asserting the requirement explicitly, we help Rust produce a more scrutable error message
+ // and thus help the user debug why the requirement isn't being met.
+ uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send);
+
+ unsafe #impl_spec {
+ type FfiType = *const ::std::os::raw::c_void;
+
+ fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType {
+ ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void
+ }
+
+ #try_lift
+
+ fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) {
+ ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
+ ::uniffi::deps::bytes::BufMut::put_u64(
+ buf,
+ <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64,
+ );
+ }
+
+ fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> {
+ ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
+ ::uniffi::check_remaining(buf, 8)?;
+ <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift(
+ ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType)
+ }
+
+ const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_code)
+ .concat_str(#mod_path)
+ .concat_str(#trait_name);
+ }
+
+ unsafe #lift_ref_impl_spec {
+ type LiftType = ::std::sync::Arc<dyn #trait_ident>;
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/utrait.rs b/third_party/rust/uniffi_macros/src/export/utrait.rs
index 3db09ea2b7..9007ae2c56 100644
--- a/third_party/rust/uniffi_macros/src/export/utrait.rs
+++ b/third_party/rust/uniffi_macros/src/export/utrait.rs
@@ -6,8 +6,10 @@ use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::ext::IdentExt;
-use super::{attributes::ExportAttributeArguments, gen_ffi_function};
+use super::gen_ffi_function;
+use crate::export::ExportedImplFnArgs;
use crate::fnsig::FnSignature;
+use crate::util::extract_docstring;
use uniffi_meta::UniffiTraitDiscriminants;
pub(crate) fn expand_uniffi_trait_export(
@@ -157,12 +159,25 @@ fn process_uniffi_trait_method(
unreachable!()
};
+ let docstring = extract_docstring(&item.attrs)?;
+
let ffi_func = gen_ffi_function(
- &FnSignature::new_method(self_ident.clone(), item.sig.clone())?,
- &ExportAttributeArguments::default(),
+ &FnSignature::new_method(
+ self_ident.clone(),
+ item.sig.clone(),
+ ExportedImplFnArgs::default(),
+ docstring.clone(),
+ )?,
+ &None,
udl_mode,
)?;
// metadata for the method, which will be packed inside metadata for the trait.
- let method_meta = FnSignature::new_method(self_ident.clone(), item.sig)?.metadata_expr()?;
+ let method_meta = FnSignature::new_method(
+ self_ident.clone(),
+ item.sig,
+ ExportedImplFnArgs::default(),
+ docstring,
+ )?
+ .metadata_expr()?;
Ok((ffi_func, method_meta))
}
diff --git a/third_party/rust/uniffi_macros/src/fnsig.rs b/third_party/rust/uniffi_macros/src/fnsig.rs
index 69b6529d1e..9c59125207 100644
--- a/third_party/rust/uniffi_macros/src/fnsig.rs
+++ b/third_party/rust/uniffi_macros/src/fnsig.rs
@@ -2,8 +2,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 crate::util::{
- create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize,
+use crate::{
+ default::{default_value_metadata_calls, DefaultValue},
+ export::{DefaultMap, ExportFnArgs, ExportedImplFnArgs},
+ util::{create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize},
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
@@ -13,7 +15,9 @@ pub(crate) struct FnSignature {
pub kind: FnKind,
pub span: Span,
pub mod_path: String,
+ // The identifier of the Rust function.
pub ident: Ident,
+ // The foreign name for this function, usually == ident.
pub name: String,
pub is_async: bool,
pub receiver: Option<ReceiverArg>,
@@ -23,30 +27,71 @@ pub(crate) struct FnSignature {
// Only use this in UDL mode.
// In general, it's not reliable because it fails for type aliases.
pub looks_like_result: bool,
+ pub docstring: String,
}
impl FnSignature {
- pub(crate) fn new_function(sig: syn::Signature) -> syn::Result<Self> {
- Self::new(FnKind::Function, sig)
+ pub(crate) fn new_function(
+ sig: syn::Signature,
+ args: ExportFnArgs,
+ docstring: String,
+ ) -> syn::Result<Self> {
+ Self::new(FnKind::Function, sig, args.name, args.defaults, docstring)
}
- pub(crate) fn new_method(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> {
- Self::new(FnKind::Method { self_ident }, sig)
+ pub(crate) fn new_method(
+ self_ident: Ident,
+ sig: syn::Signature,
+ args: ExportedImplFnArgs,
+ docstring: String,
+ ) -> syn::Result<Self> {
+ Self::new(
+ FnKind::Method { self_ident },
+ sig,
+ args.name,
+ args.defaults,
+ docstring,
+ )
}
- pub(crate) fn new_constructor(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> {
- Self::new(FnKind::Constructor { self_ident }, sig)
+ pub(crate) fn new_constructor(
+ self_ident: Ident,
+ sig: syn::Signature,
+ args: ExportedImplFnArgs,
+ docstring: String,
+ ) -> syn::Result<Self> {
+ Self::new(
+ FnKind::Constructor { self_ident },
+ sig,
+ args.name,
+ args.defaults,
+ docstring,
+ )
}
pub(crate) fn new_trait_method(
self_ident: Ident,
sig: syn::Signature,
+ args: ExportedImplFnArgs,
index: u32,
+ docstring: String,
) -> syn::Result<Self> {
- Self::new(FnKind::TraitMethod { self_ident, index }, sig)
+ Self::new(
+ FnKind::TraitMethod { self_ident, index },
+ sig,
+ args.name,
+ args.defaults,
+ docstring,
+ )
}
- pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result<Self> {
+ pub(crate) fn new(
+ kind: FnKind,
+ sig: syn::Signature,
+ name: Option<String>,
+ mut defaults: DefaultMap,
+ docstring: String,
+ ) -> syn::Result<Self> {
let span = sig.span();
let ident = sig.ident;
let looks_like_result = looks_like_result(&sig.output);
@@ -56,14 +101,11 @@ impl FnSignature {
};
let is_async = sig.asyncness.is_some();
- if is_async && matches!(kind, FnKind::Constructor { .. }) {
- return Err(syn::Error::new(
- span,
- "Async constructors are not supported",
- ));
- }
-
- let mut input_iter = sig.inputs.into_iter().map(Arg::try_from).peekable();
+ let mut input_iter = sig
+ .inputs
+ .into_iter()
+ .map(|a| Arg::new(a, &mut defaults))
+ .peekable();
let receiver = input_iter
.next_if(|a| matches!(a, Ok(a) if a.is_receiver()))
@@ -84,29 +126,43 @@ impl FnSignature {
})
})
.collect::<syn::Result<Vec<_>>>()?;
- let mod_path = mod_path()?;
+
+ if let Some(ident) = defaults.idents().first() {
+ return Err(syn::Error::new(
+ ident.span(),
+ format!("Unknown default argument: {}", ident),
+ ));
+ }
Ok(Self {
kind,
span,
- mod_path,
- name: ident_to_string(&ident),
+ mod_path: mod_path()?,
+ name: name.unwrap_or_else(|| ident_to_string(&ident)),
ident,
is_async,
receiver,
args,
return_ty: output,
looks_like_result,
+ docstring,
})
}
- pub fn return_impl(&self) -> TokenStream {
+ pub fn lower_return_impl(&self) -> TokenStream {
let return_ty = &self.return_ty;
quote! {
<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>
}
}
+ pub fn lift_return_impl(&self) -> TokenStream {
+ let return_ty = &self.return_ty;
+ quote! {
+ <#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>
+ }
+ }
+
/// Generate a closure that tries to lift all arguments into a tuple.
///
/// The closure moves all scaffolding arguments into itself and returns:
@@ -151,14 +207,6 @@ impl FnSignature {
quote! { #(#args),* }
}
- /// Write expressions for each of our arguments
- pub fn write_exprs<'a>(
- &'a self,
- buf_ident: &'a Ident,
- ) -> impl Iterator<Item = TokenStream> + 'a {
- self.args.iter().map(|a| a.write_expr(buf_ident))
- }
-
/// Parameters expressions for each of our arguments
pub fn params(&self) -> impl Iterator<Item = TokenStream> + '_ {
self.args.iter().map(NamedArg::param)
@@ -182,8 +230,18 @@ impl FnSignature {
}
/// Scaffolding parameters expressions for each of our arguments
- pub fn scaffolding_params(&self) -> impl Iterator<Item = TokenStream> + '_ {
- self.args.iter().map(NamedArg::scaffolding_param)
+ pub fn scaffolding_param_names(&self) -> impl Iterator<Item = TokenStream> + '_ {
+ self.args.iter().map(|a| {
+ let ident = &a.ident;
+ quote! { #ident }
+ })
+ }
+
+ pub fn scaffolding_param_types(&self) -> impl Iterator<Item = TokenStream> + '_ {
+ self.args.iter().map(|a| {
+ let lift_impl = a.lift_impl();
+ quote! { #lift_impl::FfiType }
+ })
}
/// Generate metadata items for this function
@@ -193,6 +251,7 @@ impl FnSignature {
return_ty,
is_async,
mod_path,
+ docstring,
..
} = &self;
let args_len = try_metadata_value_from_usize(
@@ -201,7 +260,11 @@ impl FnSignature {
self.args.len(),
"UniFFI limits functions to 256 arguments",
)?;
- let arg_metadata_calls = self.args.iter().map(NamedArg::arg_metadata);
+ let arg_metadata_calls = self
+ .args
+ .iter()
+ .map(NamedArg::arg_metadata)
+ .collect::<syn::Result<Vec<_>>>()?;
match &self.kind {
FnKind::Function => Ok(quote! {
@@ -212,6 +275,7 @@ impl FnSignature {
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
}),
FnKind::Method { self_ident } => {
@@ -225,6 +289,7 @@ impl FnSignature {
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
})
}
@@ -240,6 +305,7 @@ impl FnSignature {
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
})
}
@@ -250,9 +316,11 @@ impl FnSignature {
.concat_str(#mod_path)
.concat_str(#object_name)
.concat_str(#name)
+ .concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
})
}
}
@@ -300,6 +368,69 @@ impl FnSignature {
}
}
+ /// Generate metadata items for callback interfaces
+ ///
+ /// Unfortunately, most of this is duplicate code from [Self::metadata_items] and
+ /// [Self::metadata_expr]. However, one issue with that code is that it needs to assume if the
+ /// arguments are being lifted vs lowered in order to get TYPE_ID_META. That code uses
+ /// `<Type as Lift>::TYPE_ID_META` for arguments and `<Type as LowerReturn>::TYPE_ID_META` for
+ /// return types, which works for accidental/historical reasons.
+ ///
+ /// The one exception is callback interfaces (#1947), which are handled by this method.
+ ///
+ /// TODO: fix the metadata system so that this is not needed.
+ pub(crate) fn metadata_items_for_callback_interface(&self) -> syn::Result<TokenStream> {
+ let Self {
+ name,
+ return_ty,
+ is_async,
+ mod_path,
+ docstring,
+ ..
+ } = &self;
+ match &self.kind {
+ FnKind::TraitMethod {
+ self_ident, index, ..
+ } => {
+ let object_name = ident_to_string(self_ident);
+ let args_len = try_metadata_value_from_usize(
+ // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self
+ // params
+ self.args.len(),
+ "UniFFI limits functions to 256 arguments",
+ )?;
+ let arg_metadata_calls = self
+ .args
+ .iter()
+ .map(NamedArg::arg_metadata)
+ .collect::<syn::Result<Vec<_>>>()?;
+ let metadata_expr = quote! {
+ ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD)
+ .concat_str(#mod_path)
+ .concat_str(#object_name)
+ .concat_u32(#index)
+ .concat_str(#name)
+ .concat_bool(#is_async)
+ .concat_value(#args_len)
+ #(#arg_metadata_calls)*
+ .concat(<#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
+ };
+ Ok(create_metadata_items(
+ "method",
+ &format!("{object_name}_{name}"),
+ metadata_expr,
+ Some(self.checksum_symbol_name()),
+ ))
+ }
+
+ // This should never happen and indicates an error in the internal code
+ _ => panic!(
+ "metadata_items_for_callback_interface can only be called with `TraitMethod` sigs"
+ ),
+ }
+ }
+
pub(crate) fn checksum_symbol_name(&self) -> String {
let name = &self.name;
match &self.kind {
@@ -331,19 +462,11 @@ pub(crate) enum ArgKind {
}
impl Arg {
- pub(crate) fn is_receiver(&self) -> bool {
- matches!(self.kind, ArgKind::Receiver(_))
- }
-}
-
-impl TryFrom<FnArg> for Arg {
- type Error = syn::Error;
-
- fn try_from(syn_arg: FnArg) -> syn::Result<Self> {
+ fn new(syn_arg: FnArg, defaults: &mut DefaultMap) -> syn::Result<Self> {
let span = syn_arg.span();
let kind = match syn_arg {
FnArg::Typed(p) => match *p.pat {
- Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty))),
+ Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty, defaults)?)),
_ => Err(syn::Error::new_spanned(p, "Argument name missing")),
},
FnArg::Receiver(receiver) => Ok(ArgKind::Receiver(ReceiverArg::from(receiver))),
@@ -351,6 +474,10 @@ impl TryFrom<FnArg> for Arg {
Ok(Self { span, kind })
}
+
+ pub(crate) fn is_receiver(&self) -> bool {
+ matches!(self.kind, ArgKind::Receiver(_))
+ }
}
pub(crate) enum ReceiverArg {
@@ -379,27 +506,30 @@ pub(crate) struct NamedArg {
pub(crate) name: String,
pub(crate) ty: TokenStream,
pub(crate) ref_type: Option<Type>,
+ pub(crate) default: Option<DefaultValue>,
}
impl NamedArg {
- pub(crate) fn new(ident: Ident, ty: &Type) -> Self {
- match ty {
+ pub(crate) fn new(ident: Ident, ty: &Type, defaults: &mut DefaultMap) -> syn::Result<Self> {
+ Ok(match ty {
Type::Reference(r) => {
let inner = &r.elem;
Self {
name: ident_to_string(&ident),
- ident,
ty: quote! { <#inner as ::uniffi::LiftRef<crate::UniFfiTag>>::LiftType },
ref_type: Some(*inner.clone()),
+ default: defaults.remove(&ident),
+ ident,
}
}
_ => Self {
name: ident_to_string(&ident),
- ident,
ty: quote! { #ty },
ref_type: None,
+ default: defaults.remove(&ident),
+ ident,
},
- }
+ })
}
pub(crate) fn lift_impl(&self) -> TokenStream {
@@ -419,27 +549,15 @@ impl NamedArg {
quote! { #ident: #ty }
}
- /// Generate the scaffolding parameter for this Arg
- pub(crate) fn scaffolding_param(&self) -> TokenStream {
- let ident = &self.ident;
- let lift_impl = self.lift_impl();
- quote! { #ident: #lift_impl::FfiType }
- }
-
- /// Generate the expression to write the scaffolding parameter for this arg
- pub(crate) fn write_expr(&self, buf_ident: &Ident) -> TokenStream {
- let ident = &self.ident;
- let lower_impl = self.lower_impl();
- quote! { #lower_impl::write(#ident, &mut #buf_ident) }
- }
-
- pub(crate) fn arg_metadata(&self) -> TokenStream {
+ pub(crate) fn arg_metadata(&self) -> syn::Result<TokenStream> {
let name = &self.name;
let lift_impl = self.lift_impl();
- quote! {
+ let default_calls = default_value_metadata_calls(&self.default)?;
+ Ok(quote! {
.concat_str(#name)
.concat(#lift_impl::TYPE_ID_META)
- }
+ #default_calls
+ })
}
}
diff --git a/third_party/rust/uniffi_macros/src/lib.rs b/third_party/rust/uniffi_macros/src/lib.rs
index 4cffddfa0e..929400c885 100644
--- a/third_party/rust/uniffi_macros/src/lib.rs
+++ b/third_party/rust/uniffi_macros/src/lib.rs
@@ -5,10 +5,8 @@
#![warn(rust_2018_idioms, unused_qualifications)]
//! Macros for `uniffi`.
-//!
-//! Currently this is just for easily generating integration tests, but maybe
-//! we'll put some other code-annotation helper macros in here at some point.
+#[cfg(feature = "trybuild")]
use camino::Utf8Path;
use proc_macro::TokenStream;
use quote::quote;
@@ -18,6 +16,7 @@ use syn::{
};
mod custom;
+mod default;
mod enum_;
mod error;
mod export;
@@ -33,20 +32,6 @@ use self::{
record::expand_record,
};
-struct IdentPair {
- lhs: Ident,
- rhs: Ident,
-}
-
-impl Parse for IdentPair {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- let lhs = input.parse()?;
- input.parse::<Token![,]>()?;
- let rhs = input.parse()?;
- Ok(Self { lhs, rhs })
- }
-}
-
struct CustomTypeInfo {
ident: Ident,
builtin: Path,
@@ -107,9 +92,8 @@ fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> Toke
let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone()));
let gen_output = || {
- let args = syn::parse(attr_args)?;
let item = syn::parse(input)?;
- expand_export(item, args, udl_mode)
+ expand_export(item, attr_args, udl_mode)
};
let output = gen_output().unwrap_or_else(syn::Error::into_compile_error);
@@ -129,7 +113,7 @@ pub fn derive_record(input: TokenStream) -> TokenStream {
#[proc_macro_derive(Enum)]
pub fn derive_enum(input: TokenStream) -> TokenStream {
- expand_enum(parse_macro_input!(input), false)
+ expand_enum(parse_macro_input!(input), None, false)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
@@ -225,10 +209,14 @@ pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenSt
#[doc(hidden)]
#[proc_macro_attribute]
-pub fn derive_enum_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream {
- expand_enum(syn::parse_macro_input!(input), true)
- .unwrap_or_else(syn::Error::into_compile_error)
- .into()
+pub fn derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
+ expand_enum(
+ syn::parse_macro_input!(input),
+ Some(syn::parse_macro_input!(attrs)),
+ true,
+ )
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into()
}
#[doc(hidden)]
@@ -257,22 +245,6 @@ pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
do_export(attrs, input, true)
}
-/// Generate various support elements, including the FfiConverter implementation,
-/// for a trait interface for the scaffolding code
-#[doc(hidden)]
-#[proc_macro]
-pub fn expand_trait_interface_support(tokens: TokenStream) -> TokenStream {
- export::ffi_converter_trait_impl(&syn::parse_macro_input!(tokens), true).into()
-}
-
-/// Generate the FfiConverter implementation for an trait interface for the scaffolding code
-#[doc(hidden)]
-#[proc_macro]
-pub fn scaffolding_ffi_converter_callback_interface(tokens: TokenStream) -> TokenStream {
- let input: IdentPair = syn::parse_macro_input!(tokens);
- export::ffi_converter_callback_interface_impl(&input.lhs, &input.rhs, true).into()
-}
-
/// A helper macro to include generated component scaffolding.
///
/// This is a simple convenience macro to include the UniFFI component
@@ -373,6 +345,7 @@ pub fn use_udl_object(tokens: TokenStream) -> TokenStream {
/// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl");
/// ```
#[proc_macro]
+#[cfg(feature = "trybuild")]
pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream {
let udl_file = syn::parse_macro_input!(udl_file as LitStr);
let udl_file_string = udl_file.value();
@@ -396,15 +369,25 @@ pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream {
}.into()
}
-/// A dummy macro that does nothing.
+/// An attribute for constructors.
+///
+/// Constructors are in `impl` blocks which have a `#[uniffi::export]` attribute,
///
/// This exists so `#[uniffi::export]` can emit its input verbatim without
-/// causing unexpected errors, plus some extra code in case everything is okay.
+/// causing unexpected errors in the entire exported block.
+/// This happens very often when the proc-macro is run on an incomplete
+/// input by rust-analyzer while the developer is typing.
///
-/// It is important for `#[uniffi::export]` to not raise unexpected errors if it
-/// fails to parse the input as this happens very often when the proc-macro is
-/// run on an incomplete input by rust-analyzer while the developer is typing.
+/// So much better to do nothing here then let the impl block find the attribute.
#[proc_macro_attribute]
pub fn constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream {
input
}
+
+/// An attribute for methods.
+///
+/// Everything above applies here too.
+#[proc_macro_attribute]
+pub fn method(_attrs: TokenStream, input: TokenStream) -> TokenStream {
+ input
+}
diff --git a/third_party/rust/uniffi_macros/src/object.rs b/third_party/rust/uniffi_macros/src/object.rs
index 573a1eaadd..6bcc07a14e 100644
--- a/third_party/rust/uniffi_macros/src/object.rs
+++ b/third_party/rust/uniffi_macros/src/object.rs
@@ -1,17 +1,27 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::DeriveInput;
-use uniffi_meta::free_fn_symbol_name;
-use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header};
+use crate::util::{
+ create_metadata_items, extract_docstring, ident_to_string, mod_path, tagged_impl_header,
+};
+use uniffi_meta::ObjectImpl;
pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
let module_path = mod_path()?;
let ident = &input.ident;
+ let docstring = extract_docstring(&input.attrs)?;
let name = ident_to_string(ident);
- let free_fn_ident = Ident::new(&free_fn_symbol_name(&module_path, &name), Span::call_site());
+ let clone_fn_ident = Ident::new(
+ &uniffi_meta::clone_fn_symbol_name(&module_path, &name),
+ Span::call_site(),
+ );
+ let free_fn_ident = Ident::new(
+ &uniffi_meta::free_fn_symbol_name(&module_path, &name),
+ Span::call_site(),
+ );
let meta_static_var = (!udl_mode).then(|| {
- interface_meta_static_var(ident, false, &module_path)
+ interface_meta_static_var(ident, ObjectImpl::Struct, &module_path, docstring)
.unwrap_or_else(syn::Error::into_compile_error)
});
let interface_impl = interface_impl(ident, udl_mode);
@@ -19,7 +29,19 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr
Ok(quote! {
#[doc(hidden)]
#[no_mangle]
- pub extern "C" fn #free_fn_ident(
+ pub unsafe extern "C" fn #clone_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) -> *const ::std::ffi::c_void {
+ uniffi::rust_call(call_status, || {
+ unsafe { ::std::sync::Arc::increment_strong_count(ptr) };
+ Ok(ptr)
+ })
+ }
+
+ #[doc(hidden)]
+ #[no_mangle]
+ pub unsafe extern "C" fn #free_fn_ident(
ptr: *const ::std::ffi::c_void,
call_status: &mut ::uniffi::RustCallStatus
) {
@@ -41,6 +63,7 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr
pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
let name = ident_to_string(ident);
let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode);
+ let lower_return_impl_spec = tagged_impl_header("LowerReturn", ident, udl_mode);
let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode);
let mod_path = match mod_path() {
Ok(p) => p,
@@ -52,7 +75,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
// if they are not, but unfortunately it fails with an unactionably obscure error message.
// By asserting the requirement explicitly, we help Rust produce a more scrutable error message
// and thus help the user debug why the requirement isn't being met.
- uniffi::deps::static_assertions::assert_impl_all!(#ident: Sync, Send);
+ uniffi::deps::static_assertions::assert_impl_all!(#ident: ::core::marker::Sync, ::core::marker::Send);
#[doc(hidden)]
#[automatically_derived]
@@ -78,17 +101,10 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
::std::sync::Arc::into_raw(obj) as Self::FfiType
}
- /// When lifting, we receive a "borrow" of the `Arc` that is owned by
- /// the foreign-language code, and make a clone of it for our own use.
- ///
- /// Safety: the provided value must be a pointer previously obtained by calling
- /// the `lower()` or `write()` method of this impl.
+ /// When lifting, we receive an owned `Arc` that the foreign language code cloned.
fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> {
let v = v as *const #ident;
- // We musn't drop the `Arc` that is owned by the foreign-language code.
- let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { ::std::sync::Arc::<Self>::from_raw(v) });
- // Take a clone for our own use.
- Ok(::std::sync::Arc::clone(&*foreign_arc))
+ Ok(unsafe { ::std::sync::Arc::<Self>::from_raw(v) })
}
/// When writing as a field of a complex structure, make a clone and transfer ownership
@@ -117,8 +133,17 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE)
.concat_str(#mod_path)
- .concat_str(#name)
- .concat_bool(false);
+ .concat_str(#name);
+ }
+
+ unsafe #lower_return_impl_spec {
+ type ReturnType = <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::FfiType;
+
+ fn lower_return(obj: Self) -> ::std::result::Result<Self::ReturnType, ::uniffi::RustBuffer> {
+ Ok(<Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(::std::sync::Arc::new(obj)))
+ }
+
+ const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::TYPE_ID_META;
}
unsafe #lift_ref_impl_spec {
@@ -129,18 +154,25 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
pub(crate) fn interface_meta_static_var(
ident: &Ident,
- is_trait: bool,
+ imp: ObjectImpl,
module_path: &str,
+ docstring: String,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
+ let code = match imp {
+ ObjectImpl::Struct => quote! { ::uniffi::metadata::codes::INTERFACE },
+ ObjectImpl::Trait => quote! { ::uniffi::metadata::codes::TRAIT_INTERFACE },
+ ObjectImpl::CallbackTrait => quote! { ::uniffi::metadata::codes::CALLBACK_TRAIT_INTERFACE },
+ };
+
Ok(create_metadata_items(
"interface",
&name,
quote! {
- ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::INTERFACE)
- .concat_str(#module_path)
- .concat_str(#name)
- .concat_bool(#is_trait)
+ ::uniffi::MetadataBuffer::from_code(#code)
+ .concat_str(#module_path)
+ .concat_str(#name)
+ .concat_long_str(#docstring)
},
None,
))
diff --git a/third_party/rust/uniffi_macros/src/record.rs b/third_party/rust/uniffi_macros/src/record.rs
index abf2743ec6..41f5d016ac 100644
--- a/third_party/rust/uniffi_macros/src/record.rs
+++ b/third_party/rust/uniffi_macros/src/record.rs
@@ -1,17 +1,20 @@
use proc_macro2::{Ident, Span, TokenStream};
-use quote::{quote, ToTokens};
-use syn::{
- parse::{Parse, ParseStream},
- Data, DataStruct, DeriveInput, Field, Lit, Token,
-};
-
-use crate::util::{
- create_metadata_items, derive_all_ffi_traits, either_attribute_arg, ident_to_string, kw,
- mod_path, tagged_impl_header, try_metadata_value_from_usize, try_read_field, AttributeSliceExt,
- UniffiAttributeArgs,
+use quote::quote;
+use syn::{parse::ParseStream, Data, DataStruct, DeriveInput, Field, Token};
+
+use crate::{
+ default::{default_value_metadata_calls, DefaultValue},
+ util::{
+ create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
+ ident_to_string, kw, mod_path, tagged_impl_header, try_metadata_value_from_usize,
+ try_read_field, AttributeSliceExt, UniffiAttributeArgs,
+ },
};
pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
+ if let Some(e) = input.attrs.uniffi_attr_args_not_allowed_here() {
+ return Err(e);
+ }
let record = match input.data {
Data::Struct(s) => s,
_ => {
@@ -23,10 +26,12 @@ pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr
};
let ident = &input.ident;
+ let docstring = extract_docstring(&input.attrs)?;
let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode)
.unwrap_or_else(syn::Error::into_compile_error);
let meta_static_var = (!udl_mode).then(|| {
- record_meta_static_var(ident, &record).unwrap_or_else(syn::Error::into_compile_error)
+ record_meta_static_var(ident, docstring, &record)
+ .unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
@@ -78,35 +83,9 @@ fn write_field(f: &Field) -> TokenStream {
}
}
-pub enum FieldDefault {
- Literal(Lit),
- Null(kw::None),
-}
-
-impl ToTokens for FieldDefault {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- match self {
- FieldDefault::Literal(lit) => lit.to_tokens(tokens),
- FieldDefault::Null(kw) => kw.to_tokens(tokens),
- }
- }
-}
-
-impl Parse for FieldDefault {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- let lookahead = input.lookahead1();
- if lookahead.peek(kw::None) {
- let none_kw: kw::None = input.parse()?;
- Ok(Self::Null(none_kw))
- } else {
- Ok(Self::Literal(input.parse()?))
- }
- }
-}
-
#[derive(Default)]
pub struct FieldAttributeArguments {
- pub(crate) default: Option<FieldDefault>,
+ pub(crate) default: Option<DefaultValue>,
}
impl UniffiAttributeArgs for FieldAttributeArguments {
@@ -128,6 +107,7 @@ impl UniffiAttributeArgs for FieldAttributeArguments {
pub(crate) fn record_meta_static_var(
ident: &Ident,
+ docstring: String,
record: &DataStruct,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
@@ -144,17 +124,9 @@ pub(crate) fn record_meta_static_var(
.parse_uniffi_attr_args::<FieldAttributeArguments>()?;
let name = ident_to_string(f.ident.as_ref().unwrap());
+ let docstring = extract_docstring(&f.attrs)?;
let ty = &f.ty;
- let default = match attrs.default {
- Some(default) => {
- let default_value = default_value_concat_calls(default)?;
- quote! {
- .concat_bool(true)
- #default_value
- }
- }
- None => quote! { .concat_bool(false) },
- };
+ let default = default_value_metadata_calls(&attrs.default)?;
// Note: fields need to implement both `Lower` and `Lift` to be used in a record. The
// TYPE_ID_META should be the same for both traits.
@@ -162,6 +134,7 @@ pub(crate) fn record_meta_static_var(
.concat_str(#name)
.concat(<#ty as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
#default
+ .concat_long_str(#docstring)
})
})
.collect::<syn::Result<_>>()?;
@@ -175,50 +148,8 @@ pub(crate) fn record_meta_static_var(
.concat_str(#name)
.concat_value(#fields_len)
#concat_fields
+ .concat_long_str(#docstring)
},
None,
))
}
-
-fn default_value_concat_calls(default: FieldDefault) -> syn::Result<TokenStream> {
- match default {
- FieldDefault::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err(
- syn::Error::new_spanned(i, "integer literals with suffix not supported here"),
- ),
- FieldDefault::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err(
- syn::Error::new_spanned(f, "float literals with suffix not supported here"),
- ),
-
- FieldDefault::Literal(Lit::Str(s)) => Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_STR)
- .concat_str(#s)
- }),
- FieldDefault::Literal(Lit::Int(i)) => {
- let digits = i.base10_digits();
- Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_INT)
- .concat_str(#digits)
- })
- }
- FieldDefault::Literal(Lit::Float(f)) => {
- let digits = f.base10_digits();
- Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_FLOAT)
- .concat_str(#digits)
- })
- }
- FieldDefault::Literal(Lit::Bool(b)) => Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_BOOL)
- .concat_bool(#b)
- }),
-
- FieldDefault::Literal(_) => Err(syn::Error::new_spanned(
- default,
- "this type of literal is not currently supported as a default",
- )),
-
- FieldDefault::Null(_) => Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_NULL)
- }),
- }
-}
diff --git a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
index afdb119cc4..c82e9389bb 100644
--- a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
+++ b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
@@ -20,15 +20,7 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
let ffi_rustbuffer_free_ident = format_ident!("ffi_{module_path}_rustbuffer_free");
let ffi_rustbuffer_reserve_ident = format_ident!("ffi_{module_path}_rustbuffer_reserve");
let reexport_hack_ident = format_ident!("{module_path}_uniffi_reexport_hack");
- let ffi_foreign_executor_callback_set_ident =
- format_ident!("ffi_{module_path}_foreign_executor_callback_set");
- let ffi_rust_future_continuation_callback_set =
- format_ident!("ffi_{module_path}_rust_future_continuation_callback_set");
let ffi_rust_future_scaffolding_fns = rust_future_scaffolding_fns(&module_path);
- let continuation_cell = format_ident!(
- "RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}",
- module_path.to_uppercase()
- );
Ok(quote! {
// Unit struct to parameterize the FfiConverter trait.
@@ -68,7 +60,7 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status)
}
@@ -89,31 +81,10 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status)
}
- static #continuation_cell: ::uniffi::deps::once_cell::sync::OnceCell<::uniffi::RustFutureContinuationCallback> = ::uniffi::deps::once_cell::sync::OnceCell::new();
-
- #[allow(clippy::missing_safety_doc, missing_docs)]
- #[doc(hidden)]
- #[no_mangle]
- pub extern "C" fn #ffi_foreign_executor_callback_set_ident(callback: uniffi::ffi::ForeignExecutorCallback) {
- uniffi::ffi::foreign_executor_callback_set(callback)
- }
-
- #[allow(clippy::missing_safety_doc, missing_docs)]
- #[doc(hidden)]
- #[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_continuation_callback_set(callback: ::uniffi::RustFutureContinuationCallback) {
- if let Err(existing) = #continuation_cell.set(callback) {
- // Don't panic if this to be called multiple times with the same callback.
- if existing != callback {
- panic!("Attempt to set the RustFuture continuation callback twice");
- }
- }
- }
-
#ffi_rust_future_scaffolding_fns
// Code to re-export the UniFFI scaffolding functions.
@@ -158,12 +129,12 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
/// Generates the rust_future_* functions
///
-/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents
+/// The foreign side uses a type-erased `Handle` to interact with futures, which presents
/// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`?
///
/// Handle this by using some brute-force monomorphization. For each possible ffi type, we
/// generate a set of scaffolding functions. The bindings code is responsible for calling the one
-/// corresponds the scaffolding function that created the `RustFutureHandle`.
+/// corresponds the scaffolding function that created the `Handle`.
///
/// This introduces safety issues, but we do get some type checking. If the bindings code calls
/// the wrong rust_future_complete function, they should get an unexpected return type, which
@@ -190,41 +161,37 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream {
let ffi_rust_future_cancel = format_ident!("ffi_{module_path}_rust_future_cancel_{fn_suffix}");
let ffi_rust_future_complete = format_ident!("ffi_{module_path}_rust_future_complete_{fn_suffix}");
let ffi_rust_future_free = format_ident!("ffi_{module_path}_rust_future_free_{fn_suffix}");
- let continuation_cell = format_ident!("RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}", module_path.to_uppercase());
quote! {
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, data: *const ()) {
- let callback = #continuation_cell
- .get()
- .expect("RustFuture continuation callback not set. This is likely a uniffi bug.");
- ::uniffi::ffi::rust_future_poll::<#return_type>(handle, *callback, data);
+ pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: u64) {
+ ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data);
}
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) {
- ::uniffi::ffi::rust_future_cancel::<#return_type>(handle)
+ pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) {
+ ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle)
}
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #ffi_rust_future_complete(
- handle: ::uniffi::RustFutureHandle,
+ handle: ::uniffi::Handle,
out_status: &mut ::uniffi::RustCallStatus
) -> #return_type {
- ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status)
+ ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status)
}
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) {
- ::uniffi::ffi::rust_future_free::<#return_type>(handle)
+ pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) {
+ ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle)
}
}
})
diff --git a/third_party/rust/uniffi_macros/src/util.rs b/third_party/rust/uniffi_macros/src/util.rs
index 9f213ea1d7..97faad9c4d 100644
--- a/third_party/rust/uniffi_macros/src/util.rs
+++ b/third_party/rust/uniffi_macros/src/util.rs
@@ -8,7 +8,7 @@ use std::path::{Path as StdPath, PathBuf};
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
- Attribute, Token,
+ Attribute, Expr, Lit, Token,
};
pub fn manifest_path() -> Result<PathBuf, String> {
@@ -79,8 +79,13 @@ pub fn try_read_field(f: &syn::Field) -> TokenStream {
let ident = &f.ident;
let ty = &f.ty;
- quote! {
- #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
+ match ident {
+ Some(ident) => quote! {
+ #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
+ },
+ None => quote! {
+ <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
+ },
}
}
@@ -151,13 +156,7 @@ pub fn parse_comma_separated<T: UniffiAttributeArgs>(input: ParseStream<'_>) ->
}
#[derive(Default)]
-pub struct ArgumentNotAllowedHere;
-
-impl Parse for ArgumentNotAllowedHere {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- parse_comma_separated(input)
- }
-}
+struct ArgumentNotAllowedHere;
impl UniffiAttributeArgs for ArgumentNotAllowedHere {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
@@ -224,7 +223,11 @@ pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream {
}
}
-pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]) -> TokenStream {
+pub(crate) fn derive_ffi_traits(
+ ty: impl ToTokens,
+ udl_mode: bool,
+ trait_names: &[&str],
+) -> TokenStream {
let trait_idents = trait_names
.iter()
.map(|name| Ident::new(name, Span::call_site()));
@@ -247,11 +250,14 @@ pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]
pub mod kw {
syn::custom_keyword!(async_runtime);
syn::custom_keyword!(callback_interface);
- syn::custom_keyword!(constructor);
+ syn::custom_keyword!(with_foreign);
syn::custom_keyword!(default);
syn::custom_keyword!(flat_error);
syn::custom_keyword!(None);
+ syn::custom_keyword!(Some);
syn::custom_keyword!(with_try_read);
+ syn::custom_keyword!(name);
+ syn::custom_keyword!(non_exhaustive);
syn::custom_keyword!(Debug);
syn::custom_keyword!(Display);
syn::custom_keyword!(Eq);
@@ -276,3 +282,20 @@ impl Parse for ExternalTypeItem {
})
}
}
+
+pub(crate) fn extract_docstring(attrs: &[Attribute]) -> syn::Result<String> {
+ return attrs
+ .iter()
+ .filter(|attr| attr.path().is_ident("doc"))
+ .map(|attr| {
+ let name_value = attr.meta.require_name_value()?;
+ if let Expr::Lit(expr) = &name_value.value {
+ if let Lit::Str(lit_str) = &expr.lit {
+ return Ok(lit_str.value().trim().to_owned());
+ }
+ }
+ Err(syn::Error::new_spanned(attr, "Cannot parse doc attribute"))
+ })
+ .collect::<syn::Result<Vec<_>>>()
+ .map(|lines| lines.join("\n"));
+}