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