From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- third_party/rust/uniffi_macros/src/enum_.rs | 308 ++++++++++++++++++++++------ 1 file changed, 242 insertions(+), 66 deletions(-) (limited to 'third_party/rust/uniffi_macros/src/enum_.rs') diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs index 32abfa08cc..fd98da3129 100644 --- a/third_party/rust/uniffi_macros/src/enum_.rs +++ b/third_party/rust/uniffi_macros/src/enum_.rs @@ -1,13 +1,47 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Data, DataEnum, DeriveInput, Field, Index}; +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, ident_to_string, mod_path, tagged_impl_header, - try_metadata_value_from_usize, try_read_field, + 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, }; -pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result { +fn extract_repr(attrs: &[Attribute]) -> syn::Result> { + 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()), + // while the default repr for an enum is `isize` we don't apply that default here. + _ => None, + } + } + _ => None, + }; + Ok(()) + })? + } + } + Ok(result) +} + +pub fn expand_enum( + input: DeriveInput, + // Attributes from #[derive_error_for_udl()], if we are in udl mode + attr_from_udl_mode: Option, + udl_mode: bool, +) -> syn::Result { let enum_ = match input.data { Data::Enum(e) => e, _ => { @@ -18,10 +52,17 @@ pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result TokenStream { enum_or_error_ffi_converter_impl( ident, enum_, udl_mode, + attr, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } @@ -47,11 +90,13 @@ 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 }, ) } @@ -60,6 +105,7 @@ 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); @@ -69,19 +115,50 @@ fn enum_or_error_ffi_converter_impl( Ok(p) => p, Err(e) => return e.into_compile_error(), }; - let write_match_arms = enum_.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); + 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::>(); + 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>::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)* + 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)* } }; @@ -89,10 +166,17 @@ fn enum_or_error_ffi_converter_impl( 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); - quote! { - #idx => Self::#v_ident { #(#try_read_fields)* }, + 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: {{}}"); @@ -127,69 +211,161 @@ fn enum_or_error_ffi_converter_impl( } } -fn write_field(f: &Field) -> TokenStream { - let ident = &f.ident; - let ty = &f.ty; - - quote! { - <#ty as ::uniffi::Lower>::write(#ident, buf); - } -} - -pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result { +pub(crate) fn enum_meta_static_var( + ident: &Ident, + docstring: String, + discr_type: Option, + enum_: &DataEnum, + attr: &EnumAttr, +) -> syn::Result { 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) // forced_flatness }; + metadata_expr.extend(match discr_type { + None => quote! { .concat_bool(false) }, + Some(t) => quote! { .concat_bool(true).concat(<#t as ::uniffi::Lower>::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 { + let Some((_, e)) = &v.discriminant else { + return Ok(quote! { .concat_bool(false) }); + }; + // Attempting to expose an enum value which we don't understand is a hard-error + // rather than silently ignoring it. If we had the ability to emit a warning that + // might make more sense. + + // We can't sanely handle most expressions other than literals, but we can handle + // negative literals. + 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> { 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 + .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(|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() - .ok_or_else(|| - syn::Error::new_spanned( - v, - "UniFFI only supports enum variants with named fields (or no fields at all)", - ) - ) - .map(ident_to_string) - }) - .collect::>>()?; - - let name = ident_to_string(&v.ident); - let field_types = v.fields.iter().map(|f| &f.ty); - Ok(quote! { - .concat_str(#name) - .concat_value(#fields_len) - #( - .concat_str(#field_names) - .concat(<#field_types as ::uniffi::Lower>::TYPE_ID_META) - // field defaults not yet supported for enums - .concat_bool(false) - )* - }) - }) - ) + .map(|f| f.ident.as_ref().map(ident_to_string).unwrap_or_default()) + .collect::>(); + + 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::>>()?; + + Ok(quote! { + .concat_str(#name) + #value_tokens + .concat_value(#fields_len) + #( + .concat_str(#field_names) + .concat(<#field_types as ::uniffi::Lower>::TYPE_ID_META) + // field defaults not yet supported for enums + .concat_bool(false) + .concat_long_str(#field_docstrings) + )* + .concat_long_str(#docstring) + }) + })) .collect() } + +#[derive(Default)] +pub struct EnumAttr { + pub non_exhaustive: Option, +} + +// So ErrorAttr can be used with `parse_macro_input!` +impl Parse for EnumAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for EnumAttr { + fn parse_one(input: ParseStream<'_>) -> syn::Result { + 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 { + Ok(Self { + non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?, + }) + } +} -- cgit v1.2.3