use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, Variant,
};
use crate::util::{
create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs,
};
fn extract_repr(attrs: &[Attribute]) -> syn::Result<Option<Ident>> {
let mut result = None;
for attr in attrs {
if attr.path().is_ident("repr") {
attr.parse_nested_meta(|meta| {
result = match meta.path.get_ident() {
Some(i) => {
let s = i.to_string();
match s.as_str() {
"u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32"
| "i64" | "isize" => Some(i.clone()),
_ => None,
}
}
_ => None,
};
Ok(())
})?
}
}
Ok(result)
}
pub fn expand_enum(
input: DeriveInput,
attr_from_udl_mode: Option<EnumAttr>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let enum_ = match input.data {
Data::Enum(e) => e,
_ => {
return Err(syn::Error::new(
Span::call_site(),
"This derive must only be used on enums",
))
}
};
let ident = &input.ident;
let docstring = extract_docstring(&input.attrs)?;
let discr_type = extract_repr(&input.attrs)?;
let mut attr: EnumAttr = input.attrs.parse_uniffi_attr_args()?;
if let Some(attr_from_udl_mode) = attr_from_udl_mode {
attr = attr.merge(attr_from_udl_mode)?;
}
let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode, &attr);
let meta_static_var = (!udl_mode).then(|| {
enum_meta_static_var(ident, docstring, discr_type, &enum_, &attr)
.unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
#ffi_converter_impl
#meta_static_var
})
}
pub(crate) fn enum_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
attr: &EnumAttr,
) -> TokenStream {
enum_or_error_ffi_converter_impl(
ident,
enum_,
udl_mode,
attr,
quote! { ::uniffi::metadata::codes::TYPE_ENUM },
)
}
pub(crate) fn rich_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
attr: &EnumAttr,
) -> TokenStream {
enum_or_error_ffi_converter_impl(
ident,
enum_,
udl_mode,
attr,
quote! { ::uniffi::metadata::codes::TYPE_ENUM },
)
}
fn enum_or_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
attr: &EnumAttr,
metadata_type_code: TokenStream,
) -> TokenStream {
let name = ident_to_string(ident);
let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode);
let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode);
let mod_path = match mod_path() {
Ok(p) => p,
Err(e) => return e.into_compile_error(),
};
let mut write_match_arms: Vec<_> = enum_
.variants
.iter()
.enumerate()
.map(|(i, v)| {
let v_ident = &v.ident;
let field_idents = v
.fields
.iter()
.enumerate()
.map(|(i, f)| {
f.ident
.clone()
.unwrap_or_else(|| Ident::new(&format!("e{i}"), f.span()))
})
.collect::<Vec<Ident>>();
let idx = Index::from(i + 1);
let write_fields =
std::iter::zip(v.fields.iter(), field_idents.iter()).map(|(f, ident)| {
let ty = &f.ty;
quote! {
<#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf);
}
});
let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
let fields = if is_tuple {
quote! { ( #(#field_idents),* ) }
} else {
quote! { { #(#field_idents),* } }
};
quote! {
Self::#v_ident #fields => {
::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
#(#write_fields)*
}
}
})
.collect();
if attr.non_exhaustive.is_some() {
write_match_arms.push(quote! {
_ => panic!("Unexpected variant in non-exhaustive enum"),
})
}
let write_impl = quote! {
match obj { #(#write_match_arms)* }
};
let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
let idx = Index::from(i + 1);
let v_ident = &v.ident;
let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
let try_read_fields = v.fields.iter().map(try_read_field);
if is_tuple {
quote! {
#idx => Self::#v_ident ( #(#try_read_fields)* ),
}
} else {
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),
})
};
quote! {
#[automatically_derived]
unsafe #impl_spec {
::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag);
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
}
const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_type_code)
.concat_str(#mod_path)
.concat_str(#name);
}
#derive_ffi_traits
}
}
pub(crate) fn enum_meta_static_var(
ident: &Ident,
docstring: String,
discr_type: Option<Ident>,
enum_: &DataEnum,
attr: &EnumAttr,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
let module_path = mod_path()?;
let non_exhaustive = attr.non_exhaustive.is_some();
let mut metadata_expr = quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
.concat_str(#module_path)
.concat_str(#name)
.concat_option_bool(None) };
metadata_expr.extend(match discr_type {
None => quote! { .concat_bool(false) },
Some(t) => quote! { .concat_bool(true).concat(<#t as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) }
});
metadata_expr.extend(variant_metadata(enum_)?);
metadata_expr.extend(quote! {
.concat_bool(#non_exhaustive)
.concat_long_str(#docstring)
});
Ok(create_metadata_items("enum", &name, metadata_expr, None))
}
fn variant_value(v: &Variant) -> syn::Result<TokenStream> {
let Some((_, e)) = &v.discriminant else {
return Ok(quote! { .concat_bool(false) });
};
let mut negate = false;
let lit = match e {
Expr::Lit(lit) => lit,
Expr::Unary(expr_unary) if matches!(expr_unary.op, syn::UnOp::Neg(_)) => {
negate = true;
match *expr_unary.expr {
Expr::Lit(ref lit) => lit,
_ => {
return Err(syn::Error::new_spanned(
e,
"UniFFI disciminant values must be a literal",
));
}
}
}
_ => {
return Err(syn::Error::new_spanned(
e,
"UniFFI disciminant values must be a literal",
));
}
};
let Lit::Int(ref intlit) = lit.lit else {
return Err(syn::Error::new_spanned(
v,
"UniFFI disciminant values must be a literal integer",
));
};
if !intlit.suffix().is_empty() {
return Err(syn::Error::new_spanned(
intlit,
"integer literals with suffix not supported by UniFFI here",
));
}
let digits = if negate {
format!("-{}", intlit.base10_digits())
} else {
intlit.base10_digits().to_string()
};
Ok(quote! {
.concat_bool(true)
.concat_value(::uniffi::metadata::codes::LIT_INT)
.concat_str(#digits)
})
}
pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
let variants_len =
try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
.chain(enum_.variants.iter().map(|v| {
let fields_len = try_metadata_value_from_usize(
v.fields.len(),
"UniFFI limits enum variants to 256 fields",
)?;
let field_names = v
.fields
.iter()
.map(|f| f.ident.as_ref().map(ident_to_string).unwrap_or_default())
.collect::<Vec<_>>();
let name = ident_to_string(&v.ident);
let value_tokens = variant_value(v)?;
let docstring = extract_docstring(&v.attrs)?;
let field_types = v.fields.iter().map(|f| &f.ty);
let field_docstrings = v
.fields
.iter()
.map(|f| extract_docstring(&f.attrs))
.collect::<syn::Result<Vec<_>>>()?;
Ok(quote! {
.concat_str(#name)
#value_tokens
.concat_value(#fields_len)
#(
.concat_str(#field_names)
.concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
.concat_bool(false)
.concat_long_str(#field_docstrings)
)*
.concat_long_str(#docstring)
})
}))
.collect()
}
#[derive(Default)]
pub struct EnumAttr {
pub non_exhaustive: Option<kw::non_exhaustive>,
}
impl Parse for EnumAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
parse_comma_separated(input)
}
}
impl UniffiAttributeArgs for EnumAttr {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::non_exhaustive) {
Ok(Self {
non_exhaustive: input.parse()?,
})
} else {
Err(lookahead.error())
}
}
fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
})
}
}