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