commonlibsse_ng_proc_macro_common\ffi_enum/
mod.rs

1//! generate bitflags from enum
2pub(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    // Generate flag struct name: MyEnum -> MyEnumFlags
42    let flags_ident = match args.flag_name {
43        Some(name) => format_ident!("{name}"),
44        None => format_ident!("{enum_ident}_CEnum"),
45    };
46
47    // Generate bitflags and match arms
48    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        /// # When use this?
62        /// C's enum is really just a number and there is no guarantee that it will fit within the enum.
63        /// Therefore, it is used for the following cases that **cannot be controlled** by Rust.
64        /// - C++ members
65        /// - Function return values.
66        ///
67        /// # When not to use it?
68        /// Mainly those that can be controlled for safety on the Rust side.
69        /// - Function Arguments
70        ///
71        /// # Convenient methods
72        /// - `to_enum`/`from_enum`: To inter-convert enums.
73        /// - `count`: Returns the number of defined discriminants.
74        ///
75        /// # Memory Layout
76        /// It will always have `#[repr(transparent)]`.
77        /// In other words, it is equivalent to the size specified in `repr`.
78        #[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            /// Converts to the corresponding `enum` variant.
93            ///
94            #[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            /// Creates the struct from the `enum`.
104            ///
105            /// This allows for easy conversion back to the FFI-friendly representation.
106            #[inline]
107            pub const fn from_enum(e: #enum_ident) -> Self {
108                match e {
109                    #(#from_enum_arms,)*
110                }
111            }
112
113            /// Number of discriminant in enum.
114            ///
115            #[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
141/// Struct to store discriminant information and current value
142pub(crate) struct DiscriminantData {
143    /// .e.g `pub const #var_name: Self = Self(#value)`
144    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
156/// Generates the discriminants for the enum and prepares the corresponding quote items
157impl 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 use explicit discriminant, change from current discriminant.
172            if let Some((_, expr)) = &variant.discriminant {
173                if let Ok(parsed) = parse_discriminant(expr) {
174                    current_value = parsed; // Set the current value
175                    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            // Add bitflags constant
183            bitflags.push(quote! {
184                #(#variant_attrs)*
185                #[allow(non_upper_case_globals)]
186                pub const #var_name: Self = Self(#value)
187            });
188
189            // Add to_enum match arms
190            to_enum_arms.push(quote! {
191                #value => Some(#enum_ident::#var_name)
192            });
193
194            // Add from_enum match arms
195            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}