zerodds_lang_rules/emit/swift.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The Swift backend of the shared codec walk.
4//!
5//! DDS-XTypes 1.3 §7.4 from the clause, the same as the columns before
6//! it, and none of them calls another. Where two columns disagree on an
7//! octet, one has misread the specification — which is the only reason
8//! to have another.
9//!
10//! # The five decisions this column turns on
11//!
12//! **Every conversion is a call.** Swift has no implicit numeric
13//! conversion and no cast operator that reaches an enumeration, so the
14//! walk's casts all go through `runtime/swift`'s `zdTo(T.self, v)` —
15//! whose overloads take a `BinaryInteger` or a `RawRepresentable` on
16//! either side. That is what lets one seam cover an `Int64` holder fed
17//! from an `enum Mode: Int32`, an `OptionSet` bitmask handed to
18//! `writeBitmask`, and a union label compared against an enumerated
19//! discriminator.
20//!
21//! **The loop counter is the bound's own type.** [`Scalar::Index`] is
22//! `UInt32` — a sequence count is a `uint32` on the wire — and
23//! [`CodecTarget::count_up`] renders `for i in 0..<bound`, which types
24//! the counter from the bound. Swift has no mixed-width integer `<`, so
25//! homogeneity here is what keeps every comparison in a body compiling.
26//! A subscript takes `Int` and nothing else, which is why
27//! [`CodecTarget::index`] wraps every index in `Int(..)`.
28//!
29//! **A union branch is an `Optional` and the seams keep the two sides
30//! of it apart.** `adapter/swift.rs` declares one `T?` per branch. As a
31//! *value* the branch reads force-unwrapped — `v.x!`, inside the arm
32//! the discriminator selected — and as a *place* it is the bare
33//! property, because `v.x! = ..` traps on the `nil` every decode starts
34//! from while `v.x = ..` promotes. [`SwiftExpr`] carries which of the
35//! two a site meant.
36//!
37//! **A sequence is read by appending.** Swift has no uninitialised
38//! array slot, so [`CodecTarget::make_room`] answers `v = []` plus a
39//! `reserveCapacity`, and [`CodecTarget::sequence_element`] marks the
40//! element's place so the read renders `v.append(..)`. A map's entries
41//! are written *into* — both halves per slot — so its room is
42//! `[Entry](repeating: zd_def_Entry(), count: n)`, over the entry
43//! defaulter `default_swift` emits for exactly this.
44//!
45//! **`@optional` is `nil` and needs no wrapper on the way in.** A `T`
46//! assigned to a `T?` promotes, so the optional destination is the
47//! member itself; only `@external` routes through a constructor, the
48//! `ZdExternal(..)` box the adapter declares.
49
50use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
51use crate::adapter::swift::{
52 property_name, swift_holder, swift_map_entry_name, swift_type_name, swift_value_type,
53};
54use crate::encode::EncodeError;
55use zerodds_ast::{Ast, CharWidth, MemberNode, TypeKind, TypeNode};
56use zerodds_ast_core::Id;
57use zerodds_lang_cst::common::{Doc, Ident, Literal};
58use zerodds_lang_cst::swift;
59
60/// The Swift backend.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct Swift;
63
64/// How deep a chain of typedefs and collections is followed.
65const MAX_TYPE_DEPTH: usize = 64;
66
67/// The type name [`CodecTarget::named`] is handed for C's signed
68/// `char`; the Swift member is a `UInt8` and the signed reading is
69/// `Int8(bitPattern:)`.
70const SIGNED_CHAR: &str = "signed char";
71
72/// An expression, with the one property the walk's seams cannot carry:
73/// what kind of place the site names.
74#[derive(Debug, Clone)]
75pub enum SwiftExpr {
76 /// An ordinary expression.
77 Plain(swift::Expr),
78 /// A union branch's `T?` property. As a place it is the property —
79 /// assignment promotes — and as a value it is force-unwrapped,
80 /// because the branch's codec takes the `T`.
81 BranchSlot(swift::Expr),
82 /// An `@external` member's `ZdExternal<T>` field, as a place a
83 /// decoded `T` is about to be written into: the assignment routes
84 /// through the box's constructor.
85 ExternalSlot(swift::Expr),
86 /// A sequence element while it is being read: the collection in
87 /// `0` grows by appending, because Swift has no uninitialised slot
88 /// to write into.
89 AppendSlot(swift::Expr),
90 /// The walk's `=`, kept apart so [`Swift::perform`] can route the
91 /// place through the slot markers above.
92 Assign(Box<SwiftExpr>, Box<swift::Expr>),
93}
94
95/// The expression as a *value*, whichever form it took.
96/// zerodds-lint: recursion-depth 2 (an Assign holds one place, never another Assign)
97fn inner(expr: SwiftExpr) -> swift::Expr {
98 match expr {
99 SwiftExpr::Plain(e) | SwiftExpr::ExternalSlot(e) | SwiftExpr::AppendSlot(e) => e,
100 SwiftExpr::BranchSlot(e) => swift::Expr::ForceUnwrap(Box::new(e)),
101 // Never a value; rendered as the statement it is if it leaks.
102 SwiftExpr::Assign(place, value) => swift::Expr::Binary {
103 op: "=",
104 lhs: Box::new(inner(*place)),
105 rhs: value,
106 },
107 }
108}
109
110fn plain(e: swift::Expr) -> SwiftExpr {
111 SwiftExpr::Plain(e)
112}
113
114impl Swift {
115 /// A runtime operation's Swift spelling: `write_u32` is `writeU32`
116 /// — the first segment as it is, every later segment with its
117 /// first letter raised — the names `runtime/swift` publishes,
118 /// because that runtime was written against this conversion.
119 fn camel(method: &str) -> String {
120 let mut out = String::new();
121 for (index, segment) in method.split('_').enumerate() {
122 let mut chars = segment.chars();
123 match chars.next() {
124 Some(first) if index > 0 => {
125 out.extend(first.to_uppercase());
126 out.push_str(chars.as_str());
127 }
128 Some(first) => {
129 out.push(first);
130 out.push_str(chars.as_str());
131 }
132 None => {}
133 }
134 }
135 out
136 }
137
138 /// A free function of the runtime: `no_dheader` is `zdNoDheader`.
139 fn free_name(method: &str) -> String {
140 let mut out = String::from("zd");
141 for segment in method.split('_') {
142 let mut chars = segment.chars();
143 if let Some(first) = chars.next() {
144 out.extend(first.to_uppercase());
145 out.push_str(chars.as_str());
146 }
147 }
148 out
149 }
150
151 /// Whether `runtime/swift`'s method for `method` (the walk's own
152 /// name, before [`Self::camel`]) is `throws`.
153 ///
154 /// A malformed stream or an out-of-range sample is a `ZdWireError`
155 /// every such method throws; a method that only reports the
156 /// reader's own bookkeeping — its position, its status, a mark to
157 /// restore — never refuses and never throws. This is the one place
158 /// that answers the question, so a method the runtime adds without
159 /// updating it fails loudly: an untracked throwing call compiles
160 /// into a call with no `try`, which `swiftc` refuses outright,
161 /// while a method wrongly listed here compiles into a `try` with
162 /// nothing to catch, which `swiftc` also refuses — either mistake is
163 /// caught at the type-check gate `crates/lang-rules/tests/swift_codec.rs`
164 /// runs, not silently.
165 fn runtime_method_throws(method: &str) -> bool {
166 !matches!(
167 method,
168 "reader_position"
169 | "reader_remaining"
170 | "reader_status"
171 | "reader_refuse"
172 | "reader_clear_discard"
173 | "reader_save"
174 | "reader_restore"
175 | "finish_pid_read"
176 | "trim_utf8"
177 | "trim_utf16"
178 | "position"
179 | "finish"
180 | "write_bool"
181 | "write_u8"
182 | "write_i8"
183 | "write_char"
184 | "write_i16"
185 | "write_u16"
186 | "write_i32"
187 | "write_u32"
188 | "write_i64"
189 | "write_u64"
190 | "write_f32"
191 | "write_f64"
192 | "write_string"
193 | "write_u16_little"
194 | "write_u16_big"
195 | "write_u32_little"
196 | "write_u32_big"
197 | "write_dheader"
198 | "begin_packed_element"
199 | "finish_packed_element"
200 | "reader_begin_packed_element"
201 | "reader_finish_packed_element"
202 | "read_u16_little"
203 | "read_u16_big"
204 | "read_u32_little"
205 | "read_u32_big"
206 )
207 }
208
209 /// If `expr` is `Expr::Try`, take the throwing call out of it and
210 /// report that whatever this becomes part of has to carry the
211 /// `try` itself. Swift refuses `try` to the right of a
212 /// non-assignment operator, so a combinator that is about to fold
213 /// `expr` into a `Binary` or a `Unary` cannot leave it where it is —
214 /// it has to hoist the `try` to wrap the combinator's own result
215 /// instead, which is what every caller of this does with the
216 /// `bool` it gets back.
217 fn take_try(expr: swift::Expr) -> (swift::Expr, bool) {
218 match expr {
219 swift::Expr::Try(inner) => (*inner, true),
220 other => (other, false),
221 }
222 }
223
224 /// `expr`, wrapped in `Try` when `throws` says the operation it
225 /// came from does — the one place that decides whether a `try` is
226 /// needed at all, so every caller just hands back what this
227 /// answers.
228 fn maybe_try(expr: swift::Expr, throws: bool) -> SwiftExpr {
229 if throws {
230 plain(swift::Expr::Try(Box::new(expr)))
231 } else {
232 plain(expr)
233 }
234 }
235
236 fn text(value: &str) -> SwiftExpr {
237 plain(swift::Expr::Literal(Literal::Str(value.to_string())))
238 }
239
240 fn dotted(base: &str, name: &str) -> SwiftExpr {
241 plain(swift::Expr::Member {
242 value: Box::new(swift::Expr::Name(Ident::new(base))),
243 name: Ident::new(name),
244 })
245 }
246
247 fn invoke(receiver: Option<swift::Expr>, name: &str, args: Vec<swift::Expr>) -> swift::Expr {
248 swift::Expr::Invoke {
249 receiver: receiver.map(Box::new),
250 name: Ident::new(name),
251 args: args
252 .into_iter()
253 .map(|value| swift::Argument { label: None, value })
254 .collect(),
255 }
256 }
257
258 /// `zdTo(<T>.self, value)` — the one conversion seam; the module
259 /// documentation says why every cast is a call here.
260 fn convert(type_: swift::Type, value: swift::Expr) -> swift::Expr {
261 Self::invoke(
262 None,
263 "zdTo",
264 vec![swift::Expr::TypeSelf(Box::new(type_)), value],
265 )
266 }
267
268 /// One assignment, routed by what the place is.
269 fn assemble_assign(place: SwiftExpr, value: swift::Expr) -> swift::Stmt {
270 match place {
271 SwiftExpr::Plain(p) | SwiftExpr::BranchSlot(p) => {
272 swift::Stmt::Assign { place: p, value }
273 }
274 SwiftExpr::ExternalSlot(p) => swift::Stmt::Assign {
275 place: p,
276 value: Self::invoke(None, "ZdExternal", vec![value]),
277 },
278 SwiftExpr::AppendSlot(base) => {
279 swift::Stmt::Expr(Self::invoke(Some(base), "append", vec![value]))
280 }
281 SwiftExpr::Assign(next, first) => {
282 // `a = (b = c)` is not Swift and the walk never builds
283 // it; keep the inner assignment and drop the outer.
284 let _ = first;
285 Self::assemble_assign(*next, value)
286 }
287 }
288 }
289}
290
291impl CodecTarget for Swift {
292 type Expr = SwiftExpr;
293 type Stmt = swift::Stmt;
294 type Block = Vec<swift::Stmt>;
295 type Type = swift::Type;
296 type Decl = swift::Decl;
297
298 // -- types --------------------------------------------------------
299
300 /// The fixed-width Swift type each scalar local takes. `Index` is
301 /// `UInt32` — a sequence length is a `uint32` on the wire, and the
302 /// counter has to be the bound's own type because Swift's `<` takes
303 /// no mixed widths — and `Size` is `Int`, which is what the
304 /// reader's positions answer in and what a subscript takes.
305 fn scalar(kind: Scalar) -> Self::Type {
306 match kind {
307 Scalar::Bool => swift::Type::Primitive(swift::Primitive::Bool),
308 Scalar::Char | Scalar::UInt8 => swift::Type::Primitive(swift::Primitive::UInt8),
309 Scalar::Int8 => swift::Type::Primitive(swift::Primitive::Int8),
310 Scalar::Int16 => swift::Type::Primitive(swift::Primitive::Int16),
311 Scalar::UInt16 => swift::Type::Primitive(swift::Primitive::UInt16),
312 Scalar::Int32 => swift::Type::Primitive(swift::Primitive::Int32),
313 Scalar::UInt32 | Scalar::Index => swift::Type::Primitive(swift::Primitive::UInt32),
314 Scalar::Int64 => swift::Type::Primitive(swift::Primitive::Int64),
315 Scalar::UInt64 => swift::Type::Primitive(swift::Primitive::UInt64),
316 Scalar::Float => swift::Type::Primitive(swift::Primitive::Float),
317 Scalar::Double => swift::Type::Primitive(swift::Primitive::Double),
318 // The decimal text; `adapter/swift.rs` says why.
319 Scalar::LongDouble => swift::Type::Primitive(swift::Primitive::String),
320 // `Int` is deliberately not in the CST's primitive set —
321 // its width is the platform's and no member may take it —
322 // so the *local* type a position is held in names it.
323 Scalar::Size => swift::Type::named("Int"),
324 }
325 }
326
327 fn named(name: &str) -> Self::Type {
328 swift::Type::named(name)
329 }
330
331 /// `Void`, which a codec function that answers nothing never
332 /// writes: [`Swift::routine`] leaves the arrow off instead.
333 fn nothing() -> Self::Type {
334 swift::Type::named("Void")
335 }
336
337 /// The runtime's handles.
338 fn handle(kind: Handle) -> Self::Type {
339 swift::Type::named(match kind {
340 Handle::DHeader => "ZdDHeader",
341 Handle::EmHeader => "ZdEmHeader",
342 Handle::Pid => "ZdPid",
343 Handle::TryConstruct => "ZdTryConstruct",
344 Handle::ReaderMark => "ZdReaderMark",
345 Handle::PidRead => "ZdPidRead",
346 Handle::EmHeaderRead => "ZdEmHeaderRead",
347 })
348 }
349
350 /// The member already is the code unit — `UInt8` for a `char`,
351 /// `UInt16` for a `wchar` — so there is nothing to convert.
352 fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
353 value
354 }
355
356 /// The runtime's policy values: strings and integers where
357 /// `runtime/swift` takes them so, and the runtime's own enums for
358 /// the enumerated kinds. One arm per constant and no catch-all.
359 fn constant(which: Constant) -> Self::Expr {
360 match which {
361 Constant::WcharWidth2 => plain(swift::Expr::int(2)),
362 Constant::WcharWidth4 => plain(swift::Expr::int(4)),
363 Constant::WcharFixed => Self::text("fixed"),
364 Constant::WcharGiopLengthPrefixed => Self::text("giop-length-prefixed"),
365 Constant::WstringUnitsStream => Self::text("stream"),
366 Constant::WstringUnitsBigEndianAlways => Self::text("big-endian-always"),
367 Constant::WstringUnitsByteOrderMark => Self::text("byte-order-mark"),
368 Constant::WstringUnitsHostUnswapped => Self::text("host-unswapped"),
369 Constant::WstringSpecification => Self::text("specification"),
370 Constant::WstringCharacterCount => Self::text("character-count"),
371 Constant::WstringCharacterCountTerminated => Self::text("character-count-terminated"),
372 Constant::WstringSpecificationWideUnits => Self::text("specification-wide-units"),
373 Constant::WstringRosTypeSupport => Self::text("ros-type-support"),
374 Constant::WstringOctetsTerminated2 => Self::text("octets/terminated/2"),
375 Constant::WstringOctetsTerminated4 => Self::text("octets/terminated/4"),
376 Constant::WstringCharactersTerminated2 => Self::text("characters/terminated/2"),
377 Constant::FixedDeclared => Self::text("declared"),
378 Constant::FixedValuePrecision => Self::text("value-precision"),
379 Constant::FixedValueStringDigits => Self::text("value-string-digits"),
380 Constant::ParameterMember => Self::dotted("ZdParameterRole", "member"),
381 Constant::ParameterOptional => Self::dotted("ZdParameterRole", "optional"),
382 Constant::TryConstructDiscard => Self::dotted("ZdTryConstruct", "discard"),
383 Constant::TryConstructTrim => Self::dotted("ZdTryConstruct", "trim"),
384 Constant::TryConstructUseDefault => Self::dotted("ZdTryConstruct", "useDefault"),
385 Constant::StatusOk => Self::dotted("ZdStatus", "ok"),
386 Constant::StatusDiscarded => Self::dotted("ZdStatus", "discarded"),
387 Constant::StatusImpossibleLength => Self::dotted("ZdStatus", "impossibleLength"),
388 Constant::StatusOutOfRange => Self::dotted("ZdStatus", "outOfRange"),
389 Constant::StatusTruncated => Self::dotted("ZdStatus", "truncated"),
390 }
391 }
392
393 /// The unsigned type wide enough for `bits` of a bit set — the
394 /// same answer the materialiser's `swift_holder` gives its fields.
395 fn unsigned_holder(bits: u16) -> Self::Type {
396 swift::Type::Primitive(swift_holder(bits))
397 }
398
399 fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
400 // A type with no Swift form was refused by the materialiser
401 // before a codec walk ran; the fallback name makes the
402 // impossible fail in the Swift compiler, loudly, rather than
403 // compile into a plausible member.
404 swift_value_type(ast, id).unwrap_or_else(|_| swift::Type::named("ZdNoSwiftForm"))
405 }
406
407 fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
408 type_tag(ast, id, 0)
409 }
410
411 /// The entry name the materialiser declared, and a distinct one
412 /// per type when its tag walk refuses: the slot keeps two refused
413 /// entries from collapsing onto one declaration, which would merge
414 /// two map types under one name.
415 fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
416 swift_map_entry_name(ast, id).unwrap_or_else(|_| format!("ZdUnresolvedEntry{}", id.slot()))
417 }
418
419 /// Nothing: a declared type's Swift name is already flat — the
420 /// adapter folds the modules in — and an enumerator lives in its
421 /// enum's own scope, which
422 /// [`CodecTarget::enumerator_constant`]'s override reaches.
423 fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
424 String::new()
425 }
426
427 /// The escape and nothing else, exactly as the materialiser spells
428 /// a property: back quotes round a keyword, which do not rename.
429 fn escape(name: &str) -> String {
430 property_name(name)
431 }
432
433 /// The base is the first member `parent` (XTypes 1.3 §7.5.1.4.1.3).
434 fn inherited_path(depth: usize) -> Option<String> {
435 Some(format!("{}.", property_name("parent")).repeat(depth))
436 }
437
438 // -- expressions ---------------------------------------------------
439
440 fn name(text: &str) -> Self::Expr {
441 plain(swift::Expr::Name(Ident::new(text)))
442 }
443
444 fn int(value: i128) -> Self::Expr {
445 plain(swift::Expr::int(value))
446 }
447
448 /// The raw tokens the walk escapes through [`Literal::Int`],
449 /// rewritten into the tree's own boolean where they are one.
450 fn literal(value: Literal) -> Self::Expr {
451 let Literal::Int(text) = &value else {
452 return plain(swift::Expr::Literal(value));
453 };
454 match text.as_str() {
455 "true" => plain(swift::Expr::Literal(Literal::Bool(true))),
456 "false" => plain(swift::Expr::Literal(Literal::Bool(false))),
457 _ => plain(swift::Expr::Literal(value)),
458 }
459 }
460
461 /// A Swift integer literal is arbitrary-precision and adapts to
462 /// its context, so nearly every value is the bare number. The one
463 /// exception is a value past `Int64.MAX` reaching a *generic*
464 /// context — `zdTo`'s value parameter — where an unconstrained
465 /// literal defaults to `Int` and overflows at compile time; those
466 /// carry their type: `(16045690984833335998 as UInt64)`.
467 fn wide_int(value: i128) -> Self::Expr {
468 if value > i128::from(i64::MAX) {
469 return plain(swift::Expr::Paren(Box::new(swift::Expr::Binary {
470 op: "as",
471 lhs: Box::new(swift::Expr::int(value)),
472 rhs: Box::new(swift::Expr::Name(Ident::new("UInt64"))),
473 })));
474 }
475 plain(swift::Expr::int(value))
476 }
477
478 /// The bare number: the context types it.
479 fn unsigned_int(value: i128) -> Self::Expr {
480 Self::wide_int(value)
481 }
482
483 /// The bare number too. A mask complemented with `~` sits beside a
484 /// `UInt64` operand, which is the context that types it — Swift's
485 /// literals do not default until no context constrains them.
486 fn wide_unsigned(value: u64) -> Self::Expr {
487 plain(swift::Expr::Literal(Literal::Int(format!("{value}"))))
488 }
489
490 /// The number, for the enumerator patterns `zd_raw` is compared
491 /// against; an `Int64` operand types it.
492 fn wide_signed(value: i128) -> Self::Expr {
493 plain(swift::Expr::Literal(Literal::Int(format!("{value}"))))
494 }
495
496 /// `v.x` — and `v._d` as it stands: Swift keeps the IDL-facing
497 /// spelling, underscore and all.
498 fn member(base: &str, field: &str) -> Self::Expr {
499 Self::dotted(base, field)
500 }
501
502 fn whole(base: &str) -> Self::Expr {
503 plain(swift::Expr::Name(Ident::new(base)))
504 }
505
506 fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
507 plain(swift::Expr::Member {
508 value: Box::new(inner(value)),
509 name: Ident::new(field),
510 })
511 }
512
513 /// A header's field, in the spelling `runtime/swift` publishes:
514 /// `member_id` is `memberId`.
515 fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
516 plain(swift::Expr::Member {
517 value: Box::new(inner(value)),
518 name: Ident::new(&Self::camel(field)),
519 })
520 }
521
522 /// `v[Int(i)]`: a Swift array subscript takes `Int` and nothing
523 /// else, and the walk's counters are the wire's `UInt32`.
524 fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
525 plain(swift::Expr::Index {
526 value: Box::new(inner(value)),
527 index: Box::new(Self::invoke(None, "Int", vec![inner(at)])),
528 })
529 }
530
531 /// `UInt32(v.count)`, or the bound where the materialiser fixed
532 /// one: `count` is an `Int` and a sequence's count is a `uint32`
533 /// on the wire.
534 fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
535 match fixed {
536 Some(bound) => plain(swift::Expr::int(i128::from(bound))),
537 None => plain(Self::invoke(
538 None,
539 "UInt32",
540 vec![swift::Expr::Member {
541 value: Box::new(swift::Expr::Name(Ident::new(base))),
542 name: Ident::new("count"),
543 }],
544 )),
545 }
546 }
547
548 /// The array itself: a Swift array is the elements.
549 fn elements(base: &str) -> Self::Expr {
550 plain(swift::Expr::Name(Ident::new(base)))
551 }
552
553 /// `v.x!` as a place-marked expression: the branch property is a
554 /// `T?` and only this backend knows which side of it a site wants.
555 fn case_member(base: &str, field: &str) -> Self::Expr {
556 SwiftExpr::BranchSlot(swift::Expr::Member {
557 value: Box::new(swift::Expr::Name(Ident::new(base))),
558 name: Ident::new(field),
559 })
560 }
561
562 /// `entry.key` and `entry.value`, the two properties the
563 /// materialiser gives a map's entry struct.
564 fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
565 plain(swift::Expr::Member {
566 value: Box::new(inner(entry)),
567 name: Ident::new(if key { "key" } else { "value" }),
568 })
569 }
570
571 fn absent() -> Self::Expr {
572 plain(swift::Expr::Literal(Literal::Null))
573 }
574
575 /// Nothing: a Swift `String` and a Swift array own their storage,
576 /// and there is no allocation failure for the guard to test.
577 fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
578 None
579 }
580
581 /// `v.x!` — the value a `T?` holds, given that it holds one. Only
582 /// ever reached inside a branch the presence test guarded, where
583 /// the unwrap cannot trap.
584 fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
585 plain(swift::Expr::ForceUnwrap(Box::new(inner(Self::member(
586 base, field,
587 )))))
588 }
589
590 /// `v.x != nil`.
591 fn present(base: &str, field: &str) -> Self::Expr {
592 plain(swift::Expr::Binary {
593 op: "!=",
594 lhs: Box::new(inner(Self::member(base, field))),
595 rhs: Box::new(swift::Expr::Literal(Literal::Null)),
596 })
597 }
598
599 /// Nothing on the way in — assigning the member is what makes it
600 /// present, `T` promoting to `T?` on its own — and `v.x = nil` on
601 /// the way out.
602 fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
603 if present {
604 return Vec::new();
605 }
606 vec![swift::Stmt::Assign {
607 place: inner(Self::member(base, field)),
608 value: swift::Expr::Literal(Literal::Null),
609 }]
610 }
611
612 /// `v.x = nil` for every case: a branch property is a `T?`, and only
613 /// the selected one holds a value. The same statement as an absent
614 /// `@optional` member, [`Self::set_present`] with `false`.
615 fn clear_union_cases(base: &str, fields: &[String]) -> Vec<Self::Stmt> {
616 fields
617 .iter()
618 .flat_map(|field| Self::set_present(base, field, false))
619 .collect()
620 }
621
622 /// The member itself: the assignment's promotion is the making of
623 /// room.
624 fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
625 (Vec::new(), Self::member(base, field))
626 }
627
628 /// `true`: an `@external` member is a `ZdExternal` box, and a member on a
629 /// cycle needs the same: a Swift struct cannot store itself.
630 /// [`crate::adapter::held_externally`] is the rule the adapter and
631 /// the defaulter ask as well.
632 fn holds_a_cycle_externally() -> bool {
633 true
634 }
635
636 /// `v.x.value`, or `v.x!.value` where the box is also `@optional`:
637 /// the member's codec takes the value, not the box.
638 fn external_value(
639 _ast: &Ast,
640 _id: Id<TypeNode>,
641 base: &str,
642 field: &str,
643 optional: bool,
644 ) -> Self::Expr {
645 let member = inner(Self::member(base, field));
646 let boxed = if optional {
647 swift::Expr::ForceUnwrap(Box::new(member))
648 } else {
649 member
650 };
651 plain(swift::Expr::Member {
652 value: Box::new(boxed),
653 name: Ident::new("value"),
654 })
655 }
656
657 /// The marked place; `Swift::assemble_assign` routes the decoded
658 /// value through the `ZdExternal(..)` constructor, and where the
659 /// member is also `@optional` the box promotes to `T?` on its own.
660 fn external_destination(
661 base: &str,
662 field: &str,
663 _optional: bool,
664 ) -> (Vec<Self::Stmt>, Self::Expr) {
665 (
666 Vec::new(),
667 SwiftExpr::ExternalSlot(inner(Self::member(base, field))),
668 )
669 }
670
671 /// `v.x != nil`: the optional box is the absence, with nothing
672 /// else to carry a second one.
673 fn external_present(base: &str, field: &str) -> Self::Expr {
674 Self::present(base, field)
675 }
676
677 /// Nothing on the way in, `v.x = nil` on the way out.
678 fn external_set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
679 Self::set_present(base, field, present)
680 }
681
682 /// `v.x!.value` for a branch: the arm's property is a
683 /// `ZdExternal<T>?` and the branch's codec takes the value.
684 fn external_case_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
685 plain(swift::Expr::Member {
686 value: Box::new(inner(Self::case_member(base, field))),
687 name: Ident::new("value"),
688 })
689 }
690
691 /// The marked place, exactly as [`Swift::external_destination`]
692 /// answers for a structure member — the branch's bare property,
693 /// routed through the box constructor.
694 fn external_case_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
695 (
696 Vec::new(),
697 SwiftExpr::ExternalSlot(inner_place(Self::case_member(base, field))),
698 )
699 }
700
701 /// The operators as given — except the walk's `=`, which is kept
702 /// apart so [`Swift::perform`] can route the place through the
703 /// slot markers.
704 ///
705 /// `=`'s right side is already a safe place for a bare `try` —
706 /// assignment is the one operator Swift exempts — so `lhs`/`rhs`
707 /// there are handed to `inner` untouched and whatever `try` they
708 /// carry stays exactly where it is. Every other operator is one
709 /// Swift does not exempt, so a `try` `Self::take_try` finds on
710 /// either side is hoisted to wrap the whole `Binary` instead of
711 /// being left where `swiftc` refuses it.
712 fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
713 if op == "=" {
714 return SwiftExpr::Assign(Box::new(lhs), Box::new(inner(rhs)));
715 }
716 let (lhs, lhs_throws) = Self::take_try(inner(lhs));
717 let (rhs, rhs_throws) = Self::take_try(inner(rhs));
718 let combined = swift::Expr::Binary {
719 op,
720 lhs: Box::new(lhs),
721 rhs: Box::new(rhs),
722 };
723 Self::maybe_try(combined, lhs_throws || rhs_throws)
724 }
725
726 fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
727 let (value, throws) = Self::take_try(inner(value));
728 let combined = swift::Expr::Unary {
729 op,
730 value: Box::new(value),
731 };
732 Self::maybe_try(combined, throws)
733 }
734
735 fn paren(value: Self::Expr) -> Self::Expr {
736 let (value, throws) = Self::take_try(inner(value));
737 let combined = swift::Expr::Paren(Box::new(value));
738 Self::maybe_try(combined, throws)
739 }
740
741 /// Every conversion is a call; the module documentation carries
742 /// why. Two rewrites: a `bool` discriminator's integer label
743 /// becomes the boolean literal, and the walk's `signed char` — the
744 /// C spelling of a map key's signed order — is
745 /// `Int8(bitPattern: ..)`, which reads a `UInt8` key as the two's
746 /// complement the corpus was recorded under.
747 fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
748 if type_ == swift::Type::named(SIGNED_CHAR) {
749 return plain(swift::Expr::Invoke {
750 receiver: None,
751 name: Ident::new("Int8"),
752 args: vec![swift::Argument {
753 label: Some(Ident::new("bitPattern")),
754 value: inner(value),
755 }],
756 });
757 }
758 if type_ == swift::Type::Primitive(swift::Primitive::Bool) {
759 if let SwiftExpr::Plain(swift::Expr::Literal(Literal::Int(text))) = &value {
760 return plain(swift::Expr::Literal(Literal::Bool(text != "0")));
761 }
762 }
763 plain(Self::convert(type_, inner(value)))
764 }
765
766 /// `false` before `true`, spelled as the conjunction — Swift's `<`
767 /// takes no booleans. The same hoisting `binary` does: `&&` is not
768 /// the assignment operator either.
769 fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
770 let (left, left_throws) = Self::take_try(inner(left));
771 let (right, right_throws) = Self::take_try(inner(right));
772 let combined = swift::Expr::Binary {
773 op: "&&",
774 lhs: Box::new(swift::Expr::Paren(Box::new(swift::Expr::Binary {
775 op: "==",
776 lhs: Box::new(left),
777 rhs: Box::new(swift::Expr::Literal(Literal::Bool(false))),
778 }))),
779 rhs: Box::new(right),
780 };
781 Self::maybe_try(combined, left_throws || right_throws)
782 }
783
784 /// `v.rawValue`. `adapter/swift.rs` declares an enumeration as
785 /// `enum Name: Int32` with each enumerator's declared value as its
786 /// raw value, and a Swift enum has no `<` of its own: comparing two
787 /// keys of the enumeration directly does not compile ("cannot convert
788 /// value of type 'Colour' to expected argument type 'Int32'",
789 /// measured in the matrix refresh of 2026-10-01 on `map-key-enum`
790 /// under the Fast DDS and OpenDDS dialects). The raw value is
791 /// [`zerodds_ast_layout::MapKeyOrdering::EnumeratorValue`]'s order.
792 /// The union overlay asks the same seam for every integer arm, and
793 /// `.rawValue` on an `Int32` does not compile: `combined-everything`
794 /// under the Cyclone dialect generated `v.whole!.rawValue`. A type
795 /// that is not an enumeration passes through.
796 fn enumerator_ordinal(ast: &Ast, id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
797 if !super::resolves_to_enumeration(ast, id) {
798 return value;
799 }
800 let (value, throws) = Self::take_try(inner(value));
801 Self::maybe_try(
802 swift::Expr::Member {
803 value: Box::new(value),
804 name: Ident::new("rawValue"),
805 },
806 throws,
807 )
808 }
809
810 /// The value, as a value: a slot marker crossing a call boundary
811 /// is the branch unwrap materialising.
812 fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
813 plain(inner(value))
814 }
815
816 /// A call to a file-level function — a generated codec or
817 /// defaulter. `strcmp` is the runtime's `zdStrcmp`, which spells
818 /// C11 §7.24.4's unsigned-octet order over the UTF-8 views.
819 ///
820 /// Every other callee is another codec function this walk emits —
821 /// a member's own encoder or decoder, called recursively — and
822 /// every one of those is `throws` (see [`Swift::routine`]), so the
823 /// call is wrapped here, at the one place that builds it, rather
824 /// than trusted to whoever calls [`Swift::call`].
825 fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
826 let arguments: Vec<swift::Expr> = args.into_iter().map(inner).collect();
827 if callee == "strcmp" {
828 return plain(Self::invoke(None, "zdStrcmp", arguments));
829 }
830 plain(swift::Expr::Try(Box::new(Self::invoke(
831 None, callee, arguments,
832 ))))
833 }
834
835 /// `w.writeU32(x)` — a method on the writer or the reader, `try`
836 /// in front of it exactly where `Self::runtime_method_throws`
837 /// says the method refuses.
838 fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
839 let invoke = Self::invoke(
840 Some(swift::Expr::Name(Ident::new(receiver))),
841 &Self::camel(method),
842 args.into_iter().map(inner).collect(),
843 );
844 Self::maybe_try(invoke, Self::runtime_method_throws(method))
845 }
846
847 /// `runtime/swift` carries the three HDDS axes: `beginPackedElement`
848 /// / `finishPackedElement` on the writer and
849 /// `readerBeginPackedElement` / `readerFinishPackedElement` on the
850 /// reader, the DHEADER pair for a sequence element's own length, and
851 /// `writeWstringUtf8Octets` / `writeWstringUtf8OctetsBounded` /
852 /// `readWstringUtf8Octets`.
853 fn carries_hdds_axes() -> bool {
854 true
855 }
856
857 /// A free function of the runtime: `no_dheader` is `zdNoDheader`,
858 /// `pid_is_user_member` is `zdPidIsUserMember`.
859 fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
860 plain(Self::invoke(
861 None,
862 &Self::free_name(method),
863 args.into_iter().map(inner).collect(),
864 ))
865 }
866
867 // -- statements ----------------------------------------------------
868
869 /// `var name: T = value` — the type written, so a reader of the
870 /// generated file sees the width and an unsuffixed literal takes
871 /// it; `var name: T` where the walk asked for no value, leaning on
872 /// Swift's definite initialisation.
873 fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
874 swift::Stmt::Var {
875 name: Ident::new(name),
876 type_: Some(type_),
877 value: value.map(inner),
878 }
879 }
880
881 /// An expression evaluated for its effect — or, where the walk
882 /// built an assignment, the assignment routed through the place's
883 /// marker.
884 fn perform(value: Self::Expr) -> Self::Stmt {
885 if let SwiftExpr::Assign(place, value) = value {
886 return Self::assemble_assign(*place, *value);
887 }
888 swift::Stmt::Expr(inner(value))
889 }
890
891 /// `return v` — a Swift decoder answers with the sample it built.
892 fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
893 swift::Stmt::Return(Some(match value {
894 Some(value) => inner(value),
895 None => swift::Expr::Name(Ident::new("v")),
896 }))
897 }
898
899 fn when(
900 condition: Self::Expr,
901 then: Self::Block,
902 otherwise: Option<Self::Block>,
903 ) -> Self::Stmt {
904 swift::Stmt::If {
905 condition: inner(condition),
906 then,
907 otherwise: otherwise.filter(|body| !body.is_empty()),
908 }
909 }
910
911 /// `for i in 0..<bound`: the counter is the bound's own type,
912 /// which is what keeps every comparison in the body homogeneous.
913 fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
914 swift::Stmt::ForRange {
915 counter: Ident::new(counter),
916 bound: inner(bound),
917 body,
918 }
919 }
920
921 fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
922 swift::Stmt::While {
923 condition: inner(condition),
924 body,
925 }
926 }
927
928 fn leave() -> Self::Stmt {
929 swift::Stmt::Break
930 }
931
932 fn note(doc: Doc) -> Self::Stmt {
933 swift::Stmt::Comment(doc)
934 }
935
936 /// `_ = expr` — Swift's spelling of a value read for its effect.
937 fn drop_value(value: Self::Expr) -> Self::Stmt {
938 swift::Stmt::Assign {
939 place: swift::Expr::Name(Ident::new("_")),
940 value: inner(value),
941 }
942 }
943
944 /// `_ = name` — Swift does not warn on an unread parameter, but
945 /// the walk also marks locals a framing leaves unread, and those
946 /// do warn.
947 fn mark_used(name: &str) -> Vec<Self::Stmt> {
948 vec![swift::Stmt::Assign {
949 place: swift::Expr::Name(Ident::new("_")),
950 value: swift::Expr::Name(Ident::new(name)),
951 }]
952 }
953
954 /// `place = try zd_dec_T(r, behaviour)` — through the place's
955 /// marker: a decoder answers with its value, an `@external` place
956 /// wraps it in the box, and a sequence slot appends it. Every one
957 /// of those is a place a bare `try` sits safely in front of a call
958 /// argument or an assignment's right side, which is what
959 /// `callee` — another generated decoder, always `throws` — needs.
960 fn read_call(
961 callee: &str,
962 place: Self::Expr,
963 _form: Handoff,
964 behaviour: Self::Expr,
965 ) -> Self::Stmt {
966 let decoded = swift::Expr::Try(Box::new(Self::invoke(
967 None,
968 callee,
969 vec![swift::Expr::Name(Ident::new("r")), inner(behaviour)],
970 )));
971 Self::assemble_assign(place, decoded)
972 }
973
974 /// `v = zd_dec_T(r, zd_tc)` — a `typedef`, handed straight on.
975 fn read_forward(callee: &str) -> Self::Stmt {
976 Self::read_call(
977 callee,
978 Self::whole("v"),
979 Handoff::Value,
980 Self::name("zd_tc"),
981 )
982 }
983
984 fn block(statements: Vec<Self::Stmt>) -> Self::Block {
985 statements
986 }
987
988 fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
989 block
990 }
991
992 /// Room, in the two shapes Swift has. A sequence's elements are
993 /// *appended* — [`Swift::sequence_element`] marks the slot — so
994 /// its room is the empty array and a `reserveCapacity`. A map's
995 /// entries are written into, both halves per slot, so its room is
996 /// real entries: `[Entry](repeating: zd_def_Entry(), count: n)`,
997 /// over the defaulter `default_swift` emits for the entry struct.
998 fn make_room(
999 base: &str,
1000 element: Self::Type,
1001 count: Self::Expr,
1002 assigned_whole: bool,
1003 fill: Vec<Self::Stmt>,
1004 ) -> Vec<Self::Stmt> {
1005 let count = inner(count);
1006 if assigned_whole {
1007 let mut out = vec![
1008 swift::Stmt::Assign {
1009 place: swift::Expr::Name(Ident::new(base)),
1010 value: swift::Expr::ArrayLit(Vec::new()),
1011 },
1012 swift::Stmt::Expr(Self::invoke(
1013 Some(swift::Expr::Name(Ident::new(base))),
1014 "reserveCapacity",
1015 vec![Self::invoke(None, "Int", vec![count])],
1016 )),
1017 ];
1018 out.extend(fill);
1019 return out;
1020 }
1021 let repeated = match &element {
1022 swift::Type::Named(name) => {
1023 Self::invoke(None, &format!("zd_def_{}", name.as_str()), Vec::new())
1024 }
1025 // Only a map's entry struct reaches the written-into form,
1026 // and its type is always a name; anything else is a walk
1027 // this backend does not know, made loud in the compiler.
1028 _ => Self::invoke(None, "zd_def_unsupported_room", Vec::new()),
1029 };
1030 let mut out = vec![swift::Stmt::Assign {
1031 place: swift::Expr::Name(Ident::new(base)),
1032 value: swift::Expr::RepeatingArray {
1033 element: Box::new(element),
1034 repeated: Box::new(repeated),
1035 count: Box::new(Self::invoke(None, "Int", vec![count])),
1036 },
1037 }];
1038 out.extend(fill);
1039 out
1040 }
1041
1042 /// `v = Array(v.prefix(Int(count)))` — §7.3.1.2.1.12.3's TRIM,
1043 /// applied after every element has been read.
1044 fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
1045 vec![swift::Stmt::Assign {
1046 place: swift::Expr::Name(Ident::new(base)),
1047 value: Self::invoke(
1048 None,
1049 "Array",
1050 vec![Self::invoke(
1051 Some(swift::Expr::Name(Ident::new(base))),
1052 "prefix",
1053 vec![Self::invoke(None, "Int", vec![inner(count)])],
1054 )],
1055 ),
1056 }]
1057 }
1058
1059 /// The marked slot: the element decoders append, because a Swift
1060 /// array has no uninitialised slot to write into.
1061 fn sequence_element(
1062 base: &str,
1063 _index: Self::Expr,
1064 _element: Self::Type,
1065 ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
1066 (
1067 Vec::new(),
1068 SwiftExpr::AppendSlot(swift::Expr::Name(Ident::new(base))),
1069 Vec::new(),
1070 )
1071 }
1072
1073 /// A local the body writes into. A generated struct has no
1074 /// zero-argument initialiser, so the storage is its Table 9 value,
1075 /// through the defaulter named after the type.
1076 fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
1077 match &type_ {
1078 swift::Type::Named(type_name) => swift::Stmt::Var {
1079 name: Ident::new(name),
1080 type_: None,
1081 value: Some(Self::invoke(
1082 None,
1083 &format!("zd_def_{}", type_name.as_str()),
1084 Vec::new(),
1085 )),
1086 },
1087 _ => swift::Stmt::Var {
1088 name: Ident::new(name),
1089 type_: Some(type_),
1090 value: None,
1091 },
1092 }
1093 }
1094
1095 // -- assembly ------------------------------------------------------
1096
1097 /// `func zd_enc_T(_ w: ZdWriter, _ v: T) throws` and
1098 /// `func zd_dec_T(_ r: ZdReader, _ zd_tc: ZdTryConstruct) throws -> T`.
1099 ///
1100 /// A forward declaration is answered with nothing: a Swift
1101 /// file-scope name is in scope for the whole file however far down
1102 /// it is written.
1103 ///
1104 /// Every routine is `throws`, whether or not this particular one
1105 /// happens to call something that refuses: a routine's own body can
1106 /// change — a member added, a bound relaxed — without this
1107 /// declaration having to change with it, and a caller one call away
1108 /// from a refusal and a caller ten calls away are the same call
1109 /// either way.
1110 fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
1111 let body = spec.body?;
1112 let underscore = |name: &str, type_: swift::Type| swift::Param {
1113 label: Ident::new("_"),
1114 internal_name: Some(Ident::new(name)),
1115 type_,
1116 };
1117 let mut params = vec![underscore(
1118 if spec.writing { "w" } else { "r" },
1119 swift::Type::named(if spec.writing { "ZdWriter" } else { "ZdReader" }),
1120 )];
1121 if spec.writing {
1122 params.push(underscore("v", spec.sample.clone()));
1123 }
1124 for (type_, name) in spec.extra {
1125 params.push(underscore(&name, type_));
1126 }
1127 let (returns, body) = if spec.writing {
1128 (None, body)
1129 } else {
1130 // The sample starts as Table 9's value, through the
1131 // generated defaulter, so every position the stream does
1132 // not carry is already what the clause says it is.
1133 let defaulter = crate::default_swift::defaulter_from_decoder(&spec.name);
1134 let mut statements = vec![swift::Stmt::Var {
1135 name: Ident::new("v"),
1136 type_: None,
1137 value: Some(Self::invoke(None, &defaulter, Vec::new())),
1138 }];
1139 statements.extend(body);
1140 statements.push(swift::Stmt::Return(Some(swift::Expr::Name(Ident::new(
1141 "v",
1142 )))));
1143 (Some(spec.sample), statements)
1144 };
1145 Some(swift::Decl::Func(swift::FuncDecl {
1146 doc: spec.doc,
1147 // `public`, like the aggregates this file declares beside
1148 // it. Swift's default is `internal` — the module — so a
1149 // generated file compiled as its own module offered a
1150 // `public struct Max` that no consumer could encode. The
1151 // probe harness never saw it: it compiles its `main` into
1152 // the same module.
1153 visibility: zerodds_lang_cst::common::Visibility::Public,
1154 name: Ident::new(&spec.name),
1155 params,
1156 throws: true,
1157 returns,
1158 body,
1159 }))
1160 }
1161
1162 /// `Flags(rawValue: <holder>)` — an `OptionSet` has no conversion
1163 /// operator, so the 64-bit read narrows through the declared
1164 /// holder and enters by the raw-value initialiser.
1165 fn bitmask_value(ast: &Ast, id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
1166 let holder = swift::Type::Primitive(swift_holder(ast.effective_bit_bound(id)));
1167 plain(swift::Expr::Invoke {
1168 receiver: None,
1169 name: Ident::new(&swift_type_name(ast, id)),
1170 args: vec![swift::Argument {
1171 label: Some(Ident::new("rawValue")),
1172 value: Self::convert(holder, inner(value)),
1173 }],
1174 })
1175 }
1176
1177 /// A call to the generated defaulter, which is Table 9 once per
1178 /// type rather than once per position — routed through the place's
1179 /// marker exactly as [`Swift::read_call`]'s answer is.
1180 ///
1181 /// # Errors
1182 /// Never; the signature matches the emitters around it.
1183 fn default_into(
1184 ast: &Ast,
1185 id: Id<TypeNode>,
1186 place: Self::Expr,
1187 ) -> Result<Self::Stmt, EncodeError> {
1188 let defaulted = Self::invoke(
1189 None,
1190 &crate::default_swift::defaulter_name(ast, id),
1191 Vec::new(),
1192 );
1193 Ok(Self::assemble_assign(place, defaulted))
1194 }
1195
1196 /// # Errors
1197 /// As [`crate::default_swift::member_default`]: a `@default` this
1198 /// column cannot spell is refused rather than silently replaced by
1199 /// Table 9's zero.
1200 fn member_default(
1201 ast: &Ast,
1202 node: &MemberNode,
1203 type_: Id<TypeNode>,
1204 place: Self::Expr,
1205 ) -> Result<Vec<Self::Stmt>, EncodeError> {
1206 crate::default_swift::member_default(ast, node, type_, inner_place(place))
1207 }
1208
1209 /// `Mode.IDLE` — the enum's own scope, which is where the
1210 /// materialiser declared the case. The trait's default answers a
1211 /// bare prefixed name, which in Swift names nothing.
1212 fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
1213 plain(swift::Expr::Member {
1214 value: Box::new(swift::Expr::Name(Ident::new(&swift_type_name(ast, id)))),
1215 name: Ident::new(&property_name(enumerator)),
1216 })
1217 }
1218}
1219
1220/// The expression as a *place*: the branch slot stays the property —
1221/// assignment promotes — where [`inner`] would unwrap it.
1222/// zerodds-lint: recursion-depth 2 (an Assign holds one place, never another Assign)
1223fn inner_place(expr: SwiftExpr) -> swift::Expr {
1224 match expr {
1225 SwiftExpr::Plain(e)
1226 | SwiftExpr::BranchSlot(e)
1227 | SwiftExpr::ExternalSlot(e)
1228 | SwiftExpr::AppendSlot(e) => e,
1229 SwiftExpr::Assign(place, _) => inner_place(*place),
1230 }
1231}
1232
1233/// The bound a `string`, a `sequence` or a `map` carries, as the suffix
1234/// that keeps two of them apart.
1235///
1236/// The tag names the routine a hoisted collection's codec is emitted
1237/// under, and one body is emitted per name. With the bound left out,
1238/// `sequence<long>` and `sequence<long, 4>` are one name and whichever
1239/// was declared first decides: measured on
1240/// `@final struct S { sequence<long> b; sequence<long, 4> a; };`, the
1241/// generated Swift held no `zd_over` check at all, so `a` accepted a
1242/// stream of any length; with the members the other way round both got
1243/// the bounded body, so `b` refused — and truncated — a length the IDL
1244/// puts no limit on. `string<4>` beside `string` and `wstring<4>`
1245/// beside `wstring` collapse the same way, onto the trimming call.
1246/// Either way the reader answered with a value the IDL does not
1247/// describe and said nothing, which is why the bound is part of the
1248/// name. The same suffix the Zig column carries.
1249///
1250/// An `array` needs nothing here: its `dims` are already in the tag,
1251/// under the `Array` arm below.
1252fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
1253 match bound.expr() {
1254 None => String::new(),
1255 // `_b` and not a bare `_n`: `seq_long_4` is what the `Array`
1256 // arm answers for an array of four `long`, and the two are
1257 // different wire forms.
1258 Some(expr) => match ast.eval_integral(expr) {
1259 Ok(limit) => format!("_b{limit}"),
1260 // As the array's `_x`: a bound that does not evaluate is
1261 // still not the unbounded form, and the materialiser
1262 // reports the expression that would not evaluate.
1263 Err(_) => "_bx".to_string(),
1264 },
1265 }
1266}
1267
1268/// The name a codec function takes for the type it codes: the IDL kind
1269/// and not the Swift spelling, structurally for a collection and the
1270/// flattened Swift name for a declaration — the same scheme the D and
1271/// Go columns carry, for the reasons their `type_tag`s state. A back
1272/// quote the escape put round a keyword-named type is stripped: it is
1273/// not an identifier character, and the bare spelling cannot collide
1274/// with anything else in the file precisely because the bare spelling
1275/// is reserved.
1276///
1277/// zerodds-lint: recursion-depth 64 (collection nesting; bounded by `MAX_TYPE_DEPTH`)
1278#[must_use]
1279pub fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
1280 if depth > MAX_TYPE_DEPTH {
1281 return "unresolved".to_string();
1282 }
1283 let node = match ast.type_(id) {
1284 Ok(node) => node,
1285 Err(_) => return "unresolved".to_string(),
1286 };
1287 match &node.kind {
1288 TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
1289 TypeKind::String {
1290 width: CharWidth::Char,
1291 bound,
1292 } => format!("string{}", bound_tag(ast, bound)),
1293 TypeKind::String {
1294 width: CharWidth::WChar,
1295 bound,
1296 } => format!("wstring{}", bound_tag(ast, bound)),
1297 TypeKind::Fixed { digits, scale } => format!("fixed_{digits}_{scale}"),
1298 TypeKind::Sequence { element, bound } => match element.resolved() {
1299 Some(element) => format!(
1300 "seq{}_{}",
1301 bound_tag(ast, bound),
1302 type_tag(ast, element, depth + 1)
1303 ),
1304 None => format!("seq_unresolved{}", bound_tag(ast, bound)),
1305 },
1306 TypeKind::Array { element, dims } => {
1307 let mut name = match element.resolved() {
1308 Some(element) => format!("arr_{}", type_tag(ast, element, depth + 1)),
1309 None => "arr_unresolved".to_string(),
1310 };
1311 for dim in dims {
1312 match ast.eval_integral(*dim) {
1313 Ok(length) => name.push_str(&format!("_{length}")),
1314 // No number rather than a zero: the name has to
1315 // differ from a `[0]` array's, and the materialiser
1316 // reports the dimension that would not evaluate.
1317 Err(_) => name.push_str("_x"),
1318 }
1319 }
1320 name
1321 }
1322 TypeKind::Map { key, value, bound } => {
1323 let half = |half: Option<Id<TypeNode>>| match half {
1324 Some(target) => type_tag(ast, target, depth + 1),
1325 None => "unresolved".to_string(),
1326 };
1327 format!(
1328 "map{}_{}_{}",
1329 bound_tag(ast, bound),
1330 half(key.resolved()),
1331 half(value.resolved())
1332 )
1333 }
1334 // An alias is followed through: two IDL names for one shape are
1335 // one wire form, and two codecs for it would be two chances to
1336 // differ.
1337 TypeKind::Alias { aliased } => match aliased.resolved() {
1338 Some(target) => type_tag(ast, target, depth + 1),
1339 None => "unresolved".to_string(),
1340 },
1341 TypeKind::ValueBox { boxed } => match boxed.resolved() {
1342 Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)),
1343 None => "box_unresolved".to_string(),
1344 },
1345 TypeKind::Structure { .. }
1346 | TypeKind::Union { .. }
1347 | TypeKind::Enumeration { .. }
1348 | TypeKind::Bitmask { .. }
1349 | TypeKind::Bitset { .. }
1350 | TypeKind::Valuetype { .. }
1351 | TypeKind::Native => swift_type_name(ast, id).replace('`', ""),
1352 TypeKind::Any => "any".to_string(),
1353 TypeKind::ValueBase => "valuebase".to_string(),
1354 TypeKind::ObjectRef { .. } => "objectref".to_string(),
1355 TypeKind::AnnotationType { .. } => "annotation".to_string(),
1356 }
1357}