Skip to main content

alef/backends/go/
type_map.rs

1use std::borrow::Cow;
2
3use crate::codegen::naming::go_type_name;
4use crate::codegen::type_mapper::TypeMapper;
5use crate::core::ir::{PrimitiveType, TypeRef};
6
7/// TypeMapper for Go bindings.
8///
9/// Maps Rust types to idiomatic Go types:
10/// - Integers use Go's explicit-width types (uint8, int32, etc.)
11/// - usize/isize map to uint/int (platform-native width)
12/// - `Optional<T>` becomes `*T` (nullable pointer)
13/// - `Vec<T>` becomes `[]T`
14/// - `Map<K,V>` becomes `map[K]V`
15/// - JSON becomes json.RawMessage
16/// - Unit becomes "" (void in Go — no type in return position)
17/// - Duration becomes uint64 (milliseconds)
18pub struct GoMapper;
19
20impl TypeMapper for GoMapper {
21    fn primitive(&self, prim: &PrimitiveType) -> Cow<'static, str> {
22        Cow::Borrowed(match prim {
23            PrimitiveType::Bool => "bool",
24            PrimitiveType::U8 => "uint8",
25            PrimitiveType::U16 => "uint16",
26            PrimitiveType::U32 => "uint32",
27            PrimitiveType::U64 => "uint64",
28            PrimitiveType::I8 => "int8",
29            PrimitiveType::I16 => "int16",
30            PrimitiveType::I32 => "int32",
31            PrimitiveType::I64 => "int64",
32            PrimitiveType::F32 => "float32",
33            PrimitiveType::F64 => "float64",
34            PrimitiveType::Usize => "uint",
35            PrimitiveType::Isize => "int",
36        })
37    }
38
39    fn string(&self) -> Cow<'static, str> {
40        Cow::Borrowed("string")
41    }
42
43    fn bytes(&self) -> Cow<'static, str> {
44        Cow::Borrowed("[]byte")
45    }
46
47    fn path(&self) -> Cow<'static, str> {
48        Cow::Borrowed("string")
49    }
50
51    fn json(&self) -> Cow<'static, str> {
52        Cow::Borrowed("json.RawMessage")
53    }
54
55    fn unit(&self) -> Cow<'static, str> {
56        Cow::Borrowed("") // void — no type in Go return position
57    }
58
59    fn duration(&self) -> Cow<'static, str> {
60        Cow::Borrowed("uint64")
61    }
62
63    fn optional(&self, inner: &str) -> String {
64        format!("*{inner}")
65    }
66
67    fn vec(&self, inner: &str) -> String {
68        format!("[]{inner}")
69    }
70
71    fn map(&self, key: &str, value: &str) -> String {
72        format!("map[{key}]{value}")
73    }
74
75    fn named<'a>(&self, name: &'a str) -> Cow<'a, str> {
76        Cow::Owned(go_type_name(name))
77    }
78
79    fn error_wrapper(&self) -> &str {
80        "error"
81    }
82}
83
84/// Maps a TypeRef to its Go type representation.
85/// Used for non-optional types in general contexts.
86///
87/// Delegates to [`GoMapper`] for exhaustive TypeRef handling.
88pub fn go_type(ty: &TypeRef) -> Cow<'static, str> {
89    Cow::Owned(GoMapper.map_type(ty))
90}
91
92/// Maps a TypeRef to its optional Go type representation (pointer for option).
93///
94/// If the type is already `Optional`, delegates to `go_type` (which produces `*T`).
95/// Slices (`Vec<T>`, `Bytes`) and maps are already reference types in Go — they
96/// are not wrapped in a pointer because `*[]T` and `*map[K]V` are unidiomatic
97/// and unnecessary.
98/// String types (String, Char, Path) are wrapped in pointer: `*string`.
99/// All other non-reference types are wrapped in a pointer: `*T`.
100pub fn go_optional_type(ty: &TypeRef) -> Cow<'static, str> {
101    match ty {
102        // Already optional or reference types — use direct mapping
103        TypeRef::Optional(_) | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Bytes => go_type(ty),
104        // String types and all other types are wrapped in pointer for optionality
105        TypeRef::String
106        | TypeRef::Char
107        | TypeRef::Path
108        | TypeRef::Json
109        | TypeRef::Named(_)
110        | TypeRef::Primitive(_)
111        | TypeRef::Duration
112        | TypeRef::Unit => Cow::Owned(format!("*{}", GoMapper.map_type(ty))),
113    }
114}
115
116/// Returns the Go zero-value expression for a return-type, used in `return <zero>, fmt.Errorf(...)`
117/// early exits.
118///
119/// Must stay in sync with the return-signature logic in `gen_bindings::methods` and
120/// `gen_bindings::functions`: scalar primitives and Duration stay as value types and
121/// need an explicit zero literal (`0`, `false`); scalar types (String, Char, Path, Json)
122/// also stay as value types and use empty string `""`; everything else (Named, Vec, Map,
123/// Bytes, Optional) is emitted as a pointer or reference type whose zero is `nil`.
124pub fn go_zero_value(ty: &TypeRef) -> String {
125    match ty {
126        TypeRef::Primitive(PrimitiveType::Bool) => "false".to_string(),
127        TypeRef::Primitive(_) | TypeRef::Duration => "0".to_string(),
128        TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
129        TypeRef::Json => "nil".to_string(), // json.RawMessage zero is nil
130        TypeRef::Bytes
131        | TypeRef::Vec(_)
132        | TypeRef::Map(_, _)
133        | TypeRef::Optional(_)
134        | TypeRef::Named(_)
135        | TypeRef::Unit => "nil".to_string(),
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_primitives() {
145        let m = GoMapper;
146        assert_eq!(m.primitive(&PrimitiveType::Bool), "bool");
147        assert_eq!(m.primitive(&PrimitiveType::U8), "uint8");
148        assert_eq!(m.primitive(&PrimitiveType::U16), "uint16");
149        assert_eq!(m.primitive(&PrimitiveType::U32), "uint32");
150        assert_eq!(m.primitive(&PrimitiveType::U64), "uint64");
151        assert_eq!(m.primitive(&PrimitiveType::I8), "int8");
152        assert_eq!(m.primitive(&PrimitiveType::I16), "int16");
153        assert_eq!(m.primitive(&PrimitiveType::I32), "int32");
154        assert_eq!(m.primitive(&PrimitiveType::I64), "int64");
155        assert_eq!(m.primitive(&PrimitiveType::F32), "float32");
156        assert_eq!(m.primitive(&PrimitiveType::F64), "float64");
157        assert_eq!(m.primitive(&PrimitiveType::Usize), "uint");
158        assert_eq!(m.primitive(&PrimitiveType::Isize), "int");
159    }
160
161    #[test]
162    fn test_string_and_char() {
163        assert_eq!(GoMapper.map_type(&TypeRef::String), "string");
164        assert_eq!(GoMapper.map_type(&TypeRef::Char), "string");
165    }
166
167    #[test]
168    fn test_bytes() {
169        assert_eq!(GoMapper.map_type(&TypeRef::Bytes), "[]byte");
170    }
171
172    #[test]
173    fn test_path() {
174        assert_eq!(GoMapper.map_type(&TypeRef::Path), "string");
175    }
176
177    #[test]
178    fn test_json() {
179        assert_eq!(GoMapper.map_type(&TypeRef::Json), "json.RawMessage");
180    }
181
182    #[test]
183    fn test_unit() {
184        assert_eq!(GoMapper.map_type(&TypeRef::Unit), "");
185    }
186
187    #[test]
188    fn test_duration() {
189        assert_eq!(GoMapper.map_type(&TypeRef::Duration), "uint64");
190    }
191
192    #[test]
193    fn test_optional() {
194        assert_eq!(
195            GoMapper.map_type(&TypeRef::Optional(Box::new(TypeRef::String))),
196            "*string"
197        );
198    }
199
200    #[test]
201    fn test_vec() {
202        assert_eq!(GoMapper.map_type(&TypeRef::Vec(Box::new(TypeRef::String))), "[]string");
203    }
204
205    #[test]
206    fn test_map() {
207        assert_eq!(
208            GoMapper.map_type(&TypeRef::Map(
209                Box::new(TypeRef::String),
210                Box::new(TypeRef::Primitive(PrimitiveType::I32))
211            )),
212            "map[string]int32"
213        );
214    }
215
216    #[test]
217    fn test_go_type_delegate() {
218        let ty = TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U32)));
219        assert_eq!(go_type(&ty).as_ref(), GoMapper.map_type(&ty));
220    }
221
222    #[test]
223    fn test_go_optional_type_already_optional() {
224        // Optional<String> → go_type gives "*string"; go_optional_type gives same
225        let ty = TypeRef::Optional(Box::new(TypeRef::String));
226        assert_eq!(go_optional_type(&ty), go_type(&ty));
227    }
228
229    #[test]
230    fn test_go_optional_type_non_optional() {
231        // String when used in optional context (e.g., Optional<String>) becomes *string
232        assert_eq!(go_optional_type(&TypeRef::String), "*string");
233    }
234
235    #[test]
236    fn test_go_optional_type_vec_not_pointer() {
237        // Vec<T> is already a reference type in Go; do not wrap in *
238        let ty = TypeRef::Vec(Box::new(TypeRef::String));
239        assert_eq!(go_optional_type(&ty), "[]string");
240    }
241
242    #[test]
243    fn test_go_optional_type_bytes_not_pointer() {
244        // []byte is already a reference type in Go; do not wrap in *
245        assert_eq!(go_optional_type(&TypeRef::Bytes), "[]byte");
246    }
247
248    #[test]
249    fn test_go_optional_type_map_not_pointer() {
250        // map[K]V is already a reference type in Go; do not wrap in *
251        let ty = TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String));
252        assert_eq!(go_optional_type(&ty), "map[string]string");
253    }
254}