use std::fmt::Display;
use anyhow::Context;
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{
parse_quote,
Field,
Fields,
FieldsNamed,
FieldsUnnamed,
Item,
ItemEnum,
ItemMod,
Type,
TypeInfer,
Variant,
};
use crate::extern_module_translator::{
Arg,
Function,
ReferenceParameters,
RustWrapperType,
WrapperType,
};
use crate::utils::{check_cfg_attr, is_primitive};
use crate::BuildContext;
pub fn get_enums_from_module(
module: &ItemMod,
context: &BuildContext,
) -> anyhow::Result<Vec<ItemEnum>> {
Ok(module
.content
.as_ref()
.context("The module is empty.")?
.1
.iter()
.filter_map(|item| match item {
Item::Enum(enum_item) => Some(enum_item),
_ => None,
})
.filter(|item| {
item.attrs
.iter()
.map(|attr| check_cfg_attr(attr, context))
.all(|el| el)
})
.cloned()
.collect())
}
pub fn enum_tag_name(enum_name: impl Display) -> String {
format!("{enum_name}Tag")
}
pub fn is_primitive_enum(enum_item: &ItemEnum) -> bool {
enum_item.variants.iter().all(|v| v.fields == Fields::Unit)
}
pub fn variant_wrapper_ident(enum_ident: impl Display, variant_ident: impl Display) -> Ident {
Ident::new(&format!("{enum_ident}_{variant_ident}"), Span::call_site())
}
pub fn field_getter_ident(field: &Field, field_idx: usize) -> Ident {
let id_str = match &field.ident {
Some(name) => format!("get_{name}"),
None => format!("get_{field_idx}"),
};
Ident::new(&id_str, Span::call_site())
}
pub fn create_field_getter_function(
enum_item: &ItemEnum,
variant: &Variant,
field: &Field,
field_idx: usize,
) -> Function {
let variant_wrapper_name = variant_wrapper_ident(&enum_item.ident, &variant.ident);
let return_type: Ident = syn::parse_str(&field_type(field).unwrap_type()).unwrap();
Function {
arguments: vec![Arg {
arg_name: "self".to_owned(),
typ: WrapperType {
original_type_name: syn::parse_str(&variant_wrapper_name.to_string()).unwrap(),
wrapper_name: variant_wrapper_name.to_string(),
rust_type: RustWrapperType::Custom,
reference_parameters: Some(ReferenceParameters::shared()),
},
}],
return_type: Some(WrapperType {
original_type_name: parse_quote! {#return_type},
wrapper_name: return_type.to_string(),
rust_type: if is_primitive_field(field) {
RustWrapperType::Primitive
} else {
RustWrapperType::Custom
},
reference_parameters: None,
}),
name: field_getter_ident(field, field_idx).to_string(),
}
}
pub fn create_variant_getter_function(enum_item: &ItemEnum, variant: &Variant) -> Option<Function> {
let enum_name = enum_item.ident.to_string();
if is_many_fields_variant(variant)
|| (is_single_field_variant(variant) && !is_ignored_variant(variant))
{
Some(Function {
arguments: vec![Arg {
arg_name: "self".to_owned(),
typ: WrapperType {
original_type_name: syn::parse_str(&enum_name).unwrap(),
wrapper_name: enum_name.clone(),
rust_type: RustWrapperType::Custom,
reference_parameters: Some(ReferenceParameters::shared()),
},
}],
return_type: create_variant_getter_return_type(enum_item, variant),
name: variant_getter_ident(variant).to_string(),
})
} else {
None
}
}
pub fn create_variant_getter_return_type(
enum_item: &ItemEnum,
variant: &Variant,
) -> Option<WrapperType> {
if is_single_field_variant(variant) {
single_field_variant_getter(variant)
} else if is_many_fields_variant(variant) {
Some(many_fields_variant_getter(enum_item, variant))
} else {
None
}
}
pub fn many_fields_variant_getter(enum_item: &ItemEnum, variant: &Variant) -> WrapperType {
let wrapper_name = variant_wrapper_ident(&enum_item.ident, &variant.ident);
WrapperType {
original_type_name: parse_quote! {#wrapper_name},
wrapper_name: wrapper_name.to_string(),
rust_type: RustWrapperType::Custom,
reference_parameters: None,
}
}
pub fn single_field_variant_getter(variant: &Variant) -> Option<WrapperType> {
let field = &get_fields(variant).unwrap()[0];
match &field_type(field) {
FieldType::Type(field_type) => {
let return_type: Ident = syn::parse_str(field_type).unwrap();
Some(WrapperType {
original_type_name: parse_quote! {#return_type},
wrapper_name: return_type.to_string(),
rust_type: if is_primitive_field(field) {
RustWrapperType::Primitive
} else {
RustWrapperType::Custom
},
reference_parameters: None,
})
}
FieldType::Ignored => None,
}
}
pub fn variant_getter_ident(v: &Variant) -> Ident {
Ident::new(
&format!("get_{}", v.ident.to_string().to_case(Case::Snake)),
Span::call_site(),
)
}
#[derive(PartialEq, Eq)]
pub enum FieldType {
Type(String),
Ignored,
}
impl FieldType {
pub fn unwrap_type(self) -> String {
match self {
FieldType::Type(s) => s,
_ => panic!("Invalid field type"),
}
}
}
pub fn field_type(field: &Field) -> FieldType {
match &field.ty {
Type::Path(type_path) => FieldType::Type(
type_path
.path
.get_ident()
.unwrap_or_else(|| panic!("Invalid ident of an enum variant field {field:?}"))
.to_string(),
),
Type::Infer(TypeInfer { .. }) => FieldType::Ignored,
_ => panic!("Invalid type of an enum variant field {field:?}"),
}
}
pub fn is_primitive_field(field: &Field) -> bool {
is_primitive(field_type(field).unwrap_type().as_str())
}
pub fn field_name(field: &Field) -> Option<String> {
field.ident.as_ref().map(|i| i.to_string())
}
pub fn is_many_fields_variant(v: &Variant) -> bool {
match &v.fields {
syn::Fields::Named(FieldsNamed { named: fields, .. })
| syn::Fields::Unnamed(FieldsUnnamed {
unnamed: fields, ..
}) => fields.len() > 1,
syn::Fields::Unit => false,
}
}
pub fn is_single_field_variant(v: &Variant) -> bool {
match &v.fields {
syn::Fields::Named(FieldsNamed { named: fields, .. })
| syn::Fields::Unnamed(FieldsUnnamed {
unnamed: fields, ..
}) => fields.len() == 1,
syn::Fields::Unit => false,
}
}
pub fn is_unit_variant(v: &Variant) -> bool {
matches!(&v.fields, syn::Fields::Unit)
}
pub fn is_ignored_variant(v: &Variant) -> bool {
match get_fields(v) {
Some(fields) if fields.len() == 1 => field_type(&fields[0]) == FieldType::Ignored,
_ => false,
}
}
pub fn get_fields(v: &Variant) -> Option<&Punctuated<Field, Comma>> {
match &v.fields {
syn::Fields::Named(FieldsNamed { named: fields, .. })
| syn::Fields::Unnamed(FieldsUnnamed {
unnamed: fields, ..
}) => Some(fields),
syn::Fields::Unit => None,
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use syn::ItemMod;
use super::*;
use crate::utils::helpers;
#[test]
fn test_getting_enums_from_module() {
let rust_code = "
mod ffi {
enum En1 {
V1,
}
enum En2 {
V1,
}
struct SomeStructToBeIgnored {
field: String,
}
}
";
let module: ItemMod = syn::parse_str(rust_code).unwrap();
let enums: Vec<String> = get_enums_from_module(&module, &helpers::get_context())
.unwrap()
.into_iter()
.map(|enum_item| enum_item.ident.to_string())
.collect();
assert_eq!(enums, vec!["En1".to_owned(), "En2".to_owned()]);
}
}