Skip to main content

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}