1 use proc_macro2::{Ident, Span, TokenStream};
2 use quote::quote;
3 use syn::{
4     parse::{Parse, ParseStream},
5     spanned::Spanned,
6     Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, Variant,
7 };
8 
9 use crate::util::{
10     create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
11     ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
12     try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs,
13 };
14 
extract_repr(attrs: &[Attribute]) -> syn::Result<Option<Ident>>15 fn extract_repr(attrs: &[Attribute]) -> syn::Result<Option<Ident>> {
16     let mut result = None;
17     for attr in attrs {
18         if attr.path().is_ident("repr") {
19             attr.parse_nested_meta(|meta| {
20                 result = match meta.path.get_ident() {
21                     Some(i) => {
22                         let s = i.to_string();
23                         match s.as_str() {
24                             "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32"
25                             | "i64" | "isize" => Some(i.clone()),
26                             // while the default repr for an enum is `isize` we don't apply that default here.
27                             _ => None,
28                         }
29                     }
30                     _ => None,
31                 };
32                 Ok(())
33             })?
34         }
35     }
36     Ok(result)
37 }
38 
expand_enum( input: DeriveInput, attr_from_udl_mode: Option<EnumAttr>, udl_mode: bool, ) -> syn::Result<TokenStream>39 pub fn expand_enum(
40     input: DeriveInput,
41     // Attributes from #[derive_error_for_udl()], if we are in udl mode
42     attr_from_udl_mode: Option<EnumAttr>,
43     udl_mode: bool,
44 ) -> syn::Result<TokenStream> {
45     let enum_ = match input.data {
46         Data::Enum(e) => e,
47         _ => {
48             return Err(syn::Error::new(
49                 Span::call_site(),
50                 "This derive must only be used on enums",
51             ))
52         }
53     };
54     let ident = &input.ident;
55     let docstring = extract_docstring(&input.attrs)?;
56     let discr_type = extract_repr(&input.attrs)?;
57     let mut attr: EnumAttr = input.attrs.parse_uniffi_attr_args()?;
58     if let Some(attr_from_udl_mode) = attr_from_udl_mode {
59         attr = attr.merge(attr_from_udl_mode)?;
60     }
61     let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode, &attr);
62 
63     let meta_static_var = (!udl_mode).then(|| {
64         enum_meta_static_var(ident, docstring, discr_type, &enum_, &attr)
65             .unwrap_or_else(syn::Error::into_compile_error)
66     });
67 
68     Ok(quote! {
69         #ffi_converter_impl
70         #meta_static_var
71     })
72 }
73 
enum_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, attr: &EnumAttr, ) -> TokenStream74 pub(crate) fn enum_ffi_converter_impl(
75     ident: &Ident,
76     enum_: &DataEnum,
77     udl_mode: bool,
78     attr: &EnumAttr,
79 ) -> TokenStream {
80     enum_or_error_ffi_converter_impl(
81         ident,
82         enum_,
83         udl_mode,
84         attr,
85         quote! { ::uniffi::metadata::codes::TYPE_ENUM },
86     )
87 }
88 
rich_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, attr: &EnumAttr, ) -> TokenStream89 pub(crate) fn rich_error_ffi_converter_impl(
90     ident: &Ident,
91     enum_: &DataEnum,
92     udl_mode: bool,
93     attr: &EnumAttr,
94 ) -> TokenStream {
95     enum_or_error_ffi_converter_impl(
96         ident,
97         enum_,
98         udl_mode,
99         attr,
100         quote! { ::uniffi::metadata::codes::TYPE_ENUM },
101     )
102 }
103 
enum_or_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, attr: &EnumAttr, metadata_type_code: TokenStream, ) -> TokenStream104 fn enum_or_error_ffi_converter_impl(
105     ident: &Ident,
106     enum_: &DataEnum,
107     udl_mode: bool,
108     attr: &EnumAttr,
109     metadata_type_code: TokenStream,
110 ) -> TokenStream {
111     let name = ident_to_string(ident);
112     let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode);
113     let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode);
114     let mod_path = match mod_path() {
115         Ok(p) => p,
116         Err(e) => return e.into_compile_error(),
117     };
118     let mut write_match_arms: Vec<_> = enum_
119         .variants
120         .iter()
121         .enumerate()
122         .map(|(i, v)| {
123             let v_ident = &v.ident;
124             let field_idents = v
125                 .fields
126                 .iter()
127                 .enumerate()
128                 .map(|(i, f)| {
129                     f.ident
130                         .clone()
131                         .unwrap_or_else(|| Ident::new(&format!("e{i}"), f.span()))
132                 })
133                 .collect::<Vec<Ident>>();
134             let idx = Index::from(i + 1);
135             let write_fields =
136                 std::iter::zip(v.fields.iter(), field_idents.iter()).map(|(f, ident)| {
137                     let ty = &f.ty;
138                     quote! {
139                         <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf);
140                     }
141                 });
142             let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
143             let fields = if is_tuple {
144                 quote! { ( #(#field_idents),* ) }
145             } else {
146                 quote! { { #(#field_idents),* } }
147             };
148 
149             quote! {
150                 Self::#v_ident #fields => {
151                     ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
152                     #(#write_fields)*
153                 }
154             }
155         })
156         .collect();
157     if attr.non_exhaustive.is_some() {
158         write_match_arms.push(quote! {
159             _ => panic!("Unexpected variant in non-exhaustive enum"),
160         })
161     }
162     let write_impl = quote! {
163         match obj { #(#write_match_arms)* }
164     };
165 
166     let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
167         let idx = Index::from(i + 1);
168         let v_ident = &v.ident;
169         let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
170         let try_read_fields = v.fields.iter().map(try_read_field);
171 
172         if is_tuple {
173             quote! {
174                 #idx => Self::#v_ident ( #(#try_read_fields)* ),
175             }
176         } else {
177             quote! {
178                 #idx => Self::#v_ident { #(#try_read_fields)* },
179             }
180         }
181     });
182     let error_format_string = format!("Invalid {ident} enum value: {{}}");
183     let try_read_impl = quote! {
184         ::uniffi::check_remaining(buf, 4)?;
185 
186         Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) {
187             #(#try_read_match_arms)*
188             v => ::uniffi::deps::anyhow::bail!(#error_format_string, v),
189         })
190     };
191 
192     quote! {
193         #[automatically_derived]
194         unsafe #impl_spec {
195             ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag);
196 
197             fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
198                 #write_impl
199             }
200 
201             fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
202                 #try_read_impl
203             }
204 
205             const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_type_code)
206                 .concat_str(#mod_path)
207                 .concat_str(#name);
208         }
209 
210         #derive_ffi_traits
211     }
212 }
213 
enum_meta_static_var( ident: &Ident, docstring: String, discr_type: Option<Ident>, enum_: &DataEnum, attr: &EnumAttr, ) -> syn::Result<TokenStream>214 pub(crate) fn enum_meta_static_var(
215     ident: &Ident,
216     docstring: String,
217     discr_type: Option<Ident>,
218     enum_: &DataEnum,
219     attr: &EnumAttr,
220 ) -> syn::Result<TokenStream> {
221     let name = ident_to_string(ident);
222     let module_path = mod_path()?;
223     let non_exhaustive = attr.non_exhaustive.is_some();
224 
225     let mut metadata_expr = quote! {
226         ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
227             .concat_str(#module_path)
228             .concat_str(#name)
229             .concat_option_bool(None) // forced_flatness
230     };
231     metadata_expr.extend(match discr_type {
232         None => quote! { .concat_bool(false) },
233         Some(t) => quote! { .concat_bool(true).concat(<#t as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) }
234     });
235     metadata_expr.extend(variant_metadata(enum_)?);
236     metadata_expr.extend(quote! {
237         .concat_bool(#non_exhaustive)
238         .concat_long_str(#docstring)
239     });
240     Ok(create_metadata_items("enum", &name, metadata_expr, None))
241 }
242 
variant_value(v: &Variant) -> syn::Result<TokenStream>243 fn variant_value(v: &Variant) -> syn::Result<TokenStream> {
244     let Some((_, e)) = &v.discriminant else {
245         return Ok(quote! { .concat_bool(false) });
246     };
247     // Attempting to expose an enum value which we don't understand is a hard-error
248     // rather than silently ignoring it. If we had the ability to emit a warning that
249     // might make more sense.
250 
251     // We can't sanely handle most expressions other than literals, but we can handle
252     // negative literals.
253     let mut negate = false;
254     let lit = match e {
255         Expr::Lit(lit) => lit,
256         Expr::Unary(expr_unary) if matches!(expr_unary.op, syn::UnOp::Neg(_)) => {
257             negate = true;
258             match *expr_unary.expr {
259                 Expr::Lit(ref lit) => lit,
260                 _ => {
261                     return Err(syn::Error::new_spanned(
262                         e,
263                         "UniFFI disciminant values must be a literal",
264                     ));
265                 }
266             }
267         }
268         _ => {
269             return Err(syn::Error::new_spanned(
270                 e,
271                 "UniFFI disciminant values must be a literal",
272             ));
273         }
274     };
275     let Lit::Int(ref intlit) = lit.lit else {
276         return Err(syn::Error::new_spanned(
277             v,
278             "UniFFI disciminant values must be a literal integer",
279         ));
280     };
281     if !intlit.suffix().is_empty() {
282         return Err(syn::Error::new_spanned(
283             intlit,
284             "integer literals with suffix not supported by UniFFI here",
285         ));
286     }
287     let digits = if negate {
288         format!("-{}", intlit.base10_digits())
289     } else {
290         intlit.base10_digits().to_string()
291     };
292     Ok(quote! {
293         .concat_bool(true)
294         .concat_value(::uniffi::metadata::codes::LIT_INT)
295         .concat_str(#digits)
296     })
297 }
298 
variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>>299 pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
300     let variants_len =
301         try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
302     std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
303         .chain(enum_.variants.iter().map(|v| {
304             let fields_len = try_metadata_value_from_usize(
305                 v.fields.len(),
306                 "UniFFI limits enum variants to 256 fields",
307             )?;
308 
309             let field_names = v
310                 .fields
311                 .iter()
312                 .map(|f| f.ident.as_ref().map(ident_to_string).unwrap_or_default())
313                 .collect::<Vec<_>>();
314 
315             let name = ident_to_string(&v.ident);
316             let value_tokens = variant_value(v)?;
317             let docstring = extract_docstring(&v.attrs)?;
318             let field_types = v.fields.iter().map(|f| &f.ty);
319             let field_docstrings = v
320                 .fields
321                 .iter()
322                 .map(|f| extract_docstring(&f.attrs))
323                 .collect::<syn::Result<Vec<_>>>()?;
324 
325             Ok(quote! {
326                 .concat_str(#name)
327                 #value_tokens
328                 .concat_value(#fields_len)
329                     #(
330                         .concat_str(#field_names)
331                         .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
332                         // field defaults not yet supported for enums
333                         .concat_bool(false)
334                         .concat_long_str(#field_docstrings)
335                     )*
336                 .concat_long_str(#docstring)
337             })
338         }))
339         .collect()
340 }
341 
342 #[derive(Default)]
343 pub struct EnumAttr {
344     pub non_exhaustive: Option<kw::non_exhaustive>,
345 }
346 
347 // So ErrorAttr can be used with `parse_macro_input!`
348 impl Parse for EnumAttr {
parse(input: ParseStream<'_>) -> syn::Result<Self>349     fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
350         parse_comma_separated(input)
351     }
352 }
353 
354 impl UniffiAttributeArgs for EnumAttr {
parse_one(input: ParseStream<'_>) -> syn::Result<Self>355     fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
356         let lookahead = input.lookahead1();
357         if lookahead.peek(kw::non_exhaustive) {
358             Ok(Self {
359                 non_exhaustive: input.parse()?,
360             })
361         } else {
362             Err(lookahead.error())
363         }
364     }
365 
merge(self, other: Self) -> syn::Result<Self>366     fn merge(self, other: Self) -> syn::Result<Self> {
367         Ok(Self {
368             non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
369         })
370     }
371 }
372