1 use proc_macro2::{Ident, Span, TokenStream};
2 use quote::quote;
3 use syn::{parse::ParseStream, Data, DataStruct, DeriveInput, Field, Token};
4
5 use crate::{
6 default::{default_value_metadata_calls, DefaultValue},
7 util::{
8 create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
9 ident_to_string, kw, mod_path, tagged_impl_header, try_metadata_value_from_usize,
10 try_read_field, AttributeSliceExt, UniffiAttributeArgs,
11 },
12 };
13
expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream>14 pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
15 if let Some(e) = input.attrs.uniffi_attr_args_not_allowed_here() {
16 return Err(e);
17 }
18 let record = match input.data {
19 Data::Struct(s) => s,
20 _ => {
21 return Err(syn::Error::new(
22 Span::call_site(),
23 "This derive must only be used on structs",
24 ));
25 }
26 };
27
28 let ident = &input.ident;
29 let docstring = extract_docstring(&input.attrs)?;
30 let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode)
31 .unwrap_or_else(syn::Error::into_compile_error);
32 let meta_static_var = (!udl_mode).then(|| {
33 record_meta_static_var(ident, docstring, &record)
34 .unwrap_or_else(syn::Error::into_compile_error)
35 });
36
37 Ok(quote! {
38 #ffi_converter
39 #meta_static_var
40 })
41 }
42
record_ffi_converter_impl( ident: &Ident, record: &DataStruct, udl_mode: bool, ) -> syn::Result<TokenStream>43 pub(crate) fn record_ffi_converter_impl(
44 ident: &Ident,
45 record: &DataStruct,
46 udl_mode: bool,
47 ) -> syn::Result<TokenStream> {
48 let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode);
49 let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode);
50 let name = ident_to_string(ident);
51 let mod_path = mod_path()?;
52 let write_impl: TokenStream = record.fields.iter().map(write_field).collect();
53 let try_read_fields: TokenStream = record.fields.iter().map(try_read_field).collect();
54
55 Ok(quote! {
56 #[automatically_derived]
57 unsafe #impl_spec {
58 ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag);
59
60 fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
61 #write_impl
62 }
63
64 fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
65 Ok(Self { #try_read_fields })
66 }
67
68 const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_RECORD)
69 .concat_str(#mod_path)
70 .concat_str(#name);
71 }
72
73 #derive_ffi_traits
74 })
75 }
76
write_field(f: &Field) -> TokenStream77 fn write_field(f: &Field) -> TokenStream {
78 let ident = &f.ident;
79 let ty = &f.ty;
80
81 quote! {
82 <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(obj.#ident, buf);
83 }
84 }
85
86 #[derive(Default)]
87 pub struct FieldAttributeArguments {
88 pub(crate) default: Option<DefaultValue>,
89 }
90
91 impl UniffiAttributeArgs for FieldAttributeArguments {
parse_one(input: ParseStream<'_>) -> syn::Result<Self>92 fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
93 let _: kw::default = input.parse()?;
94 let _: Token![=] = input.parse()?;
95 let default = input.parse()?;
96 Ok(Self {
97 default: Some(default),
98 })
99 }
100
merge(self, other: Self) -> syn::Result<Self>101 fn merge(self, other: Self) -> syn::Result<Self> {
102 Ok(Self {
103 default: either_attribute_arg(self.default, other.default)?,
104 })
105 }
106 }
107
record_meta_static_var( ident: &Ident, docstring: String, record: &DataStruct, ) -> syn::Result<TokenStream>108 pub(crate) fn record_meta_static_var(
109 ident: &Ident,
110 docstring: String,
111 record: &DataStruct,
112 ) -> syn::Result<TokenStream> {
113 let name = ident_to_string(ident);
114 let module_path = mod_path()?;
115 let fields_len =
116 try_metadata_value_from_usize(record.fields.len(), "UniFFI limits structs to 256 fields")?;
117
118 let concat_fields: TokenStream = record
119 .fields
120 .iter()
121 .map(|f| {
122 let attrs = f
123 .attrs
124 .parse_uniffi_attr_args::<FieldAttributeArguments>()?;
125
126 let name = ident_to_string(f.ident.as_ref().unwrap());
127 let docstring = extract_docstring(&f.attrs)?;
128 let ty = &f.ty;
129 let default = default_value_metadata_calls(&attrs.default)?;
130
131 // Note: fields need to implement both `Lower` and `Lift` to be used in a record. The
132 // TYPE_ID_META should be the same for both traits.
133 Ok(quote! {
134 .concat_str(#name)
135 .concat(<#ty as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
136 #default
137 .concat_long_str(#docstring)
138 })
139 })
140 .collect::<syn::Result<_>>()?;
141
142 Ok(create_metadata_items(
143 "record",
144 &name,
145 quote! {
146 ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::RECORD)
147 .concat_str(#module_path)
148 .concat_str(#name)
149 .concat_value(#fields_len)
150 #concat_fields
151 .concat_long_str(#docstring)
152 },
153 None,
154 ))
155 }
156