commonlibsse_ng_proc_macro_common\ffi_enum/
mod.rs1pub(crate) mod attr_args;
3
4use core::str::FromStr as _;
5
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8use syn::ItemEnum;
9
10use crate::enum_parser::{filter_default_attr, parse_discriminant, select_bitflags_type};
11
12pub fn ffi_enum(attrs: TokenStream, item_enum: ItemEnum) -> TokenStream {
13 let args = {
14 let attr_args = match darling::ast::NestedMeta::parse_meta_list(attrs) {
15 Ok(v) => v,
16 Err(e) => {
17 return darling::Error::from(e).write_errors();
18 }
19 };
20
21 match <attr_args::MacroArgs as darling::FromMeta>::from_list(&attr_args) {
22 Ok(v) => v,
23 Err(e) => {
24 return e.write_errors();
25 }
26 }
27 };
28 ffi_enum_(args, item_enum).unwrap_or_else(syn::Error::into_compile_error)
29}
30
31fn ffi_enum_(args: attr_args::MacroArgs, item_enum: ItemEnum) -> syn::Result<TokenStream> {
32 let enum_ident = &item_enum.ident;
33 let vis = &item_enum.vis;
34
35 let repr_attr = item_enum.attrs.iter().find(|attr| attr.meta.path().is_ident("repr"));
36 let bitflags_type = match repr_attr {
37 Some(repr_attr) => select_bitflags_type(repr_attr)?,
38 None => quote! { usize },
39 };
40
41 let flags_ident = match args.flag_name {
43 Some(name) => format_ident!("{name}"),
44 None => format_ident!("{enum_ident}_CEnum"),
45 };
46
47 let DiscriminantData { bitflags, to_enum_arms, from_enum_arms, default_value } =
49 DiscriminantData::from_item_enum(&item_enum);
50 let discriminant_count = to_enum_arms.len();
51 let discriminant_count_doc = format!("Returns `{discriminant_count}`");
52
53 let struct_doc = format!("Auto-generated FFI type for `{enum_ident}`.");
54 let to_enum_doc =
55 format!("Returns `Some({enum_ident})` if the value is valid, otherwise `None`.");
56
57 let expanded = quote! {
58 #item_enum
59
60 #[doc = #struct_doc]
61 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
79 #[repr(transparent)]
80 #vis struct #flags_ident(#vis #bitflags_type);
81
82 impl Default for #flags_ident {
83 #[inline]
84 fn default() -> Self {
85 Self(#default_value)
86 }
87 }
88
89 impl #flags_ident {
90 #(#bitflags;)*
91
92 #[doc = #to_enum_doc]
95 #[inline]
96 pub const fn to_enum(self) -> Option<#enum_ident> {
97 match self.0 {
98 #(#to_enum_arms,)*
99 _ => None,
100 }
101 }
102
103 #[inline]
107 pub const fn from_enum(e: #enum_ident) -> Self {
108 match e {
109 #(#from_enum_arms,)*
110 }
111 }
112
113 #[doc = #discriminant_count_doc]
116 #[inline]
117 pub const fn count() -> usize {
118 #discriminant_count
119 }
120 }
121
122 impl TryFrom<#flags_ident> for #enum_ident {
123 type Error = &'static str;
124
125 #[inline]
126 fn try_from(value: #flags_ident) -> Result<Self, Self::Error> {
127 #flags_ident::to_enum(value).ok_or("Couldn't convert value to enum.")
128 }
129 }
130 impl From<#enum_ident> for #flags_ident {
131 #[inline]
132 fn from(value: #enum_ident) -> Self {
133 Self::from_enum(value)
134 }
135 }
136 };
137
138 Ok(expanded)
139}
140
141pub(crate) struct DiscriminantData {
143 pub(crate) bitflags: Vec<TokenStream>,
145 pub(crate) to_enum_arms: Vec<TokenStream>,
146 pub(crate) from_enum_arms: Vec<TokenStream>,
147 pub(crate) default_value: TokenStream,
148}
149
150macro_rules! to_non_suffix_num_token {
151 ($value:expr) => {
152 TokenStream::from_str(&format!("{}", $value)).unwrap()
153 };
154}
155
156impl DiscriminantData {
158 pub(crate) fn from_item_enum(item_enum: &ItemEnum) -> Self {
159 let enum_ident = &item_enum.ident;
160
161 let mut current_value = 0;
162 let mut bitflags = Vec::new();
163 let mut to_enum_arms = Vec::new();
164 let mut from_enum_arms = Vec::new();
165 let mut default_value = quote! { 0 };
166
167 for variant in &item_enum.variants {
168 let var_name = &variant.ident;
169 let (variant_attrs, found_default) = filter_default_attr(&variant.attrs);
170
171 if let Some((_, expr)) = &variant.discriminant {
173 if let Ok(parsed) = parse_discriminant(expr) {
174 current_value = parsed; if found_default {
176 default_value = to_non_suffix_num_token!(current_value);
177 }
178 };
179 };
180 let value = to_non_suffix_num_token!(current_value);
181
182 bitflags.push(quote! {
184 #(#variant_attrs)*
185 #[allow(non_upper_case_globals)]
186 pub const #var_name: Self = Self(#value)
187 });
188
189 to_enum_arms.push(quote! {
191 #value => Some(#enum_ident::#var_name)
192 });
193
194 from_enum_arms.push(quote! {
196 #enum_ident::#var_name => Self::#var_name
197 });
198
199 current_value += 1;
200 }
201
202 Self { bitflags, to_enum_arms, from_enum_arms, default_value }
203 }
204}