Skip to main content

alef_codegen/
type_mapper.rs

1use alef_core::ir::{PrimitiveType, TypeRef};
2use std::borrow::Cow;
3
4/// Trait for mapping IR types to language-specific type strings.
5/// Backends implement only what differs from the Rust default.
6pub trait TypeMapper {
7    /// Map a primitive type. Default: Rust type names.
8    fn primitive(&self, prim: &PrimitiveType) -> Cow<'static, str> {
9        Cow::Borrowed(match prim {
10            PrimitiveType::Bool => "bool",
11            PrimitiveType::U8 => "u8",
12            PrimitiveType::U16 => "u16",
13            PrimitiveType::U32 => "u32",
14            PrimitiveType::U64 => "u64",
15            PrimitiveType::I8 => "i8",
16            PrimitiveType::I16 => "i16",
17            PrimitiveType::I32 => "i32",
18            PrimitiveType::I64 => "i64",
19            PrimitiveType::F32 => "f32",
20            PrimitiveType::F64 => "f64",
21            PrimitiveType::Usize => "usize",
22            PrimitiveType::Isize => "isize",
23        })
24    }
25
26    /// Map a string type. Default: "String"
27    fn string(&self) -> Cow<'static, str> {
28        Cow::Borrowed("String")
29    }
30
31    /// Map a bytes type. Default: "Vec<u8>"
32    fn bytes(&self) -> Cow<'static, str> {
33        Cow::Borrowed("Vec<u8>")
34    }
35
36    /// Map a path type. Default: "String"
37    fn path(&self) -> Cow<'static, str> {
38        Cow::Borrowed("String")
39    }
40
41    /// Map a JSON type. Default: "serde_json::Value"
42    fn json(&self) -> Cow<'static, str> {
43        Cow::Borrowed("serde_json::Value")
44    }
45
46    /// Map a unit type. Default: "()"
47    fn unit(&self) -> Cow<'static, str> {
48        Cow::Borrowed("()")
49    }
50
51    /// Map a duration type. Default: "u64" (seconds)
52    fn duration(&self) -> Cow<'static, str> {
53        Cow::Borrowed("u64")
54    }
55
56    /// Map an optional type. Default: "Option<T>"
57    fn optional(&self, inner: &str) -> String {
58        format!("Option<{inner}>")
59    }
60
61    /// Map a vec type. Default: "Vec<T>"
62    fn vec(&self, inner: &str) -> String {
63        format!("Vec<{inner}>")
64    }
65
66    /// Map a map type. Default: "HashMap<K, V>"
67    fn map(&self, key: &str, value: &str) -> String {
68        format!("HashMap<{key}, {value}>")
69    }
70
71    /// Map a named type. Default: identity.
72    fn named<'a>(&self, name: &'a str) -> Cow<'a, str> {
73        Cow::Borrowed(name)
74    }
75
76    /// Map a full TypeRef. Typically not overridden.
77    fn map_type(&self, ty: &TypeRef) -> String {
78        match ty {
79            TypeRef::Primitive(p) => self.primitive(p).into_owned(),
80            TypeRef::String | TypeRef::Char => self.string().into_owned(),
81            TypeRef::Bytes => self.bytes().into_owned(),
82            TypeRef::Path => self.path().into_owned(),
83            TypeRef::Json => self.json().into_owned(),
84            TypeRef::Unit => self.unit().into_owned(),
85            TypeRef::Optional(inner) => self.optional(&self.map_type(inner)),
86            TypeRef::Vec(inner) => self.vec(&self.map_type(inner)),
87            TypeRef::Map(k, v) => self.map(&self.map_type(k), &self.map_type(v)),
88            TypeRef::Named(name) => self.named(name).into_owned(),
89            TypeRef::Duration => self.duration().into_owned(),
90        }
91    }
92
93    /// The error wrapper type for this language. e.g. "PyResult", "napi::Result", "PhpResult"
94    fn error_wrapper(&self) -> &str;
95
96    /// Wrap a return type with error handling if needed.
97    fn wrap_return(&self, base: &str, has_error: bool) -> String {
98        if has_error {
99            format!("{}<{base}>", self.error_wrapper())
100        } else {
101            base.to_string()
102        }
103    }
104}
105
106/// Identity mapper — all type names pass through unchanged.
107/// Used as the default mapper when no backend-specific renaming is required.
108pub struct IdentityMapper;
109
110impl TypeMapper for IdentityMapper {
111    fn error_wrapper(&self) -> &str {
112        "Result"
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    /// Minimal concrete implementation of `TypeMapper` using the default Rust mappings.
121    struct RustMapper;
122
123    impl TypeMapper for RustMapper {
124        fn error_wrapper(&self) -> &str {
125            "Result"
126        }
127    }
128
129    // -------------------------------------------------------------------------
130    // map_type — default (Rust) mappings for every TypeRef variant
131    // -------------------------------------------------------------------------
132
133    #[test]
134    fn test_map_type_primitive_bool() {
135        assert_eq!(RustMapper.map_type(&TypeRef::Primitive(PrimitiveType::Bool)), "bool");
136    }
137
138    #[test]
139    fn test_map_type_primitive_integers() {
140        let mapper = RustMapper;
141        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::U8)), "u8");
142        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::U16)), "u16");
143        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::U32)), "u32");
144        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::U64)), "u64");
145        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::I8)), "i8");
146        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::I16)), "i16");
147        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::I32)), "i32");
148        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::I64)), "i64");
149        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::Usize)), "usize");
150        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::Isize)), "isize");
151    }
152
153    #[test]
154    fn test_map_type_primitive_floats() {
155        let mapper = RustMapper;
156        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::F32)), "f32");
157        assert_eq!(mapper.map_type(&TypeRef::Primitive(PrimitiveType::F64)), "f64");
158    }
159
160    #[test]
161    fn test_map_type_string_and_char() {
162        let mapper = RustMapper;
163        assert_eq!(mapper.map_type(&TypeRef::String), "String");
164        assert_eq!(mapper.map_type(&TypeRef::Char), "String");
165    }
166
167    #[test]
168    fn test_map_type_bytes() {
169        assert_eq!(RustMapper.map_type(&TypeRef::Bytes), "Vec<u8>");
170    }
171
172    #[test]
173    fn test_map_type_path() {
174        assert_eq!(RustMapper.map_type(&TypeRef::Path), "String");
175    }
176
177    #[test]
178    fn test_map_type_json() {
179        assert_eq!(RustMapper.map_type(&TypeRef::Json), "serde_json::Value");
180    }
181
182    #[test]
183    fn test_map_type_unit() {
184        assert_eq!(RustMapper.map_type(&TypeRef::Unit), "()");
185    }
186
187    #[test]
188    fn test_map_type_duration() {
189        assert_eq!(RustMapper.map_type(&TypeRef::Duration), "u64");
190    }
191
192    #[test]
193    fn test_map_type_named_identity() {
194        assert_eq!(RustMapper.map_type(&TypeRef::Named("MyConfig".to_string())), "MyConfig");
195    }
196
197    #[test]
198    fn test_map_type_optional_wraps_inner() {
199        assert_eq!(
200            RustMapper.map_type(&TypeRef::Optional(Box::new(TypeRef::Primitive(PrimitiveType::U32)))),
201            "Option<u32>"
202        );
203    }
204
205    #[test]
206    fn test_map_type_optional_nested() {
207        // Option<Option<String>>
208        let ty = TypeRef::Optional(Box::new(TypeRef::Optional(Box::new(TypeRef::String))));
209        assert_eq!(RustMapper.map_type(&ty), "Option<Option<String>>");
210    }
211
212    #[test]
213    fn test_map_type_vec_wraps_inner() {
214        assert_eq!(
215            RustMapper.map_type(&TypeRef::Vec(Box::new(TypeRef::String))),
216            "Vec<String>"
217        );
218    }
219
220    #[test]
221    fn test_map_type_vec_of_named() {
222        assert_eq!(
223            RustMapper.map_type(&TypeRef::Vec(Box::new(TypeRef::Named("Item".to_string())))),
224            "Vec<Item>"
225        );
226    }
227
228    #[test]
229    fn test_map_type_map_string_to_u32() {
230        assert_eq!(
231            RustMapper.map_type(&TypeRef::Map(
232                Box::new(TypeRef::String),
233                Box::new(TypeRef::Primitive(PrimitiveType::U32))
234            )),
235            "HashMap<String, u32>"
236        );
237    }
238
239    #[test]
240    fn test_map_type_nested_vec_in_optional() {
241        let ty = TypeRef::Optional(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
242        assert_eq!(RustMapper.map_type(&ty), "Option<Vec<String>>");
243    }
244
245    // -------------------------------------------------------------------------
246    // wrap_return — default implementation
247    // -------------------------------------------------------------------------
248
249    #[test]
250    fn test_wrap_return_no_error_passes_through() {
251        assert_eq!(RustMapper.wrap_return("String", false), "String");
252    }
253
254    #[test]
255    fn test_wrap_return_with_error_wraps_in_error_wrapper() {
256        assert_eq!(RustMapper.wrap_return("String", true), "Result<String>");
257    }
258
259    #[test]
260    fn test_wrap_return_unit_with_error() {
261        assert_eq!(RustMapper.wrap_return("()", true), "Result<()>");
262    }
263
264    // -------------------------------------------------------------------------
265    // Overriding individual methods affects map_type output
266    // -------------------------------------------------------------------------
267
268    struct CustomMapper;
269
270    impl TypeMapper for CustomMapper {
271        fn json(&self) -> Cow<'static, str> {
272            Cow::Borrowed("JsValue")
273        }
274
275        fn named<'a>(&self, name: &'a str) -> Cow<'a, str> {
276            Cow::Owned(format!("Js{name}"))
277        }
278
279        fn vec(&self, inner: &str) -> String {
280            if inner.starts_with("Vec<") {
281                "JsValue".to_string()
282            } else {
283                format!("Vec<{inner}>")
284            }
285        }
286
287        fn error_wrapper(&self) -> &str {
288            "JsResult"
289        }
290    }
291
292    #[test]
293    fn test_custom_mapper_json_override() {
294        assert_eq!(CustomMapper.map_type(&TypeRef::Json), "JsValue");
295    }
296
297    #[test]
298    fn test_custom_mapper_named_override() {
299        assert_eq!(CustomMapper.map_type(&TypeRef::Named("Config".to_string())), "JsConfig");
300    }
301
302    #[test]
303    fn test_custom_mapper_nested_vec_override() {
304        // Vec<Vec<String>> → outer vec gets inner "Vec<String>", which starts with "Vec<" → JsValue
305        let ty = TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))));
306        assert_eq!(CustomMapper.map_type(&ty), "JsValue");
307    }
308
309    #[test]
310    fn test_custom_mapper_wrap_return_with_error() {
311        assert_eq!(CustomMapper.wrap_return("String", true), "JsResult<String>");
312    }
313}