Skip to main content

zerodds_lang_rules/emit/
go.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The Go backend of the shared codec walk.
4//!
5//! DDS-XTypes 1.3 §7.4 from the clause, the same as `runtime/c`,
6//! `runtime/python` and the rest do, and none of them calls another.
7//! Where two columns disagree on an octet, one has misread the
8//! specification — which is the only reason to have an eighth.
9//!
10//! # The four decisions this column turns on
11//!
12//! **A member holds its wire width.** `adapter/go.rs` declares `octet`
13//! as `uint8` and `unsigned long long` as `uint64`, so unlike the
14//! Python column there is no width to re-state at the call and unlike
15//! the Java one there is no signedness to work around: a runtime call
16//! takes the member as it is, and the compiler is the range check.
17//!
18//! **A `char` is `uint8` and a `wchar` is `uint16`.** The generated
19//! member holds the code unit, so [`CodecTarget::code_point`] is the
20//! identity and a union over a `wchar` compares its discriminator as a
21//! number — which is what lets a surrogate case label, refused as a
22//! `str` in the Python column, be an ordinary integer here.
23//!
24//! **A `long double` and a `fixed` are decimal text.** Go has no
25//! binary128 and `float64` holds 53 of its 113 significand bits, so
26//! `runtime/go`'s `WriteF128` parses the member's decimal — correctly
27//! rounded, once — and `ReadF128` answers the exact expansion. The
28//! member's type and the codec's agree because both come from
29//! `adapter::go::go_value_type`.
30//!
31//! **An `@optional` member is a pointer, and a pointer needs storage.**
32//! `nil` is the absence, so [`CodecTarget::present`] is a nil test and
33//! [`CodecTarget::set_present`] writes nothing on the way in — the
34//! assignment is what makes it present. The reading direction cannot
35//! assign a `T` to a `*T`, and Go has no address-of on a call's result,
36//! so [`CodecTarget::optional_destination`] marks the place and
37//! [`CodecTarget::read_call`] routes the decoded value through
38//! `zdwire.Ptr` — one statement, no temporary whose name two sites in
39//! one scope would have to share.
40
41use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
42use crate::adapter::go::{
43    field_name, go_map_entry_name, go_type_name, go_value_type, module_prefix,
44};
45use crate::encode::EncodeError;
46use zerodds_ast::{Ast, CharWidth, MemberNode, TypeKind, TypeNode};
47use zerodds_ast_core::Id;
48use zerodds_lang_cst::common::{Doc, Ident, Literal};
49use zerodds_lang_cst::go;
50
51/// The Go backend.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Go;
54
55/// How deep a chain of typedefs and collections is followed.
56const MAX_TYPE_DEPTH: usize = 64;
57
58/// The name the generated file imports the runtime under.
59const RUNTIME: &str = "zdwire";
60
61/// The type name [`CodecTarget::named`] is handed for C's signed
62/// `char`. `emit/encode.rs`'s `map_key_less` writes it where a `map`'s
63/// key is a `char` and the entry order is signed; the Go member is a
64/// `uint8` and the signed reading is `int8`.
65const SIGNED_CHAR: &str = "signed char";
66
67/// An expression, with the one property the walk's seams cannot carry:
68/// whether it is the pointer field an `@optional` member lives behind.
69///
70/// Only [`CodecTarget::optional_destination`] produces the marked form
71/// and only [`CodecTarget::read_call`] and [`CodecTarget::store`] act
72/// on it; every other constructor passes the inner expression through.
73#[derive(Debug, Clone)]
74pub enum GoExpr {
75    /// An ordinary expression.
76    Plain(go::Expr),
77    /// The `*T`-typed field of an `@optional` member, as a place a
78    /// decoded `T` is about to be written into.
79    OptionalSlot(go::Expr),
80}
81
82/// The expression inside, whichever form it took.
83fn inner(expr: GoExpr) -> go::Expr {
84    match expr {
85        GoExpr::Plain(e) | GoExpr::OptionalSlot(e) => e,
86    }
87}
88
89fn plain(e: go::Expr) -> GoExpr {
90    GoExpr::Plain(e)
91}
92
93impl Go {
94    /// A runtime name, qualified: `zdwire.X`.
95    fn runtime_name(name: &str) -> go::Expr {
96        go::Expr::selector(go::Expr::name(RUNTIME), name)
97    }
98
99    /// A runtime operation's Go spelling: `write_u32` is `WriteU32`.
100    /// Each `_`-separated segment keeps its own spelling with the first
101    /// letter raised, so `read_pid_as` is `ReadPidAs` and
102    /// `write_wstring_utf8` is `WriteWstringUtf8` — the names
103    /// `runtime/go` publishes, because that runtime was written against
104    /// this conversion.
105    fn pascal(method: &str) -> String {
106        let mut out = String::new();
107        for segment in method.split('_') {
108            let mut chars = segment.chars();
109            if let Some(first) = chars.next() {
110                out.extend(first.to_uppercase());
111                out.push_str(chars.as_str());
112            }
113        }
114        out
115    }
116
117    /// A string literal.
118    fn text(value: &str) -> GoExpr {
119        plain(go::Expr::Literal(Literal::Str(value.to_string())))
120    }
121
122    /// A runtime constant by its exported name.
123    fn runtime_constant(name: &str) -> GoExpr {
124        plain(Self::runtime_name(name))
125    }
126}
127
128impl CodecTarget for Go {
129    type Expr = GoExpr;
130    type Stmt = go::Stmt;
131    type Block = Vec<go::Stmt>;
132    type Type = go::Type;
133    type Decl = go::Decl;
134
135    // -- types --------------------------------------------------------
136
137    /// The fixed-width Go type each scalar local takes. `Index` is
138    /// `uint32` — a sequence length is a `uint32` on the wire and a Go
139    /// index may be any integer type — and `Size` is `int`, which is
140    /// what `len` and the reader's positions answer in.
141    fn scalar(kind: Scalar) -> Self::Type {
142        match kind {
143            Scalar::Bool => go::Type::Primitive(go::Primitive::Bool),
144            Scalar::Char => go::Type::Primitive(go::Primitive::UInt8),
145            Scalar::Int8 => go::Type::Primitive(go::Primitive::Int8),
146            Scalar::Int16 => go::Type::Primitive(go::Primitive::Int16),
147            Scalar::Int32 => go::Type::Primitive(go::Primitive::Int32),
148            Scalar::Int64 => go::Type::Primitive(go::Primitive::Int64),
149            Scalar::UInt8 => go::Type::Primitive(go::Primitive::UInt8),
150            Scalar::UInt16 => go::Type::Primitive(go::Primitive::UInt16),
151            Scalar::UInt32 | Scalar::Index => go::Type::Primitive(go::Primitive::UInt32),
152            Scalar::UInt64 => go::Type::Primitive(go::Primitive::UInt64),
153            Scalar::Float => go::Type::Primitive(go::Primitive::Float32),
154            Scalar::Double => go::Type::Primitive(go::Primitive::Float64),
155            // The decimal text; the module documentation says why.
156            Scalar::LongDouble => go::Type::Primitive(go::Primitive::String),
157            // `int` deliberately: a stream position is what `len`
158            // answers, and the CST's own Primitive set excludes the
159            // implementation-sized integers because a *declared member*
160            // must not change width between targets — a codec local is
161            // not a member.
162            Scalar::Size => go::Type::named("int"),
163        }
164    }
165
166    fn named(name: &str) -> Self::Type {
167        go::Type::named(name)
168    }
169
170    /// No Go form, and never rendered: a Go function with no results
171    /// has an empty result list, and [`Go::routine`] writes one.
172    fn nothing() -> Self::Type {
173        go::Type::named("")
174    }
175
176    /// The runtime's handles, by their exported names.
177    fn handle(kind: Handle) -> Self::Type {
178        go::Type::named(match kind {
179            Handle::DHeader => "zdwire.DHeader",
180            Handle::EmHeader => "zdwire.EmHeader",
181            Handle::Pid => "zdwire.Pid",
182            Handle::TryConstruct => "zdwire.TryConstruct",
183            Handle::ReaderMark => "zdwire.ReaderMark",
184            Handle::PidRead => "zdwire.PidRead",
185            Handle::EmHeaderRead => "zdwire.EmHeaderRead",
186        })
187    }
188
189    /// The member already is the code unit — `uint8` for a `char`,
190    /// `uint16` for a `wchar` — so there is nothing to convert.
191    fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
192        value
193    }
194
195    /// The runtime's policy values. Strings and integers where
196    /// `runtime/go` takes them so — the spellings are
197    /// `WstringLengthPolicy::as_str` and its siblings in
198    /// `crates/ast-layout/src/key.rs` — and exported constants for the
199    /// enumerated kinds. One arm per constant and no catch-all.
200    fn constant(which: Constant) -> Self::Expr {
201        match which {
202            Constant::WcharWidth2 => plain(go::Expr::int(2)),
203            Constant::WcharWidth4 => plain(go::Expr::int(4)),
204            Constant::WcharFixed => Self::text("fixed"),
205            Constant::WcharGiopLengthPrefixed => Self::text("giop-length-prefixed"),
206            Constant::WstringUnitsStream => Self::text("stream"),
207            Constant::WstringUnitsBigEndianAlways => Self::text("big-endian-always"),
208            Constant::WstringUnitsByteOrderMark => Self::text("byte-order-mark"),
209            Constant::WstringUnitsHostUnswapped => Self::text("host-unswapped"),
210            Constant::WstringSpecification => Self::text("specification"),
211            Constant::WstringCharacterCount => Self::text("character-count"),
212            Constant::WstringCharacterCountTerminated => Self::text("character-count-terminated"),
213            Constant::WstringSpecificationWideUnits => Self::text("specification-wide-units"),
214            Constant::WstringRosTypeSupport => Self::text("ros-type-support"),
215            Constant::WstringOctetsTerminated2 => Self::text("octets/terminated/2"),
216            Constant::WstringOctetsTerminated4 => Self::text("octets/terminated/4"),
217            Constant::WstringCharactersTerminated2 => Self::text("characters/terminated/2"),
218            Constant::FixedDeclared => Self::text("declared"),
219            Constant::FixedValuePrecision => Self::text("value-precision"),
220            Constant::FixedValueStringDigits => Self::text("value-string-digits"),
221            Constant::ParameterMember => Self::runtime_constant("ParameterMember"),
222            Constant::ParameterOptional => Self::runtime_constant("ParameterOptional"),
223            Constant::TryConstructDiscard => Self::runtime_constant("TryConstructDiscard"),
224            Constant::TryConstructTrim => Self::runtime_constant("TryConstructTrim"),
225            Constant::TryConstructUseDefault => Self::runtime_constant("TryConstructUseDefault"),
226            Constant::StatusOk => Self::runtime_constant("StatusOk"),
227            Constant::StatusDiscarded => Self::runtime_constant("StatusDiscarded"),
228            Constant::StatusImpossibleLength => Self::runtime_constant("StatusImpossibleLength"),
229            Constant::StatusOutOfRange => Self::runtime_constant("StatusOutOfRange"),
230            Constant::StatusTruncated => Self::runtime_constant("StatusTruncated"),
231        }
232    }
233
234    /// The unsigned type wide enough for `bits` of a bit set — the same
235    /// answer the materialiser's `go_holder` gives its fields.
236    fn unsigned_holder(bits: u16) -> Self::Type {
237        go::Type::Primitive(crate::adapter::go::go_holder(bits))
238    }
239
240    fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
241        // Written as the match `unwrap_or` folds away, so the fallback
242        // carries its reason where the silent-drop guard reads it: a
243        // type with no Go form was refused by the materialiser before a
244        // codec walk ran, and `any` here makes the impossible fail in
245        // `go build`, loudly, rather than compile into a plausible
246        // member.
247        #[allow(clippy::manual_unwrap_or_default, clippy::manual_unwrap_or)]
248        match go_value_type(ast, id) {
249            Ok(type_) => type_,
250            Err(_) => go::Type::Primitive(go::Primitive::Any),
251        }
252    }
253
254    fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
255        type_tag(ast, id, 0)
256    }
257
258    /// A name the entry declaration and the codec both reach, and a
259    /// distinct one per type when the tag walk refuses: the slot keeps
260    /// two refused entries from collapsing onto one declaration, which
261    /// would be a silent merge of two map types.
262    fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
263        go_map_entry_name(ast, id).unwrap_or_else(|_| format!("ZdUnresolvedEntry{}", id.slot()))
264    }
265
266    /// The flattened, exported module path — `Common_Util_` — which is
267    /// the same prefix the materialiser puts on a package-scope
268    /// constant, taken from the same function.
269    fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
270        module_prefix(ast, id)
271    }
272
273    /// Escaped first and exported second, exactly as the materialiser
274    /// spells a field: the codec has to reach the member the
275    /// declaration wrote.
276    fn escape(name: &str) -> String {
277        field_name(name)
278    }
279
280    /// The base is the first member `parent` (XTypes 1.3 §7.5.1.4.1.3).
281    fn inherited_path(depth: usize) -> Option<String> {
282        Some(format!("{}.", field_name("parent")).repeat(depth))
283    }
284
285    // -- expressions ---------------------------------------------------
286
287    fn name(text: &str) -> Self::Expr {
288        plain(go::Expr::name(text))
289    }
290
291    fn int(value: i128) -> Self::Expr {
292        plain(go::Expr::int(value))
293    }
294
295    /// The raw tokens the walk escapes through [`Literal::Int`],
296    /// rewritten where Go spells them differently — which for `true`
297    /// and `false` it does not, but the tree's `Bool` variant is what
298    /// renders them and an `Int` carrying a keyword would be pasted as
299    /// digits are.
300    fn literal(value: Literal) -> Self::Expr {
301        let Literal::Int(text) = &value else {
302            return plain(go::Expr::Literal(value));
303        };
304        match text.as_str() {
305            "true" => plain(go::Expr::Literal(Literal::Bool(true))),
306            "false" => plain(go::Expr::Literal(Literal::Bool(false))),
307            _ => plain(go::Expr::Literal(value)),
308        }
309    }
310
311    /// The number, untyped. A Go untyped constant converts to the
312    /// operand type it meets, so a union label needs no suffix; the
313    /// conversion the walk wraps around it supplies the type where one
314    /// is needed.
315    fn wide_int(value: i128) -> Self::Expr {
316        plain(go::Expr::int(value))
317    }
318
319    /// The number, with no `u` suffix: `5u` is not a Go token, and an
320    /// untyped constant already converts against a `uint32` counter.
321    fn unsigned_int(value: i128) -> Self::Expr {
322        plain(go::Expr::int(value))
323    }
324
325    /// `uint64(..)`, typed: `^` on an untyped constant is a negative
326    /// constant that does not convert to `uint64`, and a mask the walk
327    /// complements has to already be one.
328    fn wide_unsigned(value: u64) -> Self::Expr {
329        plain(go::Expr::Convert {
330            type_: Box::new(go::Type::Primitive(go::Primitive::UInt64)),
331            value: Box::new(go::Expr::int(i128::from(value))),
332        })
333    }
334
335    /// `int64(..)`, typed, for the enumerator patterns `zd_raw` is
336    /// compared against.
337    fn wide_signed(value: i128) -> Self::Expr {
338        plain(go::Expr::Convert {
339            type_: Box::new(go::Type::Primitive(go::Primitive::Int64)),
340            value: Box::new(go::Expr::int(value)),
341        })
342    }
343
344    /// `v.X` — and `v.D` for the discriminator, whose `_d` no column
345    /// shares: Go cannot export a name beginning with an underscore,
346    /// and `adapter/go.rs` declares it `D`.
347    fn member(base: &str, field: &str) -> Self::Expr {
348        let spelled = if field == "_d" { "D" } else { field };
349        plain(go::Expr::selector(go::Expr::name(base), spelled))
350    }
351
352    fn whole(base: &str) -> Self::Expr {
353        plain(go::Expr::name(base))
354    }
355
356    fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
357        plain(go::Expr::selector(inner(value), field))
358    }
359
360    /// A header's field, in the exported spelling `runtime/go`
361    /// publishes: `member_id` is `MemberId`, `must_understand` is
362    /// `MustUnderstand`.
363    fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
364        plain(go::Expr::selector(inner(value), &Self::pascal(field)))
365    }
366
367    fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
368        plain(go::Expr::Index {
369            value: Box::new(inner(value)),
370            index: Box::new(inner(at)),
371        })
372    }
373
374    /// `uint32(len(v))`, or the bound where the materialiser fixed one.
375    /// The conversion is the crossing written out: `len` answers an
376    /// `int`, a sequence's count is a `uint32` on the wire, and Go has
377    /// no implicit conversion between the two.
378    fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
379        match fixed {
380            Some(bound) => plain(go::Expr::int(i128::from(bound))),
381            None => plain(go::Expr::Convert {
382                type_: Box::new(go::Type::Primitive(go::Primitive::UInt32)),
383                value: Box::new(go::Expr::call(
384                    go::Expr::name("len"),
385                    vec![go::Expr::name(base)],
386                )),
387            }),
388        }
389    }
390
391    /// The slice itself: a Go slice is the elements.
392    fn elements(base: &str) -> Self::Expr {
393        plain(go::Expr::name(base))
394    }
395
396    /// `v.X` — the case fields sit directly on the struct, beside `D`.
397    fn case_member(base: &str, field: &str) -> Self::Expr {
398        Self::member(base, field)
399    }
400
401    /// `entry.Key` and `entry.Value`, the two fields the materialiser
402    /// gives a map's entry struct.
403    fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
404        plain(go::Expr::selector(
405            inner(entry),
406            if key { "Key" } else { "Value" },
407        ))
408    }
409
410    fn absent() -> Self::Expr {
411        plain(go::Expr::Literal(Literal::Null))
412    }
413
414    /// Nothing: a Go string and a Go slice own their storage, and there
415    /// is no allocation failure for the guard to test.
416    fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
417        None
418    }
419
420    /// `*v.X` — the pointer dereferenced, because the member's codec
421    /// takes the value. Only ever reached inside a branch the presence
422    /// test guarded.
423    fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
424        plain(go::Expr::Unary {
425            op: "*",
426            value: Box::new(inner(Self::member(base, field))),
427        })
428    }
429
430    /// `v.X != nil`.
431    fn present(base: &str, field: &str) -> Self::Expr {
432        plain(go::Expr::Binary {
433            op: "!=",
434            lhs: Box::new(inner(Self::member(base, field))),
435            rhs: Box::new(go::Expr::Literal(Literal::Null)),
436        })
437    }
438
439    /// Nothing on the way in — the assignment through `zdwire.Ptr` is
440    /// what makes the member present — and `v.X = nil` on the way out.
441    fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
442        if present {
443            return Vec::new();
444        }
445        vec![go::Stmt::Assign {
446            place: inner(Self::member(base, field)),
447            value: go::Expr::Literal(Literal::Null),
448        }]
449    }
450
451    /// The marked place, with nothing to do first: the room is made by
452    /// [`Go::read_call`], which routes the value through `zdwire.Ptr`.
453    fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
454        (
455            Vec::new(),
456            GoExpr::OptionalSlot(inner(Self::member(base, field))),
457        )
458    }
459
460    /// The operators as given: Go spells `&&`, `||` and the comparisons
461    /// the way the walk does. `=` never reaches a rendered expression —
462    /// [`Go::perform`] turns it into an assignment statement.
463    fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
464        plain(go::Expr::Binary {
465            op,
466            lhs: Box::new(inner(lhs)),
467            rhs: Box::new(inner(rhs)),
468        })
469    }
470
471    /// `!a`, and `^a` where the walk wrote C's `~`: Go's bitwise
472    /// complement is the unary `^`.
473    fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
474        plain(go::Expr::Unary {
475            op: match op {
476                "~" => "^",
477                other => other,
478            },
479            value: Box::new(inner(value)),
480        })
481    }
482
483    fn paren(value: Self::Expr) -> Self::Expr {
484        plain(go::Expr::Paren(Box::new(inner(value))))
485    }
486
487    /// `!a && b`: Go defines `<` on ordered types only, and `bool` is not
488    /// one ("operator < not defined on bool", measured in the matrix
489    /// refresh of 2026-10-01 on `map-key-boolean` under the Fast DDS and
490    /// OpenDDS dialects, which order map entries by key). `false` before
491    /// `true` is [`zerodds_ast_layout::MapKeyOrdering::UnsignedInteger`]'s
492    /// order for a `boolean`.
493    fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
494        Self::binary("&&", Self::unary("!", Self::paren(left)), right)
495    }
496
497    /// `T(v)` — a Go conversion, with two rewrites.
498    ///
499    /// A `bool` discriminator's labels arrive as integers and
500    /// `bool(1)` is not a Go conversion; the label becomes the literal.
501    /// And the walk's `signed char` — the C spelling of a map key's
502    /// signed order — is `int8` here, which reads a `uint8` key as the
503    /// two's complement the corpus was recorded under.
504    fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
505        if type_ == go::Type::named(SIGNED_CHAR) {
506            return plain(go::Expr::Convert {
507                type_: Box::new(go::Type::Primitive(go::Primitive::Int8)),
508                value: Box::new(inner(value)),
509            });
510        }
511        if type_ == go::Type::Primitive(go::Primitive::Bool) {
512            if let GoExpr::Plain(go::Expr::Literal(Literal::Int(text))) = &value {
513                return plain(go::Expr::Literal(Literal::Bool(text != "0")));
514            }
515        }
516        plain(go::Expr::Convert {
517            type_: Box::new(type_),
518            value: Box::new(inner(value)),
519        })
520    }
521
522    /// The value, unchanged: Go hands a slice by header, an array by
523    /// value and a struct by value, and the codec's parameter says
524    /// which.
525    fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
526        value
527    }
528
529    /// A call to a package-level function — a generated codec or
530    /// defaulter. `strcmp` is the one name the walk hands here that is
531    /// not one; it is the runtime's `Strcmp`, which spells C11
532    /// §7.24.4's unsigned-octet order in the language whose own string
533    /// order is exactly that.
534    fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
535        let arguments: Vec<go::Expr> = args.into_iter().map(inner).collect();
536        if callee == "strcmp" {
537            return plain(go::Expr::call(Self::runtime_name("Strcmp"), arguments));
538        }
539        plain(go::Expr::call(go::Expr::name(callee), arguments))
540    }
541
542    /// `w.WriteU32(x)` — a method on the writer or the reader.
543    ///
544    /// One operation is renamed rather than converted: the walk's
545    /// `read_pid()` reads a header **and** its value block is opened by
546    /// the later `finish_pid_read`, which is `ReadPidAs` in this
547    /// runtime — §7.4.1.2 makes the block a wire question, so the call
548    /// may not be left as it stands. The Python column does the same.
549    fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
550        if method == "read_pid" && args.is_empty() {
551            return Self::runtime(
552                receiver,
553                "read_pid_as",
554                vec![Self::constant(Constant::ParameterMember)],
555            );
556        }
557        plain(go::Expr::call(
558            go::Expr::selector(go::Expr::name(receiver), &Self::pascal(method)),
559            args.into_iter().map(inner).collect(),
560        ))
561    }
562
563    /// `runtime/go` carries the three HDDS axes: `BeginPackedElement`
564    /// and `FinishPackedElement` on the writer and
565    /// `ReaderBeginPackedElement` / `ReaderFinishPackedElement` on the
566    /// reader, the DHEADER pair for a sequence element's own length, and
567    /// `WriteWstringUtf8Octets` / `WriteWstringUtf8OctetsBounded` /
568    /// `ReadWstringUtf8Octets`.
569    fn carries_hdds_axes() -> bool {
570        true
571    }
572
573    /// A free function of the runtime — `zdwire.NoDheader()`,
574    /// `zdwire.Utf8Octets(v)` — except `pid_is_user_member`, which is a
575    /// method on the header the reader handed back, the same difference
576    /// as C's free function against Java's static.
577    fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
578        if method == "pid_is_user_member" {
579            let mut arguments = args.into_iter().map(inner);
580            if let Some(header) = arguments.next() {
581                return plain(go::Expr::call(
582                    go::Expr::selector(header, "IsUserMember"),
583                    Vec::new(),
584                ));
585            }
586            // The walk always hands the header; a call without one is a
587            // walk that changed under this file, and a method on a name
588            // no scope declares is the loudest thing Go can say here.
589            return plain(go::Expr::call(
590                Self::runtime_name("IsUserMember"),
591                Vec::new(),
592            ));
593        }
594        plain(go::Expr::call(
595            Self::runtime_name(&Self::pascal(method)),
596            args.into_iter().map(inner).collect(),
597        ))
598    }
599
600    // -- statements ----------------------------------------------------
601
602    /// `var name T = value`. The type is written because the walk knows
603    /// it and a reader of the generated file then sees the width — and
604    /// because a `var` with a type is definite whether or not the value
605    /// is there, which the header handle declared before a loop needs.
606    fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
607        go::Stmt::Var {
608            name: Ident::new(name),
609            type_: Some(type_),
610            value: value.map(inner),
611        }
612    }
613
614    /// An expression evaluated for its effect — or, where the walk
615    /// built an assignment, the assignment: `a = b` is a statement in
616    /// Go and not an expression, and this is the only place the walk's
617    /// `=` arrives.
618    fn perform(value: Self::Expr) -> Self::Stmt {
619        if let GoExpr::Plain(go::Expr::Binary { op: "=", lhs, rhs }) = value {
620            return go::Stmt::Assign {
621                place: *lhs,
622                value: *rhs,
623            };
624        }
625        go::Stmt::Expr(inner(value))
626    }
627
628    /// `return v`: a Go decoder answers with the sample it built, so a
629    /// bare `return` — C's shape, for a decoder that wrote through a
630    /// pointer — would answer nothing at all and not compile.
631    fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
632        go::Stmt::Return(Some(match value {
633            Some(value) => inner(value),
634            None => go::Expr::name("v"),
635        }))
636    }
637
638    fn when(
639        condition: Self::Expr,
640        then: Self::Block,
641        otherwise: Option<Self::Block>,
642    ) -> Self::Stmt {
643        go::Stmt::If {
644            condition: inner(condition),
645            then,
646            otherwise: otherwise.filter(|body| !body.is_empty()),
647        }
648    }
649
650    /// `for i := uint32(0); i < bound; i++`. The counter is a `uint32`
651    /// because every bound the walk hands over is one — a count off the
652    /// wire, `uint32(len(v))`, or an untyped constant that converts.
653    fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
654        go::Stmt::For {
655            counter: Ident::new(counter),
656            init: Box::new(go::Expr::Convert {
657                type_: Box::new(go::Type::Primitive(go::Primitive::UInt32)),
658                value: Box::new(go::Expr::int(0)),
659            }),
660            bound: Box::new(inner(bound)),
661            body,
662        }
663    }
664
665    fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
666        go::Stmt::While {
667            condition: inner(condition),
668            body,
669        }
670    }
671
672    fn leave() -> Self::Stmt {
673        go::Stmt::Break
674    }
675
676    fn note(doc: Doc) -> Self::Stmt {
677        go::Stmt::Comment(doc)
678    }
679
680    /// `_ = expr`: Go has no `(void)` cast and a bare call is already a
681    /// statement, but a *value* read for its effect — a discarded
682    /// DHEADER — has to say it meant the discard, and the blank
683    /// identifier is Go's spelling of exactly that.
684    fn drop_value(value: Self::Expr) -> Self::Stmt {
685        go::Stmt::Assign {
686            place: go::Expr::name("_"),
687            value: inner(value),
688        }
689    }
690
691    /// `_ = name`: Go rejects a local that is assigned and never read
692    /// — "declared and not used" is a compile error, not a warning —
693    /// and `zd_sent` under a framing that writes no sentinel is exactly
694    /// that local.
695    fn mark_used(name: &str) -> Vec<Self::Stmt> {
696        vec![go::Stmt::Assign {
697            place: go::Expr::name("_"),
698            value: go::Expr::name(name),
699        }]
700    }
701
702    /// `place = zd_dec_T(r, behaviour)` — and through `zdwire.Ptr`
703    /// where the place is an `@optional` member's pointer field: the
704    /// decoder answers a `T`, the field holds a `*T`, and Go has no
705    /// address-of on a call's result.
706    fn read_call(
707        callee: &str,
708        place: Self::Expr,
709        _form: Handoff,
710        behaviour: Self::Expr,
711    ) -> Self::Stmt {
712        let decoded = go::Expr::call(
713            go::Expr::name(callee),
714            vec![go::Expr::name("r"), inner(behaviour)],
715        );
716        match place {
717            GoExpr::OptionalSlot(field) => go::Stmt::Assign {
718                place: field,
719                value: go::Expr::call(Self::runtime_name("Ptr"), vec![decoded]),
720            },
721            GoExpr::Plain(place) => go::Stmt::Assign {
722                place,
723                value: decoded,
724            },
725        }
726    }
727
728    /// `v = zd_dec_T(r, zd_tc)` — a `typedef`, handed straight on.
729    fn read_forward(callee: &str) -> Self::Stmt {
730        Self::read_call(
731            callee,
732            Self::whole("v"),
733            Handoff::Value,
734            Self::name("zd_tc"),
735        )
736    }
737
738    fn block(statements: Vec<Self::Stmt>) -> Self::Block {
739        statements
740    }
741
742    fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
743        block
744    }
745
746    /// `v = make([]T, n)`, and the fill after it: a Go slice's slots
747    /// are storage the moment `make` returns, for a sequence's whole
748    /// elements and for a map entry's two halves alike, so
749    /// `assigned_whole` makes no difference here.
750    fn make_room(
751        base: &str,
752        element: Self::Type,
753        count: Self::Expr,
754        _assigned_whole: bool,
755        fill: Vec<Self::Stmt>,
756    ) -> Vec<Self::Stmt> {
757        let mut out = vec![go::Stmt::Assign {
758            place: go::Expr::name(base),
759            value: go::Expr::Make {
760                type_: Box::new(go::Type::slice(element)),
761                length: Box::new(inner(count)),
762            },
763        }];
764        out.extend(fill);
765        out
766    }
767
768    /// `v = v[:count]` — §7.3.1.2.1.12.3's TRIM, applied after every
769    /// element has been read.
770    fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
771        vec![go::Stmt::Assign {
772            place: go::Expr::name(base),
773            value: go::Expr::SliceTo {
774                value: Box::new(go::Expr::name(base)),
775                high: Box::new(inner(count)),
776            },
777        }]
778    }
779
780    /// The subscript, with nothing around it: a Go slice element is a
781    /// real place at every element type — there is no
782    /// `std::vector<bool>` here.
783    fn sequence_element(
784        base: &str,
785        index: Self::Expr,
786        _element: Self::Type,
787    ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
788        (
789            Vec::new(),
790            Self::index(Self::elements(base), index),
791            Vec::new(),
792        )
793    }
794
795    // -- assembly ------------------------------------------------------
796
797    /// `func Zd_enc_T(w *zdwire.Writer, v T)` and
798    /// `func Zd_dec_T(r *zdwire.Reader, zd_tc zdwire.TryConstruct) T`.
799    /// The upper-case first letter is Go's visibility; see
800    /// [`exported_name`].
801    ///
802    /// A forward declaration is answered with nothing: Go resolves a
803    /// package-level name wherever it is declared, so a recursive type
804    /// needs no declaration ahead of its body.
805    /// Go's visibility is its spelling; see [`exported_name`].
806    fn published_name(name: String) -> String {
807        exported_name(&name)
808    }
809
810    fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
811        let body = spec.body?;
812        let mut params = vec![go::Param {
813            name: Some(Ident::new(if spec.writing { "w" } else { "r" })),
814            type_: go::Type::pointer(go::Type::named(if spec.writing {
815                "zdwire.Writer"
816            } else {
817                "zdwire.Reader"
818            })),
819        }];
820        if spec.writing {
821            params.push(go::Param {
822                name: Some(Ident::new("v")),
823                type_: spec.sample.clone(),
824            });
825        }
826        for (type_, name) in spec.extra {
827            params.push(go::Param {
828                name: Some(Ident::new(&name)),
829                type_,
830            });
831        }
832        let (results, body) = if spec.writing {
833            (Vec::new(), body)
834        } else {
835            // The sample starts as Table 9's value, through the
836            // generated defaulter, so every position the stream does
837            // not carry is already what the clause says it is — the
838            // same shape the Python column's `reading_body` writes.
839            let defaulter = crate::default_go::defaulter_from_decoder(&spec.name);
840            let mut statements = vec![go::Stmt::Var {
841                name: Ident::new("v"),
842                type_: None,
843                value: Some(go::Expr::call(go::Expr::name(&defaulter), Vec::new())),
844            }];
845            statements.extend(body);
846            statements.push(go::Stmt::Return(Some(go::Expr::name("v"))));
847            (vec![spec.sample], statements)
848        };
849        Some(go::Decl::Func(go::FuncDecl {
850            doc: spec.doc,
851            // Already the published spelling: `codec_name` and
852            // `decoder_name` put it through [`Go::published_name`]
853            // before the walk ever names the routine.
854            name: Ident::new(&spec.name),
855            params,
856            results,
857            body,
858        }))
859    }
860
861    /// A call to the generated defaulter, which is Table 9 once per
862    /// type rather than once per position.
863    ///
864    /// # Errors
865    /// Never; the signature matches the emitters around it.
866    fn default_into(
867        ast: &Ast,
868        id: Id<TypeNode>,
869        place: Self::Expr,
870    ) -> Result<Self::Stmt, EncodeError> {
871        Ok(go::Stmt::Assign {
872            place: inner(place),
873            value: go::Expr::call(
874                go::Expr::name(&crate::default_go::defaulter_name(ast, id)),
875                Vec::new(),
876            ),
877        })
878    }
879
880    /// # Errors
881    /// As [`crate::default_go::member_default`]: a `@default` this
882    /// column cannot spell is refused rather than silently replaced by
883    /// Table 9's zero.
884    fn member_default(
885        ast: &Ast,
886        node: &MemberNode,
887        type_: Id<TypeNode>,
888        place: Self::Expr,
889    ) -> Result<Vec<Self::Stmt>, EncodeError> {
890        crate::default_go::member_default(ast, node, type_, inner(place))
891    }
892}
893
894/// The bound a `string`, a `sequence` or a `map` carries, as the suffix
895/// that keeps two of them apart.
896///
897/// The tag names the routine a hoisted collection's codec is emitted
898/// under, and one body is emitted per name. With the bound left out,
899/// `sequence<long>` and `sequence<long, 4>` are one name and whichever
900/// was declared first decides: measured on
901/// `@final struct S { sequence<long> b; sequence<long, 4> a; };`, the
902/// generated Go held no `zd_over` check at all, so `a` accepted a
903/// stream of any length; with the members the other way round both got
904/// the bounded body, so `b` refused — and truncated — a length the IDL
905/// puts no limit on. `string<4>` beside `string` and `wstring<4>`
906/// beside `wstring` collapse the same way, onto the trimming call.
907/// Either way the reader answered with a value the IDL does not
908/// describe and said nothing, which is why the bound is part of the
909/// name. The same suffix the Zig column carries.
910///
911/// An `array` needs nothing here: its `dims` are already in the tag,
912/// under the `Array` arm below.
913fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
914    match bound.expr() {
915        None => String::new(),
916        // `_b` and not a bare `_n`: `seq_long_4` is what the `Array`
917        // arm answers for an array of four `long`, and the two are
918        // different wire forms.
919        Some(expr) => match ast.eval_integral(expr) {
920            Ok(limit) => format!("_b{limit}"),
921            // As the array's `_x`: a bound that does not evaluate is
922            // still not the unbounded form, and the materialiser
923            // reports the expression that would not evaluate.
924            Err(_) => "_bx".to_string(),
925        },
926    }
927}
928
929/// The name a codec function takes for the type it codes.
930///
931/// The IDL kind and not the Go spelling, for the reason the Python
932/// column's `type_tag` gives at length: `octet` and `uint8` are one Go
933/// type and one wire form here, but `double` and `long double` are
934/// `float64` and `string`, and a tag built from the Go type would give
935/// a `fixed<5,2>` and a `wstring` one codec. A declared type takes its
936/// flattened Go name, which two IDL declarations cannot share —
937/// `adapter/go.rs`'s `remember` refuses the collision before a codec is
938/// generated.
939///
940/// zerodds-lint: recursion-depth 64 (collection nesting; bounded by `MAX_TYPE_DEPTH`)
941#[must_use]
942pub fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
943    if depth > MAX_TYPE_DEPTH {
944        return "unresolved".to_string();
945    }
946    let node = match ast.type_(id) {
947        Ok(node) => node,
948        Err(_) => return "unresolved".to_string(),
949    };
950    match &node.kind {
951        TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
952        TypeKind::String {
953            width: CharWidth::Char,
954            bound,
955        } => format!("string{}", bound_tag(ast, bound)),
956        TypeKind::String {
957            width: CharWidth::WChar,
958            bound,
959        } => format!("wstring{}", bound_tag(ast, bound)),
960        TypeKind::Fixed { digits, scale } => format!("fixed_{digits}_{scale}"),
961        TypeKind::Sequence { element, bound } => match element.resolved() {
962            Some(element) => format!(
963                "seq{}_{}",
964                bound_tag(ast, bound),
965                type_tag(ast, element, depth + 1)
966            ),
967            None => format!("seq_unresolved{}", bound_tag(ast, bound)),
968        },
969        TypeKind::Array { element, dims } => {
970            let mut name = match element.resolved() {
971                Some(element) => format!("arr_{}", type_tag(ast, element, depth + 1)),
972                None => "arr_unresolved".to_string(),
973            };
974            for dim in dims {
975                match ast.eval_integral(*dim) {
976                    Ok(length) => name.push_str(&format!("_{length}")),
977                    // No number rather than a zero: the name has to
978                    // differ from a `[0]` array's, and the materialiser
979                    // reports the dimension that would not evaluate by
980                    // name.
981                    Err(_) => name.push_str("_x"),
982                }
983            }
984            name
985        }
986        TypeKind::Map { key, value, bound } => {
987            let half = |half: Option<Id<TypeNode>>| match half {
988                Some(target) => type_tag(ast, target, depth + 1),
989                None => "unresolved".to_string(),
990            };
991            format!(
992                "map{}_{}_{}",
993                bound_tag(ast, bound),
994                half(key.resolved()),
995                half(value.resolved())
996            )
997        }
998        // An alias is followed through: two IDL names for one shape are
999        // one wire form, and two codecs for it would be two chances to
1000        // differ.
1001        TypeKind::Alias { aliased } => match aliased.resolved() {
1002            Some(target) => type_tag(ast, target, depth + 1),
1003            None => "unresolved".to_string(),
1004        },
1005        TypeKind::ValueBox { boxed } => match boxed.resolved() {
1006            Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)),
1007            None => "box_unresolved".to_string(),
1008        },
1009        TypeKind::Structure { .. }
1010        | TypeKind::Union { .. }
1011        | TypeKind::Enumeration { .. }
1012        | TypeKind::Bitmask { .. }
1013        | TypeKind::Bitset { .. }
1014        | TypeKind::Valuetype { .. }
1015        | TypeKind::Native => go_type_name(ast, id),
1016        TypeKind::Any => "any".to_string(),
1017        TypeKind::ValueBase => "valuebase".to_string(),
1018        TypeKind::ObjectRef { .. } => "objectref".to_string(),
1019        TypeKind::AnnotationType { .. } => "annotation".to_string(),
1020    }
1021}
1022
1023/// A generated name, in the form Go makes visible outside its package.
1024///
1025/// Go has no visibility keyword: an identifier is exported if and only
1026/// if its first character is an upper-case letter (Go specification,
1027/// "Exported identifiers"). Every generated codec, defaulter and key
1028/// holder was named `zd_enc_…`, `zd_def_…`, `zd_kenc_…` — all
1029/// lower-case — so a Go program that imported the generated package
1030/// could not call a single one of them. The probe harness never found
1031/// out: it writes its `main` into the same package.
1032///
1033/// **The first character only.** `ZdEncMax` would be the idiomatic
1034/// spelling and it is not injective — dropping the underscores maps two
1035/// different generated names onto one, and two codecs under one name is
1036/// a collision no compiler would report as one, because the second
1037/// declaration would simply be a redeclaration error somewhere else or,
1038/// worse, not an error at all. Raising the first letter is a bijection
1039/// on this name set: every generated name begins `zd`, and no two
1040/// differ only in that letter.
1041///
1042/// Names that are not generated pass through: the runtime's are reached
1043/// through `Go::runtime_name` and never arrive here, and `strcmp` is
1044/// intercepted by [`Go::call`] before this is asked.
1045pub fn exported_name(name: &str) -> String {
1046    // Idempotent: a name already raised passes through, so a second
1047    // application — `defaulter_name` calling this on a name built from
1048    // an already-published decoder — cannot raise anything twice or
1049    // produce a second spelling of one function.
1050    if !name.starts_with("zd_") {
1051        return name.to_string();
1052    }
1053    let mut chars = name.chars();
1054    match chars.next() {
1055        None => String::new(),
1056        Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
1057    }
1058}