* test: add docstring tests for proc-macros

* feat: support docstrings in metadata

* fix(bindgen): improve docstrings in Enums and Errors

* chore: add .idea to .gitignore

* chore: add changelog entry
This commit is contained in:
Didier Villevalois 2023-11-30 09:51:04 +01:00 коммит произвёл GitHub
Родитель a93bdfa249
Коммит 45d0f340f7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
32 изменённых файлов: 538 добавлений и 52 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -2,6 +2,7 @@ target
.*.swp
*.jar
.vscode
.idea
xcuserdata
docs/manual/src/internals/api
examples/app/ios/Generated

Просмотреть файл

@ -29,6 +29,7 @@
[the external types docs](https://mozilla.github.io/uniffi-rs/udl/ext_types.html)
- Add support for [docstrings in UDL](https://mozilla.github.io/uniffi-rs/udl/docstrings.html)
- Ability for UDL to use external trait interfaces [#1831](https://github.com/mozilla/uniffi-rs/issues/1831)
- Add support for docstrings via procmacros [#1862](https://github.com/mozilla/uniffi-rs/pull/1862)
[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.25.2...HEAD).

11
Cargo.lock сгенерированный
Просмотреть файл

@ -1725,6 +1725,17 @@ dependencies = [
"uniffi_testing",
]
[[package]]
name = "uniffi-fixture-docstring-proc-macro"
version = "0.22.0"
dependencies = [
"glob",
"thiserror",
"uniffi",
"uniffi_bindgen",
"uniffi_testing",
]
[[package]]
name = "uniffi-fixture-ext-types"
version = "0.22.0"

Просмотреть файл

@ -34,6 +34,7 @@ members = [
"fixtures/ext-types/sub-lib",
"fixtures/docstring",
"fixtures/docstring-proc-macro",
"fixtures/foreign-executor",
"fixtures/keywords/kotlin",
"fixtures/keywords/rust",

Просмотреть файл

@ -0,0 +1,24 @@
[package]
name = "uniffi-fixture-docstring-proc-macro"
version = "0.22.0"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
edition = "2021"
license = "MPL-2.0"
publish = false
[lib]
name = "uniffi_fixture_docstring_proc_macro"
crate-type = ["lib", "cdylib"]
[dependencies]
thiserror = "1.0"
uniffi = { path = "../../uniffi" }
[build-dependencies]
uniffi = { path = "../../uniffi", features = ["build"] }
[dev-dependencies]
glob = "0.3"
uniffi = { path = "../../uniffi", features = ["bindgen-tests"] }
uniffi_bindgen = { path = "../../uniffi_bindgen" }
uniffi_testing = { path = "../../uniffi_testing" }

Просмотреть файл

@ -0,0 +1,3 @@
# A basic test for uniffi components
This test covers docstrings in proc-macro mode.

Просмотреть файл

@ -0,0 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
fn main() {
uniffi::generate_scaffolding("./src/docstring-proc-macro.udl").unwrap();
}

Просмотреть файл

@ -0,0 +1,2 @@
/// <docstring-namespace>
namespace uniffi_docstring_proc_macro {};

Просмотреть файл

@ -0,0 +1,97 @@
/* 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 std::sync::Arc;
/// <docstring-enum>
#[derive(uniffi::Enum)]
enum EnumTest {
/// <docstring-enum-variant>
One,
/// <docstring-enum-variant-2>
Two,
}
/// <docstring-associated-enum>
#[derive(uniffi::Enum)]
pub enum AssociatedEnumTest {
/// <docstring-associated-enum-variant>
Test {
/// <docstring-variant-field>
code: i16,
},
/// <docstring-associated-enum-variant-2>
Test2 { code: i16 },
}
/// <docstring-error>
#[derive(uniffi::Error, Debug, thiserror::Error)]
#[uniffi(flat_error)]
enum ErrorTest {
/// <docstring-error-variant>
#[error("Test")]
One,
/// <docstring-error-variant-2>
#[error("Two")]
Two,
}
/// <docstring-associated-error>
#[derive(uniffi::Error, Debug, thiserror::Error)]
enum AssociatedErrorTest {
/// <docstring-associated-error-variant>
#[error("Test")]
Test { code: i16 },
/// <docstring-associated-error-variant-2>
#[error("Test2")]
Test2 { code: i16 },
}
/// <docstring-object>
#[derive(uniffi::Object)]
pub struct ObjectTest {}
#[uniffi::export]
impl ObjectTest {
/// <docstring-primary-constructor>
#[uniffi::constructor]
pub fn new() -> Arc<Self> {
Arc::new(ObjectTest {})
}
/// <docstring-alternate-constructor>
#[uniffi::constructor]
pub fn new_alternate() -> Arc<Self> {
Arc::new(ObjectTest {})
}
/// <docstring-method>
pub fn test(&self) {}
}
/// <docstring-record>
#[derive(uniffi::Record)]
struct RecordTest {
/// <docstring-record-field>
test: i32,
}
/// <docstring-function>
#[uniffi::export]
pub fn test() {
let _ = ErrorTest::One;
let _ = ErrorTest::Two;
}
#[uniffi::export]
pub fn test_without_docstring() {}
/// <docstring-callback>
#[uniffi::export(callback_interface)]
pub trait CallbackTest {
/// <docstring-callback-method>
fn test(&self);
}
uniffi::include_scaffolding!("docstring-proc-macro");

Просмотреть файл

@ -0,0 +1,35 @@
/* 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/. */
// This test ensures the generated code works as expected when documentation comments are generated.
// Note: we do not check for existence of the doc comments here, as they are not programmatically
// exposed to the code.
// https://github.com/mozilla/uniffi-rs/pull/1493#discussion_r1375337478
import uniffi.fixture.docstring.proc.macro.*;
test()
EnumTest.ONE
EnumTest.TWO
AssociatedEnumTest.Test(0)
AssociatedEnumTest.Test2(0)
ErrorTest.One("hello")
ErrorTest.Two("hello")
AssociatedErrorTest.Test(0)
AssociatedErrorTest.Test2(0)
val obj1 = ObjectTest
val obj2 = ObjectTest.newAlternate()
obj2.test()
val rec = RecordTest(123)
val recField = rec.test
class CallbackImpls() : CallbackTest {
override fun test() {}
}

Просмотреть файл

@ -0,0 +1,52 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Test namespace
import uniffi_docstring_proc_macro
assert uniffi_docstring_proc_macro.__doc__
from uniffi_docstring_proc_macro import *
# Test function
assert test.__doc__ == "<docstring-function>"
assert test_without_docstring.__doc__ is None
# Test enums
assert EnumTest.__doc__ == "<docstring-enum>"
# Simple enum variants can't be tested, because `__doc__` is not supported for enums
# assert EnumTest.ONE.__doc__ == "<docstring-enum-variant>"
# assert EnumTest.TWO.__doc__ == "<docstring-enum-variant-2>"
assert AssociatedEnumTest.__doc__ == "<docstring-associated-enum>"
# `__doc__` is lost because of how enum templates are generated
# https://github.com/mozilla/uniffi-rs/blob/eb97592f8c48a7f5cf02a94662b8b7861a6544f3/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py#L60
# assert AssociatedEnumTest.TEST.__doc__ == "<docstring-associated-enum-variant>"
# assert AssociatedEnumTest.TEST2.__doc__ == "<docstring-associated-enum-variant-2>"
# Test errors
assert ErrorTest.__doc__ == "<docstring-error>"
assert ErrorTest.One.__doc__ == "<docstring-error-variant>"
assert ErrorTest.Two.__doc__ == "<docstring-error-variant-2>"
assert AssociatedErrorTest.__doc__ == "<docstring-associated-error>"
assert AssociatedErrorTest.Test.__doc__ == "<docstring-associated-error-variant>"
assert AssociatedErrorTest.Test2.__doc__ == "<docstring-associated-error-variant-2>"
# Test objects
assert ObjectTest.__doc__ == "<docstring-object>"
assert ObjectTest.__init__.__doc__ == "<docstring-primary-constructor>"
assert ObjectTest.new_alternate.__doc__ == "<docstring-alternate-constructor>"
assert ObjectTest.test.__doc__ == "<docstring-method>"
# Test records
assert RecordTest.__doc__ == "<docstring-record>"
# `__doc__` is not supported for class fields
# assert RecordTest.test.__doc__ == "<docstring-record-field>"
# Test callbacks
assert CallbackTest.__doc__ == "<docstring-callback>"
assert CallbackTest.test.__doc__ == "<docstring-callback-method>"

Просмотреть файл

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This test ensures the generated code works as expected when documentation comments are generated.
// Note: we do not check for existence of the doc comments here, as they are not programmatically
// exposed to the code.
// https://github.com/mozilla/uniffi-rs/pull/1493#discussion_r1375337478
import uniffi_docstring_proc_macro
test()
var _ = EnumTest.one
var _ = EnumTest.two
var _ = AssociatedEnumTest.test(code: 0)
var _ = AssociatedEnumTest.test2(code: 0)
var _ = ErrorTest.One(message: "hello")
var _ = ErrorTest.Two(message: "hello")
var _ = AssociatedErrorTest.Test(code: 0)
var _ = AssociatedErrorTest.Test2(code: 0)
var obj1 = ObjectTest()
var obj2 = ObjectTest.newAlternate()
obj2.test()
var rec = RecordTest(test: 123)
var recField = rec.test
class CallbackImpls: CallbackTest {
func test() {}
}

Просмотреть файл

@ -0,0 +1,99 @@
uniffi::build_foreign_language_testcases!(
"tests/bindings/test_docstring.kts",
"tests/bindings/test_docstring.swift",
"tests/bindings/test_docstring.py",
);
#[cfg(test)]
mod tests {
use uniffi_bindgen::bindings::TargetLanguage;
use uniffi_testing::UniFFITestHelper;
const DOCSTRINGS: &[&str] = &[
"<docstring-alternate-constructor>",
"<docstring-associated-enum-variant-2>",
"<docstring-associated-enum-variant>",
"<docstring-associated-enum>",
"<docstring-associated-error-variant-2>",
"<docstring-associated-error-variant>",
"<docstring-associated-error>",
"<docstring-callback-method>",
"<docstring-callback>",
"<docstring-enum-variant-2>",
"<docstring-enum-variant>",
"<docstring-enum>",
"<docstring-error-variant-2>",
"<docstring-error-variant>",
"<docstring-error>",
"<docstring-function>",
"<docstring-method>",
"<docstring-namespace>",
"<docstring-object>",
"<docstring-primary-constructor>",
"<docstring-record-field>",
"<docstring-record>",
"<docstring-variant-field>",
];
fn test_docstring(language: TargetLanguage, file_extension: &str) {
let test_helper = UniFFITestHelper::new(std::env!("CARGO_PKG_NAME")).unwrap();
let out_dir = test_helper
.create_out_dir(
std::env!("CARGO_TARGET_TMPDIR"),
format!("test-docstring-proc-macro-{}", language),
)
.unwrap();
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir).unwrap();
uniffi_bindgen::library_mode::generate_bindings(
&cdylib_path,
None,
&[language],
None,
&out_dir,
false,
)
.unwrap();
let glob_pattern = out_dir.join(format!("**/*.{}", file_extension));
let sources = glob::glob(glob_pattern.as_str())
.unwrap()
.flatten()
.map(|p| String::from(p.to_string_lossy()))
.collect::<Vec<String>>();
assert_eq!(sources.len(), 1);
let bindings_source = std::fs::read_to_string(&sources[0]).unwrap();
let expected: Vec<String> = vec![];
assert_eq!(
expected,
DOCSTRINGS
.iter()
.filter(|v| !bindings_source.contains(*v))
.map(|v| v.to_string())
.collect::<Vec::<_>>(),
"docstrings not found in {}",
&sources[0]
);
}
#[test]
fn test_docstring_kotlin() {
test_docstring(TargetLanguage::Kotlin, "kt");
}
#[test]
fn test_docstring_python() {
test_docstring(TargetLanguage::Python, "py");
}
#[test]
fn test_docstring_swift() {
test_docstring(TargetLanguage::Swift, "swift");
}
}

Просмотреть файл

@ -0,0 +1,9 @@
[bindings.kotlin]
package_name = "uniffi.fixture.docstring.proc.macro"
cdylib_name = "uniffi_fixture_docstring_proc_macro"
[bindings.python]
cdylib_name = "uniffi_fixture_docstring_proc_macro"
[bindings.swift]
cdylib_name = "uniffi_fixture_docstring_proc_macro"

Просмотреть файл

@ -41,6 +41,7 @@ sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% e
{% else -%}
data class {{ variant|type_name(ci) }}(
{% for field in variant.fields() -%}
{%- call kt::docstring(field, 8) %}
val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
) : {{ type_name }}() {

Просмотреть файл

@ -22,6 +22,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di
{%- let variant_name = variant|error_variant_name %}
class {{ variant_name }}(
{% for field in variant.fields() -%}
{%- call kt::docstring(field, 8) %}
val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
) : {{ type_name }}() {

Просмотреть файл

@ -25,7 +25,8 @@ class {{ type_name }}:
{%- call py::docstring(variant, 8) %}
{%- for field in variant.fields() %}
{{ field.name()|var_name }}: "{{ field|type_name }}";
{{ field.name()|var_name }}: "{{ field|type_name }}"
{%- call py::docstring(field, 8) %}
{%- endfor %}
@typing.no_type_check

Просмотреть файл

@ -4,7 +4,9 @@
public enum {{ type_name }} {
{% for variant in e.variants() %}
{%- call swift::docstring(variant, 4) %}
case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}(
{%- call swift::field_list_decl(variant) %}
){% endif -%}
{% endfor %}
}

Просмотреть файл

@ -10,7 +10,9 @@ public enum {{ type_name }} {
{%- else %}
{% for variant in e.variants() %}
{%- call swift::docstring(variant, 4) %}
case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}(
{%- call swift::field_list_decl(variant) %}
){% endif -%}
{% endfor %}
{%- endif %}

Просмотреть файл

@ -58,6 +58,7 @@
-#}
{% macro field_list_decl(item) %}
{%- for field in item.fields() -%}
{%- call docstring(field, 8) %}
{{ field.name()|var_name }}: {{ field|type_name -}}
{%- match field.default_value() %}
{%- when Some with(literal) %} = {{ literal|literal_swift(field) }}

Просмотреть файл

@ -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<TokenStream> {
@ -18,10 +18,12 @@ pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStrea
}
};
let ident = &input.ident;
let docstring = extract_docstring(&input.attrs)?;
let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode);
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, &enum_)
.unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
@ -136,7 +138,11 @@ fn write_field(f: &Field) -> TokenStream {
}
}
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,
enum_: &DataEnum,
) -> syn::Result<TokenStream> {
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<Vec<TokenStream>> {
.collect::<syn::Result<Vec<_>>>()?;
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::<syn::Result<Vec<_>>>()?;
Ok(quote! {
.concat_str(#name)
.concat_value(#fields_len)
@ -187,7 +200,9 @@ pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
.concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
// field defaults not yet supported for enums
.concat_bool(false)
.concat_str(#field_docstrings)
)*
.concat_str(#docstring)
})
})
)

Просмотреть файл

@ -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<TokenStream> {
@ -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<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_str(#docstring)
})
}))
.collect())
.collect()
}
#[derive(Default)]

Просмотреть файл

@ -68,18 +68,23 @@ pub(crate) fn expand_export(
items,
self_ident,
callback_interface: false,
} => trait_interface::gen_trait_scaffolding(&mod_path, args, self_ident, items, udl_mode),
docstring,
} => trait_interface::gen_trait_scaffolding(
&mod_path, args, self_ident, items, udl_mode, docstring,
),
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);

Просмотреть файл

@ -182,6 +182,7 @@ 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(
@ -191,6 +192,7 @@ pub(super) fn metadata_items(
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE)
.concat_str(#module_path)
.concat_str(#trait_name)
.concat_str(#docstring)
},
None,
);

Просмотреть файл

@ -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<ImplItem>,
callback_interface: bool,
docstring: String,
},
Struct {
self_ident: Ident,
@ -32,7 +34,8 @@ impl ExportItem {
pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result<Self> {
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,7 @@ impl ExportItem {
}
let self_ident = item.ident.to_owned();
let docstring = extract_docstring(&item.attrs)?;
let items = item
.items
.into_iter()
@ -132,6 +142,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 +154,7 @@ impl ExportItem {
self_ident.clone(),
tim.sig,
i as u32,
docstring,
)?)
};
@ -154,6 +166,7 @@ impl ExportItem {
items,
self_ident,
callback_interface,
docstring,
})
}

Просмотреть файл

@ -21,6 +21,7 @@ pub(super) fn gen_trait_scaffolding(
self_ident: Ident,
items: Vec<ImplItem>,
udl_mode: bool,
docstring: String,
) -> syn::Result<TokenStream> {
if let Some(rt) = args.async_runtime {
return Err(syn::Error::new_spanned(rt, "not supported for traits"));
@ -66,7 +67,7 @@ pub(super) fn gen_trait_scaffolding(
.collect::<syn::Result<_>>()?;
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, docstring)
.unwrap_or_else(syn::Error::into_compile_error)
});
let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode);

Просмотреть файл

@ -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))
}

Просмотреть файл

@ -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> {
Self::new(FnKind::Function, sig)
pub(crate) fn new_function(sig: syn::Signature, docstring: String) -> syn::Result<Self> {
Self::new(FnKind::Function, sig, 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,
docstring: String,
) -> syn::Result<Self> {
Self::new(FnKind::Method { self_ident }, sig, 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,
docstring: String,
) -> syn::Result<Self> {
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> {
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<Self> {
pub(crate) fn new(kind: FnKind, sig: syn::Signature, docstring: String) -> syn::Result<Self> {
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<crate::UniFfiTag>>::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<crate::UniFfiTag>>::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<crate::UniFfiTag>>::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<crate::UniFfiTag>>::TYPE_ID_META)
.concat_str(#docstring)
})
}
}

Просмотреть файл

@ -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<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 meta_static_var = (!udl_mode).then(|| {
interface_meta_static_var(ident, false, &module_path)
interface_meta_static_var(ident, false, &module_path, docstring)
.unwrap_or_else(syn::Error::into_compile_error)
});
let interface_impl = interface_impl(ident, udl_mode);
@ -131,16 +134,19 @@ pub(crate) fn interface_meta_static_var(
ident: &Ident,
is_trait: bool,
module_path: &str,
docstring: String,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
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(::uniffi::metadata::codes::INTERFACE)
.concat_str(#module_path)
.concat_str(#name)
.concat_bool(#is_trait)
.concat_str(#docstring)
},
None,
))

Просмотреть файл

@ -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<TokenStream> {
@ -23,10 +23,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! {
@ -128,6 +130,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,6 +147,7 @@ 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) => {
@ -162,6 +166,7 @@ pub(crate) fn record_meta_static_var(
.concat_str(#name)
.concat(<#ty as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
#default
.concat_str(#docstring)
})
})
.collect::<syn::Result<_>>()?;
@ -175,6 +180,7 @@ pub(crate) fn record_meta_static_var(
.concat_str(#name)
.concat_value(#fields_len)
#concat_fields
.concat_str(#docstring)
},
None,
))

Просмотреть файл

@ -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> {
@ -276,3 +276,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());
}
}
Err(syn::Error::new_spanned(attr, "Cannot parse doc attribute"))
})
.collect::<syn::Result<Vec<_>>>()
.map(|lines| lines.join("\n"));
}

Просмотреть файл

@ -105,6 +105,10 @@ impl<'a> MetadataReader<'a> {
String::from_utf8(slice.into()).context("Invalid string data")
}
fn read_optional_string(&mut self) -> Result<Option<String>> {
Ok(Some(self.read_string()?).filter(|str| !str.is_empty()))
}
fn read_type(&mut self) -> Result<Type> {
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()