Skip to main content

zerodds_lang_rules/
default_rust.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The value a generated Rust member takes when the stream does not
4//! carry it.
5//!
6//! Table 9 of DDS-XTypes 1.3, as a Rust expression. The reflective
7//! column's copy of the same table is
8//! `zerodds_wire_codec::default` (re-exported by `crates/wire-probe/src/default.rs`), which carries the clause
9//! quotations and the reasoning for the kinds Table 9 does not list.
10//! Deliberately a third derivation and not a call into the first: the
11//! columns are compared against each other, and a shared helper would
12//! make them agree about a mistake.
13//!
14//! ## Why an expression and not `Default::default()`
15//!
16//! The types this column materialises derive nothing —
17//! `crate::adapter::cst` sets `derives: Vec::new()` on every struct and
18//! enum it emits, because a derive is a decision about the target
19//! language and not about the wire. So the default is written out: a
20//! struct literal with a value per member, an enum's first variant, an
21//! empty `Vec`. `Default` would also be the *language's* zero and not
22//! Table 9's, and the two part company at an enumeration whose first
23//! literal has a `@value` other than zero.
24
25use crate::default_common::{discriminator_default_value, resolve_alias};
26use crate::encode::EncodeError;
27use crate::fixed_literal::fixed_text;
28use zerodds_ast::{
29    Ast, ConstExpr, Literal as AstLiteral, MemberNode, PrimitiveKind, TypeKind, TypeNode,
30};
31use zerodds_ast_core::Id;
32use zerodds_lang_cst::common::{Ident, Literal, Path};
33use zerodds_lang_cst::rust;
34
35/// How deep a default may nest before the model is taken to be cyclic.
36const MAX_DEPTH: usize = 64;
37
38/// One member's default: its `@default`, or its type's.
39///
40/// §7.2.2.4.4.4.9 puts the explicit one first — "An explicit definition
41/// associates a default value with the member at the time where the
42/// aggregated type is defined" — and falls back to "the default value of
43/// the member type. See Table 9."
44///
45/// # Errors
46/// [`EncodeError::NoWriter`] for a type or a `@default` this column
47/// cannot spell. Refused rather than replaced by Table 9's zero: a
48/// member the designer gave a default and a member that got a zero
49/// instead are different samples.
50pub fn member_default(
51    ast: &Ast,
52    from: Id<zerodds_ast::ModuleNode>,
53    node: &MemberNode,
54    type_: Id<TypeNode>,
55) -> Result<rust::Expr, EncodeError> {
56    // §7.4.1.2.3: "if the receiver's member is not optional, it
57    // logically takes its default value" — so an `@optional` one does
58    // not, and the absence is the value. `None`, not Table 9's zero:
59    // the two are different samples, and a reader that substituted the
60    // zero would report a member the writer deliberately left out.
61    if node.facets.is_optional() {
62        return Ok(rust::Expr::Path(Path::single("None")));
63    }
64    let value = member_value_default(ast, from, node, type_)?;
65    Ok(crate::adapter::cst::external_value(ast, node, value))
66}
67
68/// Default of the present value, before optional presence or external
69/// storage wraps it. Bound handling operates on that value, not its holder.
70pub(crate) fn member_value_default(
71    ast: &Ast,
72    from: Id<zerodds_ast::ModuleNode>,
73    node: &MemberNode,
74    type_: Id<TypeNode>,
75) -> Result<rust::Expr, EncodeError> {
76    match node.facets.xtypes.as_ref().and_then(|x| x.default_value) {
77        Some(expr) => explicit(ast, expr, type_, &node.name),
78        None => default_of(ast, from, type_, &node.name),
79    }
80}
81
82/// Table 9's default for a type.
83///
84/// # Errors
85/// As [`member_default`].
86pub fn default_of(
87    ast: &Ast,
88    from: Id<zerodds_ast::ModuleNode>,
89    type_: Id<TypeNode>,
90    at: &str,
91) -> Result<rust::Expr, EncodeError> {
92    at_depth(ast, from, type_, at, 0)
93}
94
95/// zerodds-lint: recursion-depth 64 (collection and typedef nesting;
96/// bounded by `MAX_DEPTH`, which returns `EncodeError::NoWriter`
97/// naming the member whose default nested)
98#[allow(clippy::too_many_lines)]
99fn at_depth(
100    ast: &Ast,
101    from: Id<zerodds_ast::ModuleNode>,
102    type_: Id<TypeNode>,
103    at: &str,
104    depth: usize,
105) -> Result<rust::Expr, EncodeError> {
106    let refuse = |kind: &'static str| EncodeError::NoWriter {
107        member: at.to_string(),
108        kind,
109    };
110    if depth > MAX_DEPTH {
111        return Err(refuse(
112            "type whose default nests deeper than an acyclic model can",
113        ));
114    }
115    let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
116        detail: format!("{error}"),
117    })?;
118    Ok(match &node.kind {
119        TypeKind::Primitive(kind) => primitive_default(*kind),
120        // Table 9's `STRING_TYPE` row, for both widths.
121        TypeKind::String { .. } => call_path(&["String", "new"]),
122        // `fixed<P,S>` has no Table 9 row and is a decimal string in
123        // this column; zero at the declared scale is the one spelling
124        // the canonical form accepts.
125        TypeKind::Fixed { scale, .. } => {
126            let text = if *scale == 0 {
127                String::from("0")
128            } else {
129                format!("0.{}", "0".repeat(usize::from(*scale)))
130            };
131            rust::Expr::MethodCall {
132                receiver: Box::new(rust::Expr::Literal(Literal::string(&text))),
133                method: Ident::new("to_string"),
134                args: Vec::new(),
135            }
136        }
137        TypeKind::Alias { aliased } => {
138            let target = aliased
139                .resolved()
140                .ok_or_else(|| refuse("unresolved typedef"))?;
141            at_depth(ast, from, target, at, depth + 1)?
142        }
143        // "The first literal in the enumeration", or the one
144        // `@default_literal` names — §7.3.1.2.1.10.
145        TypeKind::Enumeration { .. } => {
146            let variant = default_enumerator(ast, type_)
147                .ok_or_else(|| refuse("enumeration with no enumerator to take as its default"))?;
148            // The enumerator is named from the module this expression
149            // lands in, not from the enumeration's own.
150            let path = crate::adapter::cst::relative_path(ast, from, node)
151                .map_err(|_| refuse("an enumeration the model has no module for"))?;
152            let mut segments: Vec<String> = path
153                .segments()
154                .iter()
155                .map(|s| s.as_str().to_string())
156                .collect();
157            segments.push(variant.clone());
158            let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
159            rust::Expr::Path(Path::new(&borrowed))
160        }
161        // No bit set: the holder's Table 9 default is zero, and zero
162        // sets none. A bitmask is its holder integer in this column.
163        TypeKind::Bitmask { .. } => rust::Expr::Literal(Literal::int(0)),
164        // A bitset has no Table 9 line of its own; §7.4.1.1.4 makes it
165        // the aggregate of its bitfields, and a bitfield holds an
166        // unsigned integer whose Table 9 default is zero. Written out
167        // field by field rather than answered with a `Default::default`
168        // call, because the struct's `impl Default` is built *from* this
169        // expression and a call to it here would be a cycle.
170        //
171        // Padding is left out: an anonymous bitfield has no field in the
172        // materialised struct to name.
173        TypeKind::Bitset { .. } => {
174            let mut fields = Vec::new();
175            for field in &ast.all_bitfields_of(type_) {
176                // A stale handle is refused: a struct literal missing
177                // one field does not compile, which is loud, but the
178                // refusal names the construct instead.
179                let field_node = ast
180                    .bitfield(*field)
181                    .map_err(|_| refuse("bitset with a stale bitfield handle"))?;
182                // An anonymous bitfield is padding and has no field in
183                // the materialised struct to name — the one omission
184                // here that is the mapping and not a loss.
185                let Some(field_name) = field_node.name.as_ref() else {
186                    continue;
187                };
188                fields.push((
189                    Ident::new(&rust::escape_keyword(field_name)),
190                    rust::Expr::Literal(Literal::int(0)),
191                ));
192            }
193            rust::Expr::StructLit {
194                path: crate::adapter::cst::relative_path(ast, from, node)
195                    .map_err(|_| refuse("a bitset the model has no module for"))?,
196                fields,
197                base: None,
198            }
199        }
200        // "A zero-length sequence of the same element type".
201        TypeKind::Sequence { .. } => call_path(&["Vec", "new"]),
202        // "An empty map of the same key and value types" — and an empty
203        // `BTreeMap`, which is the container `adapter::cst` materialises
204        // a `map` into. `Vec::new()` was written here while nothing
205        // emitted a map member, so no generated file ever carried the
206        // mismatch; the first one that did would have failed at `rustc`
207        // with `expected BTreeMap, found Vec`.
208        TypeKind::Map { .. } => match crate::adapter::cst::rust_map_form() {
209            crate::adapter::cst::RustMapForm::BTreeMap => call_path(&["BTreeMap", "new"]),
210            crate::adapter::cst::RustMapForm::Entries => call_path(&["Vec", "new"]),
211        },
212        // "An array of the same dimensions and same element type whose
213        // elements take the default value for their corresponding type."
214        TypeKind::Array { element, dims } => {
215            let element = element
216                .resolved()
217                .ok_or_else(|| refuse("unresolved array element"))?;
218            let mut expression = at_depth(ast, from, element, at, depth + 1)?;
219            // `[value; N]` is a repeat expression and Rust requires the
220            // element to be `Copy` for it. The materialised types derive
221            // `Debug, Clone, PartialEq` and not `Copy`, so an array of
222            // structs — or of sequences, where the element is a `Vec` —
223            // has to be written out element by element. A primitive is
224            // `Copy` and keeps the short form, which is every array in
225            // the trees this emitter had been pointed at before an
226            // `impl Default` asked for the rest.
227            let copyable = matches!(
228                ast.type_(element).ok().map(|node| &node.kind),
229                Some(TypeKind::Primitive(_))
230            );
231            for dim in dims.iter().rev() {
232                let count = ast
233                    .eval_integral(*dim)
234                    .map_err(|_| refuse("array whose length does not evaluate"))?;
235                expression = if copyable {
236                    rust::Expr::Repeat {
237                        value: Box::new(expression),
238                        count: Box::new(rust::Expr::Literal(Literal::int(count))),
239                    }
240                } else {
241                    let count = usize::try_from(count)
242                        .map_err(|_| refuse("array whose length does not fit a usize"))?;
243                    rust::Expr::Array(vec![expression.clone(); count])
244                };
245            }
246            expression
247        }
248        TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
249            let mut fields = Vec::new();
250            // The base is the first member `parent` (XTypes 1.3 §7.5.1.4.1.3).
251            if let Some(base) = crate::adapter::c::struct_base(ast, type_) {
252                fields.push((
253                    Ident::new(crate::adapter::cst::PARENT),
254                    at_depth(ast, from, base, crate::adapter::cst::PARENT, depth + 1)?,
255                ));
256            }
257            for member in ast.members_of(type_) {
258                let Ok(member_node) = ast.member(*member) else {
259                    continue;
260                };
261                let member_type = member_node
262                    .type_
263                    .resolved()
264                    .ok_or_else(|| refuse("member with an unresolved type"))?;
265                // §7.4.1.2.3: an `@optional` member's default is the
266                // absence and not Table 9's zero — the field is an
267                // `Option<T>` and takes `None`. Reading the member's own
268                // default here rather than its type's is what carries
269                // that: going straight to the type produced `flag: 0`
270                // for an `Option<u8>` field, which does not compile.
271                let value = if member_node.facets.is_optional() {
272                    rust::Expr::Path(Path::single("None"))
273                } else {
274                    let value = match member_node
275                        .facets
276                        .xtypes
277                        .as_ref()
278                        .and_then(|x| x.default_value)
279                    {
280                        Some(expr) => explicit(ast, expr, member_type, &member_node.name)?,
281                        None => at_depth(ast, from, member_type, &member_node.name, depth + 1)?,
282                    };
283                    crate::adapter::cst::external_value(ast, member_node, value)
284                };
285                fields.push((Ident::new(&rust::escape_keyword(&member_node.name)), value));
286            }
287            rust::Expr::StructLit {
288                path: crate::adapter::cst::relative_path(ast, from, node)
289                    .map_err(|_| refuse("a struct the model has no module for"))?,
290                fields,
291                base: None,
292            }
293        }
294        // "A union with the discriminator set to the default value for
295        // the discriminator type. If this selects a branch, then the
296        // selected member is also set to the default value for the
297        // member type."
298        //
299        // The variant that branch is, with the discriminator ahead of
300        // the member where the variant carries it; the variant that
301        // holds the discriminator alone where it selects no branch
302        // (`crate::rust_union`).
303        TypeKind::Union { .. } => {
304            let shape = crate::rust_union::shape(ast, type_).map_err(refuse)?;
305            let selector = discriminator_default_value(ast, shape.discriminator)
306                .ok_or_else(|| refuse("union whose discriminator has no default value"))?;
307            let discriminator_value =
308                crate::rust_union::label_literal(ast, from, shape.kind, selector)
309                    .map_err(refuse)?
310                    .expr();
311            let path = crate::adapter::cst::relative_path(ast, from, node)
312                .map_err(|_| refuse("a union the model has no module for"))?;
313            let mut segments: Vec<String> = path
314                .segments()
315                .iter()
316                .map(|s| s.as_str().to_string())
317                .collect();
318            let selected = shape
319                .cases
320                .iter()
321                .find(|case| case.labels.contains(&selector))
322                .or_else(|| shape.cases.iter().find(|case| case.is_default));
323            let args = match selected {
324                Some(case) => {
325                    let member = case.member;
326                    let member_type = member
327                        .type_
328                        .resolved()
329                        .ok_or_else(|| refuse("union case with an unresolved type"))?;
330                    segments.push(case.variant.clone());
331                    let mut args = Vec::new();
332                    if case.carries_discriminator() {
333                        args.push(discriminator_value);
334                    }
335                    args.push(crate::adapter::cst::external_value(
336                        ast,
337                        member,
338                        at_depth(ast, from, member_type, &member.name, depth + 1)?,
339                    ));
340                    args
341                }
342                None => {
343                    segments.push(crate::rust_union::NO_MEMBER.to_string());
344                    vec![discriminator_value]
345                }
346            };
347            let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
348            rust::Expr::Call {
349                callee: Box::new(rust::Expr::Path(Path::new(&borrowed))),
350                args,
351            }
352        }
353        other => {
354            let _ = other;
355            return Err(refuse("type with no default value in this column"));
356        }
357    })
358}
359
360/// Table 9's primitive rows as Rust expressions.
361fn primitive_default(kind: PrimitiveKind) -> rust::Expr {
362    match kind {
363        PrimitiveKind::Boolean => rust::Expr::Literal(Literal::Bool(false)),
364        // `'\0'`, which is what a `char` field holds.
365        PrimitiveKind::Char | PrimitiveKind::WChar => rust::Expr::Literal(Literal::Char('\0')),
366        PrimitiveKind::Float32 => rust::Expr::Literal(Literal::Float(String::from("0.0f32"))),
367        PrimitiveKind::Float64 => rust::Expr::Literal(Literal::Float(String::from("0.0f64"))),
368        PrimitiveKind::Float128 => rust::Expr::Path(Path::new(&["Float128", "ZERO"])),
369        PrimitiveKind::Octet
370        | PrimitiveKind::UInt8
371        | PrimitiveKind::Int8
372        | PrimitiveKind::Int16
373        | PrimitiveKind::UInt16
374        | PrimitiveKind::Int32
375        | PrimitiveKind::UInt32
376        | PrimitiveKind::Int64
377        | PrimitiveKind::UInt64 => rust::Expr::Literal(Literal::int(0)),
378    }
379}
380
381/// The enumerator a default takes, as its Rust variant name.
382fn default_enumerator(ast: &Ast, type_: Id<TypeNode>) -> Option<String> {
383    let mut first = None;
384    for enumerator in ast.enumerators_of(type_) {
385        let Ok(node) = ast.enumerator(*enumerator) else {
386            continue;
387        };
388        let variant = rust::escape_keyword(&node.name);
389        if node
390            .facets
391            .xtypes
392            .as_ref()
393            .is_some_and(|x| x.is_default_literal)
394        {
395            return Some(variant);
396        }
397        if first.is_none() {
398            first = Some(variant);
399        }
400    }
401    first
402}
403
404/// An explicit `@default` as a Rust expression of the member's type.
405fn explicit(
406    ast: &Ast,
407    expr: Id<zerodds_ast::ConstExprNode>,
408    type_: Id<TypeNode>,
409    member_name: &str,
410) -> Result<rust::Expr, EncodeError> {
411    let refuse = |kind: &'static str| EncodeError::NoWriter {
412        member: member_name.to_string(),
413        kind,
414    };
415    let node = ast
416        .expr(expr)
417        .map_err(|_| refuse("a `@default` this frontend cannot follow"))?;
418    let resolved = resolve_alias(ast, type_);
419    Ok(match &node.expr {
420        ConstExpr::Literal(AstLiteral::Boolean(flag)) => rust::Expr::Literal(Literal::Bool(*flag)),
421        ConstExpr::Literal(AstLiteral::Char(ch)) => rust::Expr::Literal(Literal::Char(*ch)),
422        ConstExpr::Literal(AstLiteral::WChar(code)) => {
423            let ch = char::from_u32(*code)
424                .ok_or_else(|| refuse("a `@default` wide character that is not a code point"))?;
425            rust::Expr::Literal(Literal::Char(ch))
426        }
427        ConstExpr::Literal(AstLiteral::String(text)) => rust::Expr::MethodCall {
428            receiver: Box::new(rust::Expr::Literal(Literal::string(text))),
429            method: Ident::new("to_string"),
430            args: Vec::new(),
431        },
432        ConstExpr::Literal(AstLiteral::WString(codes)) => {
433            let mut text = String::new();
434            for code in codes {
435                let ch = char::from_u32(*code)
436                    .ok_or_else(|| refuse("a `@default` wide string that is not code points"))?;
437                text.push(ch);
438            }
439            rust::Expr::MethodCall {
440                receiver: Box::new(rust::Expr::Literal(Literal::string(&text))),
441                method: Ident::new("to_string"),
442                args: Vec::new(),
443            }
444        }
445        ConstExpr::Literal(AstLiteral::Float {
446            mantissa,
447            exponent: None,
448        }) => rust::Expr::Literal(Literal::Float(mantissa.clone())),
449        ConstExpr::Literal(AstLiteral::Float {
450            mantissa,
451            exponent: Some(exponent),
452        }) => rust::Expr::Literal(Literal::Float(format!("{mantissa}e{exponent}"))),
453        ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => rust::Expr::MethodCall {
454            receiver: Box::new(rust::Expr::Literal(Literal::string(&fixed_text(
455                digits, *scale,
456            )))),
457            method: Ident::new("to_string"),
458            args: Vec::new(),
459        },
460        ConstExpr::Literal(AstLiteral::Enumerator(reference)) => {
461            let target = reference
462                .resolved()
463                .ok_or_else(|| refuse("a `@default` enumerator that is unresolved"))?;
464            let enumerator = ast
465                .enumerator(target)
466                .map_err(|_| refuse("a `@default` enumerator this frontend cannot follow"))?;
467            let owner = ast
468                .type_(resolved)
469                .map_err(|_| refuse("a `@default` whose member type is stale"))?;
470            rust::Expr::Path(Path::new(&[
471                &owner.name,
472                &rust::escape_keyword(&enumerator.name),
473            ]))
474        }
475        // An integer literal, a named constant or arithmetic over them,
476        // folded the way a bound or a case label is.
477        _ => {
478            let value = ast
479                .eval_integral(expr)
480                .map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
481            rust::Expr::Literal(Literal::int(value))
482        }
483    })
484}
485
486fn call_path(segments: &[&str]) -> rust::Expr {
487    rust::Expr::Call {
488        callee: Box::new(rust::Expr::Path(Path::new(segments))),
489        args: Vec::new(),
490    }
491}