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 §ion.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}