1 use proc_macro2::{Ident, Span, TokenStream};
2 use quote::quote;
3 use syn::{
4     parse::{Parse, ParseStream},
5     Data, DataEnum, DeriveInput, Index,
6 };
7 
8 use crate::{
9     enum_::{rich_error_ffi_converter_impl, variant_metadata, EnumAttr},
10     util::{
11         chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, extract_docstring,
12         ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
13         try_metadata_value_from_usize, AttributeSliceExt, UniffiAttributeArgs,
14     },
15 };
16 
expand_error( input: DeriveInput, attr_from_udl_mode: Option<ErrorAttr>, udl_mode: bool, ) -> syn::Result<TokenStream>17 pub fn expand_error(
18     input: DeriveInput,
19     // Attributes from #[derive_error_for_udl()], if we are in udl mode
20     attr_from_udl_mode: Option<ErrorAttr>,
21     udl_mode: bool,
22 ) -> syn::Result<TokenStream> {
23     let enum_ = match input.data {
24         Data::Enum(e) => e,
25         _ => {
26             return Err(syn::Error::new(
27                 Span::call_site(),
28                 "This derive currently only supports enums",
29             ));
30         }
31     };
32     let ident = &input.ident;
33     let docstring = extract_docstring(&input.attrs)?;
34     let mut attr: ErrorAttr = input.attrs.parse_uniffi_attr_args()?;
35     if let Some(attr_from_udl_mode) = attr_from_udl_mode {
36         attr = attr.merge(attr_from_udl_mode)?;
37     }
38     let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode)?;
39     let meta_static_var = (!udl_mode).then(|| {
40         error_meta_static_var(ident, docstring, &enum_, &attr)
41             .unwrap_or_else(syn::Error::into_compile_error)
42     });
43 
44     let variant_errors: TokenStream = enum_
45         .variants
46         .iter()
47         .flat_map(|variant| {
48             chain(
49                 variant.attrs.uniffi_attr_args_not_allowed_here(),
50                 variant
51                     .fields
52                     .iter()
53                     .flat_map(|field| field.attrs.uniffi_attr_args_not_allowed_here()),
54             )
55         })
56         .map(syn::Error::into_compile_error)
57         .collect();
58 
59     Ok(quote! {
60         #ffi_converter_impl
61         #meta_static_var
62         #variant_errors
63     })
64 }
65 
error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr, udl_mode: bool, ) -> syn::Result<TokenStream>66 fn error_ffi_converter_impl(
67     ident: &Ident,
68     enum_: &DataEnum,
69     attr: &ErrorAttr,
70     udl_mode: bool,
71 ) -> syn::Result<TokenStream> {
72     Ok(if attr.flat.is_some() {
73         flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr)
74     } else {
75         rich_error_ffi_converter_impl(ident, enum_, udl_mode, &attr.clone().try_into()?)
76     })
77 }
78 
79 // FfiConverters for "flat errors"
80 //
81 // These are errors where we only lower the to_string() value, rather than any associated data.
82 // We lower the to_string() value unconditionally, whether the enum has associated data or not.
flat_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, attr: &ErrorAttr, ) -> TokenStream83 fn flat_error_ffi_converter_impl(
84     ident: &Ident,
85     enum_: &DataEnum,
86     udl_mode: bool,
87     attr: &ErrorAttr,
88 ) -> TokenStream {
89     let name = ident_to_string(ident);
90     let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode);
91     let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode);
92     let derive_ffi_traits = derive_ffi_traits(ident, udl_mode, &["ConvertError"]);
93     let mod_path = match mod_path() {
94         Ok(p) => p,
95         Err(e) => return e.into_compile_error(),
96     };
97 
98     let lower_impl = {
99         let mut match_arms: Vec<_> = enum_.variants.iter().enumerate().map(|(i, v)| {
100             let v_ident = &v.ident;
101             let idx = Index::from(i + 1);
102 
103             quote! {
104                 Self::#v_ident { .. } => {
105                     ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
106                     <::std::string::String as ::uniffi::Lower<crate::UniFfiTag>>::write(error_msg, buf);
107                 }
108             }
109         }).collect();
110         if attr.non_exhaustive.is_some() {
111             match_arms.push(quote! {
112                 _ => panic!("Unexpected variant in non-exhaustive enum"),
113             })
114         }
115 
116         quote! {
117             #[automatically_derived]
118             unsafe #lower_impl_spec {
119                 type FfiType = ::uniffi::RustBuffer;
120 
121                 fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
122                     let error_msg = ::std::string::ToString::to_string(&obj);
123                     match obj { #(#match_arms)* }
124                 }
125 
126                 fn lower(obj: Self) -> ::uniffi::RustBuffer {
127                     <Self as ::uniffi::Lower<crate::UniFfiTag>>::lower_into_rust_buffer(obj)
128                 }
129 
130                 const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_ENUM)
131                     .concat_str(#mod_path)
132                     .concat_str(#name);
133             }
134         }
135     };
136 
137     let lift_impl = if attr.with_try_read.is_some() {
138         let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
139             let v_ident = &v.ident;
140             let idx = Index::from(i + 1);
141 
142             quote! {
143                 #idx => Self::#v_ident,
144             }
145         });
146         quote! {
147             #[automatically_derived]
148             unsafe #lift_impl_spec {
149                 type FfiType = ::uniffi::RustBuffer;
150 
151                 fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
152                     Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) {
153                         #(#match_arms)*
154                         v => ::uniffi::deps::anyhow::bail!("Invalid #ident enum value: {}", v),
155                     })
156                 }
157 
158                 fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result<Self> {
159                     <Self as ::uniffi::Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer(v)
160                 }
161 
162                 const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META;
163             }
164 
165         }
166     } else {
167         quote! {
168             // Lifting flat errors is not currently supported, but we still define the trait so
169             // that dicts containing flat errors don't cause compile errors (see ReturnOnlyDict in
170             // coverall.rs).
171             //
172             // Note: it would be better to not derive `Lift` for dictionaries containing flat
173             // errors, but getting the trait bounds and derived impls there would be much harder.
174             // For now, we just fail at runtime.
175             #[automatically_derived]
176             unsafe #lift_impl_spec {
177                 type FfiType = ::uniffi::RustBuffer;
178 
179                 fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
180                     panic!("Can't lift flat errors")
181                 }
182 
183                 fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result<Self> {
184                     panic!("Can't lift flat errors")
185                 }
186 
187                 const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META;
188             }
189         }
190     };
191 
192     quote! {
193         #lower_impl
194         #lift_impl
195         #derive_ffi_traits
196     }
197 }
198 
error_meta_static_var( ident: &Ident, docstring: String, enum_: &DataEnum, attr: &ErrorAttr, ) -> syn::Result<TokenStream>199 pub(crate) fn error_meta_static_var(
200     ident: &Ident,
201     docstring: String,
202     enum_: &DataEnum,
203     attr: &ErrorAttr,
204 ) -> syn::Result<TokenStream> {
205     let name = ident_to_string(ident);
206     let module_path = mod_path()?;
207     let flat = attr.flat.is_some();
208     let non_exhaustive = attr.non_exhaustive.is_some();
209     let mut metadata_expr = quote! {
210             ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
211                 .concat_str(#module_path)
212                 .concat_str(#name)
213                 .concat_option_bool(Some(#flat))
214                 .concat_bool(false) // discr_type: None
215     };
216     if flat {
217         metadata_expr.extend(flat_error_variant_metadata(enum_)?)
218     } else {
219         metadata_expr.extend(variant_metadata(enum_)?);
220     }
221     metadata_expr.extend(quote! {
222         .concat_bool(#non_exhaustive)
223         .concat_long_str(#docstring)
224     });
225     Ok(create_metadata_items("error", &name, metadata_expr, None))
226 }
227 
flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>>228 pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
229     let variants_len =
230         try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
231     std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
232         .chain(enum_.variants.iter().map(|v| {
233             let name = ident_to_string(&v.ident);
234             let docstring = extract_docstring(&v.attrs)?;
235             Ok(quote! {
236                 .concat_str(#name)
237                 .concat_long_str(#docstring)
238             })
239         }))
240         .collect()
241 }
242 
243 #[derive(Clone, Default)]
244 pub struct ErrorAttr {
245     pub flat: Option<kw::flat_error>,
246     pub with_try_read: Option<kw::with_try_read>,
247     pub non_exhaustive: Option<kw::non_exhaustive>,
248 }
249 
250 impl UniffiAttributeArgs for ErrorAttr {
parse_one(input: ParseStream<'_>) -> syn::Result<Self>251     fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
252         let lookahead = input.lookahead1();
253         if lookahead.peek(kw::flat_error) {
254             Ok(Self {
255                 flat: input.parse()?,
256                 ..Self::default()
257             })
258         } else if lookahead.peek(kw::with_try_read) {
259             Ok(Self {
260                 with_try_read: input.parse()?,
261                 ..Self::default()
262             })
263         } else if lookahead.peek(kw::non_exhaustive) {
264             Ok(Self {
265                 non_exhaustive: input.parse()?,
266                 ..Self::default()
267             })
268         } else if lookahead.peek(kw::handle_unknown_callback_error) {
269             // Not used anymore, but still allowed
270             Ok(Self::default())
271         } else {
272             Err(lookahead.error())
273         }
274     }
275 
merge(self, other: Self) -> syn::Result<Self>276     fn merge(self, other: Self) -> syn::Result<Self> {
277         Ok(Self {
278             flat: either_attribute_arg(self.flat, other.flat)?,
279             with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?,
280             non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
281         })
282     }
283 }
284 
285 // So ErrorAttr can be used with `parse_macro_input!`
286 impl Parse for ErrorAttr {
parse(input: ParseStream<'_>) -> syn::Result<Self>287     fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
288         parse_comma_separated(input)
289     }
290 }
291 
292 impl TryFrom<ErrorAttr> for EnumAttr {
293     type Error = syn::Error;
294 
try_from(error_attr: ErrorAttr) -> Result<Self, Self::Error>295     fn try_from(error_attr: ErrorAttr) -> Result<Self, Self::Error> {
296         if error_attr.flat.is_some() {
297             Err(syn::Error::new(
298                 Span::call_site(),
299                 "flat attribute not valid for rich enum errors",
300             ))
301         } else if error_attr.with_try_read.is_some() {
302             Err(syn::Error::new(
303                 Span::call_site(),
304                 "with_try_read attribute not valid for rich enum errors",
305             ))
306         } else {
307             Ok(EnumAttr {
308                 non_exhaustive: error_attr.non_exhaustive,
309             })
310         }
311     }
312 }
313