Skip to main content

zerodds_lang_rules/
default_cpp.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The value a C++ member takes when the stream does not carry it.
4//!
5//! Table 9 of DDS-XTypes 1.3, as C++. [`crate::default_c`] is the same
6//! table as C and `crates/wire-probe/src/default.rs` as Rust; that last
7//! module carries the clause quotations and the reasoning for the three
8//! kinds Table 9 does not list. Deliberately a third derivation and not
9//! a call into either of the first two: the columns are compared against
10//! each other, and a shared helper would make them agree about a
11//! mistake.
12//!
13//! ## Why a function per type and not `T{}`
14//!
15//! Value-initialisation is not Table 9. `T{}` zeroes a structure's
16//! members, which is right for the integers and wrong for three kinds
17//! the corpus reaches:
18//!
19//! - an **enumeration** whose first literal is not zero — `@value(3)` on
20//!   the first enumerator, or a `@default_literal` naming another one —
21//!   takes that literal (§7.3.1.2.1.10) and not the pattern zero;
22//! - a **union** takes its discriminator's default *and the branch that
23//!   selects*, which no aggregate initialiser reaches;
24//! - a member with an explicit `@default` takes the literal its
25//!   declaration gave it (§7.2.2.4.4.4.9), which is not the type's
26//!   value at all.
27//!
28//! Beyond that, `adapter/cpp.rs` emits no member initialisers, so `T{}`
29//! on a class with no user-provided constructor is
30//! value-initialisation of members that are themselves classes and
31//! zero-initialisation of the scalars — a rule that reads differently
32//! for `struct S { int a; }` and `struct S { S2 a; }`, and one the C
33//! column has no counterpart for. Two columns answering Table 9 by
34//! different mechanisms is a divergence no byte comparison shows,
35//! because both produce *a* value.
36//!
37//! ## What C++ makes easier than C
38//!
39//! Storage. A `string` is a `std::string` and its Table 9 value is the
40//! empty one, with no heap for the caller to have supplied and no
41//! failure to report; a `sequence<T>` is a `std::vector<T>` and
42//! §7.2.2.4.4.4.7's "zero-length sequence of the same element type" is
43//! `clear()`. The C column's `_length`/`_buffer` pair and its `NULL` are
44//! not here, and neither is C's array-versus-pointer split in the
45//! parameter: a `std::array` is a value, so **every** defaulter takes a
46//! plain reference.
47//!
48//! An `@optional` member is NULL and nothing else —
49//! "Implementations shall not provide a default value in such a case."
50
51use crate::adapter::cpp::{cpp_type_tag, cpp_value_type, cxx_name};
52use crate::default_common::{resolve_alias, selected_case};
53use crate::encode::EncodeError;
54use crate::fixed_literal::{fixed_text, zero_decimal};
55use zerodds_ast::{
56    Ast, ConstExpr, Literal as AstLiteral, MemberNode, PrimitiveKind, TypeKind, TypeNode,
57};
58use zerodds_ast_core::Id;
59use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
60use zerodds_lang_cst::cpp;
61
62/// The defaulter function name for a type.
63///
64/// Keyed the same way [`crate::emit::decode::decoder_name`] is, so a
65/// primitive's defaulter is named by its IDL kind: `double` and
66/// `long double` are one C++ built-in away from each other in
67/// `cpp_primitive` and are eight bytes apart on the wire, and
68/// `cpp_type_tag` is what keeps every such pair apart.
69#[must_use]
70pub fn defaulter_name(ast: &Ast, id: Id<TypeNode>) -> String {
71    match ast.type_(id).map(|node| node.kind.clone()) {
72        Ok(TypeKind::Primitive(kind)) => {
73            format!("zd_def_p_{}", kind.as_str().replace(' ', "_"))
74        }
75        _ => format!("zd_def_{}", cpp_type_tag(ast, id)),
76    }
77}
78
79/// `zd_def_<T>(<place>);`
80///
81/// No address-of and no array exception, unlike the C column's: the
82/// parameter is a reference, so the destination is written the same way
83/// whatever the type is.
84#[must_use]
85pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: cpp::Expr) -> cpp::Statement {
86    cpp::Statement::Expr(cpp::Expr::Call {
87        callee: Box::new(path(&defaulter_name(ast, id))),
88        args: vec![place],
89    })
90}
91
92/// `zerodds::wire::external_slot(place)` — see
93/// `runtime/cpp/include/zerodds/wire.hpp`.
94fn external_slot(place: cpp::Expr) -> cpp::Expr {
95    cpp::Expr::Call {
96        callee: Box::new(cpp::Expr::Path(Path::new(&[
97            "zerodds",
98            "wire",
99            "external_slot",
100        ]))),
101        args: vec![place],
102    }
103}
104
105/// One member's default: its `@default`, or its type's.
106///
107/// §7.2.2.4.4.4.9 puts the explicit one first — "An explicit definition
108/// associates a default value with the member at the time where the
109/// aggregated type is defined" — and falls back to "the default value of
110/// the member type. See Table 9."
111///
112/// # Errors
113/// [`EncodeError::NoWriter`] where a `@default` names a value this
114/// column cannot spell as a C++ initialiser. Refused rather than
115/// silently replaced by Table 9's zero: a member the designer gave a
116/// default and a member that got a zero instead are different samples.
117pub fn member_default(
118    ast: &Ast,
119    node: &MemberNode,
120    type_: Id<TypeNode>,
121    place: cpp::Expr,
122) -> Result<Vec<cpp::Statement>, EncodeError> {
123    // A non-optional `@external` member is Table 9's value behind the
124    // column's own indirection — `std::shared_ptr<T>`, DDS-XTypes 1.3
125    // Table 21 — so the pointer is filled and the value written into
126    // what it points at. Here and not at the two call sites: the
127    // defaulter routine and `emit/decode.rs`'s `defaults_for_absent`
128    // both come through this function, which is where the D and Zig
129    // columns answer the same question (`default_d.rs`,
130    // `default_zig.rs`).
131    let place = if crate::adapter::held_externally(ast, node) {
132        external_slot(place)
133    } else {
134        place
135    };
136    let Some(expr) = node.facets.xtypes.as_ref().and_then(|x| x.default_value) else {
137        return Ok(vec![default_into(ast, type_, place)]);
138    };
139    let value = explicit(ast, expr, type_, &node.name)?;
140    Ok(vec![assign(place, value)])
141}
142
143/// The defaulters `decls` call, and exactly those.
144///
145/// The C column's [`crate::default_c::c_defaulters_called_by`] reads its
146/// set off the emitted calls rather than deriving it a second time from
147/// the model, and the reason it gives applies here unchanged: a
148/// derivation beside the decoder's is a second reading of §7.2.2.7, and
149/// the two drift. That file records the drift it had — a bounded
150/// collection defaults *itself* when the stream overruns its bound, and
151/// the derivation that seeded from the members which can go missing did
152/// not know it, so six corpus families called a function nothing
153/// declared.
154///
155/// The other half of the contract is the same too: the C++ probe is
156/// compiled with `-Wall -Wextra -Werror`, where a `static` function with
157/// no caller is an error, and a set taken from the calls has none by
158/// construction.
159///
160/// # Errors
161/// [`EncodeError`] for a construct with no C++ default.
162pub fn cpp_defaulters_called_by(
163    ast: &Ast,
164    decls: &[cpp::Decl],
165) -> Result<Vec<cpp::Decl>, EncodeError> {
166    cpp_defaulters_called_by_excluding(ast, decls, &[])
167}
168
169/// [`cpp_defaulters_called_by`] without the types in `excluded`, whose
170/// defaulters are written by hand elsewhere.
171///
172/// # Errors
173/// As [`cpp_defaulters_called_by`].
174pub fn cpp_defaulters_called_by_excluding(
175    ast: &Ast,
176    decls: &[cpp::Decl],
177    excluded: &[Id<TypeNode>],
178) -> Result<Vec<cpp::Decl>, EncodeError> {
179    let index = defaulter_index(ast);
180    let mut called: Vec<String> = Vec::new();
181    for decl in decls {
182        calls_in_decl(decl, &mut called);
183    }
184    let mut wanted: Vec<Id<TypeNode>> = Vec::new();
185    let mut have: Vec<String> = Vec::new();
186    // A defaulter body calls other defaulters — a structure's calls its
187    // members', an array's calls its element's — so one pass over the
188    // decoder is not the closure. Emitting and re-reading is: the
189    // fixpoint is reached when the batch calls nothing it does not also
190    // define, which is the property the C++ compiler checks anyway.
191    //
192    // Bounded by the number of named defaulters, because every round but
193    // the last adds at least one. The bound is written down rather than
194    // left to the loop, so a name that never resolves cannot spin.
195    for _ in 0..=index.len() {
196        let mut grew = false;
197        for name in &called {
198            if have.contains(name) {
199                continue;
200            }
201            have.push(name.clone());
202            // A name with no type behind it cannot happen for a call
203            // this generator emitted — every one came from
204            // `defaulter_name` over a type in this model — and if it
205            // ever does, the omission is a C++ compile error naming the
206            // function, which is louder than anything this could return.
207            if let Some(id) = index
208                .iter()
209                .find(|(n, _)| n == name)
210                .map(|(_, id)| *id)
211                .filter(|id| !excluded.contains(id))
212            {
213                wanted.push(id);
214                grew = true;
215            }
216        }
217        let produced = cpp_defaulters_over(ast, &wanted)?;
218        if !grew {
219            return Ok(produced);
220        }
221        for decl in &produced {
222            calls_in_decl(decl, &mut called);
223        }
224    }
225    cpp_defaulters_over(ast, &wanted)
226}
227
228/// `decls` without the routines `entry` does not reach.
229///
230/// For a probe that calls one routine of a codec it generated whole —
231/// the KeyHash harness calls the key holder and nothing else. Every
232/// generated routine is `static`, and the probe is compiled with
233/// `-Wall -Werror`, so a routine nothing calls is an error; pruning it
234/// keeps that warning sharp instead of silencing it, which is what the C
235/// column's harness does for the same reason. A declaration that is not
236/// a function — a type, a constant — is kept.
237#[must_use]
238pub fn reachable_from(decls: Vec<cpp::Decl>, entry: &str) -> Vec<cpp::Decl> {
239    let mut wanted = vec![entry.to_string()];
240    let mut index = 0;
241    while index < wanted.len() {
242        let name = wanted[index].clone();
243        index += 1;
244        for decl in &decls {
245            if let cpp::Decl::Function(function) = decl
246                && function.name.as_str() == name
247            {
248                let mut called = Vec::new();
249                calls_in_decl(decl, &mut called);
250                for callee in called {
251                    if !wanted.contains(&callee) {
252                        wanted.push(callee);
253                    }
254                }
255            }
256        }
257    }
258    decls
259        .into_iter()
260        .filter(|decl| match decl {
261            cpp::Decl::Function(function) => {
262                wanted.iter().any(|kept| kept == function.name.as_str())
263            }
264            _ => true,
265        })
266        .collect()
267}
268
269/// Every type in the model under the defaulter name it would take.
270///
271/// The name and not the id is the key the emitted C++ carries, and it is
272/// a sound one for the reason the C column's index is: two ids that
273/// share a defaulter name share a defaulter *body*. `zd_def_string` is
274/// `v.clear()` for `string<5>` and for `string<10>`; `zd_def_seq_int32`
275/// is an empty vector at any bound; an array's tag carries its element
276/// and its dimensions; every other tag is a flattened scoped name and is
277/// unique. [`cpp_defaulters_over`] dedupes on the same key.
278fn defaulter_index(ast: &Ast) -> Vec<(String, Id<TypeNode>)> {
279    ast.types()
280        .map(|(id, _)| (defaulter_name(ast, id), id))
281        .collect()
282}
283
284/// The `zd_def_*` functions one declaration calls, appended to `out`.
285///
286/// zerodds-lint: recursion-depth 64 (declaration nesting the walk builds).
287/// The tree walked here is the one this generator emitted and owns, so
288/// it holds no cycle; the depth is that declaration's height.
289fn calls_in_decl(decl: &cpp::Decl, out: &mut Vec<String>) {
290    match decl {
291        cpp::Decl::Function(function) => {
292            if let cpp::FunctionBody::Block(body) = &function.body {
293                calls_in_block(body, out);
294            }
295        }
296        cpp::Decl::Variable(variable) => {
297            if let Some(value) = &variable.value {
298                calls_in_expr(value, out);
299            }
300        }
301        cpp::Decl::Namespace(namespace) => {
302            for inner in &namespace.decls {
303                calls_in_decl(inner, out);
304            }
305        }
306        cpp::Decl::Template(template) => calls_in_decl(&template.decl, out),
307        cpp::Decl::Class(class) => {
308            for section in &class.sections {
309                for member in &section.members {
310                    calls_in_class_member(member, out);
311                }
312            }
313        }
314        cpp::Decl::Enum(_)
315        | cpp::Decl::Alias(_)
316        | cpp::Decl::Comment(_)
317        | cpp::Decl::Verbatim(_)
318        | cpp::Decl::Forward { .. } => {}
319    }
320}
321
322/// zerodds-lint: recursion-depth 64 (declaration nesting the walk builds).
323/// The tree walked here is the one this generator emitted and owns, so
324/// it holds no cycle; the depth is that declaration's height.
325fn calls_in_class_member(member: &cpp::ClassMember, out: &mut Vec<String>) {
326    match member {
327        cpp::ClassMember::Method(method) => {
328            if let Some(body) = &method.body {
329                calls_in_block(body, out);
330            }
331        }
332        cpp::ClassMember::Constructor(constructor) => {
333            for (_, value) in &constructor.initialisers {
334                calls_in_expr(value, out);
335            }
336            if let cpp::FunctionBody::Block(body) = &constructor.body {
337                calls_in_block(body, out);
338            }
339        }
340        cpp::ClassMember::Destructor(destructor) => {
341            if let cpp::FunctionBody::Block(body) = &destructor.body {
342                calls_in_block(body, out);
343            }
344        }
345        cpp::ClassMember::Nested(decl) => calls_in_decl(decl, out),
346        cpp::ClassMember::Field(field) => {
347            if let Some(value) = &field.default {
348                calls_in_expr(value, out);
349            }
350        }
351        cpp::ClassMember::Alias(_)
352        | cpp::ClassMember::Comment(_)
353        | cpp::ClassMember::Verbatim(_) => {}
354    }
355}
356
357/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
358/// The tree walked here is the one this generator emitted and owns, so
359/// it holds no cycle; the depth is that body's height, and a defaulter
360/// body is a handful of statements deep.
361fn calls_in_block(block: &cpp::Block, out: &mut Vec<String>) {
362    for statement in &block.statements {
363        calls_in_statement(statement, out);
364    }
365}
366
367/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
368/// The tree walked here is the one this generator emitted and owns, so
369/// it holds no cycle; the depth is that body's height, and a defaulter
370/// body is a handful of statements deep.
371fn calls_in_statement(statement: &cpp::Statement, out: &mut Vec<String>) {
372    match statement {
373        cpp::Statement::Declare { value, .. } => {
374            if let Some(value) = value {
375                calls_in_expr(value, out);
376            }
377        }
378        cpp::Statement::Expr(expr) | cpp::Statement::Throw(expr) => calls_in_expr(expr, out),
379        cpp::Statement::Return(value) => {
380            if let Some(value) = value {
381                calls_in_expr(value, out);
382            }
383        }
384        cpp::Statement::If {
385            condition,
386            then,
387            otherwise,
388        } => {
389            calls_in_expr(condition, out);
390            calls_in_block(then, out);
391            if let Some(otherwise) = otherwise {
392                calls_in_block(otherwise, out);
393            }
394        }
395        cpp::Statement::For {
396            init,
397            condition,
398            step,
399            body,
400        } => {
401            calls_in_statement(init, out);
402            calls_in_expr(condition, out);
403            calls_in_expr(step, out);
404            calls_in_block(body, out);
405        }
406        cpp::Statement::While { condition, body } => {
407            calls_in_expr(condition, out);
408            calls_in_block(body, out);
409        }
410        cpp::Statement::Try { body, handlers } => {
411            calls_in_block(body, out);
412            for handler in handlers {
413                calls_in_block(&handler.body, out);
414            }
415        }
416        cpp::Statement::ForEach { range, body, .. } => {
417            calls_in_expr(range, out);
418            calls_in_block(body, out);
419        }
420        cpp::Statement::Switch { value, cases } => {
421            calls_in_expr(value, out);
422            for case in cases {
423                for label in &case.labels {
424                    calls_in_expr(label, out);
425                }
426                calls_in_block(&case.body, out);
427            }
428        }
429        cpp::Statement::Break | cpp::Statement::Comment(_) => {}
430    }
431}
432
433/// zerodds-lint: recursion-depth 64 (expression nesting the walk builds).
434/// The tree walked here is the one this generator emitted and owns, so
435/// it holds no cycle; the depth is that expression's height, which
436/// follows the member nesting the type walk already bounded.
437fn calls_in_expr(expr: &cpp::Expr, out: &mut Vec<String>) {
438    match expr {
439        cpp::Expr::Call { callee, args } => {
440            if let cpp::Expr::Path(path) = callee.as_ref()
441                && let Some(last) = path.last()
442            {
443                let name = last.as_str();
444                // Every generated routine's name, not only the
445                // defaulters': the callee of a codec call is always a
446                // one-segment path this generator built, and the
447                // defaulter set looks each name up in its own index, so
448                // an encoder's name in the list adds nothing to it.
449                // [`reachable_from`] needs them all.
450                if name.starts_with("zd_") && !out.iter().any(|seen| seen == name) {
451                    out.push(name.to_string());
452                }
453            }
454            calls_in_expr(callee, out);
455            for arg in args {
456                calls_in_expr(arg, out);
457            }
458        }
459        cpp::Expr::MethodCall { receiver, args, .. } => {
460            calls_in_expr(receiver, out);
461            for arg in args {
462                calls_in_expr(arg, out);
463            }
464        }
465        cpp::Expr::Member { value, .. }
466        | cpp::Expr::Arrow { value, .. }
467        | cpp::Expr::AddressOf(value)
468        | cpp::Expr::Deref(value)
469        | cpp::Expr::Paren(value)
470        | cpp::Expr::Cast { value, .. } => calls_in_expr(value, out),
471        cpp::Expr::Index { value, index } => {
472            calls_in_expr(value, out);
473            calls_in_expr(index, out);
474        }
475        cpp::Expr::Binary { lhs, rhs, .. } => {
476            calls_in_expr(lhs, out);
477            calls_in_expr(rhs, out);
478        }
479        cpp::Expr::Unary { value, .. } => calls_in_expr(value, out),
480        cpp::Expr::Construct { args, .. } | cpp::Expr::InitList(args) => {
481            for arg in args {
482                calls_in_expr(arg, out);
483            }
484        }
485        cpp::Expr::Literal(_) | cpp::Expr::Path(_) | cpp::Expr::SizeOf(_) => {}
486    }
487}
488
489/// Build the defaulter for each of `types`, prototypes first.
490///
491/// The shape `crate::emit::decode::decoder_over` uses, and for the same
492/// reason: a defaulter may call another, and C++ resolves a call to a
493/// free function against what is declared above it.
494///
495/// # Errors
496/// [`EncodeError`] for a construct with no C++ default.
497pub fn cpp_defaulters_over(
498    ast: &Ast,
499    types: &[Id<TypeNode>],
500) -> Result<Vec<cpp::Decl>, EncodeError> {
501    let mut prototypes = Vec::new();
502    let mut bodies = Vec::new();
503    let mut emitted: Vec<String> = Vec::new();
504    for id in types.iter().copied() {
505        let function_name = defaulter_name(ast, id);
506        if emitted.contains(&function_name) {
507            continue;
508        }
509        let Some(body) = body_of(ast, id, &function_name)? else {
510            continue;
511        };
512        emitted.push(function_name.clone());
513        prototypes.push(function(ast, id, &function_name, None)?);
514        bodies.push(function(ast, id, &function_name, Some(body))?);
515    }
516    prototypes.extend(bodies);
517    Ok(prototypes)
518}
519
520/// `static void <name>(<T> &v)`, with or without a body.
521///
522/// A reference for every type, including an array: `std::array` is a
523/// value and does not decay, so the C column's split between a pointer
524/// parameter and a bare array has no counterpart.
525///
526/// No `Reader`. A default is a property of the type and reads nothing,
527/// so a defaulter that took one could fail, and a default value that can
528/// fail is not a default value.
529///
530/// # Errors
531/// [`EncodeError::NoWriter`] when the materialiser has no C++ type for
532/// `id` — refused by name rather than spelled approximately, which is
533/// the rule `adapter/cpp.rs` states for every type it cannot write.
534fn function(
535    ast: &Ast,
536    id: Id<TypeNode>,
537    function_name: &str,
538    body: Option<cpp::Block>,
539) -> Result<cpp::Decl, EncodeError> {
540    let type_ = cpp_value_type(ast, id, function_name).map_err(|error| EncodeError::NoWriter {
541        member: cpp_type_tag(ast, id),
542        kind: match error {
543            crate::adapter::cst::MaterialiseError::NoLanguageForm { .. } => {
544                "type with no C++ form, whose Table 9 default therefore has nowhere to go"
545            }
546            _ => "type whose C++ spelling does not evaluate",
547        },
548    })?;
549    Ok(cpp::Decl::Function(cpp::Function {
550        doc: Doc::none(),
551        returns: cpp::Type::Void,
552        name: Ident::new(function_name),
553        params: vec![cpp::Param {
554            type_: cpp::Type::Ref(Box::new(type_)),
555            name: Some(Ident::new("v")),
556            default: None,
557        }],
558        is_inline: false,
559        is_static: true,
560        is_constexpr: false,
561        is_noexcept: false,
562        is_maybe_unused: false,
563        body: body.map_or(cpp::FunctionBody::None, cpp::FunctionBody::Block),
564    }))
565}
566
567#[allow(clippy::too_many_lines)]
568fn body_of(ast: &Ast, id: Id<TypeNode>, own_name: &str) -> Result<Option<cpp::Block>, EncodeError> {
569    // Refused by name, not answered as `Ok(None)`. The caller reads
570    // `None` as "nothing to emit for this one" and skips it, so a
571    // type whose handle no longer resolves quietly loses its
572    // defaulter and the first sign of it is a member left at
573    // whatever the language zero-initialises to.
574    let Ok(node) = ast.type_(id) else {
575        return Err(EncodeError::NoWriter {
576            member: own_name.to_string(),
577            kind: "a type the model does not hold",
578        });
579    };
580    let statements = match &node.kind {
581        TypeKind::Primitive(kind) => vec![assign(name("v"), primitive_default(*kind))],
582
583        // `v.clear()` and not `v = ""`: the same value, and the member
584        // function says the type owns storage where the assignment would
585        // read as a conversion from a literal.
586        //
587        // Both widths. A `wstring` is UTF-8 in this column — the reason
588        // is in `adapter/cpp.rs` — and the empty string is the same
589        // octets either way.
590        TypeKind::String { .. } => vec![clear("v")],
591
592        // `fixed<P,S>` is a decimal string here, and
593        // `canonical::decimal` accepts exactly one spelling of zero for
594        // a given scale. Not `clear()`: an empty string is not a
595        // decimal, and the C column writes the same digits.
596        TypeKind::Fixed { scale, .. } => {
597            vec![assign(name("v"), string_literal(&zero_decimal(*scale)))]
598        }
599
600        TypeKind::Alias { aliased } => {
601            let Some(target) = aliased.resolved() else {
602                return Err(EncodeError::NoWriter {
603                    member: own_name.to_string(),
604                    kind: "typedef with an unresolved target",
605                });
606            };
607            vec![cpp::Statement::Expr(cpp::Expr::Call {
608                callee: Box::new(path(&defaulter_name(ast, target))),
609                args: vec![name("v")],
610            })]
611        }
612
613        // "The first literal in the enumeration", or the one
614        // `@default_literal` names — §7.3.1.2.1.10.
615        TypeKind::Enumeration { .. } => {
616            let Some(literal) = crate::emit::default_enumerator_name(ast, id) else {
617                return Err(EncodeError::NoWriter {
618                    member: own_name.to_string(),
619                    kind: "enumeration with no enumerator to take as its default",
620                });
621            };
622            vec![assign(
623                name("v"),
624                path(&crate::emit::cpp::enumerator_constant_name(
625                    ast, id, &literal,
626                )),
627            )]
628        }
629
630        // No bit set: the holder's Table 9 default is zero and zero sets
631        // none. The type is an integer alias, so a plain zero is a value
632        // of it.
633        TypeKind::Bitmask { .. } => vec![assign(name("v"), cpp::Expr::Literal(Literal::int(0)))],
634
635        TypeKind::Bitset { .. } => {
636            let mut statements = Vec::new();
637            for field in &ast.all_bitfields_of(id) {
638                let Ok(field_node) = ast.bitfield(*field) else {
639                    continue;
640                };
641                // An anonymous bitfield is padding and holds no value.
642                if let Some(field_name) = field_node.name.as_ref() {
643                    statements.push(assign(
644                        member("v", &cxx_name(field_name)),
645                        cpp::Expr::Literal(Literal::int(0)),
646                    ));
647                }
648            }
649            statements
650        }
651
652        // "A zero-length sequence of the same element type", and the
653        // same for a map's entries — which are a `std::vector` of pairs
654        // here, so one call answers both.
655        TypeKind::Sequence { .. } | TypeKind::Map { .. } => vec![clear("v")],
656
657        // "An array of the same dimensions and same element type whose
658        // elements take the default value for their corresponding type."
659        TypeKind::Array { element, dims } => {
660            let Some(element) = element.resolved() else {
661                return Err(EncodeError::NoWriter {
662                    member: own_name.to_string(),
663                    kind: "array with an unresolved element",
664                });
665            };
666            let mut lengths = Vec::new();
667            for dim in dims {
668                lengths.push(ast.eval_integral(*dim).map_err(|_| EncodeError::NoWriter {
669                    member: own_name.to_string(),
670                    kind: "array whose length does not evaluate",
671                })?);
672            }
673            let mut access = name("v");
674            for level in 0..lengths.len() {
675                access = cpp::Expr::Index {
676                    value: Box::new(access),
677                    index: Box::new(name(&format!("zd_i{level}"))),
678                };
679            }
680            let mut inner = cpp::Block {
681                statements: vec![default_into(ast, element, access)],
682            };
683            for (level, length) in lengths.iter().enumerate().rev() {
684                inner = cpp::Block {
685                    statements: vec![counted_loop(&format!("zd_i{level}"), *length, inner)],
686                };
687            }
688            inner.statements
689        }
690
691        // "A structure without any of the optional members and with
692        // other members set to their default values."
693        TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
694            let mut statements = Vec::new();
695            for member_id in &ast.all_members_of(id) {
696                let Ok(member_node) = ast.member(*member_id) else {
697                    continue;
698                };
699                let field = cxx_name(&member_node.name);
700                if member_node.facets.is_optional() {
701                    // The member is NULL and nothing else. §7.2.2.4.4.4.7:
702                    // "Implementations shall not provide a default value
703                    // in such a case." An `@optional` member is a plain
704                    // pointer (XTypes 1.3 §7.5.1.2.4.2) and an `@external`
705                    // one a `std::shared_ptr`; `destroy` lets go of what
706                    // the member held and leaves it empty for both.
707                    statements.push(cpp::Statement::Expr(cpp::Expr::Call {
708                        callee: Box::new(cpp::Expr::Path(Path::new(&[
709                            "zerodds", "wire", "destroy",
710                        ]))),
711                        args: vec![member("v", &field)],
712                    }));
713                    continue;
714                }
715                let Some(member_type) = member_node.type_.resolved() else {
716                    return Err(EncodeError::NoWriter {
717                        member: member_node.name.clone(),
718                        kind: "member with an unresolved type",
719                    });
720                };
721                statements.extend(member_default(
722                    ast,
723                    member_node,
724                    member_type,
725                    member("v", &field),
726                )?);
727            }
728            statements
729        }
730
731        // "A union with the discriminator set to the default value for
732        // the discriminator type. If this selects a branch, then the
733        // selected member is also set to the default value for the
734        // member type."
735        TypeKind::Union { discriminator, .. } => {
736            let Some(discriminator) = discriminator.resolved() else {
737                return Err(EncodeError::NoWriter {
738                    member: own_name.to_string(),
739                    kind: "union with an unresolved discriminator",
740                });
741            };
742            let mut statements = vec![default_into(
743                ast,
744                discriminator,
745                member("v", crate::adapter::cpp::UNION_DISCRIMINATOR),
746            )];
747            if let Some((member_type, field)) = selected_by_default(ast, id, discriminator)? {
748                // One field per case directly on the class, so there is
749                // no `_u` to go through. `adapter/cpp.rs` says why: two
750                // cases may carry the same type, which a `std::variant`
751                // could not tell apart.
752                statements.push(default_into(
753                    ast,
754                    member_type,
755                    member("v", &crate::adapter::cpp::union_storage(&field)),
756                ));
757            }
758            statements
759        }
760
761        TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
762            return Ok(None);
763        }
764        TypeKind::ValueBox { .. } => {
765            return Err(EncodeError::NoWriter {
766                member: own_name.to_string(),
767                kind: "boxed value, whose presence flag has no settled wire form",
768            });
769        }
770        // Refused by name rather than passed over, exactly as the C
771        // column refuses them. `any` is in the model because IDL 4.2 has
772        // it; DDS-XTypes has no `any` and no encoding for one, and only
773        // CORBA CDR writes it — as a TypeCode ahead of the value.
774        // Returning `Ok(None)` here would generate a codec that silently
775        // omitted the member.
776        TypeKind::Any => {
777            return Err(EncodeError::NoWriter {
778                member: own_name.to_string(),
779                kind: "`any`, which DDS-XTypes has no encoding for; only CORBA CDR writes one, \
780                       as a TypeCode ahead of the value",
781            });
782        }
783        TypeKind::ValueBase => {
784            return Err(EncodeError::NoWriter {
785                member: own_name.to_string(),
786                kind: "`ValueBase`, which DDS-XTypes has no encoding for; only CORBA CDR writes \
787                       one, as a chunked value with its repository id",
788            });
789        }
790    };
791    // An empty aggregate has nothing to default and its `v` would be
792    // the one unused parameter in the file — the probes compile with
793    // `-Werror=unused-parameter`, and `empty-struct-in-sequence` found
794    // the defaulter this way. The codec functions suppress
795    // unconditionally; here only the empty body needs it.
796    let mut statements = statements;
797    if statements.is_empty() {
798        statements.push(cpp::Statement::Expr(cpp::Expr::Cast {
799            kind: cpp::CastKind::Static,
800            type_: Box::new(cpp::Type::Void),
801            value: Box::new(name("v")),
802        }));
803    }
804    Ok(Some(cpp::Block { statements }))
805}
806
807/// Table 9's primitive rows as C++ initialisers.
808fn primitive_default(kind: PrimitiveKind) -> cpp::Expr {
809    match kind {
810        PrimitiveKind::Boolean => cpp::Expr::Literal(Literal::Bool(false)),
811        // `'\0'`, written as the number it is: `read_char` hands back a
812        // code point and the field holds one.
813        PrimitiveKind::Char | PrimitiveKind::WChar => cpp::Expr::Literal(Literal::int(0)),
814        PrimitiveKind::Float32 => cpp::Expr::Literal(Literal::Float("0.0f".to_string())),
815        PrimitiveKind::Float64 => cpp::Expr::Literal(Literal::Float("0.0".to_string())),
816        // The `long double` column carries a `zerodds::wire::Float128`,
817        // whose zero is its zero bit pattern. `Float128::zero()` is the
818        // runtime's name for it, so the spelling does not depend on the
819        // struct's layout — and `Float128{}` would not do: the type is
820        // an aggregate of two `std::uint64_t` and its zero *is* two
821        // zeroes today, which is a fact about the representation and not
822        // about the value.
823        PrimitiveKind::Float128 => cpp::Expr::Call {
824            callee: Box::new(cpp::Expr::Path(Path::rooted(&[
825                "zerodds", "wire", "Float128", "zero",
826            ]))),
827            args: Vec::new(),
828        },
829        PrimitiveKind::Octet
830        | PrimitiveKind::UInt8
831        | PrimitiveKind::Int8
832        | PrimitiveKind::Int16
833        | PrimitiveKind::UInt16
834        | PrimitiveKind::Int32
835        | PrimitiveKind::UInt32
836        | PrimitiveKind::Int64
837        | PrimitiveKind::UInt64 => cpp::Expr::Literal(Literal::int(0)),
838    }
839}
840
841/// The case a defaulted discriminator selects, if any.
842///
843/// The discriminator's own default is Table 9's for its type — zero for
844/// an integer, false for a boolean, the first literal for an enumeration
845/// — and the case it lands in is decided here rather than at runtime,
846/// because the type already fixes it.
847fn selected_by_default(
848    ast: &Ast,
849    id: Id<TypeNode>,
850    discriminator: Id<TypeNode>,
851) -> Result<Option<(Id<TypeNode>, String)>, EncodeError> {
852    let Some(selector) = discriminator_default_value(ast, discriminator) else {
853        return Ok(None);
854    };
855    Ok(selected_case(ast, id, selector)?
856        .map(|(member, member_type)| (member_type, cxx_name(&member.name))))
857}
858
859/// The integer a defaulted discriminator has, for label comparison.
860fn discriminator_default_value(ast: &Ast, discriminator: Id<TypeNode>) -> Option<i128> {
861    // Resolved by the counted loop and not by an `Alias` arm calling
862    // this function again: a `typedef` cycle is a finding the model's
863    // own invariant pass reports rather than a shape it rejects, and
864    // the recursive form read such a chain with nothing counting it.
865    let discriminator = resolve_alias(ast, discriminator);
866    match ast.type_(discriminator).map(|node| node.kind.clone()) {
867        Ok(TypeKind::Enumeration { .. }) => {
868            // The first literal's *value*, which `@value` may have moved
869            // away from zero.
870            let literal = crate::emit::default_enumerator_name(ast, discriminator)?;
871            for enumerator in ast.enumerators_of(discriminator) {
872                // `None`, not `continue`. This function answers "the
873                // discriminator's default value", and the line below already
874                // says `None` when an enumerator's value does not evaluate.
875                // Skipping the enumerator instead makes the *next* one the
876                // default — a union that selects a different arm than the
877                // source declared, with nothing to show for it.
878                let Ok(node) = ast.enumerator(*enumerator) else {
879                    return None;
880                };
881                if node.name == literal {
882                    return ast.effective_enumerator_value(*enumerator);
883                }
884            }
885            None
886        }
887        // Every other discriminator kind IDL 4.2 §7.4.1.4.4.4.2 allows —
888        // integer, `char`, `boolean` — has zero for its Table 9 default,
889        // and `'\0'` and `false` are zero.
890        Ok(TypeKind::Primitive(_)) => Some(0),
891        _ => None,
892    }
893}
894
895/// An explicit `@default` as a C++ initialiser for `type_`.
896fn explicit(
897    ast: &Ast,
898    expr: Id<zerodds_ast::ConstExprNode>,
899    type_: Id<TypeNode>,
900    member_name: &str,
901) -> Result<cpp::Expr, EncodeError> {
902    let refuse = |kind: &'static str| EncodeError::NoWriter {
903        member: member_name.to_string(),
904        kind,
905    };
906    let node = ast
907        .expr(expr)
908        .map_err(|_| refuse("a `@default` this frontend cannot follow"))?;
909    let resolved = resolve_alias(ast, type_);
910    match (
911        &node.expr,
912        ast.type_(resolved).map(|node| node.kind.clone()),
913    ) {
914        (ConstExpr::Literal(AstLiteral::Boolean(flag)), _) => {
915            Ok(cpp::Expr::Literal(Literal::Bool(*flag)))
916        }
917        // The code point as a number, not a character literal: the
918        // member holds a code point and `cpp_primitive` gives `wchar` a
919        // fixed-width integer, because the platform's `wchar_t` is not
920        // the wire's code unit.
921        (ConstExpr::Literal(AstLiteral::Char(ch)), _) => {
922            Ok(cpp::Expr::Literal(Literal::int(i128::from(u32::from(*ch)))))
923        }
924        (ConstExpr::Literal(AstLiteral::WChar(code)), _) => {
925            Ok(cpp::Expr::Literal(Literal::int(i128::from(*code))))
926        }
927        (ConstExpr::Literal(AstLiteral::String(text)), _) => Ok(string_literal(text)),
928        (ConstExpr::Literal(AstLiteral::WString(codes)), _) => {
929            let mut text = String::new();
930            for code in codes {
931                let Some(ch) = char::from_u32(*code) else {
932                    return Err(refuse("a `@default` wide string that is not code points"));
933                };
934                text.push(ch);
935            }
936            Ok(string_literal(&text))
937        }
938        (ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }), kind) => {
939            // `long double` is `zerodds::wire::Float128`, a class with
940            // no literal, and the compiler's own `long double` is three
941            // different formats across the platforms this project builds
942            // for — `adapter/cpp.rs` refuses a `long double` *constant*
943            // for exactly this reason, and a `@default` is the same
944            // problem one declaration along.
945            if matches!(kind, Ok(TypeKind::Primitive(PrimitiveKind::Float128))) {
946                return Err(refuse(
947                    "a `@default` on a `long double`, which has no C++ literal",
948                ));
949            }
950            // A `double` initialiser narrowing into a `float` is what
951            // `-Wconversion` reports, and the probe is compiled with
952            // `-Werror`.
953            let suffix = match kind {
954                Ok(TypeKind::Primitive(PrimitiveKind::Float32)) => "f",
955                _ => "",
956            };
957            let exponent = exponent
958                .as_ref()
959                .map_or(String::new(), |text| format!("e{text}"));
960            Ok(cpp::Expr::Literal(Literal::Float(format!(
961                "{mantissa}{exponent}{suffix}"
962            ))))
963        }
964        (ConstExpr::Literal(AstLiteral::Fixed { digits, scale }), _) => {
965            Ok(string_literal(&fixed_text(digits, *scale)))
966        }
967        (ConstExpr::Literal(AstLiteral::Enumerator(reference)), _) => {
968            let target = reference
969                .resolved()
970                .ok_or_else(|| refuse("a `@default` enumerator that is unresolved"))?;
971            let enumerator = ast
972                .enumerator(target)
973                .map_err(|_| refuse("a `@default` enumerator this frontend cannot follow"))?;
974            Ok(path(&crate::emit::cpp::enumerator_constant_name(
975                ast,
976                resolved,
977                &enumerator.name,
978            )))
979        }
980        (_, kind) => {
981            // An integer literal, a named constant or arithmetic over
982            // them, folded the same way a bound or a case label is.
983            let value = ast
984                .eval_integral(expr)
985                .map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
986            // A decimal literal with no suffix takes the first of `int`,
987            // `long` and `long long` that holds it, and a value above
988            // `i64::MAX` holds in none of them — which clang reports as
989            // `-Wimplicitly-unsigned-literal`. `adapter/cpp.rs` writes
990            // the same suffix for the same reason.
991            //
992            // The other end of the same range: −2^63 is a minus over a
993            // decimal one past `LLONG_MAX` and so is not a literal
994            // either — `zerodds_lang_render::cpp::integer_text` carries
995            // the clause and answers with the subtraction.
996            Ok(cpp::Expr::Literal(if value > i128::from(i64::MAX) {
997                Literal::Int(format!("{value}u"))
998            } else {
999                let _ = kind;
1000                Literal::Int(zerodds_lang_render::cpp::integer_text(value))
1001            }))
1002        }
1003    }
1004}
1005
1006// -- the small builders this file keeps ---------------------------------
1007
1008fn name(text: &str) -> cpp::Expr {
1009    cpp::Expr::Path(Path::single(text))
1010}
1011
1012/// A name that may already carry its own scoping, as
1013/// [`Cpp::enumerator_constant`] and [`defaulter_name`] hand it over.
1014fn path(text: &str) -> cpp::Expr {
1015    let rooted = text.starts_with("::");
1016    let segments: Vec<&str> = text.trim_start_matches("::").split("::").collect();
1017    cpp::Expr::Path(if rooted {
1018        Path::rooted(&segments)
1019    } else {
1020        Path::new(&segments)
1021    })
1022}
1023
1024fn string_literal(text: &str) -> cpp::Expr {
1025    cpp::Expr::Literal(Literal::Str(text.to_string()))
1026}
1027
1028fn member(base: &str, field: &str) -> cpp::Expr {
1029    cpp::Expr::Member {
1030        value: Box::new(name(base)),
1031        name: Ident::new(field),
1032    }
1033}
1034
1035fn clear(base: &str) -> cpp::Statement {
1036    cpp::Statement::Expr(cpp::Expr::MethodCall {
1037        receiver: Box::new(name(base)),
1038        name: Ident::new("clear"),
1039        args: Vec::new(),
1040    })
1041}
1042
1043fn assign(place: cpp::Expr, value: cpp::Expr) -> cpp::Statement {
1044    cpp::Statement::Expr(cpp::Expr::Binary {
1045        op: "=",
1046        lhs: Box::new(place),
1047        rhs: Box::new(value),
1048    })
1049}
1050
1051/// `for (std::size_t i = 0; i < count; i = i + 1)`, the same spelling
1052/// [`Cpp::count_up`] writes, so an array's defaulter and its decoder
1053/// read alike.
1054fn counted_loop(index: &str, count: i128, body: cpp::Block) -> cpp::Statement {
1055    cpp::Statement::For {
1056        init: Box::new(cpp::Statement::Declare {
1057            type_: cpp::Type::named(Path::new(&["std", "size_t"])),
1058            name: Ident::new(index),
1059            value: Some(cpp::Expr::Literal(Literal::int(0))),
1060        }),
1061        condition: cpp::Expr::Binary {
1062            op: "<",
1063            lhs: Box::new(name(index)),
1064            rhs: Box::new(cpp::Expr::Literal(Literal::Int(format!("{count}u")))),
1065        },
1066        step: cpp::Expr::Binary {
1067            op: "=",
1068            lhs: Box::new(name(index)),
1069            rhs: Box::new(cpp::Expr::Binary {
1070                op: "+",
1071                lhs: Box::new(name(index)),
1072                rhs: Box::new(cpp::Expr::Literal(Literal::int(1))),
1073            }),
1074        },
1075        body,
1076    }
1077}