Skip to main content

vox_codegen/targets/swift/
encode.rs

1//! Swift encoding expression generation.
2//!
3//! Generates Swift code that encodes Rust types into byte arrays.
4
5use facet_core::{ScalarType, Shape};
6use heck::ToLowerCamelCase;
7use vox_types::{
8    EnumInfo, ShapeKind, StructInfo, VariantKind, classify_shape, classify_variant, is_bytes,
9};
10
11/// Generate a Swift encode expression for a given shape and value.
12pub fn generate_encode_expr(shape: &'static Shape, value: &str) -> String {
13    if is_bytes(shape) {
14        return format!("encodeBytes(Array({value}))");
15    }
16
17    match classify_shape(shape) {
18        ShapeKind::Scalar(scalar) => {
19            let encode_fn = swift_encode_fn(scalar);
20            format!("{encode_fn}({value})")
21        }
22        ShapeKind::List { element }
23        | ShapeKind::Slice { element }
24        | ShapeKind::Array { element, .. } => {
25            let inner_encode = generate_encode_closure(element);
26            format!("encodeVec({value}, encoder: {inner_encode})")
27        }
28        ShapeKind::Option { inner } => {
29            let inner_encode = generate_encode_closure(inner);
30            format!("encodeOption({value}, encoder: {inner_encode})")
31        }
32        ShapeKind::Tx { .. } | ShapeKind::Rx { .. } => {
33            format!("encodeVarint({value}.channelId)")
34        }
35        ShapeKind::Tuple { elements } if elements.len() == 2 => {
36            let a_encode = generate_encode_closure(elements[0].shape);
37            let b_encode = generate_encode_closure(elements[1].shape);
38            format!("{a_encode}({value}.0) + {b_encode}({value}.1)")
39        }
40        ShapeKind::TupleStruct { fields } if fields.len() == 2 => {
41            let a_encode = generate_encode_closure(fields[0].shape());
42            let b_encode = generate_encode_closure(fields[1].shape());
43            format!("{a_encode}({value}.0) + {b_encode}({value}.1)")
44        }
45        ShapeKind::Struct(StructInfo { fields, .. }) => {
46            // Encode each field and concatenate
47            let field_encodes: Vec<String> = fields
48                .iter()
49                .map(|f| {
50                    let field_name = f.name.to_lower_camel_case();
51                    generate_encode_expr(f.shape(), &format!("{value}.{field_name}"))
52                })
53                .collect();
54            if field_encodes.is_empty() {
55                "[]".into()
56            } else {
57                field_encodes.join(" + ")
58            }
59        }
60        ShapeKind::Enum(EnumInfo { .. }) => {
61            let encode_closure = generate_encode_closure(shape);
62            format!("{encode_closure}({value})")
63        }
64        ShapeKind::Pointer { pointee } => generate_encode_expr(pointee, value),
65        ShapeKind::Result { ok, err } => {
66            // Encode Result<T, E> - discriminant 0 = Ok, 1 = Err
67            let ok_encode = generate_encode_closure(ok);
68            let err_encode = generate_encode_closure(err);
69            format!(
70                "{{ switch {value} {{ case .success(let v): return encodeVarint(UInt64(0)) + {ok_encode}(v); case .failure(let e): return encodeVarint(UInt64(1)) + {err_encode}(e) }} }}()"
71            )
72        }
73        _ => "[]".into(), // fallback
74    }
75}
76
77/// Generate a Swift encode closure for use with encodeVec, encodeOption, etc.
78pub fn generate_encode_closure(shape: &'static Shape) -> String {
79    if is_bytes(shape) {
80        return "{ encodeBytes(Array($0)) }".into();
81    }
82
83    match classify_shape(shape) {
84        ShapeKind::Scalar(scalar) => {
85            let encode_fn = swift_encode_fn(scalar);
86            format!("{{ {encode_fn}($0) }}")
87        }
88        ShapeKind::List { element } | ShapeKind::Slice { element } => {
89            let inner = generate_encode_closure(element);
90            format!("{{ encodeVec($0, encoder: {inner}) }}")
91        }
92        ShapeKind::Option { inner } => {
93            let inner_closure = generate_encode_closure(inner);
94            format!("{{ encodeOption($0, encoder: {inner_closure}) }}")
95        }
96        ShapeKind::Tx { .. } | ShapeKind::Rx { .. } => "{ encodeVarint($0.channelId) }".into(),
97        ShapeKind::Tuple { elements } if elements.len() == 2 => {
98            let a_encode = generate_encode_closure(elements[0].shape);
99            let b_encode = generate_encode_closure(elements[1].shape);
100            format!("{{ {a_encode}($0.0) + {b_encode}($0.1) }}")
101        }
102        ShapeKind::TupleStruct { fields } if fields.len() == 2 => {
103            let a_encode = generate_encode_closure(fields[0].shape());
104            let b_encode = generate_encode_closure(fields[1].shape());
105            format!("{{ {a_encode}($0.0) + {b_encode}($0.1) }}")
106        }
107        ShapeKind::Struct(StructInfo { fields, .. }) => {
108            // Generate inline struct encode closure
109            let field_encodes: Vec<String> = fields
110                .iter()
111                .map(|f| {
112                    let field_name = f.name.to_lower_camel_case();
113                    generate_encode_expr(f.shape(), &format!("$0.{field_name}"))
114                })
115                .collect();
116            if field_encodes.is_empty() {
117                "{ _ in [] }".into()
118            } else {
119                format!("{{ {} }}", field_encodes.join(" + "))
120            }
121        }
122        ShapeKind::Enum(EnumInfo {
123            name: Some(_name),
124            variants,
125            ..
126        }) => {
127            // Generate inline enum encode closure with switch
128            let mut code = "{ v in\n    switch v {\n".to_string();
129            for (i, v) in variants.iter().enumerate() {
130                let variant_name = v.name.to_lower_camel_case();
131                match classify_variant(v) {
132                    VariantKind::Unit => {
133                        code.push_str(&format!(
134                            "    case .{variant_name}:\n        return encodeVarint(UInt64({i}))\n"
135                        ));
136                    }
137                    VariantKind::Newtype { inner } => {
138                        let inner_encode = generate_encode_expr(inner, "val");
139                        code.push_str(&format!(
140                            "    case .{variant_name}(let val):\n        return encodeVarint(UInt64({i})) + {inner_encode}\n"
141                        ));
142                    }
143                    VariantKind::Tuple { fields } => {
144                        let bindings: Vec<String> =
145                            (0..fields.len()).map(|j| format!("f{j}")).collect();
146                        let field_encodes: Vec<String> = fields
147                            .iter()
148                            .enumerate()
149                            .map(|(j, f)| generate_encode_expr(f.shape(), &format!("f{j}")))
150                            .collect();
151                        code.push_str(&format!(
152                            "    case .{variant_name}({}):\n        return encodeVarint(UInt64({i})) + {}\n",
153                            bindings
154                                .iter()
155                                .map(|b| format!("let {b}"))
156                                .collect::<Vec<_>>()
157                                .join(", "),
158                            field_encodes.join(" + ")
159                        ));
160                    }
161                    VariantKind::Struct { fields } => {
162                        let bindings: Vec<String> = fields
163                            .iter()
164                            .map(|f| f.name.to_lower_camel_case())
165                            .collect();
166                        let field_encodes: Vec<String> = fields
167                            .iter()
168                            .map(|f| {
169                                let field_name = f.name.to_lower_camel_case();
170                                generate_encode_expr(f.shape(), &field_name)
171                            })
172                            .collect();
173                        code.push_str(&format!(
174                            "    case .{variant_name}({}):\n        return encodeVarint(UInt64({i})) + {}\n",
175                            bindings
176                                .iter()
177                                .map(|b| format!("let {b}"))
178                                .collect::<Vec<_>>()
179                                .join(", "),
180                            field_encodes.join(" + ")
181                        ));
182                    }
183                }
184            }
185            code.push_str("    }\n}");
186            code
187        }
188        ShapeKind::Pointer { pointee } => generate_encode_closure(pointee),
189        ShapeKind::Result { ok, err } => {
190            let ok_encode = generate_encode_closure(ok);
191            let err_encode = generate_encode_closure(err);
192            format!(
193                "{{ switch $0 {{ case .success(let v): return encodeVarint(UInt64(0)) + {ok_encode}(v); case .failure(let e): return encodeVarint(UInt64(1)) + {err_encode}(e) }} }}"
194            )
195        }
196        _ => "{ _ in [] }".into(), // fallback
197    }
198}
199
200/// Get the Swift encode function name for a scalar type.
201pub fn swift_encode_fn(scalar: ScalarType) -> &'static str {
202    match scalar {
203        ScalarType::Bool => "encodeBool",
204        ScalarType::U8 => "encodeU8",
205        ScalarType::I8 => "encodeI8",
206        ScalarType::U16 => "encodeU16",
207        ScalarType::I16 => "encodeI16",
208        ScalarType::U32 => "encodeU32",
209        ScalarType::I32 => "encodeI32",
210        ScalarType::U64 | ScalarType::USize => "encodeVarint",
211        ScalarType::I64 | ScalarType::ISize => "encodeI64",
212        ScalarType::F32 => "encodeF32",
213        ScalarType::F64 => "encodeF64",
214        ScalarType::Char | ScalarType::Str | ScalarType::CowStr | ScalarType::String => {
215            "encodeString"
216        }
217        ScalarType::Unit => "{ _ in [] }",
218        _ => "encodeBytes", // fallback
219    }
220}