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