diff options
Diffstat (limited to 'third_party/rust/uniffi_macros')
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", "e! { dyn #trait_ident }, udl_mode); - let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { 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", "e! { dyn #trait_ident }, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { 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")); +} |