Skip to main content

zerodds_lang_rules/emit/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The target a codec walk emits into.
4//!
5//! [`encode`] walks the layout plan and decides, member by
6//! member, what the generated program does: which header opens, what the
7//! length code is, in what order a `map`'s entries go, where a `DHEADER`
8//! is closed. None of that is C. What is C is the shape the decision is
9//! written in — `v->x` rather than `v.x`, `&v->x` rather than `v.x` at a
10//! call site, a counter declared `uint32_t` rather than `int`.
11//!
12//! This trait is that seam. A walk written against it emits the same
13//! decisions into any language whose runtime implements the wire, and a
14//! new column costs a backend rather than a second copy of the walk.
15//!
16//! ## What this does not share, on purpose
17//!
18//! Not the runtime. `runtime/c`, `runtime/cpp`, `runtime/java` and
19//! `runtime/python` each implement DDS-XTypes 1.3 §7.4 from the clause,
20//! and that is what makes a column a second reading of the specification
21//! rather than a rendering of the first one's answer. What a backend
22//! contributes here is call construction: which method name, which
23//! receiver, which argument form. Where two columns disagree on a byte,
24//! the disagreement is still between two runtimes.
25//!
26//! Sharing the walk removes the other thing — four copies of one framing
27//! decision, of which a later clause fix reaches three. That failure
28//! leaves every test green.
29//!
30//! ## Moving one of these files
31//!
32//! Several guards key on the path of a walk rather than on its contents,
33//! deliberately: they check that exactly one site in the encoder crosses
34//! from a declared bound to the effective one, and a check that searched
35//! the whole tree would pass on a site in a test. So a rename breaks
36//! them, and it broke them twice while these two walks moved here.
37//!
38//! What to sweep, both times it was these:
39//!
40//! - `tools/silent-drop-allowlist.tsv` — keyed on path *and* text, so a
41//!   turbofish added by a genericisation counts as a new site. Move the
42//!   reasons rather than re-judging: the code in the function did not
43//!   change, and a reason written a second time is a reason that has
44//!   drifted from what it was written about.
45//! - `crates/wire-probe/tests/{enum_holder_width,enumerator_value,
46//!   fixed_digit_count,map_entry_order,union_label_match}.rs` — one
47//!   `SITES` table each.
48//! - `docs/{c-column,wire-feature-inventory,wire-axes-followup}.md` and
49//!   the interop-suite spec.
50//!
51//! `cargo test --workspace` stops after the first failing binary, which
52//! is why the first sweep looked like one guard and was five. Use
53//! `--no-fail-fast`.
54//!
55//! ## The scalar set
56//!
57//! [`Scalar`] is the C set because the C set is the union: every other
58//! target has a name for each of these, and none needs one the list has
59//! not got. `LongDouble` is the awkward member and is kept abstract for
60//! the same reason `codec_name` refuses to name a function after the C
61//! spelling — `double` and `long double` share a C type and do not share
62//! a wire form.
63
64pub mod ada;
65pub mod ada83;
66pub mod c;
67pub mod cpp;
68pub mod csharp;
69pub mod d;
70pub mod decode;
71pub mod elixir;
72pub mod encode;
73pub mod fsharp;
74pub mod go;
75pub mod interface_cpp;
76pub mod interface_java;
77pub mod interface_rust;
78pub mod interface_typescript;
79pub mod java;
80pub mod julia;
81pub mod kotlin;
82pub mod lua;
83pub mod nim;
84pub mod node;
85pub mod ocaml;
86pub mod python;
87pub mod swift;
88pub mod type_pair_cpp;
89pub mod type_pair_java;
90pub mod type_pair_rust;
91pub mod typescript;
92pub mod zig;
93
94use crate::encode::EncodeError;
95use zerodds_ast::{Ast, MemberNode, TypeNode};
96use zerodds_ast_core::Id;
97use zerodds_lang_cst::common::{Doc, Literal};
98
99/// Whether `id` is an enumeration once typedefs are seen through.
100///
101/// [`CodecTarget::enumerator_ordinal`] is asked for a map key of an
102/// enumeration type and for the integer arms of a union overlay alike,
103/// and a target whose override converts an enumeration value (`.rawValue`,
104/// `@intFromEnum`) has to leave the integer arms as they are: the overlay
105/// never carries an enumeration
106/// ([`zerodds_ast_layout::union_negative_matches_nothing`]).
107pub(crate) fn resolves_to_enumeration(ast: &Ast, id: Id<TypeNode>) -> bool {
108    const MAX_ALIAS_DEPTH: usize = 64;
109    let mut current = id;
110    for _ in 0..MAX_ALIAS_DEPTH {
111        match ast.type_(current).map(|node| &node.kind) {
112            Ok(zerodds_ast::TypeKind::Enumeration { .. }) => return true,
113            Ok(zerodds_ast::TypeKind::Alias { aliased }) => match aliased.resolved() {
114                Some(target) => current = target,
115                None => return false,
116            },
117            _ => return false,
118        }
119    }
120    false
121}
122
123/// Whether the walk reaches `member` through `T`'s `external_` seams:
124/// it carries `@external`, or `T` keeps a member on a type cycle the
125/// same way ([`CodecTarget::holds_a_cycle_externally`]). One answer for
126/// the four places the walks ask it, so the reader, the writer and the
127/// union overlay cannot hold one member two ways.
128pub(crate) fn reached_externally<T: CodecTarget>(
129    ast: &Ast,
130    member: &zerodds_ast::MemberNode,
131) -> bool {
132    if T::holds_a_cycle_externally() {
133        // A column that holds `@optional` members behind a pointer of
134        // their own (`T *`, §7.5.1.2.4.2) holds one that closes a cycle
135        // there too: it is optional before it is external.
136        if T::optionals_are_pointers()
137            && member.facets.is_optional()
138            && !crate::adapter::annotated_external(member)
139        {
140            return false;
141        }
142        crate::adapter::held_externally(ast, member)
143    } else {
144        crate::adapter::annotated_external(member)
145    }
146}
147
148/// Whether this column keeps a member behind an indirection that
149/// [`CodecTarget::behind_indirection`] reaches through: the member
150/// closes a cycle, or it is `@external` in a column that keeps externals
151/// behind a pointer ([`CodecTarget::externals_are_pointers`]) and its
152/// value is not a pointer already.
153///
154/// `external` is the answer of [`reached_externally`] for the member.
155/// One answer for the encoder, the decoder and the defaulter, so that
156/// what the header declares and what the codec reaches through cannot
157/// part.
158pub(crate) fn behind_pointer<T: CodecTarget>(
159    ast: &Ast,
160    enclosing: Id<TypeNode>,
161    member_type: Id<TypeNode>,
162    external: bool,
163    optional: bool,
164) -> bool {
165    zerodds_ast_layout::member_held_inline_closes_a_cycle(ast, enclosing, member_type)
166        || (((external && T::externals_are_pointers())
167            || (optional && !external && T::optionals_are_pointers()))
168            && !T::value_is_pointer(ast, member_type))
169}
170
171/// How a walk reaches one member of an aggregate: the spelling of its field
172/// and the structure that declares it.
173pub(crate) struct Reach {
174    /// The field, with the path through the base members that a column
175    /// binds as `parent` ([`CodecTarget::inherited_path`]).
176    pub field: String,
177    /// The structure whose declaration holds the member: `aggregate`, or the
178    /// base it is inherited from where the column nests the base.
179    pub holder: Id<TypeNode>,
180}
181
182/// How many structures up the base chain of `aggregate` declare `member`.
183pub(crate) fn inheritance_depth(
184    ast: &Ast,
185    aggregate: Id<TypeNode>,
186    member: zerodds_ast_core::NodeId,
187) -> usize {
188    let mut current = aggregate;
189    for depth in 0..64 {
190        let declares = ast
191            .members_of(current)
192            .iter()
193            .any(|handle| ast.member(*handle).is_ok_and(|node| node.node_id == member));
194        if declares {
195            return depth;
196        }
197        match ast.type_(current).map(|node| &node.kind) {
198            Ok(zerodds_ast::TypeKind::Structure {
199                base: Some(base), ..
200            }) => match base.resolved() {
201                Some(next) => current = next,
202                None => return 0,
203            },
204            _ => return 0,
205        }
206    }
207    0
208}
209
210/// The structure `depth` steps up the base chain of `aggregate`.
211pub(crate) fn base_at(ast: &Ast, aggregate: Id<TypeNode>, depth: usize) -> Id<TypeNode> {
212    let mut current = aggregate;
213    for _ in 0..depth {
214        match ast.type_(current).map(|node| &node.kind) {
215            Ok(zerodds_ast::TypeKind::Structure {
216                base: Some(base), ..
217            }) => match base.resolved() {
218                Some(next) => current = next,
219                None => return aggregate,
220            },
221            _ => return aggregate,
222        }
223    }
224    current
225}
226
227/// Where the walk of `aggregate` finds `member`, named `name` in IDL.
228///
229/// XTypes 1.3 §7.5.1.4.1.3: a language with no mapping of its own for
230/// inheritance binds a derived structure "as if an instance of the base type
231/// were the first member of the subtype with the name 'parent'". A column that
232/// does answers [`CodecTarget::inherited_path`], and a member of a base is
233/// reached through it; one that does not keeps the base's members in the
234/// derived structure, where `field` is the plain name and `holder` is the
235/// aggregate itself.
236/// The base of a derived structure and the names of every member it has, inherited ones
237/// included. XTypes 1.3 §7.5.1.4.1.3: a language with no mapping of its own for inheritance
238/// binds the base as the first member `parent`; the code that fills a sample takes these keys
239/// out of the flat object it is given and fills the base from them. `None` for a structure
240/// with no base.
241#[must_use]
242pub fn inherited_member_names(
243    ast: &Ast,
244    aggregate: Id<TypeNode>,
245) -> Option<(Id<TypeNode>, Vec<String>)> {
246    let base = crate::adapter::c::struct_base(ast, aggregate)?;
247    let names = ast
248        .all_members_of(base)
249        .iter()
250        .filter_map(|id| ast.member(*id).ok())
251        .map(|member| member.name.clone())
252        .collect();
253    Some((base, names))
254}
255
256/// The field a walk of `aggregate` writes `name` as, through the base members a column binds
257/// as `parent` (see `reach`); for the code that fills and prints a sample of a generated type.
258#[must_use]
259pub fn member_field<T: CodecTarget>(
260    ast: &Ast,
261    aggregate: Id<TypeNode>,
262    member: zerodds_ast_core::NodeId,
263    name: &str,
264) -> String {
265    reach::<T>(ast, aggregate, member, name).field
266}
267
268pub(crate) fn reach<T: CodecTarget>(
269    ast: &Ast,
270    aggregate: Id<TypeNode>,
271    member: zerodds_ast_core::NodeId,
272    name: &str,
273) -> Reach {
274    let escaped = T::escape(name);
275    let depth = inheritance_depth(ast, aggregate, member);
276    match (depth, T::inherited_path(depth)) {
277        (0, _) | (_, None) => Reach {
278            field: escaped,
279            holder: aggregate,
280        },
281        (depth, Some(path)) => Reach {
282            field: format!("{path}{escaped}"),
283            holder: base_at(ast, aggregate, depth),
284        },
285    }
286}
287
288/// The enumerator an enumeration takes as its default, by IDL name.
289///
290/// §7.3.1.2.1.10: the one `@default_literal` names, and "the first
291/// literal in the enumeration" where none does. A question about the
292/// specification and not about a language, so it is answered once here
293/// and each column spells the answer through
294/// [`CodecTarget::enumerator_constant`]. `default_c` derives the same
295/// thing for its own defaulters and takes it from here, so the reader's
296/// fallback and the defaulter's cannot name two different enumerators.
297#[must_use]
298pub fn default_enumerator_name(ast: &Ast, id: Id<TypeNode>) -> Option<String> {
299    let mut first = None;
300    for enumerator in ast.enumerators_of(id) {
301        let Ok(node) = ast.enumerator(*enumerator) else {
302            continue;
303        };
304        if node
305            .facets
306            .xtypes
307            .as_ref()
308            .is_some_and(|x| x.is_default_literal)
309        {
310            return Some(node.name.clone());
311        }
312        if first.is_none() {
313            first = Some(node.name.clone());
314        }
315    }
316    first
317}
318
319/// A scalar the generated program declares a local of.
320///
321/// Locals only. A member's own type comes from the language's
322/// materialiser, which knows things this does not: what a `sequence`
323/// widens to, whether an `@optional` is a nullable reference or a
324/// wrapper. These are the counters, lengths and saved header positions a
325/// codec body needs for itself.
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
327pub enum Scalar {
328    /// The natural type of a `for` counter and a container length.
329    ///
330    /// `uint32_t` in C, `std::size_t` in C++, `int` in Java — the
331    /// backend's own answer, and deliberately not one of the fixed-width
332    /// members below. A sequence length is a `uint32` on the wire, so C
333    /// counts in that type and `-Wsign-conversion` stays quiet where the
334    /// counter meets one; Java has no unsigned type to count in and a
335    /// `long` counter would need a cast at every `List.get`.
336    Index,
337    /// The type a byte count and a stream position are held in.
338    ///
339    /// `size_t` in C and `int` in Java, and distinct from
340    /// [`Scalar::Index`] because C's two are not the same type: a
341    /// reader compares a position against a delimiter's end in
342    /// `size_t` and counts elements in `uint32_t`, and mapping both to
343    /// one of the two is a narrowing `-Wshorten-64-to-32` catches.
344    Size,
345    /// `bool`
346    Bool,
347    /// `char`, the octet-wide one.
348    Char,
349    /// `int8_t`
350    Int8,
351    /// `int16_t`
352    Int16,
353    /// `int32_t`
354    Int32,
355    /// `int64_t`
356    Int64,
357    /// `uint8_t`
358    UInt8,
359    /// `uint16_t`
360    UInt16,
361    /// `uint32_t`
362    UInt32,
363    /// `uint64_t`
364    UInt64,
365    /// `float`
366    Float,
367    /// `double`
368    Double,
369    /// binary128, whatever the target calls it.
370    LongDouble,
371}
372
373/// A small value of the runtime's that the generated code declares.
374///
375/// A header is opened before the value it frames and closed after it,
376/// and the close needs to know where the open wrote. Each runtime keeps
377/// that in a small value of its own — `zdw_dheader` in C, `DHeader` in
378/// Java — and the walk holds one in a local without knowing what it is.
379///
380/// Naming these in the walk was the last thing C's spelling reached
381/// into: a Java backend that inherited `zdw_dheader` as a type name
382/// would generate source no compiler has a type for.
383///
384/// The reading direction adds four, and they were the leak the C++
385/// reader found: the walk spelled `zdw_try_construct`,
386/// `zdw_reader_mark`, `zdw_pid_read` and `zdw_emheader_read` into
387/// [`CodecTarget::named`] directly, which is C's namespace written into
388/// what is supposed to be language-neutral. They went unnoticed because
389/// the C column was the only one that generated a reader.
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
391pub enum Handle {
392    /// What [`CodecTarget::runtime`]'s `write_dheader` returns.
393    DHeader,
394    /// What `write_emheader` returns.
395    EmHeader,
396    /// What `write_pid` and `write_pid_extended` return.
397    Pid,
398    /// The behaviour §7.2.2.7 attaches to a position, as the reader's
399    /// extra parameter takes it.
400    ///
401    /// `zdw_try_construct` in C, `TryConstruct` in Java and C++. A
402    /// parameter rather than something the runtime hands back, and here
403    /// rather than beside [`Constant`] because the walk needs the
404    /// *type* to declare the parameter with; the three values it takes
405    /// are `Constant::TryConstruct*`. A walk that wrote C's spelling
406    /// would name a type `javac` has never seen.
407    TryConstruct,
408    /// What `reader_save` returns and `reader_restore` takes.
409    ReaderMark,
410    /// What `read_pid` and `read_pid_as` return.
411    PidRead,
412    /// What `read_emheader` returns.
413    EmHeaderRead,
414}
415
416/// What a codec routine is made of.
417///
418/// A struct rather than a parameter list because the reader takes a
419/// parameter the writer does not — the try-construct behaviour, which
420/// DDS-XTypes 1.3 §7.2.2.7 attaches to the position rather than to the
421/// type — and eight positional arguments is where a caller starts
422/// passing them in the wrong order.
423pub struct Routine<T: CodecTarget + ?Sized> {
424    /// The comment above it.
425    pub doc: Doc,
426    /// Its name.
427    pub name: String,
428    /// The type of the value being coded.
429    pub sample: T::Type,
430    /// How the sample arrives. See [`Handoff`].
431    pub held: Handoff,
432    /// Whether this is the writing direction.
433    pub writing: bool,
434    /// Whether it may legitimately have no caller.
435    pub tolerate_unused: bool,
436    /// Parameters after the sample, in written order.
437    pub extra: Vec<(T::Type, String)>,
438    /// The body, absent to ask for a forward declaration.
439    pub body: Option<T::Block>,
440}
441
442/// A policy value the generated code names.
443///
444/// Every runtime publishes the same set — they implement the same
445/// clauses — and each spells them its own way: `ZDW_WCHAR_WIDTH_2` in C
446/// against `WcharWidth.OCTETS_2` in Java. An enum rather than the C
447/// string, so a backend that forgets one does not compile instead of
448/// emitting a name javac has never seen.
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
450pub enum Constant {
451    /// A scalar `wchar` is two octets wide.
452    WcharWidth2,
453    /// A scalar `wchar` is four octets wide.
454    WcharWidth4,
455    /// A `wchar` carries no length octet.
456    WcharFixed,
457    /// A `wchar` carries GIOP's length octet.
458    WcharGiopLengthPrefixed,
459    /// A `wstring`'s code units go in the stream's byte order.
460    WstringUnitsStream,
461    /// Big-endian whatever the stream is.
462    WstringUnitsBigEndianAlways,
463    /// Preceded by a byte-order mark.
464    WstringUnitsByteOrderMark,
465    /// In the host's order, unswapped.
466    WstringUnitsHostUnswapped,
467    /// §7.4.1.1.2: octets, no terminator.
468    WstringSpecification,
469    /// Characters rather than octets.
470    WstringCharacterCount,
471    /// Characters, and a terminator.
472    WstringCharacterCountTerminated,
473    /// Octets in wide units.
474    WstringSpecificationWideUnits,
475    /// What `rosidl` writes.
476    WstringRosTypeSupport,
477    /// Octets, terminated, two-octet units.
478    WstringOctetsTerminated2,
479    /// Octets, terminated, four-octet units.
480    WstringOctetsTerminated4,
481    /// Characters, terminated, two-octet units.
482    WstringCharactersTerminated2,
483    /// A `fixed` writes the digits its type declared.
484    FixedDeclared,
485    /// A `fixed` writes as many digits as the value has.
486    FixedValuePrecision,
487    /// A `fixed` writes the digit count of the input string, padded to odd.
488    FixedValueStringDigits,
489    /// A PL_CDR1 parameter carries a member.
490    ParameterMember,
491    /// A PL_CDR1 parameter carries an `@optional` member.
492    ParameterOptional,
493    /// §7.2.2.7: the sample is discarded.
494    TryConstructDiscard,
495    /// The value is trimmed to what fits.
496    TryConstructTrim,
497    /// The member takes Table 9's value.
498    TryConstructUseDefault,
499    /// The reader is still good.
500    StatusOk,
501    /// The reader gave up: the sample is discarded.
502    StatusDiscarded,
503    /// A length no stream that long can carry.
504    StatusImpossibleLength,
505    /// A value outside what the type can hold.
506    StatusOutOfRange,
507    /// The stream ended where a value was expected.
508    StatusTruncated,
509}
510
511/// How a value reaches a nested codec.
512///
513/// The one place the targets genuinely disagree, and the reason it is an
514/// enum rather than a `bool`: C hands an aggregate's address and an
515/// array's first element, which are written differently and mean the
516/// same thing, while a target with references hands the value either
517/// way. A caller says which of the two situations it is in and the
518/// backend says how that is spelled.
519#[derive(Debug, Clone, Copy, PartialEq, Eq)]
520pub enum Handoff {
521    /// The value is an array, which C adjusts to a pointer on its own.
522    Array,
523    /// Anything else.
524    Value,
525}
526
527/// What a union branch's storage octets are read out of.
528///
529/// The Cyclone union overlay (`union_negative_selects_nothing`): a
530/// discriminator that selects a suppressed negative label makes the
531/// peer write its union storage *as the default arm*, so the emitted
532/// codec has to produce the value's storage bits — the `u64` whose
533/// little-endian bytes are the value's little-endian storage octets,
534/// zero-extended. Sign extension is the mistake this enum exists to
535/// prevent: a target with no unsigned types masks, it does not cast.
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537pub enum BitPatternSource {
538    /// An integer, character, enumeration or bitmask holder of this
539    /// many octets. The pattern is the value's two's-complement bits,
540    /// masked to the width — `-2` in four octets is
541    /// `0x00000000FFFFFFFE`. The walk hands the value through the
542    /// target's `Int64` cast first, the same seam the enumeration
543    /// codec reads its value through, so every target receives its
544    /// widest integer and only masks.
545    Int {
546        /// 1, 2, 4 or 8.
547        octets: u8,
548    },
549    /// A boolean: one octet, 0 or 1.
550    Bool,
551    /// Binary32: the IEEE 754 bits, zero-extended.
552    Float32,
553    /// Binary64: the IEEE 754 bits.
554    Float64,
555}
556
557/// The syntax a codec walk builds.
558///
559/// Every method is a constructor: the trait holds no state and a backend
560/// is a unit type. What varies between backends is spelling, not order —
561/// a walk calls these in the sequence the layout plan dictates and the
562/// backend never reorders.
563pub trait CodecTarget {
564    /// An expression.
565    type Expr: Clone;
566    /// A statement.
567    type Stmt: Clone;
568    /// A sequence of statements.
569    type Block: Clone;
570    /// A type, as written in a declaration.
571    type Type: Clone;
572    /// A top-level declaration — in C a function, in Java a method.
573    type Decl;
574
575    // -- types --------------------------------------------------------
576
577    /// A scalar local's type.
578    fn scalar(kind: Scalar) -> Self::Type;
579
580    /// A type named by the materialiser.
581    fn named(name: &str) -> Self::Type;
582
583    /// The type of a handle the runtime hands back. See [`Handle`].
584    fn handle(kind: Handle) -> Self::Type;
585
586    /// A policy value the runtime publishes. See [`Constant`].
587    fn constant(which: Constant) -> Self::Expr;
588
589    /// A character's code point, as the runtime's parameter wants it.
590    ///
591    /// `narrow` is a `char` rather than a `wchar`: C truncates to an
592    /// octet and widens again, which is what its runtime is handed
593    /// today and what 17 220 recorded slots agree on. A target whose
594    /// character type is already a code unit hands it over and lets the
595    /// runtime keep the low byte — the same octet, decided one step
596    /// later.
597    ///
598    /// A seam and not a cast, because the width is the backend's:
599    /// `Scalar::UInt32` is `uint32_t` in C and `long` in Java, and
600    /// `writeChar` takes an `int`.
601    fn code_point(value: Self::Expr, narrow: bool) -> Self::Expr;
602
603    /// Whether `left` is less than `right` as **unsigned** integers of
604    /// this width.
605    ///
606    /// The default is the target's own `<`, which is right wherever the
607    /// held type is unsigned or strictly wider than the value. Five
608    /// columns are not in that position at eight octets, and each
609    /// overrides: Java and Kotlin hold an IDL `unsigned long long` as
610    /// the bit pattern in a `long` and reach `Long.compareUnsigned`,
611    /// Lua reaches `math.ult`, OCaml's `Int64` reaches its runtime's
612    /// `u64_less`, and Ada 83 — which has no modular type at all, only
613    /// `Long_Integer` — reaches `Zd_Wire83.U64_Less`. Every one of them
614    /// sorted `16045690984833335998` ahead of `7` in a `map`'s
615    /// ascending-key order before the override existed (measured
616    /// against Fast DDS on `map-key-ulonglong`; Ada 83 against Fast DDS
617    /// and OpenDDS, all four encodings, 2026-09-04). A target in that
618    /// position overrides.
619    ///
620    /// The narrower widths need no override anywhere: an `unsigned
621    /// long` tops out at 4294967295, which every column above holds
622    /// zero-extended in a signed type at least 64 bits wide, so the
623    /// signed order and the unsigned order are the same order.
624    fn unsigned_less(octets: u8, left: Self::Expr, right: Self::Expr) -> Self::Expr {
625        let _ = octets;
626        Self::binary("<", left, right)
627    }
628
629    /// Whether `left` is less than `right` as **boolean** map keys —
630    /// `false` before `true`, the ascending-key order's reading of the
631    /// one-octet wire value.
632    ///
633    /// The default is the target's own `<`, which C, C++, Rust and
634    /// Python all give two booleans. A language whose `<` refuses the
635    /// pair — Java, C# and TypeScript name a type error — overrides
636    /// with a spelling of the same order.
637    fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
638        Self::binary("<", left, right)
639    }
640
641    /// The value's storage bits, zero-extended into the runtime's
642    /// 64-bit pattern argument. See [`BitPatternSource`]; consumed by
643    /// the runtime's `write_union_overlay`, which writes the low
644    /// `octets` of the pattern as one integer of that width in wire
645    /// endianness — the Cyclone union overlay.
646    ///
647    /// The default spells each form as a free function of the runtime —
648    /// `bit_pattern_int(value, octets)`, `bit_pattern_bool`,
649    /// `bit_pattern_f32`, `bit_pattern_f64` — which fits every target
650    /// whose [`CodecTarget::runtime_static`] names free functions. A
651    /// target that routes `runtime_static` elsewhere, or that has a
652    /// native spelling for a form, overrides.
653    fn bit_pattern(source: BitPatternSource, value: Self::Expr) -> Self::Expr {
654        match source {
655            BitPatternSource::Int { octets } => Self::runtime_static(
656                "bit_pattern_int",
657                vec![value, Self::int(i128::from(octets))],
658            ),
659            BitPatternSource::Bool => Self::runtime_static("bit_pattern_bool", vec![value]),
660            BitPatternSource::Float32 => Self::runtime_static("bit_pattern_f32", vec![value]),
661            BitPatternSource::Float64 => Self::runtime_static("bit_pattern_f64", vec![value]),
662        }
663    }
664
665    /// The unsigned type wide enough to hold `bits` of a bit set.
666    ///
667    /// A `@bit_bound(12)` set is assembled in something at least twelve
668    /// bits wide, and which type that is belongs to the target: C picks
669    /// the smallest `uintN_t` that fits, and a language with no unsigned
670    /// types picks the next signed one up and lets the runtime read the
671    /// bits. The walk asks for room, not for a spelling.
672    fn unsigned_holder(bits: u16) -> Self::Type;
673
674    /// An operation on the runtime that has no receiver.
675    ///
676    /// `no_dheader` is the only one: the plan says a value takes no
677    /// delimiter and the walk still needs a handle to hand the close,
678    /// so the runtime supplies one that finishes nothing. C exports it
679    /// as a free function and Java as a static method on the writer,
680    /// which is the same difference as [`CodecTarget::runtime`] and not
681    /// the same call.
682    fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr;
683
684    /// What a codec function returns when it returns nothing.
685    fn nothing() -> Self::Type;
686
687    // -- expressions --------------------------------------------------
688
689    /// A bare name.
690    fn name(text: &str) -> Self::Expr;
691
692    /// An integer literal.
693    fn int(value: i128) -> Self::Expr;
694
695    /// Any other literal the common tree can hold.
696    fn literal(value: Literal) -> Self::Expr;
697
698    /// A call to a free function or, in a target that has no free
699    /// functions, to the static method standing in for one.
700    fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr;
701
702    /// An operation on the runtime's writer or reader.
703    ///
704    /// `method` is the operation's bare name — `write_u32`, `read_pid`,
705    /// `finish_dheader` — and the backend spells it: C prefixes the
706    /// runtime's single namespace and passes the receiver first,
707    /// `zdw_write_u32(w, x)`; a target with methods writes
708    /// `w.writeU32(x)`. Separate from [`CodecTarget::call`] because the
709    /// two differ in more than the name, and a walk that built the C
710    /// form and let a renderer guess at the other would be deciding a
711    /// language question in the walk.
712    ///
713    /// `receiver` is the local the body holds the writer or reader in,
714    /// which is `w` and `r` in every backend so far.
715    fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr;
716
717    /// Whether this target's runtime carries the three axes
718    /// [`zerodds_ast_layout::Dialect::Hdds`] selects.
719    ///
720    /// The axes are [`zerodds_ast_layout::key::ElementPadding::Packed`],
721    /// [`zerodds_ast_layout::key::SequenceElementDelimiter::BeforeEachNonFinalElement`]
722    /// and [`zerodds_ast_layout::key::WstringCodeUnits::Utf8`]. The
723    /// reference is `zerodds-wire-codec`; a target whose runtime has the
724    /// packed-element origin, the element length in DHEADER form and a
725    /// UTF-8 `wstring` answers `true` and may be generated under that
726    /// dialect. The default is `false`, so a runtime that has not been
727    /// given them is refused with the axis named rather than generated in
728    /// the specification's form — a different octet string wearing this
729    /// key's name.
730    fn carries_hdds_axes() -> bool {
731        false
732    }
733
734    /// A member of a value the codec was handed.
735    ///
736    /// `v->x` where the parameter is a pointer, `v.x` where it is a
737    /// reference. Not [`CodecTarget::field_of`], which reaches into a
738    /// value the body already owns.
739    fn member(base: &str, field: &str) -> Self::Expr;
740
741    /// The whole value the codec was handed, rather than one member.
742    ///
743    /// `*v` where the parameter is a pointer, `v` where it is a
744    /// reference. What a primitive's codec writes.
745    fn whole(base: &str) -> Self::Expr;
746
747    /// An integer literal that has to survive its own width.
748    ///
749    /// A union label the size of `16045690984833335998` is not an `int`
750    /// in any of these languages, and a bare decimal is a constant whose
751    /// type the compiler picks — with a warning where the reader is
752    /// lucky and a quietly different comparison where they are not. C
753    /// writes `UINT64_C(..)`, Java an `L` suffix. Separate from
754    /// [`CodecTarget::int`] because most literals are small and the
755    /// suffixed form would be noise on all of them.
756    fn wide_int(value: i128) -> Self::Expr;
757
758    /// A count, in whatever spelling keeps the target's conversions
759    /// quiet.
760    ///
761    /// A sequence bound, an element count and a member id are all
762    /// unsigned on the wire, and C writes them with the `u` suffix so
763    /// that a comparison against a `uint32_t` does not trip
764    /// `-Wsign-compare`. Java has no unsigned literal and `5u` is not a
765    /// token there at all. The walk asks for a count and each backend
766    /// spells it.
767    fn unsigned_int(value: i128) -> Self::Expr {
768        Self::literal(Literal::Int(format!("{value}u")))
769    }
770
771    /// A 64-bit pattern, in a spelling wide enough to survive itself.
772    ///
773    /// A bitmask's declared-bit mask is sixty-four bits and a bare
774    /// decimal is a constant whose type the compiler picks. C writes
775    /// `UINT64_C(..)`; Java has no unsigned integer and writes the
776    /// signed pattern with an `L`.
777    fn wide_unsigned(value: u64) -> Self::Expr {
778        Self::literal(Literal::Int(format!("UINT64_C({value})")))
779    }
780
781    /// A signed 64-bit constant, as [`CodecTarget::wide_unsigned`].
782    fn wide_signed(value: i128) -> Self::Expr {
783        Self::literal(Literal::Int(format!("INT64_C({value})")))
784    }
785
786    /// A member of an expression.
787    fn field_of(value: Self::Expr, field: &str) -> Self::Expr;
788
789    /// `a[i]`
790    fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr;
791
792    /// How many elements the collection in `base` holds.
793    ///
794    /// `_length` on the C sequence struct, `.length` on a Java array,
795    /// and for a fixed-size array the bound the materialiser fixed. The
796    /// base name and not an expression, for the reason
797    /// [`CodecTarget::elements`] takes one: how a target reaches into
798    /// its own collection is the target's.
799    fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr;
800
801    /// `a op b`, written as given.
802    fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr;
803
804    /// `op a`, written as given.
805    fn unary(op: &'static str, value: Self::Expr) -> Self::Expr;
806
807    /// Parentheses written on purpose.
808    ///
809    /// The trees carry no precedence, so a walk that needs a bracket
810    /// says so and every backend honours it.
811    fn paren(value: Self::Expr) -> Self::Expr;
812
813    /// A conversion the target requires and the walk knows is exact.
814    fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr;
815
816    /// A value on its way into a nested codec.
817    ///
818    /// The abstraction of `&v->x` against `v.x`; see [`Handoff`].
819    fn handoff(value: Self::Expr, form: Handoff) -> Self::Expr;
820
821    /// All of `count` octet elements of `base` at once, in place of the
822    /// element loop of a `sequence<octet>` or a one-dimensional
823    /// `octet[N]` (and the same over `uint8`); `None`, the default, keeps
824    /// the loop. The octets are the loop's: an octet has alignment 1 and
825    /// no byte order (XTypes 1.3 §7.4.3.4.1), so a run of them is the same
826    /// bytes however it is copied. What a target gains is the per-element
827    /// call — measured in the C++ column: `sequence<octet, 8192>` element
828    /// by element cost ~100 µs per roundtrip of four codec passes.
829    fn bulk_octets_write(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
830        let _ = (base, count);
831        None
832    }
833
834    /// The read side of [`Self::bulk_octets_write`]: `count` octets into
835    /// `base`, which already holds room for them.
836    fn bulk_octets_read(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
837        let _ = (base, count);
838        None
839    }
840
841    // -- statements ---------------------------------------------------
842
843    /// A local declaration with an optional initialiser.
844    fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt;
845
846    /// An expression evaluated for its effect.
847    fn perform(value: Self::Expr) -> Self::Stmt;
848
849    /// `return`, with or without a value.
850    fn give_back(value: Option<Self::Expr>) -> Self::Stmt;
851
852    /// `if (cond) { .. } else { .. }`
853    fn when(condition: Self::Expr, then: Self::Block, otherwise: Option<Self::Block>)
854    -> Self::Stmt;
855
856    /// A counted loop from zero to `bound`, exclusive.
857    ///
858    /// Given rather than assembled from the pieces because the counter's
859    /// type is the target's business: C declares `size_t`, Java `int`,
860    /// and a walk that wrote the declaration itself would be choosing
861    /// for both.
862    fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt;
863
864    /// A loop with no counter.
865    ///
866    /// A `@mutable` aggregate's headers run until the delimiter is used
867    /// up and a PL_CDR1 list until its sentinel; neither has a bound,
868    /// and a counted loop with the clauses left empty would claim an
869    /// induction variable somebody forgot.
870    fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt;
871
872    /// Leave the enclosing loop.
873    fn leave() -> Self::Stmt;
874
875    /// A comment on its own line.
876    fn note(doc: Doc) -> Self::Stmt;
877
878    /// A value read for its effect and not kept.
879    ///
880    /// `(void)zdw_read_dheader(r);` in C, where an ignored return is a
881    /// `-Wunused-result` away from a warning and the cast is how a
882    /// caller says it meant it. A target where a call is already a
883    /// statement writes the call: `(void) expr;` is not a Java
884    /// statement at all.
885    fn drop_value(value: Self::Expr) -> Self::Stmt {
886        Self::perform(Self::cast(Self::nothing(), value))
887    }
888
889    /// How this target says a parameter is deliberately not read.
890    ///
891    /// Most decoders have nothing to do with the try-construct
892    /// behaviour — a `long` has no bound to miss — and the C probe is
893    /// built with `-Wunused-parameter -Werror`, where that fails the
894    /// build. `(void)zd_tc;` is C's way of saying so. A target whose
895    /// compiler does not warn on an unread parameter answers with no
896    /// statement, which is a legitimate answer and not an omission:
897    /// `javac` has no such warning, and `zd_tc;` is not a Java
898    /// statement at all.
899    fn mark_used(name: &str) -> Vec<Self::Stmt> {
900        vec![Self::perform(Self::cast(Self::nothing(), Self::name(name)))]
901    }
902
903    /// `zd_dec_<T>(r, <destination>, <behaviour>);` — one nested
904    /// decoder run, with its answer put where `place` says.
905    ///
906    /// The read direction's counterpart of [`Handoff`], and a whole
907    /// method rather than an argument form because the two targets do
908    /// not put the destination in the same syntactic position. C passes
909    /// its address and the callee writes through it. Java has no
910    /// address to pass and no out-parameter to pass it in, so the
911    /// decoder answers with the value and the destination is the
912    /// assignment's left-hand side — the same decision, one step later.
913    ///
914    /// A backend that overrides this is saying its decoders return
915    /// their value; [`CodecTarget::routine`] has to agree with it, and
916    /// so does [`CodecTarget::give_back`].
917    fn read_call(
918        callee: &str,
919        place: Self::Expr,
920        form: Handoff,
921        behaviour: Self::Expr,
922    ) -> Self::Stmt {
923        Self::perform(Self::call(
924            callee,
925            vec![Self::name("r"), Self::handoff(place, form), behaviour],
926        ))
927    }
928
929    /// The whole sample handed straight on to another type's decoder.
930    ///
931    /// What a `typedef` reads as: transparent on the wire, transparent
932    /// to §7.2.2.7, and in C transparent in storage too — the
933    /// destination and the behaviour go through untouched, and neither
934    /// an address nor a dereference is taken because `v` is already the
935    /// one the caller gave. Separate from [`CodecTarget::read_call`]
936    /// because there is no `place` here to take: the destination is the
937    /// parameter itself.
938    fn read_forward(callee: &str) -> Self::Stmt {
939        Self::perform(Self::call(
940            callee,
941            vec![Self::name("r"), Self::name("v"), Self::name("zd_tc")],
942        ))
943    }
944
945    // -- assembly -----------------------------------------------------
946
947    /// Room for `count` values of `element`, with `fill` inside it.
948    ///
949    /// The one operation in the reader that is not a spelling. C sets
950    /// the length, nulls the buffer, allocates `count * sizeof(element)`
951    /// out of the reader's arena, returns if that fails, and runs the
952    /// fill inside the guard — five statements. A target whose
953    /// allocation throws rather than returning nothing writes one and
954    /// has no guard to put the fill in.
955    ///
956    /// `base` is the local the codec holds the sequence in, not a
957    /// finished expression: how a target reaches into it is the
958    /// target's own — C's sequence keeps a `_length` and a `_buffer`
959    /// behind a pointer, and a language with a list type has neither.
960    /// `fill` is the statements that fill it, already built; a backend
961    /// may drop the guard but must not drop the fill.
962    ///
963    /// Shaped from the C column, which is the only caller so far. A
964    /// second reader backend is what will say whether the shape holds —
965    /// a target that appends rather than writing into a slot will want
966    /// the element's place from here too, and it is better to widen this
967    /// when that caller exists than to guess at it now.
968    /// `assigned_whole` says what `fill` does with each slot: assign it
969    /// outright, as a sequence's element decoder does, or write into it,
970    /// as a `map`'s two halves do. It is the difference between a
971    /// target whose slot *is* storage and one whose slot is a reference
972    /// that has to point at some — C ignores it and Java does not.
973    fn make_room(
974        base: &str,
975        element: Self::Type,
976        count: Self::Expr,
977        assigned_whole: bool,
978        fill: Vec<Self::Stmt>,
979    ) -> Vec<Self::Stmt>;
980
981    /// [`CodecTarget::make_room`] for a sequence whose element is
982    /// `element_id`. A target that cannot spell a slot's start value from
983    /// `element` alone overrides it: Kotlin's `Array(n) { T() }` has no
984    /// constructor to call where `T` is a `typealias` of `Int`.
985    fn make_room_of(
986        _ast: &Ast,
987        _element_id: Id<TypeNode>,
988        base: &str,
989        element: Self::Type,
990        count: Self::Expr,
991        assigned_whole: bool,
992        fill: Vec<Self::Stmt>,
993    ) -> Vec<Self::Stmt> {
994        Self::make_room(base, element, count, assigned_whole, fill)
995    }
996
997    /// One scalar, as it comes off the runtime, into a place of
998    /// `type_`.
999    ///
1000    /// C's runtime hands back the member's own width and the assignment
1001    /// is the whole of it. Java has no unsigned type and no integer
1002    /// narrower than `int` in an expression: `readI8` answers an `int`
1003    /// because that is what a number in [-128, 127] is once it has been
1004    /// widened, and a `byte` field does not take one without being told
1005    /// to. Which of its holders need that is the backend's, and a walk
1006    /// that cast unconditionally would put a redundant cast in the C
1007    /// column, where `-Wall` names it.
1008    fn store(place: Self::Expr, type_: Self::Type, value: Self::Expr) -> Self::Stmt {
1009        let _ = type_;
1010        Self::perform(Self::binary("=", place, value))
1011    }
1012
1013    /// A local the body writes *into* rather than assigns.
1014    ///
1015    /// A C local of a struct type is storage the moment it is declared.
1016    /// A Java local of a class type is a reference to none, and a body
1017    /// that assigns its fields would be dereferencing a null. Separate
1018    /// from [`CodecTarget::declare`], which is for a local the body
1019    /// assigns before it reads — those need no storage of their own and
1020    /// a target that made some would be allocating for nothing.
1021    fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
1022        Self::declare(type_, name, None)
1023    }
1024
1025    /// One field of a header the runtime handed back.
1026    ///
1027    /// `zd_h.member_id` in C. A target whose runtime publishes its
1028    /// headers behind accessors answers with the call — `zd_h.memberId()`
1029    /// — and the walk names the field rather than the spelling. Not
1030    /// [`CodecTarget::field_of`], which reaches into a value of a
1031    /// *generated* type, where both columns write a field.
1032    fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
1033        Self::field_of(value, field)
1034    }
1035
1036    /// Where a **sequence**'s element goes while it is being read, and
1037    /// what it takes to get it there: the statements before, the place,
1038    /// and the statements after.
1039    ///
1040    /// Almost always `(nothing, base[index], nothing)`, and it is a seam
1041    /// because of one container. `std::vector<bool>` is specialised —
1042    /// [vector.bool] — and its `operator[]` returns a proxy object, not a
1043    /// `bool &`. A codec that takes its destination by reference has
1044    /// nowhere to point, so the C++ column reads into a local and assigns
1045    /// it back. Every other element of every other target is a real
1046    /// object and answers with the subscript.
1047    ///
1048    /// Only a sequence needs it. `T a[2]` is a `std::array`, which is not
1049    /// specialised for any element, and a `map`'s halves are a
1050    /// `std::pair`'s members, which are real objects too.
1051    ///
1052    /// The write direction has no such problem: reading a proxy is what
1053    /// a proxy is for, and `write_bool(v[i])` converts.
1054    fn sequence_element(
1055        base: &str,
1056        index: Self::Expr,
1057        element: Self::Type,
1058    ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>);
1059
1060    /// Keep the first `count` elements of the collection in `base` and
1061    /// drop the rest.
1062    ///
1063    /// §7.3.1.2.1.12.3's TRIM for a bounded collection, which the reader
1064    /// applies after every element has been read: stopping at the bound
1065    /// would leave the rest of the sequence in the stream and put every
1066    /// following member at the wrong offset.
1067    ///
1068    /// A statement and not an assignment to [`CodecTarget::count_of`],
1069    /// which is what this used to be. C's sequence is a `_length` beside
1070    /// a `_buffer` and the length *is* the answer — the elements past
1071    /// the bound stay in the buffer, unreferenced. A `std::vector` knows
1072    /// its own size and `v.size() = 5u` is not an assignment; a
1073    /// `List` shortens differently again. The walk says how many to
1074    /// keep and the target says how.
1075    fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt>;
1076
1077    /// Whether an `@optional` member is present.
1078    ///
1079    /// The last structural assumption C made and no other target
1080    /// shares. C declares a `boolean _has_m` beside the member and this
1081    /// reads it; `adapter/java.rs` declares one boxed field and lets
1082    /// `null` mean absent, "because a `boolean _has_m` and an `m` are
1083    /// two things that can disagree, and nothing in the type system
1084    /// stops them". A backend that inherited C's flag would reach a
1085    /// field the materialiser never emitted.
1086    fn present(base: &str, field: &str) -> Self::Expr;
1087
1088    /// Say that an `@optional` member is present, or is not.
1089    ///
1090    /// C assigns the parallel flag. A target where absence is the value
1091    /// assigns nothing on the way in — writing the member is what makes
1092    /// it present — and assigns [`CodecTarget::absent`] on the way out.
1093    /// Returning no statement is a legitimate answer.
1094    fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt>;
1095
1096    /// Statements that leave every case member of a union unset, before
1097    /// the member the discriminator selects is read.
1098    ///
1099    /// The decoder starts from the union's Table 9 default, which carries
1100    /// the member the default discriminator selects. A column whose union
1101    /// has a "not set" for a case field — Python's and Julia's `None` and
1102    /// `nothing`, Swift's `nil`, Nim's and OCaml's `Option`, a missing
1103    /// key or property in Lua, Node and TypeScript, Elixir's `nil` — and
1104    /// promises that at most one case is live has to clear that member,
1105    /// or a decoded sample carries the default branch's value beside the
1106    /// one read (measured: `idl-conformance/_interop` `mapenum`, a `Sel`
1107    /// read with `_d = 2` came back with `n = 9` *and* `z = 0`). Nothing
1108    /// by default: in C the cases share storage, and where a case field
1109    /// has no "not set" (C++, C#, Java, Go, D, Zig, F#, Ada, Kotlin) its
1110    /// default value is what an unselected field holds anyway.
1111    fn clear_union_cases(base: &str, fields: &[String]) -> Vec<Self::Stmt> {
1112        let _ = (base, fields);
1113        Vec::new()
1114    }
1115
1116    /// Statements that make an array's Table 9 default writable, before
1117    /// its elements are read into it; `lengths` are its dimensions,
1118    /// outermost first.
1119    ///
1120    /// Nothing by default: every column but one holds an array in
1121    /// something its elements can be assigned into. Python declares the
1122    /// member `tuple[T, …]` (`adapter/python.rs`) and a tuple cannot be
1123    /// written into, so its default is a tuple and the reader turns it
1124    /// into lists here and back in [`CodecTarget::close_array`].
1125    fn open_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
1126        let _ = (base, lengths);
1127        Vec::new()
1128    }
1129
1130    /// Statements that give a read array the type its member declares,
1131    /// after its last element is read. Nothing by default; see
1132    /// [`CodecTarget::open_array`].
1133    fn close_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
1134        let _ = (base, lengths);
1135        Vec::new()
1136    }
1137
1138    /// A union's selected case member.
1139    ///
1140    /// C declares the cases in an anonymous `union _u` inside the
1141    /// struct, so a member is `v->_u.x`; `adapter/java.rs` declares one
1142    /// field per case directly on the class, so it is `v.x`.
1143    fn case_member(base: &str, field: &str) -> Self::Expr;
1144
1145    /// A union's discriminant.
1146    ///
1147    /// `_d` in every column whose materialiser names it that, which is
1148    /// all of them but one: DDS-TS 1.0 §7.5 names the property
1149    /// `discriminator`, and the TypeScript materialiser declares it
1150    /// under that name. The default is the name the other columns
1151    /// share, so a column that does not override generates exactly what
1152    /// it generated before this seam existed.
1153    fn discriminant(base: &str) -> Self::Expr {
1154        Self::member(base, "_d")
1155    }
1156
1157    /// The integer an enumeration's value carries, given the value.
1158    ///
1159    /// The identity in every column but one, and deliberately so: an
1160    /// enumeration is a numeric constant at the language surface there,
1161    /// so the value in the variable *is* the integer — under
1162    /// [`zerodds_ast_layout::EnumeratorValue::Declared`] it is even the
1163    /// integer that goes on the wire, which is why
1164    /// [`encode::enumeration_write`] emits no chain for that setting.
1165    ///
1166    /// DDS-TS 1.0 §7.6 makes an enumeration a string-literal union, so
1167    /// that column has no integer in the variable at all and answers
1168    /// with a lookup in the generated `<Enum>Ordinal` table. Measured:
1169    /// without this seam `enum-sparse-values` wrote `70 79 87` — the
1170    /// first code unit of each enumerator's name — where the ledger has
1171    /// `65535 1 100`.
1172    ///
1173    /// [`encode::enumeration_write`]: crate::emit::encode
1174    fn enumerator_ordinal(ast: &Ast, id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
1175        let _ = (ast, id);
1176        value
1177    }
1178
1179    /// The value a union case label is compared against, in this
1180    /// target's spelling of the discriminator's type.
1181    ///
1182    /// The default is the label folded to an integer and narrowed to the
1183    /// discriminator's own type, which is what every column with a
1184    /// numeric enumeration needs. A column whose enumeration is not
1185    /// numeric at the language surface — DDS-TS 1.0 §7.6 makes it a
1186    /// string-literal union — cannot compare a discriminant against a
1187    /// number at all, and answers with its own constant.
1188    fn case_label(ast: &Ast, discriminator: Id<TypeNode>, value: i128) -> Self::Expr {
1189        Self::cast(Self::value_type(ast, discriminator), Self::wide_int(value))
1190    }
1191
1192    /// One half of a `map`'s entry.
1193    ///
1194    /// C generates a struct with `key` and `value`; C++'s entry is a
1195    /// `std::pair`, whose halves are `first` and `second`. The walk asks
1196    /// for the half and the backend names it.
1197    fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr;
1198
1199    /// The place a member is reached at, given that the column could
1200    /// not put the member's value inside its own aggregate.
1201    ///
1202    /// [`zerodds_ast_layout::member_held_inline_closes_a_cycle`] is what
1203    /// says so: the member's type reaches the aggregate that declares it
1204    /// over nothing but inline edges, so a column whose member *is* the
1205    /// value has no size for it. `place` is the place the seams below
1206    /// would have reached — the member, the `@optional` accessor, the
1207    /// `@external` one, or a union branch — and the answer is that place
1208    /// as the column keeps it once the indirection is there.
1209    ///
1210    /// The default is the place unchanged, and it is the right answer
1211    /// for fifteen of the seventeen columns: a member that is already a
1212    /// reference — Python's `Optional[T]`, Rust's `Option<Box<T>>`,
1213    /// Java's boxed field — has a size whatever it points at, so nothing
1214    /// about it changes when it closes a cycle. C is the column that
1215    /// asks: `T next;` beside a `_has_next` flag is storage that
1216    /// contains itself, and the member has to become `T *next`.
1217    ///
1218    /// Not folded into [`CodecTarget::optional_value`]: that seam is
1219    /// handed the member's type and not the aggregate that declares it,
1220    /// and the cycle is a fact about the pair. The call sites hold both
1221    /// and ask this only where the pair closes one, so a column that
1222    /// takes the default emits exactly the source it emitted before this
1223    /// seam existed.
1224    fn behind_indirection(ast: &Ast, id: Id<TypeNode>, place: Self::Expr) -> Self::Expr {
1225        let _ = (ast, id);
1226        place
1227    }
1228
1229    /// The reading counterpart of [`CodecTarget::behind_indirection`]:
1230    /// the same place, and what has to happen before a reader can write
1231    /// through it.
1232    ///
1233    /// A column that keeps an indirection has nothing to write into
1234    /// until the indirection points somewhere, so this is where the
1235    /// storage is made. `id` is the member's type, which is what an
1236    /// allocation needs to know the size of.
1237    ///
1238    /// Returns the statements that make room, followed by the place, in
1239    /// the shape [`CodecTarget::optional_destination`] uses and for the
1240    /// same reason.
1241    fn make_indirection(
1242        ast: &Ast,
1243        id: Id<TypeNode>,
1244        place: Self::Expr,
1245    ) -> (Vec<Self::Stmt>, Self::Expr) {
1246        let _ = (ast, id);
1247        (Vec::new(), place)
1248    }
1249
1250    /// Where a *reader* puts an `@optional` member's value, and what it
1251    /// takes to have somewhere to put it.
1252    ///
1253    /// The write direction has [`CodecTarget::optional_value`], which
1254    /// reaches a value that is already there. Reading is the other way
1255    /// round and the two targets differ in a way no expression covers:
1256    /// C's optional member is a plain `T` beside a `_has_` flag, so the
1257    /// destination is the member and nothing has to happen first; C++'s
1258    /// is a `std::optional<T>` and the member's decoder takes a `T&`, so
1259    /// the optional has to be *engaged* before there is a `T` to write
1260    /// into at all.
1261    ///
1262    /// Returns the statements that make room, followed by the place. The
1263    /// statements go immediately before the read; a backend that needs
1264    /// none returns an empty vector, and the generated source is then
1265    /// exactly what it was before this seam existed.
1266    ///
1267    /// Not folded into [`CodecTarget::set_present`]: that is called
1268    /// *after* the read at two of the five sites and before it at the
1269    /// other three, and moving it would reorder the C column's output
1270    /// for a difference that is not C's.
1271    fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr);
1272
1273    /// The value an `@optional` member holds, given that it holds one.
1274    ///
1275    /// C's optional member is a plain `T` beside a `_has_` flag, so this
1276    /// is the member. C++'s is a `std::optional<T>` and the codec takes
1277    /// a `T`, so it is `.value()`. Java's boxed field is already the
1278    /// value.
1279    ///
1280    /// The member's type is a parameter because one target's answer
1281    /// depends on it and no other's does. `adapter/csharp.rs` wraps a
1282    /// *value* type in `Nullable<T>` and leaves a reference type alone,
1283    /// so the unwrapping is `.Value` for the first and nothing for the
1284    /// second — and `string?.Value` does not compile any more than
1285    /// passing an `int?` where an `int` is wanted does. A backend that
1286    /// answered from the name alone would be right for one half of its
1287    /// own mapping.
1288    ///
1289    /// Only ever reached inside a branch the presence test guarded, so a
1290    /// target whose accessor throws on an absent value is safe here.
1291    fn optional_value(ast: &Ast, id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr;
1292
1293    /// Whether this column holds an `@optional` or `@external` member of
1294    /// `aggregate`, of type `member_type`, behind a reference of its own
1295    /// — where the shared walk would reach it through
1296    /// [`CodecTarget::present`], [`CodecTarget::optional_value`] and
1297    /// their `external_` counterparts. A member this answers for goes
1298    /// through [`CodecTarget::reference_present`],
1299    /// [`CodecTarget::reference_value`],
1300    /// [`CodecTarget::reference_destination`] and
1301    /// [`CodecTarget::reference_absent`] instead.
1302    ///
1303    /// `false` by default, which leaves every column's output as it
1304    /// was. Ada answers from its adapter
1305    /// (`crate::adapter::ada::held_by_reference`): a member whose type is
1306    /// still incomplete where the aggregate is declared is an access
1307    /// type there (RM 3.10.1), which depends on the order Ada declares
1308    /// in and on nothing the layout plans. The generated Ada for
1309    /// `recursive-optional-external` asked such a member for `Present`
1310    /// and did not compile (44 matrix cells, 2026-10-01).
1311    fn holds_by_reference(
1312        ast: &Ast,
1313        aggregate: Id<TypeNode>,
1314        member_type: Id<TypeNode>,
1315        optional: bool,
1316        external: bool,
1317    ) -> bool {
1318        let _ = (ast, aggregate, member_type, optional, external);
1319        false
1320    }
1321
1322    /// Whether a member [`CodecTarget::holds_by_reference`] answers for
1323    /// is present. Reached only where that answered `true`.
1324    fn reference_present(base: &str, field: &str) -> Self::Expr {
1325        Self::present(base, field)
1326    }
1327
1328    /// The value behind such a member, given that it is present.
1329    fn reference_value(ast: &Ast, id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
1330        Self::optional_value(ast, id, base, field)
1331    }
1332
1333    /// Where a reader puts such a member's value, and what it takes to
1334    /// have somewhere to put it; `id` is the member's type.
1335    fn reference_destination(
1336        ast: &Ast,
1337        id: Id<TypeNode>,
1338        base: &str,
1339        field: &str,
1340    ) -> (Vec<Self::Stmt>, Self::Expr) {
1341        let _ = (ast, id);
1342        Self::optional_destination(base, field)
1343    }
1344
1345    /// The statements that leave such a member absent.
1346    fn reference_absent(base: &str, field: &str) -> Vec<Self::Stmt> {
1347        Self::set_present(base, field, false)
1348    }
1349
1350    /// Whether this column keeps a member that closes a type cycle the
1351    /// way it keeps an `@external` one — through the `external_` seams
1352    /// below, whether or not the member carries the annotation.
1353    ///
1354    /// `false` by default, which leaves a column's output as it was. The
1355    /// columns that answer `true` are the ones whose `@external` member
1356    /// is an indirection of their own (C++'s `std::shared_ptr`, D's and
1357    /// Zig's pointer, Nim's `ref`, Swift's `ZdExternal`) and whose plain
1358    /// member is the value, so a member on a cycle needs the same holder;
1359    /// [`crate::adapter::held_externally`] is the rule, and their adapters
1360    /// and defaulters ask it too. Ada decides by its declaration order
1361    /// ([`CodecTarget::holds_by_reference`]) and C by the pointer it declares
1362    /// (`behind_pointer`), so neither takes this.
1363    fn holds_a_cycle_externally() -> bool {
1364        false
1365    }
1366
1367    /// Whether this column holds an `@external` member behind a pointer
1368    /// of its own, where the member's value is not a pointer already.
1369    ///
1370    /// XTypes 1.3 §7.5.1.2.3.1 maps an external member of C to
1371    /// "pointer-to-X". The member is then reached through
1372    /// [`CodecTarget::behind_indirection`] and a reader makes storage for
1373    /// it with [`CodecTarget::make_indirection`], the way both do for a
1374    /// member that closes a cycle (`behind_pointer` asks both questions
1375    /// at once). `false` by default; the columns that keep an `@external`
1376    /// member behind a holder of their own go through the `external_`
1377    /// seams instead.
1378    fn externals_are_pointers() -> bool {
1379        false
1380    }
1381
1382    /// Whether this column holds an `@optional` member behind a pointer
1383    /// of its own, where the member's value is not a pointer already.
1384    ///
1385    /// XTypes 1.3 §7.5.1.2.4.1 maps an optional member of C to
1386    /// "pointer-to-X" and says "A NULL pointer shall indicate an omitted
1387    /// value"; §7.5.1.2.4.2 does the same for C++. The member is then
1388    /// reached through [`CodecTarget::behind_indirection`] and a reader
1389    /// makes storage for it with [`CodecTarget::make_indirection`],
1390    /// exactly as for an `@external` one; [`CodecTarget::present`] asks
1391    /// whether the pointer is not NULL. `false` by default: every other
1392    /// column holds an absent value in its own way.
1393    fn optionals_are_pointers() -> bool {
1394        false
1395    }
1396
1397    /// Whether the column's value of `id` is a pointer already, so that
1398    /// an `@external` or `@optional` member of that type needs no pointer
1399    /// of its own. C's `string`, `wstring` and `fixed` are
1400    /// `const char *` (XTypes 1.3 §7.5.1.2.3.1); a C++ `std::string` is
1401    /// a value.
1402    fn value_is_pointer(ast: &Ast, id: Id<TypeNode>) -> bool {
1403        crate::adapter::c::value_is_pointer(ast, id)
1404    }
1405
1406    /// Statements that stop a writer from going on when an `@external`
1407    /// member that is not `@optional` has no value at the time of the
1408    /// write.
1409    ///
1410    /// XTypes 1.3 §7.5.1.2.3.1.1 (C) and §7.5.1.2.3.2.1 (C++): "If the
1411    /// member is only external but not optional, then it is not allowed
1412    /// for the member to be NULL at the time of a write." `present` says
1413    /// whether the member has a value. Nothing by default: a column that
1414    /// holds an external member inline has no unset state to refuse.
1415    fn refuse_unset_external(present: Self::Expr) -> Vec<Self::Stmt> {
1416        let _ = present;
1417        Vec::new()
1418    }
1419
1420    /// The value of an `@external` member, as the member's codec takes
1421    /// it.
1422    ///
1423    /// DDS-XTypes 1.3 Table 21: `@external` changes no octet — it says
1424    /// where the binding keeps the value. Most columns keep it inline,
1425    /// and the default is then exactly what the walk wrote before this
1426    /// seam existed: the member, or the optional accessor where the
1427    /// member is also `@optional`. A column that keeps it behind an
1428    /// indirection — D's `T*`, which is also what gives a
1429    /// self-recursive type a size at all — answers with the
1430    /// dereference, for both. A walk that reached the member directly
1431    /// would hand a pointer to a codec that takes the value.
1432    fn external_value(
1433        ast: &Ast,
1434        id: Id<TypeNode>,
1435        base: &str,
1436        field: &str,
1437        optional: bool,
1438    ) -> Self::Expr {
1439        if optional {
1440            return Self::optional_value(ast, id, base, field);
1441        }
1442        Self::member(base, field)
1443    }
1444
1445    /// Where a *reader* puts an `@external` member's value, and what it
1446    /// takes to have somewhere to put it. The reading counterpart of
1447    /// [`CodecTarget::external_value`], shaped like
1448    /// [`CodecTarget::optional_destination`] and for the same reason: a
1449    /// column whose member is an indirection has to make storage before
1450    /// there is a value to write into. The default is the walk's old
1451    /// answer for both combinations.
1452    fn external_destination(
1453        base: &str,
1454        field: &str,
1455        optional: bool,
1456    ) -> (Vec<Self::Stmt>, Self::Expr) {
1457        if optional {
1458            return Self::optional_destination(base, field);
1459        }
1460        (Vec::new(), Self::member(base, field))
1461    }
1462
1463    /// Whether an `@optional @external` member is present.
1464    ///
1465    /// Separate from [`CodecTarget::present`] because the two
1466    /// annotations can answer differently in one column: D's plain
1467    /// `@optional` is `Nullable!(T)` and its `isNull`, while
1468    /// `@optional @external` is one pointer and its `null` — a wrapper
1469    /// over the pointer would carry a present-but-null state the wire
1470    /// form has no encoding for.
1471    fn external_present(base: &str, field: &str) -> Self::Expr {
1472        Self::present(base, field)
1473    }
1474
1475    /// Say that an `@optional @external` member is present, or is not.
1476    /// The counterpart of [`CodecTarget::set_present`], split for the
1477    /// reason [`CodecTarget::external_present`] gives.
1478    fn external_set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
1479        Self::set_present(base, field, present)
1480    }
1481
1482    /// The value of an `@external` union branch, as the branch's codec
1483    /// takes it. The union counterpart of
1484    /// [`CodecTarget::external_value`], reached through
1485    /// [`CodecTarget::case_member`] because that is the accessor a
1486    /// branch has; there is no `optional` parameter because no union
1487    /// member is `@optional` (DDS-XTypes 1.3 §7.2.2.4.4.4.7). The
1488    /// default is the accessor itself — the columns that keep the value
1489    /// inline — and a column that keeps an indirection answers with the
1490    /// dereference, exactly as it does for a structure member.
1491    fn external_case_value(ast: &Ast, id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
1492        let _ = (ast, id);
1493        Self::case_member(base, field)
1494    }
1495
1496    /// Where a *reader* puts an `@external` union branch's value, and
1497    /// what it takes to have somewhere to put it. The union counterpart
1498    /// of [`CodecTarget::external_destination`], shaped the same way
1499    /// and defaulted the same way.
1500    fn external_case_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
1501        (Vec::new(), Self::case_member(base, field))
1502    }
1503
1504    /// Where a sequence keeps the elements themselves.
1505    ///
1506    /// C's sequence is a `_length` and a `_buffer` and this is the
1507    /// buffer; a target whose sequence *is* the array answers the value.
1508    fn elements(base: &str) -> Self::Expr;
1509
1510    /// Whether a value the reader had to materialise is there at all.
1511    ///
1512    /// C's `zdw_read_string_alloc` answers `NULL` where the reader's
1513    /// heap is exhausted, and every use of what it produced is guarded
1514    /// against that — a decoder that measured a null string would read
1515    /// through the pointer. A target whose string type owns its own
1516    /// storage has no such state and answers `None`, and the guard is
1517    /// then not written at all rather than written against a value that
1518    /// is always there: `std::string != std::nullopt` is not an
1519    /// expression.
1520    ///
1521    /// Distinct from [`CodecTarget::present`], which asks about an
1522    /// `@optional` member — a property of the sample. This asks whether
1523    /// the *read* produced anything, which is a property of the runtime.
1524    fn materialised(value: Self::Expr) -> Option<Self::Expr>;
1525
1526    /// The value that says a target holds nothing.
1527    ///
1528    /// `NULL` in C, `null` in Java and C#, `None` in Python. What a
1529    /// decoder writes into a pointer before it has allocated, and what
1530    /// it compares against to find out whether the allocation worked.
1531    fn absent() -> Self::Expr;
1532
1533    /// Statements gathered into a body.
1534    fn block(statements: Vec<Self::Stmt>) -> Self::Block;
1535
1536    /// A body taken apart again.
1537    ///
1538    /// A walk that has built a nested block and then finds it belongs
1539    /// inline — an array's innermost loop under a `DHEADER`, a union's
1540    /// if-chain after the last arm — would otherwise have to carry a
1541    /// `Vec` and a `Block` in parallel and keep them agreeing.
1542    fn into_statements(block: Self::Block) -> Vec<Self::Stmt>;
1543
1544    /// One generated name, in the spelling this target publishes it
1545    /// under.
1546    ///
1547    /// The identity everywhere but Go, and it exists because Go has no
1548    /// visibility keyword: an identifier is exported if and only if its
1549    /// first character is upper case (Go specification, "Exported
1550    /// identifiers"), so a name is the only place a Go column can put
1551    /// the decision. Applied inside
1552    /// [`crate::emit::encode::codec_name`] and
1553    /// [`crate::emit::decode::decoder_name`], which is what makes it a
1554    /// single decision: every caller of either — the walk's own nested
1555    /// calls, `wireprobe-zerodds-go-gen`'s entry point, a test naming a
1556    /// function — gets the published spelling without asking.
1557    ///
1558    /// A target that overrides this owes the transform an inverse in
1559    /// name only: it must be injective, or two codecs collide under one
1560    /// name and no compiler reports that as a collision.
1561    fn published_name(name: String) -> String {
1562        name
1563    }
1564
1565    /// A codec function or method.
1566    ///
1567    /// `body` absent asks for a forward declaration, which C needs ahead
1568    /// of the bodies so a recursive or mutually recursive type does not
1569    /// depend on the order the walk visited its types in. A target that
1570    /// has no such thing returns `None` and the caller emits nothing.
1571    ///
1572    /// [`Routine::tolerate_unused`] marks a codec that may legitimately
1573    /// have no caller. Generated code is compiled with an uncalled
1574    /// function as an error, and that is worth keeping: normally it
1575    /// means the generator emitted the wrong thing. The exception is a
1576    /// codec reached from one position and written inline at another,
1577    /// where having a caller is a property of the dialect rather than
1578    /// of the type graph.
1579    fn routine(spec: Routine<Self>) -> Option<Self::Decl>;
1580
1581    /// A new reader entry with an explicit, target-derived initializer.
1582    /// Targets that cannot preserve this contract refuse the new entry only;
1583    /// existing data routines and their initialization remain unchanged.
1584    fn initialized_reader(_spec: Routine<Self>, _initializer: Self::Stmt) -> Option<Self::Decl> {
1585        None
1586    }
1587
1588    // -- the materialiser's boundary ----------------------------------
1589
1590    /// How this target spells the type of `id` where a codec holds it.
1591    ///
1592    /// The materialiser's answer, not the walk's: a `sequence<long>` is
1593    /// a struct with a buffer and a length in C, a `std::vector` in C++
1594    /// and a `List<Integer>` in Java, and only the target knows which.
1595    fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type;
1596
1597    /// The 64-bit pattern a bitmask arrives as, in the bitmask's own
1598    /// declared type.
1599    ///
1600    /// One cast to [`Self::value_type`] by default, which every target
1601    /// with a real conversion operator answers with — `(Flags)zd_bits`
1602    /// in C, `(int) zd_bits` in Java. TypeScript overrides it: its
1603    /// declared bitmask type is an alias whose holder is a `number` up
1604    /// to 32 bits, its `as` is erased and refuses `bigint` to `number`
1605    /// outright (TS2352), so the narrowing has to be a runtime
1606    /// conversion through the holder before the alias names it.
1607    fn bitmask_value(ast: &Ast, id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
1608        Self::cast(Self::value_type(ast, id), value)
1609    }
1610
1611    /// Table 9's value for `id`, written into `place`.
1612    ///
1613    /// What a reader puts where a member the stream did not carry
1614    /// belongs — DDS-XTypes 1.3 Table 9. The value is the
1615    /// specification's and the same in every column; what differs is
1616    /// where a target keeps it. C calls a generated defaulter because a
1617    /// struct-valued default is not an expression it can assign; a
1618    /// target with literals for its aggregates assigns one.
1619    ///
1620    /// # Errors
1621    /// When the target has no form for Table 9's value. Fallible
1622    /// because a backend under construction has to be able to refuse
1623    /// here: a reader generated with a hole in it and no error is a
1624    /// reader that fails where it runs rather than where it was built,
1625    /// and the two are a long way apart.
1626    fn default_into(
1627        ast: &Ast,
1628        id: Id<TypeNode>,
1629        place: Self::Expr,
1630    ) -> Result<Self::Stmt, EncodeError>;
1631
1632    /// The declared `@default` of `node`, written into `place`, or
1633    /// Table 9's value where the member declares none.
1634    ///
1635    /// Fallible on purpose: a `@default` a target cannot spell is
1636    /// reported rather than quietly replaced by Table 9's zero, which
1637    /// is a different sample from the one the designer declared.
1638    ///
1639    /// # Errors
1640    /// When the declared default has no form in this target.
1641    fn member_default(
1642        ast: &Ast,
1643        node: &MemberNode,
1644        type_: Id<TypeNode>,
1645        place: Self::Expr,
1646    ) -> Result<Vec<Self::Stmt>, EncodeError>;
1647
1648    /// An IDL identifier as this target spells a member or a field.
1649    ///
1650    /// Every language escapes the identifiers that collide with its own
1651    /// keywords, and each escapes them differently — C prefixes, Java
1652    /// prefixes with a different mark, C# writes `@`. Both halves of a
1653    /// column have to agree: a member the materialiser declared escaped
1654    /// and the codec reaches unescaped is a field no compiler finds.
1655    fn escape(name: &str) -> String;
1656
1657    /// The path from a derived structure to the base members `depth` levels up,
1658    /// for a column that binds inheritance as a first member `parent`
1659    /// (XTypes 1.3 §7.5.1.4.1.3), ending in the separator: `parent.` for the
1660    /// base, `parent.parent.` for its base. `None`, the default, is a column
1661    /// that declares the base's members in the derived structure.
1662    fn inherited_path(depth: usize) -> Option<String> {
1663        let _ = depth;
1664        None
1665    }
1666
1667    /// The name this target gives a `map`'s entry type.
1668    ///
1669    /// A `map<K, V>` has no direct form in most targets and becomes a
1670    /// collection of a generated pair; what that pair is called is the
1671    /// materialiser's, and the codec has to call it the same thing.
1672    fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String;
1673
1674    /// The type a `map`'s entry is *declared* with — the reader's
1675    /// scratch entry and `make_room`'s element.
1676    ///
1677    /// The default names [`CodecTarget::map_entry_name`], which is
1678    /// right wherever the materialiser declares a type of that name. It
1679    /// is not right in C++, whose materialiser spells the entry as
1680    /// `std::pair<K, V>` and declares nothing under the tag —
1681    /// `map_string_long zd_scratch;` was a name no compiler finds (20
1682    /// zerodds-cpp read cells across the map family).
1683    fn map_entry_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
1684        Self::named(&Self::map_entry_name(ast, id))
1685    }
1686
1687    /// The constant one enumerator of `id` is named by.
1688    ///
1689    /// The materialiser's spelling. C flattens the modules into a
1690    /// prefix and writes a bare identifier; Java declares a class of
1691    /// `public static final int` and every use is written from the
1692    /// outer class down — `ZdTypes.common.Colour.BLUE`. A walk that
1693    /// built the name from [`CodecTarget::module_prefix`] alone would
1694    /// emit an unqualified `BLUE`, which in Java names nothing.
1695    ///
1696    /// The default is C's and not the general answer: it is
1697    /// [`CodecTarget::module_prefix`] and [`CodecTarget::escape`] put
1698    /// together, which works wherever an enumerator sits in the
1699    /// enclosing scope. C++ declares a *scoped* enumeration, so the
1700    /// same enumerator is `::a::b::Colour::red` and carries the type's
1701    /// own name in the middle, which no prefix supplies; that column
1702    /// overrides.
1703    fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
1704        Self::name(&format!(
1705            "{}{}",
1706            Self::module_prefix(ast, id),
1707            Self::escape(enumerator)
1708        ))
1709    }
1710
1711    /// The prefix this target puts before a module's declarations.
1712    ///
1713    /// C has no namespaces and flattens `module a { module b { … } }`
1714    /// into a prefix; a target with namespaces answers the empty
1715    /// string and lets its own scoping do the work.
1716    fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String;
1717
1718    /// The name this target gives the type of `id`.
1719    ///
1720    /// What a generated codec function is named after, and the reason a
1721    /// codec name is built from this rather than from the IDL: two IDL
1722    /// types that collapse to one name in a target must not end up with
1723    /// one codec, and a target that escapes a keyword has to escape it
1724    /// the same way in both halves.
1725    fn type_name(ast: &Ast, id: Id<TypeNode>) -> String;
1726}