summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros/src/enum_.rs
blob: c4e49beb8b043bfc4dd0cc3ece6cff4272184e27 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Index, Token, Variant};
use uniffi_meta::{EnumMetadata, FieldMetadata, VariantMetadata};

use crate::{
    export::metadata::convert::convert_type,
    util::{assert_type_eq, create_metadata_static_var, try_read_field},
};

pub fn expand_enum(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
    let variants = match input.data {
        Data::Enum(e) => Some(e.variants),
        _ => None,
    };

    let ident = &input.ident;

    let ffi_converter_impl = enum_ffi_converter_impl(variants.as_ref(), ident);

    let meta_static_var = if let Some(variants) = variants {
        match enum_metadata(ident, variants, module_path) {
            Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
            Err(e) => e.into_compile_error(),
        }
    } else {
        syn::Error::new(Span::call_site(), "This derive must only be used on enums")
            .into_compile_error()
    };

    let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });

    quote! {
        #ffi_converter_impl
        #meta_static_var
        #type_assertion
    }
}

pub(crate) fn enum_ffi_converter_impl(
    variants: Option<&Punctuated<Variant, Token![,]>>,
    ident: &Ident,
) -> TokenStream {
    let (write_impl, try_read_impl) = match variants {
        Some(variants) => {
            let write_match_arms = variants.iter().enumerate().map(|(i, v)| {
                let v_ident = &v.ident;
                let fields = v.fields.iter().map(|f| &f.ident);
                let idx = Index::from(i + 1);
                let write_fields = v.fields.iter().map(write_field);

                quote! {
                    Self::#v_ident { #(#fields),* } => {
                        ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
                        #(#write_fields)*
                    }
                }
            });
            let write_impl = quote! {
                match obj { #(#write_match_arms)* }
            };

            let try_read_match_arms = variants.iter().enumerate().map(|(i, v)| {
                let idx = Index::from(i + 1);
                let v_ident = &v.ident;
                let try_read_fields = v.fields.iter().map(try_read_field);

                quote! {
                    #idx => Self::#v_ident { #(#try_read_fields)* },
                }
            });
            let error_format_string = format!("Invalid {ident} enum value: {{}}");
            let try_read_impl = quote! {
                ::uniffi::check_remaining(buf, 4)?;

                Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) {
                    #(#try_read_match_arms)*
                    v => ::uniffi::deps::anyhow::bail!(#error_format_string, v),
                })
            };

            (write_impl, try_read_impl)
        }
        None => {
            let unimplemented = quote! { ::std::unimplemented!() };
            (unimplemented.clone(), unimplemented)
        }
    };

    quote! {
        #[automatically_derived]
        impl ::uniffi::RustBufferFfiConverter for #ident {
            type RustType = Self;

            fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
                #write_impl
            }

            fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
                #try_read_impl
            }
        }
    }
}

fn enum_metadata(
    ident: &Ident,
    variants: Punctuated<Variant, Token![,]>,
    module_path: Vec<String>,
) -> syn::Result<EnumMetadata> {
    let name = ident.to_string();
    let variants = variants
        .iter()
        .map(variant_metadata)
        .collect::<syn::Result<_>>()?;

    Ok(EnumMetadata {
        module_path,
        name,
        variants,
    })
}

pub(crate) fn variant_metadata(v: &Variant) -> syn::Result<VariantMetadata> {
    let name = v.ident.to_string();
    let fields = v
        .fields
        .iter()
        .map(|f| field_metadata(f, v))
        .collect::<syn::Result<_>>()?;

    Ok(VariantMetadata { name, fields })
}

fn field_metadata(f: &Field, v: &Variant) -> syn::Result<FieldMetadata> {
    let name = f
        .ident
        .as_ref()
        .ok_or_else(|| {
            syn::Error::new_spanned(
                v,
                "UniFFI only supports enum variants with named fields (or no fields at all)",
            )
        })?
        .to_string();

    Ok(FieldMetadata {
        name,
        ty: convert_type(&f.ty)?,
    })
}

fn write_field(f: &Field) -> TokenStream {
    let ident = &f.ident;
    let ty = &f.ty;

    quote! {
        <#ty as ::uniffi::FfiConverter>::write(#ident, buf);
    }
}