Skip to main content

ts_gen/codegen/
enums.rs

1//! Enum code generation: string enums and numeric enums.
2//!
3//! String enums use wasm_bindgen's native string enum support:
4//!
5//! ```rust,ignore
6//! #[wasm_bindgen]
7//! #[derive(Debug, Clone, Copy, PartialEq, Eq)]
8//! pub enum QueueContentType {
9//!     #[wasm_bindgen(js_name = "text")]
10//!     Text,
11//!     #[wasm_bindgen(js_name = "bytes")]
12//!     Bytes,
13//! }
14//! ```
15//!
16//! Numeric enums use `#[repr(u32)]` or `#[repr(i32)]` (selected based on
17//! discriminant values) with wasm_bindgen:
18//!
19//! ```rust,ignore
20//! #[wasm_bindgen]
21//! #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22//! #[repr(u32)]
23//! pub enum ImportType {
24//!     Static = 1,
25//!     Dynamic = 2,
26//!     ImportMeta = 3,
27//! }
28//! ```
29
30use proc_macro2::TokenStream;
31use quote::quote;
32
33use crate::ir::{NumericEnumDecl, StringEnumDecl};
34
35/// Generate a wasm_bindgen string enum.
36pub fn generate_string_enum(decl: &StringEnumDecl) -> TokenStream {
37    let name = super::typemap::make_ident(&decl.name);
38
39    let variants: Vec<_> = decl
40        .variants
41        .iter()
42        .map(|v| {
43            let rust_name = super::typemap::make_ident(&v.rust_name);
44            let js_value = &v.js_value;
45            quote! {
46                #[wasm_bindgen(js_name = #js_value)]
47                #rust_name
48            }
49        })
50        .collect();
51
52    quote! {
53        #[wasm_bindgen]
54        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
55        pub enum #name {
56            #(#variants,)*
57        }
58    }
59}
60
61/// Determine the appropriate repr for a numeric enum based on its discriminant values.
62fn numeric_enum_repr(decl: &NumericEnumDecl) -> TokenStream {
63    let min = decl.variants.iter().map(|v| v.value).min().unwrap_or(0);
64    let max = decl.variants.iter().map(|v| v.value).max().unwrap_or(0);
65
66    if min >= 0 && max <= u32::MAX as i64 {
67        quote! { u32 }
68    } else if min >= i32::MIN as i64 && max <= i32::MAX as i64 {
69        quote! { i32 }
70    } else {
71        // Fallback — wasm_bindgen doesn't support i64 repr, so clamp to i32
72        // and emit the best we can. This is an extreme edge case.
73        quote! { i32 }
74    }
75}
76
77/// Generate a wasm_bindgen numeric enum with automatically selected repr.
78pub fn generate_numeric_enum(decl: &NumericEnumDecl) -> TokenStream {
79    let name = super::typemap::make_ident(&decl.name);
80    let repr = numeric_enum_repr(decl);
81
82    let has_negative = decl.variants.iter().any(|v| v.value < 0);
83
84    let variants: Vec<_> = decl
85        .variants
86        .iter()
87        .map(|v| {
88            let rust_name = super::typemap::make_ident(&v.rust_name);
89            let doc = crate::codegen::doc_tokens(&v.doc);
90            if has_negative {
91                let value = i32::try_from(v.value).unwrap_or({
92                    // Value out of i32 range — truncation is lossy but we
93                    // already warned at parse time if out of i64 range.
94                    v.value as i32
95                });
96                quote! {
97                    #doc
98                    #rust_name = #value
99                }
100            } else {
101                let value = u32::try_from(v.value).unwrap_or(v.value as u32);
102                quote! {
103                    #doc
104                    #rust_name = #value
105                }
106            }
107        })
108        .collect();
109
110    quote! {
111        #[wasm_bindgen]
112        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
113        #[repr(#repr)]
114        pub enum #name {
115            #(#variants,)*
116        }
117    }
118}