diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index 32abfa08cc..e400a37872 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -3,8 +3,8 @@ use quote::quote; use syn::{Data, DataEnum, DeriveInput, Field, Index}; 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, extract_docstring, ident_to_string, mod_path, + tagged_impl_header, try_metadata_value_from_usize, try_read_field, }; pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result { @@ -18,10 +18,12 @@ pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result TokenStream { } } -pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result { +pub(crate) fn enum_meta_static_var( + ident: &Ident, + docstring: String, + enum_: &DataEnum, +) -> syn::Result { let name = ident_to_string(ident); let module_path = mod_path()?; @@ -146,6 +152,7 @@ pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Resu .concat_str(#name) }; metadata_expr.extend(variant_metadata(enum_)?); + metadata_expr.extend(quote! { .concat_str(#docstring) }); Ok(create_metadata_items("enum", &name, metadata_expr, None)) } @@ -178,7 +185,13 @@ pub fn variant_metadata(enum_: &DataEnum) -> syn::Result> { .collect::>>()?; let name = ident_to_string(&v.ident); + 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::>>()?; + Ok(quote! { .concat_str(#name) .concat_value(#fields_len) @@ -187,7 +200,9 @@ pub fn variant_metadata(enum_: &DataEnum) -> syn::Result> { .concat(<#field_types as ::uniffi::Lower>::TYPE_ID_META) // field defaults not yet supported for enums .concat_bool(false) + .concat_str(#field_docstrings) )* + .concat_str(#docstring) }) }) ) diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index a2ee7cf603..106ad9614f 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -8,9 +8,9 @@ use syn::{ use crate::{ enum_::{rich_error_ffi_converter_impl, variant_metadata}, 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 meta_static_var = (!udl_mode).then(|| { - error_meta_static_var(ident, &enum_, attr.flat.is_some()) + error_meta_static_var(ident, docstring, &enum_, attr.flat.is_some()) .unwrap_or_else(syn::Error::into_compile_error) }); @@ -192,6 +193,7 @@ fn flat_error_ffi_converter_impl( pub(crate) fn error_meta_static_var( ident: &Ident, + docstring: String, enum_: &DataEnum, flat: bool, ) -> syn::Result { @@ -210,18 +212,23 @@ pub(crate) fn error_meta_static_var( } else { metadata_expr.extend(variant_metadata(enum_)?); } + metadata_expr.extend(quote! { .concat_str(#docstring) }); Ok(create_metadata_items("error", &name, metadata_expr, None)) } pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result> { 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_str(#docstring) + }) })) - .collect()) + .collect() } #[derive(Default)] diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index 7d3f54da93..8d8dec0bde 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -68,18 +68,21 @@ pub(crate) fn expand_export( items, self_ident, callback_interface: false, + .. } => trait_interface::gen_trait_scaffolding(&mod_path, args, self_ident, items, udl_mode), ExportItem::Trait { items, self_ident, callback_interface: true, + docstring, } => { let trait_name = ident_to_string(&self_ident); 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 = callback_interface::metadata_items(&self_ident, &items, &mod_path) - .unwrap_or_else(|e| vec![e.into_compile_error()]); + let metadata_items = + callback_interface::metadata_items(&self_ident, &items, &mod_path, docstring) + .unwrap_or_else(|e| vec![e.into_compile_error()]); let ffi_converter_tokens = ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident, udl_mode); diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs index 85a8b0663a..8c8943cea7 100644 --- a/uniffi_macros/src/export/callback_interface.rs +++ b/uniffi_macros/src/export/callback_interface.rs @@ -182,18 +182,21 @@ pub(super) fn metadata_items( self_ident: &Ident, items: &[ImplItem], module_path: &str, + docstring: Option, ) -> syn::Result> { let trait_name = ident_to_string(self_ident); - let callback_interface_items = create_metadata_items( - "callback_interface", - &trait_name, - quote! { - ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE) - .concat_str(#module_path) - .concat_str(#trait_name) - }, - None, - ); + + let mut metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE) + .concat_str(#module_path) + .concat_str(#trait_name) + }; + if let Some(docstring) = docstring { + metadata_expr.extend(quote! { .concat_str(#docstring) }) + } + + let callback_interface_items = + create_metadata_items("callback_interface", &trait_name, metadata_expr, None); iter::once(Ok(callback_interface_items)) .chain(items.iter().map(|item| match item { diff --git a/uniffi_macros/src/export/item.rs b/uniffi_macros/src/export/item.rs index 98c7d0ebe2..7349ee2ca9 100644 --- a/uniffi_macros/src/export/item.rs +++ b/uniffi_macros/src/export/item.rs @@ -7,6 +7,7 @@ use proc_macro2::{Ident, Span}; use quote::ToTokens; use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes}; +use crate::util::extract_docstring; use uniffi_meta::UniffiTraitDiscriminants; pub(super) enum ExportItem { @@ -21,6 +22,7 @@ pub(super) enum ExportItem { self_ident: Ident, items: Vec, callback_interface: bool, + docstring: Option, }, Struct { self_ident: Ident, @@ -32,7 +34,8 @@ impl ExportItem { pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result { match item { syn::Item::Fn(item) => { - let sig = FnSignature::new_function(item.sig)?; + let docstring = extract_docstring(&item.attrs)?; + let sig = FnSignature::new_function(item.sig, docstring)?; Ok(Self::Function { sig }) } syn::Item::Impl(item) => Self::from_impl(item, args.constructor.is_some()), @@ -88,14 +91,20 @@ impl ExportItem { } }; + let docstring = extract_docstring(&impl_fn.attrs)?; let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?; let item = if force_constructor || attrs.constructor { ImplItem::Constructor(FnSignature::new_constructor( self_ident.clone(), impl_fn.sig, + docstring, )?) } else { - ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?) + ImplItem::Method(FnSignature::new_method( + self_ident.clone(), + impl_fn.sig, + docstring, + )?) }; Ok(item) @@ -117,6 +126,11 @@ impl ExportItem { } let self_ident = item.ident.to_owned(); + let docstring = if callback_interface { + Some(extract_docstring(&item.attrs)?) + } else { + None + }; let items = item .items .into_iter() @@ -132,6 +146,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( @@ -143,6 +158,7 @@ impl ExportItem { self_ident.clone(), tim.sig, i as u32, + docstring, )?) }; @@ -154,6 +170,7 @@ impl ExportItem { items, self_ident, callback_interface, + docstring, }) } diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index 53a7f78c17..5faddb5c4d 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -66,7 +66,7 @@ pub(super) fn gen_trait_scaffolding( .collect::>()?; let meta_static_var = (!udl_mode).then(|| { - interface_meta_static_var(&self_ident, true, mod_path) + interface_meta_static_var(&self_ident, true, mod_path, None) .unwrap_or_else(syn::Error::into_compile_error) }); let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, false); diff --git a/uniffi_macros/src/export/utrait.rs b/uniffi_macros/src/export/utrait.rs index 3db09ea2b7..508d0c231a 100644 --- a/uniffi_macros/src/export/utrait.rs +++ b/uniffi_macros/src/export/utrait.rs @@ -8,6 +8,7 @@ use syn::ext::IdentExt; use super::{attributes::ExportAttributeArguments, gen_ffi_function}; use crate::fnsig::FnSignature; +use crate::util::extract_docstring; use uniffi_meta::UniffiTraitDiscriminants; pub(crate) fn expand_uniffi_trait_export( @@ -157,12 +158,15 @@ 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())?, + &FnSignature::new_method(self_ident.clone(), item.sig.clone(), docstring.clone())?, &ExportAttributeArguments::default(), 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, docstring)?.metadata_expr()?; Ok((ffi_func, method_meta)) } diff --git a/uniffi_macros/src/fnsig.rs b/uniffi_macros/src/fnsig.rs index 69b6529d1e..e2c9504d5f 100644 --- a/uniffi_macros/src/fnsig.rs +++ b/uniffi_macros/src/fnsig.rs @@ -23,30 +23,40 @@ 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::new(FnKind::Function, sig) + pub(crate) fn new_function(sig: syn::Signature, docstring: String) -> syn::Result { + Self::new(FnKind::Function, sig, docstring) } - pub(crate) fn new_method(self_ident: Ident, sig: syn::Signature) -> syn::Result { - Self::new(FnKind::Method { self_ident }, sig) + pub(crate) fn new_method( + self_ident: Ident, + sig: syn::Signature, + docstring: String, + ) -> syn::Result { + Self::new(FnKind::Method { self_ident }, sig, docstring) } - pub(crate) fn new_constructor(self_ident: Ident, sig: syn::Signature) -> syn::Result { - Self::new(FnKind::Constructor { self_ident }, sig) + pub(crate) fn new_constructor( + self_ident: Ident, + sig: syn::Signature, + docstring: String, + ) -> syn::Result { + Self::new(FnKind::Constructor { self_ident }, sig, docstring) } pub(crate) fn new_trait_method( self_ident: Ident, sig: syn::Signature, index: u32, + docstring: String, ) -> syn::Result { - Self::new(FnKind::TraitMethod { self_ident, index }, sig) + Self::new(FnKind::TraitMethod { self_ident, index }, sig, docstring) } - pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result { + pub(crate) fn new(kind: FnKind, sig: syn::Signature, docstring: String) -> syn::Result { let span = sig.span(); let ident = sig.ident; let looks_like_result = looks_like_result(&sig.output); @@ -97,6 +107,7 @@ impl FnSignature { args, return_ty: output, looks_like_result, + docstring, }) } @@ -193,6 +204,7 @@ impl FnSignature { return_ty, is_async, mod_path, + docstring, .. } = &self; let args_len = try_metadata_value_from_usize( @@ -212,6 +224,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn>::TYPE_ID_META) + .concat_str(#docstring) }), FnKind::Method { self_ident } => { @@ -225,6 +238,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn>::TYPE_ID_META) + .concat_str(#docstring) }) } @@ -240,6 +254,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn>::TYPE_ID_META) + .concat_str(#docstring) }) } @@ -253,6 +268,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn>::TYPE_ID_META) + .concat_str(#docstring) }) } } diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 697641c829..b0b3a1dffa 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -3,15 +3,18 @@ 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, +}; pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result { 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 meta_static_var = (!udl_mode).then(|| { - interface_meta_static_var(ident, false, &module_path) + interface_meta_static_var(ident, false, &module_path, Some(docstring)) .unwrap_or_else(syn::Error::into_compile_error) }); let interface_impl = interface_impl(ident, udl_mode); @@ -131,17 +134,24 @@ pub(crate) fn interface_meta_static_var( ident: &Ident, is_trait: bool, module_path: &str, + docstring: Option, ) -> syn::Result { let name = ident_to_string(ident); + + let mut metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::INTERFACE) + .concat_str(#module_path) + .concat_str(#name) + .concat_bool(#is_trait) + }; + if let Some(docstring) = docstring { + metadata_expr.extend(quote! { .concat_str(#docstring) }); + } + 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) - }, + metadata_expr, None, )) } diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index abf2743ec6..9ce2987929 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -6,9 +6,9 @@ use syn::{ }; 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, + 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 { @@ -23,10 +23,12 @@ pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result syn::Result { let name = ident_to_string(ident); @@ -144,6 +147,7 @@ pub(crate) fn record_meta_static_var( .parse_uniffi_attr_args::()?; 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) => { @@ -162,6 +166,7 @@ pub(crate) fn record_meta_static_var( .concat_str(#name) .concat(<#ty as ::uniffi::Lower>::TYPE_ID_META) #default + .concat_str(#docstring) }) }) .collect::>()?; @@ -175,6 +180,7 @@ pub(crate) fn record_meta_static_var( .concat_str(#name) .concat_value(#fields_len) #concat_fields + .concat_str(#docstring) }, None, )) diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 9f213ea1d7..503e745ea5 100644 --- a/uniffi_macros/src/util.rs +++ b/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 { @@ -276,3 +276,20 @@ impl Parse for ExternalTypeItem { }) } } + +pub(crate) fn extract_docstring(attrs: &[Attribute]) -> syn::Result { + 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()); + } + } + Err(syn::Error::new_spanned(attr, "Cannot parse doc attribute")) + }) + .collect::>>() + .map(|lines| lines.join("\n")); +} diff --git a/uniffi_meta/src/reader.rs b/uniffi_meta/src/reader.rs index fc00af11be..93b717016a 100644 --- a/uniffi_meta/src/reader.rs +++ b/uniffi_meta/src/reader.rs @@ -105,6 +105,10 @@ impl<'a> MetadataReader<'a> { String::from_utf8(slice.into()).context("Invalid string data") } + fn read_optional_string(&mut self) -> Result> { + Ok(Some(self.read_string()?).filter(|str| !str.is_empty())) + } + fn read_type(&mut self) -> Result { let value = self.read_u8()?; Ok(match value { @@ -198,6 +202,7 @@ impl<'a> MetadataReader<'a> { let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_string()?; Ok(FnMetadata { module_path, name, @@ -205,7 +210,7 @@ impl<'a> MetadataReader<'a> { inputs, return_type, throws, - docstring: None, + docstring, checksum: self.calc_checksum(), }) } @@ -216,6 +221,7 @@ impl<'a> MetadataReader<'a> { let name = self.read_string()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_string()?; return_type .filter(|t| { @@ -233,7 +239,7 @@ impl<'a> MetadataReader<'a> { inputs, throws, checksum: self.calc_checksum(), - docstring: None, + docstring, }) } @@ -244,6 +250,7 @@ impl<'a> MetadataReader<'a> { let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_string()?; Ok(MethodMetadata { module_path, self_name, @@ -254,7 +261,7 @@ impl<'a> MetadataReader<'a> { throws, takes_self_by_arc: false, // not emitted by macros checksum: self.calc_checksum(), - docstring: None, + docstring, }) } @@ -263,7 +270,7 @@ impl<'a> MetadataReader<'a> { module_path: self.read_string()?, name: self.read_string()?, fields: self.read_fields()?, - docstring: None, + docstring: self.read_optional_string()?, }) } @@ -280,7 +287,7 @@ impl<'a> MetadataReader<'a> { module_path, name, variants, - docstring: None, + docstring: self.read_optional_string()?, }) } @@ -295,7 +302,7 @@ impl<'a> MetadataReader<'a> { module_path: self.read_string()?, name: self.read_string()?, imp: ObjectImpl::from_is_trait(self.read_bool()?), - docstring: None, + docstring: self.read_optional_string()?, }) } @@ -328,7 +335,7 @@ impl<'a> MetadataReader<'a> { Ok(CallbackInterfaceMetadata { module_path: self.read_string()?, name: self.read_string()?, - docstring: None, + docstring: self.read_optional_string()?, }) } @@ -340,6 +347,7 @@ impl<'a> MetadataReader<'a> { let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_string()?; Ok(TraitMethodMetadata { module_path, trait_name, @@ -351,7 +359,7 @@ impl<'a> MetadataReader<'a> { throws, takes_self_by_arc: false, // not emitted by macros checksum: self.calc_checksum(), - docstring: None, + docstring, }) } @@ -366,7 +374,7 @@ impl<'a> MetadataReader<'a> { name, ty, default, - docstring: None, + docstring: self.read_optional_string()?, }) }) .collect() @@ -379,7 +387,7 @@ impl<'a> MetadataReader<'a> { Ok(VariantMetadata { name: self.read_string()?, fields: self.read_fields()?, - docstring: None, + docstring: self.read_optional_string()?, }) }) .collect() @@ -392,7 +400,7 @@ impl<'a> MetadataReader<'a> { Ok(VariantMetadata { name: self.read_string()?, fields: vec![], - docstring: None, + docstring: self.read_optional_string()?, }) }) .collect()