zerodds_lang_rules/encode.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Encoder and decoder bodies, built from the layout plan. Spec §5, §6.2.
4//!
5//! The body of an `encode` is a language CST — statements, calls, casts —
6//! not a template with holes for names. A template would put the
7//! per-construct decisions back where no diff of two languages can see
8//! them, one hole at a time, which is what this rebuild took them out
9//! of.
10//!
11//! Every framing decision is read from `zerodds-ast-layout`, never
12//! re-derived here. The plan is where the length-code policy and the
13//! XCDR2 alignment rule live; a second derivation would be a second
14//! place to get them wrong.
15
16use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TryConstruct, TypeKind, TypeNode};
17use zerodds_ast_core::{Id, NodeId};
18use zerodds_ast_layout::key::{
19 ElementPadding, FixedDigitCount, LayoutKey, PlCdr1HeaderPolicy, Purpose, WcharPosition,
20 WirePosition, WstringCodeUnits, WstringLengthCounts, WstringLengthPolicy, WstringTerminator,
21 WstringUnitOrder, WstringUnitWidth,
22};
23use zerodds_ast_layout::plan::{
24 Extent, LayoutError, MemberHeader, MemberPlan, ScalarWrite, TypeLayout,
25};
26use zerodds_ast_layout::xcdr::DepCollector;
27use zerodds_lang_cst::common::{Doc, Ident, Literal, Path, Visibility};
28use zerodds_lang_cst::rust;
29
30/// Why an encoder could not be built.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum EncodeError {
33 /// The layout could not be planned, with the plan's own reason.
34 NoLayout {
35 /// What the layout crate said.
36 detail: String,
37 },
38 /// The specification itself does not admit the type, in the
39 /// planner's own words and by its clause.
40 ///
41 /// Kept apart from [`Self::NoLayout`] because the two answer
42 /// different questions and `docs/specs/zerodds-verdicts-1.0.md`
43 /// gives them different exit codes: a gap is "a tool of **ours**
44 /// has not written it" (exit 6, `harness-gap`), and this is "it was
45 /// asked and said no" (exit 1, `refused`). Reporting a clause of
46 /// IDL 4.2 as a gap of this tree states something about this tree
47 /// that is not true.
48 ///
49 /// The distinction was a string until 2026-08-26, and twelve
50 /// generators each classified the same refusal on their own:
51 /// measured over `recursive-array`, ada and d answered exit 3, zig
52 /// answered 1, and twelve answered 6.
53 SpecRefuses {
54 /// The clause and what it refuses, from the planner.
55 detail: String,
56 },
57 /// A member's type has no writer.
58 ///
59 /// Reported rather than skipped: a member left out of an encoder is
60 /// a wire format that silently disagrees with its own plan.
61 NoWriter {
62 /// The member.
63 member: String,
64 /// What kind of type it is.
65 kind: &'static str,
66 },
67 /// An enumerator's wire value could not be computed.
68 ///
69 /// Reported rather than skipped, for the same reason as
70 /// [`Self::NoWriter`] and with a sharper edge. The C encoder's
71 /// enumeration chain initialises its holder to the **declared**
72 /// value and lets each arm overwrite it, so an enumerator that
73 /// reaches no arm is not absent from the stream — it is written
74 /// with the declared integer. Under
75 /// `zerodds_ast_layout::EnumeratorValue::Ordinal` that is a
76 /// different integer from the one every other column writes, and
77 /// nothing at run time would say so.
78 NoEnumeratorValue {
79 /// The enumeration the enumerator belongs to.
80 enumeration: String,
81 /// The enumerator.
82 enumerator: String,
83 /// Why its wire value could not be computed.
84 detail: &'static str,
85 },
86 /// A member's PL_CDR1 header form is not one the sample's flag can
87 /// express.
88 ///
89 /// Under a [per-sample](`PlCdr1HeaderPolicy::decided_per_sample`)
90 /// policy the header is chosen at run time, and the choice is read
91 /// by planning the type under the two policies the flag stands
92 /// between — [`PlCdr1HeaderPolicy::Specification`] and
93 /// [`PlCdr1HeaderPolicy::ExtendedForConstructed`]. A member whose
94 /// two plans differ in `extended` alone is one the flag decides; a
95 /// member whose two plans differ in anything else is a case where
96 /// `zd_extended` is not the whole of the answer, and picking one of
97 /// the two plans would write bytes nothing measured.
98 AmbiguousHeader {
99 /// The member the two plans disagree about.
100 member: String,
101 /// What each plan said, so the difference can be read.
102 detail: String,
103 },
104}
105
106impl EncodeError {
107 /// Whether the specification refused the type, rather than this tree
108 /// not having written a writer for it.
109 ///
110 /// The one place a generator asks the question, so that sixteen of
111 /// them do not each answer it from the error's text.
112 #[must_use]
113 pub const fn is_spec_refusal(&self) -> bool {
114 matches!(self, Self::SpecRefuses { .. })
115 }
116}
117
118/// Refuses a layout key whose axes the **non-Rust** generated codecs do
119/// not carry.
120///
121/// [`zerodds_ast_layout::Dialect::Hdds`] selects three writer axes the
122/// planner and `zerodds-wire-codec` carry: collection elements packed
123/// without alignment, a length in front of each element of a sequence of a
124/// non-`@final` type, and a `wstring` in UTF-8. The Rust codec carries all
125/// three — see [`axes_the_rust_codec_carries`], which [`rust_codec`] and
126/// [`rust_codec_pair`] call instead. A generated codec of another column
127/// asks [`axes_the_codecs_carry_for`], which passes for a target whose
128/// runtime carries them and refuses the rest: generating their default
129/// form under them would write the specification's octets where the key
130/// asked for another implementation's. The refusal is a gap of this tree
131/// (`NoLayout`, exit 6), named axis by axis; this function is it for a
132/// caller that names no target.
133///
134/// # Errors
135/// [`EncodeError::NoLayout`] naming every such axis the key selects.
136pub fn axes_the_codecs_carry(key: LayoutKey) -> Result<(), EncodeError> {
137 axes_the_codecs_carry_named(key, false)
138}
139
140/// [`axes_the_codecs_carry`] for a generated codec, which carries the
141/// axes its runtime does.
142///
143/// A target whose runtime has the packed-element origin, the element
144/// length in DHEADER form and a UTF-8 `wstring` — the three axes
145/// [`zerodds_ast_layout::Dialect::Hdds`] selects — answers
146/// [`crate::emit::CodecTarget::carries_hdds_axes`] `true` and passes;
147/// one that has not been given them is refused with the axes named.
148///
149/// # Errors
150/// [`EncodeError::NoLayout`] naming every axis the key selects that the
151/// target does not carry.
152pub fn axes_the_codecs_carry_for<T: crate::emit::CodecTarget>(
153 key: LayoutKey,
154) -> Result<(), EncodeError> {
155 axes_the_codecs_carry_named(key, T::carries_hdds_axes())
156}
157
158fn axes_the_codecs_carry_named(key: LayoutKey, carries: bool) -> Result<(), EncodeError> {
159 if carries {
160 return Ok(());
161 }
162 let mut missing: Vec<&str> = Vec::new();
163 if !key.element_padding.is_default() {
164 missing.push(key.element_padding.as_str());
165 }
166 if !key.sequence_element_delimiter.is_default() {
167 missing.push(key.sequence_element_delimiter.as_str());
168 }
169 if !key.wstring_code_units.is_default() {
170 missing.push(key.wstring_code_units.as_str());
171 }
172 if missing.is_empty() {
173 Ok(())
174 } else {
175 Err(EncodeError::NoLayout {
176 detail: format!(
177 "the generated codecs do not carry {} yet",
178 missing.join(", ")
179 ),
180 })
181 }
182}
183
184/// The same check for the Rust target, which carries every HDDS axis.
185///
186/// [`ElementPadding::Packed`],
187/// [`zerodds_ast_layout::SequenceElementDelimiter::BeforeEachNonFinalElement`]
188/// and [`WstringCodeUnits::Utf8`] are emitted by [`rust_codec`] and
189/// [`rust_codec_pair`] exactly as `zerodds-wire-codec` writes them, so
190/// this target has no axis left to refuse. The function exists so that the
191/// two targets ask one question through one name rather than the Rust
192/// path skipping the check by having no call at all.
193///
194/// # Errors
195/// Nothing today; the signature matches [`axes_the_codecs_carry_for`],
196/// the check of the other columns.
197pub fn axes_the_rust_codec_carries(_key: LayoutKey) -> Result<(), EncodeError> {
198 Ok(())
199}
200
201impl core::fmt::Display for EncodeError {
202 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203 match self {
204 Self::NoLayout { detail } => write!(f, "the layout could not be planned: {detail}"),
205 Self::SpecRefuses { detail } => write!(f, "{detail}"),
206 Self::NoWriter { member, kind } => {
207 write!(f, "`{member}` is a {kind}, which has no writer yet")
208 }
209 Self::NoEnumeratorValue {
210 enumeration,
211 enumerator,
212 detail,
213 } => write!(
214 f,
215 "`{enumeration}::{enumerator}` has no wire value: {detail}"
216 ),
217 Self::AmbiguousHeader { member, detail } => write!(
218 f,
219 "`{member}` has no single PL_CDR1 header form to choose between: {detail}"
220 ),
221 }
222 }
223}
224
225impl core::error::Error for EncodeError {}
226
227/// The name the **second** codec pair on a type carries.
228///
229/// A Rust type may hold only one inherent function per name, so a type
230/// that carries both of its XCDR streams needs two names. The XCDR
231/// version is what earns one: XTypes 1.3 §7.4 frames the two versions
232/// differently — no DHEADER under version 1, PID headers instead of
233/// EMHEADERs for `@mutable`, `MAXALIGN` eight instead of four — so the
234/// two bodies are different code.
235///
236/// The byte order does not earn one. Nothing in this emitter reads
237/// [`LayoutKey::endianness`]: the order is the `Writer`'s and the
238/// `Reader`'s, so one body serves both and a suffix per order would
239/// duplicate identical code.
240///
241/// [`rust_codec`] keeps the bare names whatever encoding it is given, so
242/// every consumer that generates one codec per type is unaffected by
243/// this existing; only [`rust_codec_pair`], which exists to put a second
244/// one beside it, spells them out.
245pub const SECONDARY_SUFFIX: &str = "_xcdr1";
246
247/// The parameter that carries a sample's RTI PL_CDR1 step decision down
248/// to a nested type's encoder.
249///
250/// RTI frames a sample under XCDR1 with the short parameter header until
251/// its type's maximum serialized size passes a step, and from there
252/// gives `PID_EXTENDED` to every member whose type is not a primitive or
253/// an enumeration (XTypes 1.3 §7.4.1.2, in RTI's measured reading of it).
254/// The step is a property of the **sample's** type and not of the type
255/// that declares a member, so a generated type cannot bake the answer in:
256/// the same nested `@mutable` type is framed short inside a small sample
257/// and extended inside a large one. The flag is threaded at run time; the
258/// sample's root supplies it through [`sample_past_step`], which asks the
259/// same crossing the reflective encoder asks.
260const EXTENDED_PARAM: &str = "zd_extended";
261
262/// The suffix the encoder **body** takes under a [per-sample](`per_sample`)
263/// plan.
264///
265/// A Rust type may hold only one inherent function per name, so a type
266/// that needs both a body taking the flag and a public entry does so the
267/// same way [`SECONDARY_SUFFIX`] splits the two encodings.
268const EXTENDED_SUFFIX: &str = "_zd_extended";
269
270/// Whether the plan's PL_CDR1 header form is decided per sample, so every
271/// generated encoder takes [`EXTENDED_PARAM`] and threads it to the types
272/// it calls.
273fn per_sample(key: LayoutKey) -> bool {
274 key.pl_cdr1_headers.decided_per_sample() && key.encoding.is_v1()
275}
276
277/// Whether a sample of `root` passes RTI's short-header step, the
278/// generation-time half of the per-sample decision.
279///
280/// The same count [`LayoutKey::for_sample`] makes, asked through it
281/// rather than through `exceeds_short_length` so that the generated
282/// entry and the reflective path read one answer.
283fn sample_past_step(ast: &Ast, root: Id<TypeNode>, key: LayoutKey) -> Result<bool, EncodeError> {
284 let resolved = key.for_sample(ast, root).map_err(plan_refusal)?;
285 Ok(resolved.pl_cdr1_headers == PlCdr1HeaderPolicy::ExtendedForConstructed)
286}
287
288/// The name of the encoder **body** for a codec suffix: the method that
289/// takes [`EXTENDED_PARAM`], or the public entry itself when the plan's
290/// header form is fixed.
291///
292/// Under [`per_sample`] one type needs two functions. The body carries
293/// the sample's decision as a parameter, so one type's codec can frame
294/// its constructed members short in a small sample and extended in a
295/// large one; the public entry bakes the decision in for the sample whose
296/// root this type is. Without the split, the nested `Inner` of
297/// `tools/idlc/tests/dialect_byte_equality.rs`'s `rti_step_cases` could
298/// not serve both its samples.
299fn body_name(codec: &str, key: LayoutKey) -> String {
300 if per_sample(key) {
301 framed_body(codec)
302 } else {
303 format!("encode{codec}")
304 }
305}
306
307/// The name of an encoder body that takes the sample's decision, for a
308/// nested call that carries `extended`.
309fn nested_method(codec: &str, extended: Extended) -> String {
310 match extended {
311 Extended::Fixed => format!("encode{codec}"),
312 Extended::Inherited | Extended::Constant(_) => framed_body(codec),
313 }
314}
315
316/// [`nested_method`]'s name on the other side of the split.
317fn framed_body(codec: &str) -> String {
318 format!("encode{codec}{EXTENDED_SUFFIX}")
319}
320
321/// The sample's RTI PL_CDR1 step decision, as a nested type's call
322/// carries it.
323///
324/// A data sample's body passes its own flag down, because the sample's
325/// root resolved it; a key holder passes [`Self::Constant`] `false`. A
326/// key holder keeps the short form whatever the sample: the choice of
327/// `PID_EXTENDED` for a constructed member is a data-sample decision —
328/// [`LayoutKey::for_sample`] is asked for one, and
329/// `zerodds_ast_layout::xcdr::extended_as_constructed` refuses the key
330/// purpose for the same reason — and the reflective planner keeps a key
331/// holder short. The data path resolves the choice once at the root and
332/// hands it down; the holder has no root decision to hand down and states
333/// the constant.
334///
335/// `pub(crate)`: it is a field of [`MemberWrite::Nested`], which is.
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337pub(crate) enum Extended {
338 /// The plan fixes the header form: the call names the public entry,
339 /// which takes no flag. Every plan outside [`per_sample`].
340 Fixed,
341 /// Hand the enclosing body's flag to the nested call.
342 Inherited,
343 /// Pass this constant instead.
344 Constant(bool),
345}
346
347impl Extended {
348 /// The answer for a codec written under `key`.
349 ///
350 /// The key holder's constant is what `crate::emit::encode`'s
351 /// `nested_call_with` passes for the same reason.
352 #[must_use]
353 fn of(key: LayoutKey) -> Self {
354 if !per_sample(key) {
355 Self::Fixed
356 } else if key.purpose == Purpose::Data {
357 Self::Inherited
358 } else {
359 Self::Constant(false)
360 }
361 }
362
363 /// The flag argument the nested call passes, if it passes one.
364 #[must_use]
365 fn argument(self) -> Option<rust::Expr> {
366 match self {
367 Self::Fixed => None,
368 Self::Inherited => Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
369 Self::Constant(value) => Some(rust::Expr::Literal(Literal::Bool(value))),
370 }
371 }
372}
373
374/// Which of one type's members take the sample's step decision at run
375/// time.
376///
377/// A member is one exactly when the two policies a per-sample plan is
378/// between disagree about its `extended` bit:
379/// [`PlCdr1HeaderPolicy::Specification`] frames a sample that stays
380/// within the short form's length,
381/// [`PlCdr1HeaderPolicy::ExtendedForConstructed`] one that can exceed it.
382/// Keyed by member identity and not by position, because a union finds
383/// its case's plan in the layout by identity.
384struct StepHeaders {
385 /// Whether the plan decides the form per sample at all, and the
386 /// entries below mean anything.
387 per_sample: bool,
388 /// One entry per member of the layout: does the flag choose its
389 /// header form?
390 members: Vec<(NodeId, bool)>,
391 /// The same for a `@mutable` union's discriminator, which rules (27)
392 /// and (28) frame as an `MMEMBER` at member id 0 and which is
393 /// therefore a header the flag can choose too.
394 discriminator: bool,
395}
396
397impl StepHeaders {
398 /// No member takes the flag, which is what every plan outside
399 /// [`per_sample`] plans.
400 fn none() -> Self {
401 Self {
402 per_sample: false,
403 members: Vec::new(),
404 discriminator: false,
405 }
406 }
407
408 /// Whether the flag chooses `plan`'s member's header form.
409 ///
410 /// # Errors
411 /// [`EncodeError::NoLayout`] for a member this set does not name —
412 /// a plan that lost one would leave a member whose header nothing
413 /// wrote.
414 fn member(&self, plan: &MemberPlan, name: &str) -> Result<bool, EncodeError> {
415 if !self.per_sample {
416 return Ok(false);
417 }
418 self.members
419 .iter()
420 .find(|(id, _)| *id == plan.member)
421 .map(|(_, chosen)| *chosen)
422 .ok_or_else(|| EncodeError::NoLayout {
423 detail: format!(
424 "the PL_CDR1 plans for a sample past RTI's step name no member `{name}`, \
425 and its header form would be left unwritten"
426 ),
427 })
428 }
429}
430
431/// The plan `type_` takes under one PL_CDR1 header policy.
432///
433/// # Errors
434/// [`EncodeError`] when the type cannot be planned under that policy.
435fn plan_under(
436 ast: &Ast,
437 type_: Id<TypeNode>,
438 key: LayoutKey,
439 policy: PlCdr1HeaderPolicy,
440) -> Result<TypeLayout, EncodeError> {
441 zerodds_ast_layout::xcdr::plan(
442 ast,
443 type_,
444 key.with_pl_cdr1_headers(policy),
445 &mut DepCollector::default(),
446 )
447 .map_err(plan_refusal)
448}
449
450/// Whether the flag chooses this member's header form, from the two plans
451/// a per-sample decision stands between.
452///
453/// `framed` is the plan the codec is written from — the unresolved key's,
454/// which frames short because the choice belongs to the sample and not to
455/// the type — `below` the one a sample under the step gets and `past` the
456/// one a sample over it gets. Where `below` and `past` agree, the form is
457/// fixed and nothing is threaded; where they differ in `extended` alone,
458/// the flag picks; anything else is a case `zd_extended` cannot express
459/// and is refused rather than answered with one of the two plans.
460///
461/// `framed` is checked against `below` too. The unresolved policy is
462/// neither of the two, so a member it framed differently from a small
463/// sample would mean the two are not the ends of one decision.
464///
465/// # Errors
466/// [`EncodeError::AmbiguousHeader`] naming the member when two of the
467/// three plans disagree about more than `extended`.
468fn header_is_chosen(
469 member: &str,
470 framed: MemberHeader,
471 below: MemberHeader,
472 past: MemberHeader,
473) -> Result<bool, EncodeError> {
474 let refuse = || ambiguous(member, framed, below, past);
475 if below == past {
476 if framed == below {
477 return Ok(false);
478 }
479 return Err(refuse());
480 }
481 // `MemberHeader`'s own equality has already settled that the two
482 // differ in nothing but `extended`, so what is left is which way and
483 // which header: both forms of a PL_CDR1 parameter header take the
484 // same branch, `write_pid` below the step and `write_pid_extended`
485 // past it, and nothing else does.
486 match (below, past) {
487 (
488 MemberHeader::PidCdr1 {
489 extended: false, ..
490 },
491 MemberHeader::PidCdr1 { extended: true, .. },
492 )
493 | (
494 MemberHeader::OptionalCdr1 {
495 extended: false, ..
496 },
497 MemberHeader::OptionalCdr1 { extended: true, .. },
498 ) if framed == below => Ok(true),
499 _ => Err(refuse()),
500 }
501}
502
503/// The refusal [`header_is_chosen`] makes, naming all three plans.
504fn ambiguous(
505 member: &str,
506 framed: MemberHeader,
507 below: MemberHeader,
508 past: MemberHeader,
509) -> EncodeError {
510 EncodeError::AmbiguousHeader {
511 member: member.to_string(),
512 detail: format!(
513 "the codec's own plan frames it {framed:?}, a sample within RTI's short-header step \
514 {below:?} and a sample past it {past:?}, and one flag cannot choose between them"
515 ),
516 }
517}
518
519/// Every member of `type_` whose PL_CDR1 header form the sample decides,
520/// under `framed`.
521///
522/// [`StepHeaders::none`] for every plan outside [`per_sample`], so a
523/// dialect whose header form is fixed never pays for two more plans and
524/// generates exactly the code it generated before.
525///
526/// # Errors
527/// [`EncodeError`] when one of the two plans cannot be made, when they do
528/// not name the same members as `framed`, or when they differ in anything
529/// but `extended`.
530fn step_headers(
531 ast: &Ast,
532 type_: Id<TypeNode>,
533 key: LayoutKey,
534 framed: &TypeLayout,
535) -> Result<StepHeaders, EncodeError> {
536 if !per_sample(key) {
537 return Ok(StepHeaders::none());
538 }
539 let name = ast
540 .type_(type_)
541 .map_or_else(|_| "<unresolved>".to_string(), |node| node.name.clone());
542 let below = plan_under(ast, type_, key, PlCdr1HeaderPolicy::Specification)?;
543 let past = plan_under(ast, type_, key, PlCdr1HeaderPolicy::ExtendedForConstructed)?;
544 if below.members.len() != framed.members.len() || past.members.len() != framed.members.len() {
545 return Err(EncodeError::NoLayout {
546 detail: format!(
547 "`{name}`: the PL_CDR1 plans for the two sides of RTI's step name {} and {} \
548 members where the codec's own plan names {}",
549 below.members.len(),
550 past.members.len(),
551 framed.members.len()
552 ),
553 });
554 }
555 let mut members = Vec::with_capacity(framed.members.len());
556 for ((own, below), past) in framed.members.iter().zip(&below.members).zip(&past.members) {
557 let member = member_name(ast, type_, own.member);
558 members.push((
559 own.member,
560 header_is_chosen(&member, own.header, below.header, past.header)?,
561 ));
562 }
563 let discriminator = match (
564 framed.discriminator,
565 below.discriminator,
566 past.discriminator,
567 ) {
568 (None, None, None) => false,
569 (Some(framed), Some(below), Some(past)) => {
570 header_is_chosen(&format!("{name}::discriminator"), framed, below, past)?
571 }
572 _ => {
573 return Err(EncodeError::NoLayout {
574 detail: format!(
575 "`{name}`: the PL_CDR1 plans for the two sides of RTI's step disagree about \
576 whether its discriminator carries a header at all"
577 ),
578 });
579 }
580 };
581 Ok(StepHeaders {
582 per_sample: true,
583 members,
584 discriminator,
585 })
586}
587
588/// The name a member node has inside `type_`, for an error that names it.
589fn member_name(ast: &Ast, type_: Id<TypeNode>, member: NodeId) -> String {
590 ast.all_members_of(type_)
591 .into_iter()
592 .find(|id| ast.member(*id).is_ok_and(|node| node.node_id == member))
593 .and_then(|id| ast.member(id).ok())
594 .map_or_else(|| alloc_format(&member), |node| node.name.clone())
595}
596
597/// Build the `encode` and `decode` functions for one type, with the
598/// `Default` impl and — for a keyed type — the key-holder pair.
599///
600/// This column writes a member's collection where the member is, in the
601/// parent's own body and through a closure, and never hoists it into a
602/// routine named after its element type. So the bound-in-the-name
603/// defect the other columns carry — `sequence<long>` and
604/// `sequence<long, 4>` sharing one hoisted routine, the first declared
605/// deciding whether the check exists — cannot reach here: every bound
606/// travels as an argument at the member's own call site
607/// (`write_string_bounded`, `write_sequence_bounded`, and the `fit_…`
608/// pair on the read side). Measured on
609/// `@final struct S { sequence<long> b; sequence<long, 4> a; };` in
610/// both declaration orders: both members keep exactly the bound their
611/// declaration gives them. `crates/lang-rules/src/emit/zig.rs`'s
612/// `bound_tag` is the fix in the columns that do hoist.
613///
614/// # Errors
615/// [`EncodeError`] when the layout cannot be planned or a member has no
616/// writer — never a body that leaves a member out.
617pub fn rust_codec(
618 ast: &Ast,
619 type_: Id<TypeNode>,
620 key: LayoutKey,
621) -> Result<Vec<rust::Item>, EncodeError> {
622 codec_items(ast, type_, key, Companions::Emit)
623}
624
625/// Build only the `encode` and `decode` pair for one type.
626///
627/// For a **second** plan over a type that already has one: the `Default`
628/// impl and the key holder do not belong to an encoding and would be
629/// emitted twice under the same name, which Rust refuses. The key holder
630/// in particular is not a per-encoding thing at all — XTypes 1.3 §7.6.8
631/// fixes it as PLAIN_CDR2 big-endian whatever the sample's encoding is —
632/// so a second copy of it would be the same bytes under a name that
633/// suggests otherwise.
634///
635/// # Errors
636/// As [`rust_codec`].
637pub fn rust_codec_pair(
638 ast: &Ast,
639 type_: Id<TypeNode>,
640 key: LayoutKey,
641) -> Result<Vec<rust::Item>, EncodeError> {
642 codec_items(ast, type_, key, Companions::Omit)
643}
644
645/// Whether [`rust_codec`] gives a type of this kind a codec of its own:
646/// an aggregate (structure, union, valuetype), an enumeration, a bitmask
647/// or a bitset. A bitmask's answer is empty, since its Rust form is an
648/// alias of a primitive and its member site writes the holder. Every
649/// other kind — a primitive, a string, a collection, an alias — is
650/// written where it is used and has no `impl` to hang a pair on.
651#[must_use]
652pub const fn has_codec(kind: &TypeKind) -> bool {
653 matches!(
654 kind,
655 TypeKind::Structure { .. }
656 | TypeKind::Union { .. }
657 | TypeKind::Valuetype { .. }
658 | TypeKind::Enumeration { .. }
659 | TypeKind::Bitmask { .. }
660 | TypeKind::Bitset { .. }
661 )
662}
663
664/// Whether a codec run also emits what does not belong to an encoding.
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
666enum Companions {
667 /// The `Default` impl and the key holder come with the codec.
668 Emit,
669 /// Only the codec pair.
670 Omit,
671}
672
673fn codec_items(
674 ast: &Ast,
675 type_: Id<TypeNode>,
676 key: LayoutKey,
677 companions: Companions,
678) -> Result<Vec<rust::Item>, EncodeError> {
679 axes_the_rust_codec_carries(key)?;
680 // Which set of names this run writes, and — through
681 // `MemberWrite::Nested` — which set it calls on a member. The two
682 // have to be the same set: a nested call that named the XCDR2
683 // encoder from inside the XCDR1 body would frame the member under
684 // version 2 in a version 1 stream, and the sample would decode into
685 // wrong values with nothing reporting it.
686 let codec = match companions {
687 Companions::Emit => "",
688 Companions::Omit => SECONDARY_SUFFIX,
689 };
690 // Asked for a kind with no codec of its own, the generic path below
691 // emitted `impl long { … }` or `impl Count { … }` for a
692 // `typedef long Count;` — not Rust (E0390), and an `encode` that wrote
693 // nothing. Every caller filtered the kinds it asked for; the refusal
694 // is here so none has to.
695 let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
696 detail: alloc_format(&error),
697 })?;
698 if !has_codec(&node.kind) {
699 return Err(EncodeError::NoWriter {
700 member: node.name.clone(),
701 kind: "a type with no codec of its own; it is written where it is used",
702 });
703 }
704 let mut deps = DepCollector::default();
705 let layout =
706 zerodds_ast_layout::xcdr::plan(ast, type_, key, &mut deps).map_err(plan_refusal)?;
707 // Which of this type's members the sample's PL_CDR1 step decides. One
708 // set per codec, shared by every encoder below, because they are all
709 // the same type under the same plan.
710 let steps = step_headers(ast, type_, key, &layout)?;
711
712 // An enumeration has no layout **members**, and the generic path
713 // below walks exactly those. It therefore produced
714 //
715 // pub fn encode(&self, w: &mut Writer) -> Result<(), WireError> { Ok(()) }
716 // pub fn decode(r: &mut Reader) -> Result<Self, WireError> { Ok(Self { }) }
717 //
718 // — an encoder that puts zero octets on the wire for a value that
719 // has a wire form, and a decoder that is not Rust at all: `Self { }`
720 // is a struct literal and the type is an enum. It was invisible
721 // because an enumeration is normally reached as a *member*, where
722 // `MemberWrite::Enumeration` writes its holder, and nothing in the
723 // tree asked an enumeration for its own codec until a CORBA
724 // operation took one as a parameter — an argument has no enclosing
725 // aggregate to be a member of.
726 if let TypeKind::Enumeration { enumerators } = &node.kind {
727 let mut items = enumeration_encode_fns(ast, type_, enumerators, key, codec)?;
728 items.push(enumeration_decode_fn(ast, type_, enumerators, key, codec)?);
729 return Ok(vec![rust::Item::Impl(rust::Impl {
730 doc: Doc::none(),
731 attrs: Vec::new(),
732 generics: Vec::new(),
733 trait_: None,
734 self_type: rust::Type::named(Path::single(&node.name)),
735 items,
736 })]);
737 }
738
739 // A bitmask has **no** codec of its own, and cannot have one.
740 // `crate::adapter::cst` materialises it as `pub type Flags = u16;`,
741 // and `impl Flags` on a type alias to a primitive is E0390 —
742 // "cannot define inherent `impl` for primitive types". The generic
743 // path below emitted exactly that, with an `encode` that put zero
744 // octets on the wire and a `decode` returning `Ok(Self { })`: a
745 // file that did not compile, so a model with a `bitmask` in it had
746 // no Rust output at all. `MemberWrite::Bitmask` writes the holder
747 // at the member site instead, which is why nothing has to be
748 // emitted here and why an empty answer is not a member left out.
749 //
750 // The name stays in `generate::rust_codec_pairs`' list because the
751 // other twenty columns do give a bitmask a codec function; this is
752 // the one column whose language mapping has nowhere to put it.
753 if matches!(node.kind, TypeKind::Bitmask { .. }) {
754 return Ok(Vec::new());
755 }
756
757 // A bitset has a codec and had an empty one. It materialises as a
758 // struct with one field per named bitfield, so there is an `impl`
759 // to hang the pair on — but a bitset's layout has no **members**,
760 // and the generic path walks exactly those. What came out was an
761 // `encode` that wrote nothing at all for a type with a wire form
762 // and a `decode` returning `Ok(Self { })` for a struct with two
763 // fields. The same hole as the enumeration's above, and the same
764 // fix: the construct's own pair.
765 if matches!(node.kind, TypeKind::Bitset { .. }) {
766 let mut items = bitset_encode_fns(ast, type_, &layout, key, codec)?;
767 items.push(bitset_decode_fn(ast, type_, &layout, codec)?);
768 let mut out = vec![rust::Item::Impl(rust::Impl {
769 doc: Doc::none(),
770 attrs: Vec::new(),
771 generics: Vec::new(),
772 trait_: None,
773 self_type: rust::Type::named(Path::single(&node.name)),
774 items,
775 })];
776 if companions == Companions::Emit
777 && let Some(default) = default_impl(ast, type_)?
778 {
779 out.push(default);
780 }
781 return Ok(out);
782 }
783
784 // A union has the same shape of hole as an enumeration, one level
785 // up: `crate::adapter::cst` materialises it as a Rust enum with one
786 // payload variant per case, and the generic path below builds a
787 // struct literal over the layout's members. It produced an `encode`
788 // that wrote nothing and a `decode` that returned `Ok(Self { })`.
789 if let TypeKind::Union {
790 discriminator,
791 cases,
792 ..
793 } = &node.kind
794 {
795 let items = union_codec(
796 ast,
797 type_,
798 &layout,
799 &steps,
800 discriminator,
801 cases,
802 key,
803 codec,
804 )?;
805 let mut out = vec![rust::Item::Impl(rust::Impl {
806 doc: Doc::none(),
807 attrs: Vec::new(),
808 generics: Vec::new(),
809 trait_: None,
810 self_type: rust::Type::named(Path::single(&node.name)),
811 items,
812 })];
813 // A union returns from here and not through the tail, so its
814 // `impl Default` has to be added on this path too. It was not,
815 // and the omission was silent: the type simply had no `Default`,
816 // which shows up wherever a caller writes `Default::default()`
817 // for a member of one.
818 if companions == Companions::Emit
819 && let Some(default) = default_impl(ast, type_)?
820 {
821 out.push(default);
822 }
823 return Ok(out);
824 }
825
826 let mut items = encode_fns(ast, type_, &layout, &steps, key, codec)?;
827 items.push(decode_fn(ast, type_, &layout, key, codec)?);
828 if companions == Companions::Emit {
829 items.append(&mut key_holder_fns(ast, type_, key)?);
830 }
831
832 let mut out = vec![rust::Item::Impl(rust::Impl {
833 doc: Doc::none(),
834 attrs: Vec::new(),
835 generics: Vec::new(),
836 trait_: None,
837 self_type: rust::Type::named(Path::single(&node.name)),
838 items,
839 })];
840 // The specification's default values, as an `impl Default`. A member
841 // that has none — a type this column cannot give a default for —
842 // leaves the impl out rather than substituting one.
843 if companions == Companions::Emit
844 && let Some(default) = default_impl(ast, type_)?
845 {
846 out.push(default);
847 }
848 Ok(out)
849}
850
851/// `encode` and `decode` for a union. §7.4.3.5 rule (10): the
852/// discriminator, then the member the discriminator selects — and around
853/// them the framing the plan gives the union's extensibility: rule (11)'s
854/// DHEADER for an `@appendable` one, rules (27) and (28)'s member headers
855/// for a `@mutable` one, the discriminator under member id 0. This column
856/// wrote rule (10) for every union, so an `@appendable` union went out
857/// without the DHEADER the C, Java and C# columns and every vendor write
858/// (measured: `idl-conformance/_interop` `mapenum`, a `sequence<Sel>` of
859/// an `@appendable` union — 44 octets here, 48 in C).
860///
861/// The Rust form is `crate::adapter::cst`'s — an enum with one payload
862/// variant per case — so `encode` matches the variant and writes the
863/// label that selects it, and `decode` reads the discriminator and
864/// matches its value back.
865///
866/// A discriminator value that selects no case and no `default:` leaves
867/// the member absent (§7.4.3.5); it is read into the variant
868/// `crate::rust_union::NO_MEMBER`, which holds the discriminator alone.
869/// A variant that carries its discriminator is checked before it is
870/// written: a pairing the type does not allow is refused.
871#[allow(clippy::too_many_arguments)]
872fn union_codec(
873 ast: &Ast,
874 type_: Id<TypeNode>,
875 layout: &TypeLayout,
876 steps: &StepHeaders,
877 discriminator: &zerodds_ast_core::Ref<TypeNode>,
878 cases: &[Id<zerodds_ast::CaseNode>],
879 key: LayoutKey,
880 codec: &'static str,
881) -> Result<Vec<rust::Item>, EncodeError> {
882 let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
883 detail: alloc_format(&error),
884 })?;
885 let name = node.name.clone();
886 let discriminator = discriminator.resolved().ok_or(EncodeError::NoWriter {
887 member: name.clone(),
888 kind: "a union with an unresolved discriminator",
889 })?;
890 // The discriminator is written as its *value* and not through the
891 // enumeration's own codec: what a union writes here is the label the
892 // selected branch carries, which the emitter already has as an
893 // integer. A member of an enumeration type is the other case and
894 // goes through `<path>::encode` — see the arm in `write_of`.
895 let discriminator_write = match ast.type_(discriminator).map(|d| &d.kind) {
896 Ok(TypeKind::Enumeration { .. }) => MemberWrite::Enumeration {
897 bits: zerodds_ast_layout::enumeration_bit_bound(ast, discriminator, key.enum_holder),
898 },
899 _ => write_of(
900 ast,
901 node.module,
902 discriminator,
903 key,
904 codec,
905 Position {
906 behaviour: zerodds_ast::TryConstruct::Discard,
907 written: None,
908 },
909 ),
910 };
911
912 // The union as this column holds it: which variant carries its
913 // discriminator, whether one holds a discriminator alone, and how a
914 // label is spelled. `crate::rust_union`, shared with the declaration
915 // and the default.
916 let shape = crate::rust_union::shape(ast, type_).map_err(|kind| EncodeError::NoWriter {
917 member: name.clone(),
918 kind,
919 })?;
920 let refuse_label = |_| EncodeError::NoWriter {
921 member: name.clone(),
922 kind: "a union label with no source form in this column",
923 };
924 // A label as the discriminator's *write* takes it: the integer an
925 // enumeration's holder carries, the literal otherwise.
926 let written_label = |value: i128| -> Result<rust::Expr, EncodeError> {
927 Ok(match shape.kind {
928 crate::rust_union::DiscriminatorKind::Enumeration(_) => {
929 rust::Expr::Literal(Literal::int(value))
930 }
931 kind => crate::rust_union::label_literal(ast, node.module, kind, value)
932 .map_err(refuse_label)?
933 .expr(),
934 })
935 };
936 // A label as a pattern over the discriminator a variant carries, and
937 // over the one `decode` reads: an enumeration's is converted to the
938 // enumeration first.
939 // An enumeration discriminator's own name, for a refusal that names it.
940 let enumeration_name = |enumeration: Id<TypeNode>| {
941 ast.type_(enumeration)
942 .map_or_else(|_| name.clone(), |node| node.name.clone())
943 };
944 let label_pattern = |value: i128| -> Result<rust::Pattern, EncodeError> {
945 Ok(
946 crate::rust_union::label_literal(ast, node.module, shape.kind, value)
947 .map_err(refuse_label)?
948 .pattern(),
949 )
950 };
951 let refused =
952 |what: String, sink: &str| rust::Expr::Return(Some(Box::new(refused_err(sink, &what))));
953 // A carried discriminator as a match scrutinee: the binding, which a
954 // `match` on `&self` makes a reference, dereferenced for the copyable
955 // primitives; an enumeration is matched through the reference.
956 let carried_scrutinee = || match shape.kind {
957 crate::rust_union::DiscriminatorKind::Enumeration(_) => {
958 rust::Expr::Path(Path::single("discriminator"))
959 }
960 _ => rust::Expr::Deref(Box::new(rust::Expr::Path(Path::single("discriminator")))),
961 };
962 // A carried discriminator written: an enumeration through its holder
963 // integer, every other kind as itself.
964 let write_carried = || -> Result<rust::Expr, EncodeError> {
965 match shape.kind {
966 crate::rust_union::DiscriminatorKind::Enumeration(enumeration) => {
967 let mut arms = Vec::new();
968 for enumerator in ast.enumerators_of(enumeration) {
969 // The enumerator by name, and the integer the wire
970 // carries for it — which the setting decides.
971 let (_, value) = enumerator_variant_and_value(
972 ast,
973 &enumeration_name(enumeration),
974 *enumerator,
975 key,
976 )?;
977 arms.push(rust::MatchArm {
978 pattern: rust::Pattern::Path(
979 crate::rust_union::enumerator_path_of(
980 ast,
981 node.module,
982 enumeration,
983 *enumerator,
984 )
985 .map_err(refuse_label)?,
986 ),
987 guard: None,
988 body: rust::Expr::Literal(Literal::int(value)),
989 });
990 }
991 write_expr_for(
992 &discriminator_write,
993 rust::Expr::Match {
994 scrutinee: Box::new(rust::Expr::Path(Path::single("discriminator"))),
995 arms,
996 },
997 &name,
998 false,
999 )
1000 }
1001 _ => write_expr_for(
1002 &discriminator_write,
1003 rust::Expr::Path(Path::single("discriminator")),
1004 &name,
1005 true,
1006 ),
1007 }
1008 };
1009 // The check ahead of a carried discriminator's write: one of `own`
1010 // where the variant is a case with several labels; none of every
1011 // case's labels where it is the `default:` case or the variant that
1012 // holds no member. A pairing the type does not allow is refused, not
1013 // written: a peer would read another branch than the one the value
1014 // names.
1015 let check_carried =
1016 |own: Option<&[i128]>, what: String| -> Result<rust::Statement, EncodeError> {
1017 let mut arms = Vec::new();
1018 match own {
1019 Some(labels) => {
1020 for value in labels {
1021 arms.push(rust::MatchArm {
1022 pattern: label_pattern(*value)?,
1023 guard: None,
1024 body: rust::Expr::Block(rust::Block {
1025 statements: Vec::new(),
1026 }),
1027 });
1028 }
1029 arms.push(rust::MatchArm {
1030 pattern: rust::Pattern::Wildcard,
1031 guard: None,
1032 body: refused(what, "w"),
1033 });
1034 }
1035 None => {
1036 for value in shape.all_labels() {
1037 arms.push(rust::MatchArm {
1038 pattern: label_pattern(value)?,
1039 guard: None,
1040 body: refused(what.clone(), "w"),
1041 });
1042 }
1043 arms.push(rust::MatchArm {
1044 pattern: rust::Pattern::Wildcard,
1045 guard: None,
1046 body: rust::Expr::Block(rust::Block {
1047 statements: Vec::new(),
1048 }),
1049 });
1050 }
1051 }
1052 Ok(rust::Statement::Semi(rust::Expr::Match {
1053 scrutinee: Box::new(carried_scrutinee()),
1054 arms,
1055 }))
1056 };
1057
1058 // The discriminator's header, as a plan the header helpers take: only
1059 // a `@mutable` union has one, under member id 0.
1060 let discriminator_plan = layout.discriminator.map(|header| MemberPlan {
1061 member: layout.type_,
1062 member_id: 0,
1063 header,
1064 offset: zerodds_ast_layout::plan::Offset::Dynamic,
1065 align: 1,
1066 });
1067
1068 let mut encode_arms = Vec::new();
1069 let mut decode_arms = Vec::new();
1070 let mut default_arm = None;
1071
1072 for case in cases {
1073 let case_node = ast.case(*case).map_err(|error| EncodeError::NoLayout {
1074 detail: alloc_format(&error),
1075 })?;
1076 let member = ast
1077 .member(case_node.member)
1078 .map_err(|error| EncodeError::NoLayout {
1079 detail: alloc_format(&error),
1080 })?;
1081 let rust_case = shape
1082 .cases
1083 .iter()
1084 .find(|c| c.member.node_id == member.node_id)
1085 .ok_or(EncodeError::NoWriter {
1086 member: member.name.clone(),
1087 kind: "a union case the shape does not hold",
1088 })?;
1089 let variant = rust_case.variant.clone();
1090 let carries = rust_case.carries_discriminator();
1091 let member_type = member.type_.resolved().ok_or(EncodeError::NoWriter {
1092 member: member.name.clone(),
1093 kind: "a union case with an unresolved type",
1094 })?;
1095 let member_write = write_of(
1096 ast,
1097 node.module,
1098 member_type,
1099 key,
1100 codec,
1101 Position {
1102 behaviour: ast.effective_try_construct(None, Some(member_type)),
1103 written: None,
1104 },
1105 );
1106 let member_write = external_write(ast, member, member_write);
1107 let label_values = rust_case.labels.clone();
1108 let is_default = rust_case.is_default;
1109
1110 // The case's own header, which only a `@mutable` union's plan has.
1111 let case_plan = layout
1112 .members
1113 .iter()
1114 .find(|plan| plan.member == member.node_id)
1115 .copied();
1116
1117 // `Self::Variant(value)`, or `Self::Variant(discriminator, value)`
1118 // where the variant carries what `decode` read.
1119 let construct = |value: rust::Expr| {
1120 let mut args = Vec::new();
1121 if carries {
1122 args.push(rust::Expr::Path(Path::single("discriminator")));
1123 }
1124 args.push(value);
1125 rust::Expr::Call {
1126 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1127 args: vec![rust::Expr::Call {
1128 callee: Box::new(rust::Expr::Path(Path::new(&["Self", &variant]))),
1129 args,
1130 }],
1131 }
1132 };
1133 let read_member = read_for(&member.name, &member_write)?;
1134 let build = match case_plan {
1135 None => construct(rust::Expr::Try(Box::new(read_member))),
1136 Some(plan) => {
1137 let mut statements = read_header(&plan);
1138 statements.push(let_statement(
1139 "value",
1140 rust::Expr::Try(Box::new(read_member)),
1141 ));
1142 statements.extend(finish_read_header(&plan));
1143 statements.push(rust::Statement::Tail(construct(rust::Expr::Path(
1144 Path::single("value"),
1145 ))));
1146 rust::Expr::Block(rust::Block { statements })
1147 }
1148 };
1149
1150 if is_default {
1151 default_arm = Some(build.clone());
1152 } else {
1153 for value in &label_values {
1154 decode_arms.push(rust::MatchArm {
1155 pattern: label_pattern(*value)?,
1156 guard: None,
1157 body: build.clone(),
1158 });
1159 }
1160 }
1161
1162 // `Self::Variant(value) => { <discriminator write>; <member write>; }`
1163 // with the discriminator the case fixes, or
1164 // `Self::Variant(discriminator, value) => { <check>; <discriminator
1165 // write>; <member write>; }` with the one the variant carries.
1166 let mut statements = Vec::new();
1167 if carries {
1168 let own = (!is_default).then_some(label_values.as_slice());
1169 let what = if is_default {
1170 format!("a discriminator no label of `{name}` takes, for its `default:` member")
1171 } else {
1172 format!("a label of the member `{}` of `{name}`", member.name)
1173 };
1174 statements.push(check_carried(own, what)?);
1175 }
1176 if let Some(plan) = &discriminator_plan {
1177 statements.extend(open_header(plan, steps.discriminator));
1178 }
1179 let discriminator_write_expr = if carries {
1180 write_carried()?
1181 } else {
1182 // A case that does not carry its discriminator has exactly one
1183 // label: `carries_discriminator` is true for every other.
1184 let label = label_values.first().copied().ok_or(EncodeError::NoWriter {
1185 member: member.name.clone(),
1186 kind: "a union case carrying no case label",
1187 })?;
1188 write_expr_for(&discriminator_write, written_label(label)?, &name, false)?
1189 };
1190 statements.push(rust::Statement::Semi(discriminator_write_expr));
1191 if let Some(plan) = &discriminator_plan {
1192 statements.extend(close_header(plan));
1193 }
1194 if let Some(plan) = &case_plan {
1195 statements.extend(open_header(plan, steps.member(plan, &member.name)?));
1196 }
1197 // `value` is the variant's binding, which a `match` on `&self`
1198 // makes a reference.
1199 statements.push(rust::Statement::Semi(write_expr_for(
1200 &member_write,
1201 rust::Expr::Path(Path::single("value")),
1202 &member.name,
1203 true,
1204 )?));
1205 if let Some(plan) = &case_plan {
1206 statements.extend(close_header(plan));
1207 }
1208 let mut elements = Vec::new();
1209 if carries {
1210 elements.push(rust::Pattern::Binding(Ident::new("discriminator")));
1211 }
1212 elements.push(rust::Pattern::Binding(Ident::new("value")));
1213 encode_arms.push(rust::MatchArm {
1214 pattern: rust::Pattern::TupleStruct {
1215 path: Path::new(&["Self", &variant]),
1216 elements,
1217 },
1218 guard: None,
1219 body: rust::Expr::Block(rust::Block { statements }),
1220 });
1221 }
1222
1223 // The variant that holds a discriminator selecting no member
1224 // (§7.4.3.5: the member is absent): the discriminator and nothing
1225 // after it, under the same check as the `default:` case's.
1226 if shape.no_member {
1227 let mut statements = vec![check_carried(
1228 None,
1229 format!("a discriminator no label of `{name}` takes, for no member"),
1230 )?];
1231 if let Some(plan) = &discriminator_plan {
1232 statements.extend(open_header(plan, steps.discriminator));
1233 }
1234 statements.push(rust::Statement::Semi(write_carried()?));
1235 if let Some(plan) = &discriminator_plan {
1236 statements.extend(close_header(plan));
1237 }
1238 encode_arms.push(rust::MatchArm {
1239 pattern: rust::Pattern::TupleStruct {
1240 path: Path::new(&["Self", crate::rust_union::NO_MEMBER]),
1241 elements: vec![rust::Pattern::Binding(Ident::new("discriminator"))],
1242 },
1243 guard: None,
1244 body: rust::Expr::Block(rust::Block { statements }),
1245 });
1246 }
1247
1248 // Past the labels: the `default:` case, or the variant that holds no
1249 // member. A Rust `char` has more values than an IDL `char` or `wchar`
1250 // labels can cover, so a character discriminator always has the arm;
1251 // an integer, `bool` or enumeration discriminator whose labels cover
1252 // its type has no value left for it.
1253 let rest = default_arm.or_else(|| {
1254 shape.no_member.then(|| rust::Expr::Call {
1255 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1256 args: vec![rust::Expr::Call {
1257 callee: Box::new(rust::Expr::Path(Path::new(&[
1258 "Self",
1259 crate::rust_union::NO_MEMBER,
1260 ]))),
1261 args: vec![rust::Expr::Path(Path::single("discriminator"))],
1262 }],
1263 })
1264 });
1265 match rest {
1266 Some(body) => decode_arms.push(rust::MatchArm {
1267 pattern: rust::Pattern::Wildcard,
1268 guard: None,
1269 body,
1270 }),
1271 None if shape.kind == crate::rust_union::DiscriminatorKind::Character => {
1272 decode_arms.push(refusal_arm(&format!(
1273 "a discriminator selecting a case of `{name}`"
1274 )));
1275 }
1276 None => {}
1277 }
1278
1279 // An enumeration discriminator is read as its holder integer and
1280 // turned into the enumerator here, so the arms above match — and the
1281 // variants carry — the enumeration and not its holder. A holder no
1282 // enumerator has is refused, as the enumeration's own `decode` does.
1283 let to_enumeration = match shape.kind {
1284 crate::rust_union::DiscriminatorKind::Enumeration(enumeration) => {
1285 let mut arms = Vec::new();
1286 for enumerator in ast.enumerators_of(enumeration) {
1287 let (_, value) = enumerator_variant_and_value(
1288 ast,
1289 &enumeration_name(enumeration),
1290 *enumerator,
1291 key,
1292 )?;
1293 arms.push(rust::MatchArm {
1294 pattern: rust::Pattern::Literal(Literal::int(value)),
1295 guard: None,
1296 body: rust::Expr::Path(
1297 crate::rust_union::enumerator_path_of(
1298 ast,
1299 node.module,
1300 enumeration,
1301 *enumerator,
1302 )
1303 .map_err(refuse_label)?,
1304 ),
1305 });
1306 }
1307 arms.push(rust::MatchArm {
1308 pattern: rust::Pattern::Wildcard,
1309 guard: None,
1310 body: refused(
1311 format!("an enumerator of the discriminator of `{name}`"),
1312 "r",
1313 ),
1314 });
1315 Some(rust::Statement::Let {
1316 name: Ident::new("discriminator"),
1317 type_: None,
1318 value: Some(rust::Expr::Match {
1319 scrutinee: Box::new(rust::Expr::Path(Path::single("discriminator"))),
1320 arms,
1321 }),
1322 mutable: false,
1323 })
1324 }
1325 _ => None,
1326 };
1327
1328 let mut encode_statements = Vec::new();
1329 if per_sample(key) {
1330 // As in `encode_fns`: the flag is read only by a case member or
1331 // by the discriminator whose header form the sample decides.
1332 encode_statements.push(rust::Statement::Let {
1333 name: Ident::new("_"),
1334 type_: None,
1335 value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
1336 mutable: false,
1337 });
1338 }
1339 if layout.needs_dheader {
1340 encode_statements.push(let_statement("dheader", {
1341 rust::Expr::MethodCall {
1342 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
1343 method: Ident::new("write_dheader"),
1344 args: Vec::new(),
1345 }
1346 }));
1347 }
1348 encode_statements.push(rust::Statement::Semi(rust::Expr::Match {
1349 scrutinee: Box::new(rust::Expr::Path(Path::single("self"))),
1350 arms: encode_arms,
1351 }));
1352 if layout.needs_sentinel {
1353 encode_statements.push(call_statement("write_sentinel", Vec::new()));
1354 }
1355 if layout.needs_dheader {
1356 encode_statements.push(call_statement(
1357 "finish_dheader",
1358 vec![rust::Expr::Path(Path::single("dheader"))],
1359 ));
1360 }
1361 encode_statements.push(rust::Statement::Tail(rust::Expr::Call {
1362 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1363 args: vec![rust::Expr::Tuple(Vec::new())],
1364 }));
1365 // As in `encode_fns`: under `ElementPadding::Packed` a union value is
1366 // its own alignment block. See `zerodds-wire-codec`'s `write_union`.
1367 let encode_statements = wrap_packed_body(
1368 encode_statements,
1369 "w",
1370 key.element_padding == ElementPadding::Packed,
1371 );
1372 let encode = encode_function(&body_name(codec, key), &encode_statements, per_sample(key));
1373
1374 let decode = rust::Item::Function(rust::Function {
1375 doc: Doc::none(),
1376 attrs: Vec::new(),
1377 visibility: Visibility::Public,
1378 name: Ident::new(&format!("decode{codec}")),
1379 generics: Vec::new(),
1380 params: vec![rust::Param {
1381 name: Some(Ident::new("r")),
1382 type_: rust::Type::Ref {
1383 target: Box::new(rust::Type::named(Path::single("Reader"))),
1384 mutable: true,
1385 lifetime: None,
1386 },
1387 receiver: None,
1388 }],
1389 returns: Some(rust::Type::generic(
1390 Path::single("Result"),
1391 vec![
1392 rust::Type::named(Path::single("Self")),
1393 rust::Type::named(Path::single("WireError")),
1394 ],
1395 )),
1396 body: Some(rust::Block {
1397 statements: wrap_packed_body(
1398 union_decode_body(
1399 layout,
1400 discriminator_plan,
1401 &discriminator_write,
1402 to_enumeration,
1403 decode_arms,
1404 )?,
1405 "r",
1406 key.element_padding == ElementPadding::Packed,
1407 ),
1408 }),
1409 });
1410
1411 let mut out = with_entry(encode, encode_entry(ast, type_, key, codec)?);
1412 out.push(decode);
1413 Ok(out)
1414}
1415
1416/// The body of a union's `decode`: the framing the plan gives it around
1417/// the discriminator's read and the match that reads the branch.
1418///
1419/// A `@final` union keeps the shape it always had — the discriminator,
1420/// then the match as the tail — so its generated code does not change.
1421fn union_decode_body(
1422 layout: &TypeLayout,
1423 discriminator_plan: Option<MemberPlan>,
1424 discriminator_write: &MemberWrite,
1425 to_enumeration: Option<rust::Statement>,
1426 decode_arms: Vec<rust::MatchArm>,
1427) -> Result<Vec<rust::Statement>, EncodeError> {
1428 let read_discriminator = rust::Statement::Let {
1429 name: Ident::new("discriminator"),
1430 type_: None,
1431 value: Some(rust::Expr::Try(Box::new(read_for(
1432 "discriminator",
1433 discriminator_write,
1434 )?))),
1435 mutable: false,
1436 };
1437 let branch = rust::Expr::Match {
1438 scrutinee: Box::new(rust::Expr::Path(Path::single("discriminator"))),
1439 arms: decode_arms,
1440 };
1441 if !layout.needs_dheader && !layout.needs_sentinel && discriminator_plan.is_none() {
1442 let mut statements = vec![read_discriminator];
1443 statements.extend(to_enumeration);
1444 statements.push(rust::Statement::Tail(branch));
1445 return Ok(statements);
1446 }
1447
1448 let mut statements = Vec::new();
1449 if layout.needs_dheader {
1450 // Where the writer's union ends: a newer writer's branch may
1451 // carry more than this reader's type knows, and the reader steps
1452 // over it rather than reading the next value from inside it.
1453 statements.push(let_statement(
1454 "end",
1455 rust::Expr::Binary {
1456 op: rust::BinaryOp::Add,
1457 lhs: Box::new(rust::Expr::Try(Box::new(call("read_dheader", Vec::new())))),
1458 rhs: Box::new(call("position", Vec::new())),
1459 },
1460 ));
1461 }
1462 if let Some(plan) = &discriminator_plan {
1463 statements.extend(read_header(plan));
1464 }
1465 statements.push(read_discriminator);
1466 if let Some(plan) = &discriminator_plan {
1467 statements.extend(finish_read_header(plan));
1468 }
1469 statements.extend(to_enumeration);
1470 statements.push(let_statement("value", rust::Expr::Try(Box::new(branch))));
1471 if layout.needs_sentinel {
1472 statements.extend(read_sentinel());
1473 }
1474 if layout.needs_dheader {
1475 statements.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
1476 "skip_to",
1477 vec![rust::Expr::Path(Path::single("end"))],
1478 )))));
1479 }
1480 statements.push(rust::Statement::Tail(rust::Expr::Call {
1481 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1482 args: vec![rust::Expr::Path(Path::single("value"))],
1483 }));
1484 Ok(statements)
1485}
1486
1487/// Rule (23)'s `PID_SENTINEL` at the end of a PL_CDR1 parameter list,
1488/// read and checked: a parameter list that ends anywhere else is refused.
1489fn read_sentinel() -> Vec<rust::Statement> {
1490 vec![
1491 let_statement(
1492 "sentinel",
1493 rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
1494 ),
1495 rust::Statement::Semi(rust::Expr::If {
1496 condition: Box::new(rust::Expr::Not(Box::new(rust::Expr::Field {
1497 receiver: Box::new(rust::Expr::Path(Path::single("sentinel"))),
1498 name: Ident::new("end"),
1499 }))),
1500 then: rust::Block {
1501 statements: vec![rust::Statement::Semi(rust::Expr::Return(Some(Box::new(
1502 rust::Expr::Call {
1503 callee: Box::new(rust::Expr::Path(Path::single("Err"))),
1504 args: vec![rust::Expr::Call {
1505 callee: Box::new(rust::Expr::Path(Path::new(&[
1506 "WireError",
1507 "refused",
1508 ]))),
1509 args: vec![
1510 call("position", Vec::new()),
1511 rust::Expr::Literal(Literal::string(
1512 "PID_SENTINEL closing the parameter list, per rule (23)",
1513 )),
1514 ],
1515 }],
1516 },
1517 ))))],
1518 },
1519 otherwise: None,
1520 }),
1521 ]
1522}
1523
1524/// A member header's read, before the value, for a member read where
1525/// the stream puts it — the decoder's half of [`open_header`].
1526fn read_header(plan: &MemberPlan) -> Vec<rust::Statement> {
1527 match plan.header {
1528 MemberHeader::NoHeader | MemberHeader::OptionalFlag => Vec::new(),
1529 MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => vec![let_statement(
1530 "pid",
1531 rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
1532 )],
1533 MemberHeader::EmHeader { .. } => vec![let_statement(
1534 "_emheader",
1535 rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
1536 )],
1537 }
1538}
1539
1540/// What closes a header after its value on the read side — the
1541/// decoder's half of [`close_header`].
1542fn finish_read_header(plan: &MemberPlan) -> Vec<rust::Statement> {
1543 match plan.header {
1544 MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => {
1545 vec![rust::Statement::Semi(rust::Expr::Try(Box::new(call(
1546 "finish_pid",
1547 vec![rust::Expr::Path(Path::single("pid"))],
1548 ))))]
1549 }
1550 MemberHeader::NoHeader | MemberHeader::OptionalFlag | MemberHeader::EmHeader { .. } => {
1551 Vec::new()
1552 }
1553 }
1554}
1555
1556/// The write of one value, as an expression, with its `?` where the
1557/// write can refuse.
1558fn write_expr_for(
1559 kind: &MemberWrite,
1560 value: rust::Expr,
1561 at: &str,
1562 borrowed: bool,
1563) -> Result<rust::Expr, EncodeError> {
1564 let (expr, fallible) = write_call("w", SinkShape::Reference, at, kind, value, borrowed)?;
1565 Ok(if fallible {
1566 rust::Expr::Try(Box::new(expr))
1567 } else {
1568 expr
1569 })
1570}
1571
1572/// The unsigned Rust type an enumeration's holder takes, and the
1573/// `Writer`/`Reader` method for it.
1574///
1575/// `zerodds_ast_layout::enumeration_bit_bound` decides the width, the
1576/// same crossing function the member position asks — a second table
1577/// here would be a second place for a holder to be the wrong size.
1578const fn enum_holder(bits: u16) -> (rust::Primitive, &'static str, &'static str) {
1579 match bits {
1580 0..=8 => (rust::Primitive::U8, "write_u8", "read_u8"),
1581 9..=16 => (rust::Primitive::U16, "write_u16", "read_u16"),
1582 17..=32 => (rust::Primitive::U32, "write_u32", "read_u32"),
1583 _ => (rust::Primitive::U64, "write_u64", "read_u64"),
1584 }
1585}
1586
1587/// The encoder body for an enumeration: its holder, at the width its bit
1588/// bound gives, and — under [`per_sample`] — the public entry beside it.
1589///
1590/// An enumeration's body reads no [`EXTENDED_PARAM`] and never writes a
1591/// parameter header: `keeps_short_header` counts an enumeration among the
1592/// types that keep the short form in a sample past RTI's step. The body
1593/// takes the flag anyway because a `MemberWrite::Nested` call on an
1594/// enumeration names it, and the same name under every plan is what keeps
1595/// a call from framing an enumeration member with the wrong encoder.
1596///
1597/// A `match` and not `*self as uN`. The cast is a move, and
1598/// `crate::adapter::cst` derives nothing on what it declares — not
1599/// `Copy` either — so the cast form did not compile. The match is also
1600/// the honest one: it names each enumerator's wire value where a
1601/// reader can compare it with `decode`'s.
1602fn enumeration_encode_fns(
1603 ast: &Ast,
1604 type_: Id<TypeNode>,
1605 enumerators: &[Id<zerodds_ast::EnumeratorNode>],
1606 key: LayoutKey,
1607 codec: &'static str,
1608) -> Result<Vec<rust::Item>, EncodeError> {
1609 let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
1610 detail: alloc_format(&error),
1611 })?;
1612 let bits = zerodds_ast_layout::enumeration_bit_bound(ast, type_, key.enum_holder);
1613 let (primitive, writer, _) = enum_holder(bits);
1614
1615 let mut arms = Vec::new();
1616 for id in enumerators {
1617 let (name, value) = enumerator_variant_and_value(ast, &node.name, *id, key)?;
1618 arms.push(rust::MatchArm {
1619 pattern: rust::Pattern::Path(Path::new(&["Self", &name])),
1620 guard: None,
1621 body: rust::Expr::Literal(Literal::int(value)),
1622 });
1623 }
1624
1625 let mut statements = Vec::new();
1626 if per_sample(key) {
1627 statements.push(rust::Statement::Let {
1628 name: Ident::new("_"),
1629 type_: None,
1630 value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
1631 mutable: false,
1632 });
1633 }
1634 statements.extend([
1635 rust::Statement::Let {
1636 name: Ident::new("value"),
1637 type_: Some(rust::Type::Primitive(primitive)),
1638 value: Some(rust::Expr::Match {
1639 scrutinee: Box::new(rust::Expr::Path(Path::single("self"))),
1640 arms,
1641 }),
1642 mutable: false,
1643 },
1644 rust::Statement::Semi(rust::Expr::MethodCall {
1645 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
1646 method: Ident::new(writer),
1647 args: vec![rust::Expr::Path(Path::single("value"))],
1648 }),
1649 rust::Statement::Tail(rust::Expr::Call {
1650 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1651 args: vec![rust::Expr::Tuple(Vec::new())],
1652 }),
1653 ]);
1654 Ok(with_entry(
1655 encode_function(&body_name(codec, key), &statements, per_sample(key)),
1656 encode_entry(ast, type_, key, codec)?,
1657 ))
1658}
1659
1660/// One enumerator's Rust variant name and the integer it serialises as.
1661///
1662/// The value goes through
1663/// [`zerodds_ast_layout::enumerator_wire_value`] and not through
1664/// `Ast::effective_enumerator_value`. The two answer differently under
1665/// [`zerodds_ast_layout::EnumeratorValue::Ordinal`], and this integer is
1666/// the one that leaves the process: `enumeration_encode_fn` puts it in
1667/// the holder and `enumeration_decode_fn` matches an incoming holder
1668/// against it. Reading the declared value here wrote `@value(200)`
1669/// where a front end that drops the annotation writes `1`, in both
1670/// directions, with nothing at run time saying so — the Rust column
1671/// carries no corpus row that reaches this code, so only
1672/// `crates/wire-probe/tests/enumerator_value.rs::the_sites_go_through_the_crossing`
1673/// stands between it and a silent bypass.
1674///
1675/// The Rust *discriminant* is a different question and stays declared:
1676/// `crate::adapter::cst` writes it, and it is the language mapping.
1677fn enumerator_variant_and_value(
1678 ast: &Ast,
1679 enumeration: &str,
1680 id: Id<zerodds_ast::EnumeratorNode>,
1681 key: LayoutKey,
1682) -> Result<(String, i128), EncodeError> {
1683 let enumerator = ast.enumerator(id).map_err(|error| EncodeError::NoLayout {
1684 detail: alloc_format(&error),
1685 })?;
1686 let value = zerodds_ast_layout::enumerator_wire_value(ast, id, key.enumerator_value)
1687 .ok_or_else(|| EncodeError::NoEnumeratorValue {
1688 enumeration: enumeration.to_string(),
1689 enumerator: enumerator.name.clone(),
1690 detail: "the enumerator has no computable wire value",
1691 })?;
1692 // `escape_keyword` and not `variant_name`: the declaration is
1693 // `crate::adapter::cst`'s and it writes the enumerator's own name.
1694 // `variant_name` capitalises, which is what a *union* case takes —
1695 // it is built from a member name — and using it here produced
1696 // `Self::Missing_node` for an enumerator declared `missing_node`.
1697 Ok((rust::escape_keyword(&enumerator.name), value))
1698}
1699
1700/// `fn decode(r: &mut Reader) -> Result<Self, WireError>` for an
1701/// enumeration.
1702///
1703/// A value that names no enumerator is **refused** and not mapped onto
1704/// the first variant. §7.2.2.4.1.2 gives an enumeration a fixed set of
1705/// literals; a decoder that answered `RED` for a value nobody wrote
1706/// would hand the caller a sample that never existed, which is the
1707/// silent half of the drop class.
1708fn enumeration_decode_fn(
1709 ast: &Ast,
1710 type_: Id<TypeNode>,
1711 enumerators: &[Id<zerodds_ast::EnumeratorNode>],
1712 key: LayoutKey,
1713 codec: &'static str,
1714) -> Result<rust::Item, EncodeError> {
1715 let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
1716 detail: alloc_format(&error),
1717 })?;
1718 let bits = zerodds_ast_layout::enumeration_bit_bound(ast, type_, key.enum_holder);
1719 let (_, _, reader) = enum_holder(bits);
1720
1721 let mut arms = Vec::new();
1722 for id in enumerators {
1723 let (name, value) = enumerator_variant_and_value(ast, &node.name, *id, key)?;
1724 arms.push(rust::MatchArm {
1725 pattern: rust::Pattern::Literal(Literal::int(value)),
1726 guard: None,
1727 body: rust::Expr::Call {
1728 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1729 args: vec![rust::Expr::Path(Path::new(&["Self", &name]))],
1730 },
1731 });
1732 }
1733 arms.push(refusal_arm(&format!("an enumerator of `{}`", node.name)));
1734
1735 let statements = vec![
1736 rust::Statement::Let {
1737 name: Ident::new("value"),
1738 type_: None,
1739 value: Some(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
1740 receiver: Box::new(rust::Expr::Path(Path::single("r"))),
1741 method: Ident::new(reader),
1742 args: Vec::new(),
1743 }))),
1744 mutable: false,
1745 },
1746 rust::Statement::Tail(rust::Expr::Match {
1747 scrutinee: Box::new(rust::Expr::Path(Path::single("value"))),
1748 arms,
1749 }),
1750 ];
1751
1752 Ok(rust::Item::Function(rust::Function {
1753 doc: Doc::none(),
1754 attrs: Vec::new(),
1755 visibility: Visibility::Public,
1756 name: Ident::new(&format!("decode{codec}")),
1757 generics: Vec::new(),
1758 params: vec![rust::Param {
1759 name: Some(Ident::new("r")),
1760 type_: rust::Type::Ref {
1761 target: Box::new(rust::Type::named(Path::single("Reader"))),
1762 mutable: true,
1763 lifetime: None,
1764 },
1765 receiver: None,
1766 }],
1767 returns: Some(rust::Type::generic(
1768 Path::single("Result"),
1769 vec![
1770 rust::Type::named(Path::single("Self")),
1771 rust::Type::named(Path::single("WireError")),
1772 ],
1773 )),
1774 body: Some(rust::Block { statements }),
1775 }))
1776}
1777
1778/// The anchor a bitset packs into, in octets.
1779///
1780/// The **plan's** answer and not a sum of the widths: an anchor is one,
1781/// two, four or eight octets whatever the bitfields add up to, and
1782/// `zerodds_ast_layout::xcdr::plan` is the crossing the other twenty
1783/// columns ask through `Extent::Sized`.
1784fn bitset_anchor(layout: &TypeLayout, name: &str) -> Result<u32, EncodeError> {
1785 match layout.extent {
1786 Extent::Sized(anchor) => Ok(anchor),
1787 Extent::Unbounded => Err(EncodeError::NoWriter {
1788 member: name.to_string(),
1789 kind: "bitset whose anchor the planner does not size",
1790 }),
1791 }
1792}
1793
1794/// Each named bitfield with the width and the position it takes in the
1795/// anchor.
1796///
1797/// Positions run cumulatively from zero in declaration order, **padding
1798/// included**: an anonymous `bitfield<4>` carries no value and still
1799/// moves the next field along. The whole inheritance chain, base first,
1800/// because that is what the planner sizes the anchor over —
1801/// `crates/lang-rules/src/emit/encode.rs` reads the same list the same
1802/// way.
1803///
1804/// # Errors
1805/// [`EncodeError::NoWriter`] for a chain that runs past the sixty-four
1806/// bits an anchor holds — never a field silently dropped off the end.
1807fn bitset_fields(
1808 ast: &Ast,
1809 type_: Id<TypeNode>,
1810 name: &str,
1811) -> Result<Vec<(String, u16, u32)>, EncodeError> {
1812 let mut out = Vec::new();
1813 let mut position: u32 = 0;
1814 for field in &ast.all_bitfields_of(type_) {
1815 // Refused and not skipped: a skipped field takes its width out
1816 // of the running position, so every field after it would be
1817 // packed at an offset the peer does not read it from — a sample
1818 // that encodes, reports success and means something else.
1819 let Ok(field_node) = ast.bitfield(*field) else {
1820 return Err(EncodeError::NoWriter {
1821 member: name.to_string(),
1822 kind: "bitset with a stale bitfield handle",
1823 });
1824 };
1825 let width = u32::from(field_node.bits);
1826 if position.saturating_add(width) > 64 {
1827 return Err(EncodeError::NoWriter {
1828 member: name.to_string(),
1829 kind: "bitset wider than the sixty-four bits an anchor holds",
1830 });
1831 }
1832 if let Some(field_name) = field_node.name.as_ref() {
1833 out.push((
1834 rust::escape_keyword(field_name).to_string(),
1835 field_node.bits,
1836 position,
1837 ));
1838 }
1839 position = position.saturating_add(width);
1840 }
1841 Ok(out)
1842}
1843
1844/// A bitset's encoder body — each field shifted into the anchor, then the
1845/// anchor — and, under [`per_sample`], the public entry beside it.
1846///
1847/// As `enumeration_encode_fns`'s: the body reads no [`EXTENDED_PARAM`]
1848/// and takes it anyway, because a `MemberWrite::Nested` call on a bitset
1849/// names it.
1850///
1851/// `Writer::bitfield` does the shift and refuses a value the field's
1852/// width cannot hold. The generated field is as wide as its **holder**
1853/// — `bitfield<3> a` is a `u8` — so a value that does not fit is a
1854/// program error the writer reports rather than an octet it masks off.
1855fn bitset_encode_fns(
1856 ast: &Ast,
1857 type_: Id<TypeNode>,
1858 layout: &TypeLayout,
1859 key: LayoutKey,
1860 codec: &'static str,
1861) -> Result<Vec<rust::Item>, EncodeError> {
1862 let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
1863 detail: alloc_format(&error),
1864 })?;
1865 let anchor = bitset_anchor(layout, &node.name)?;
1866 let mut statements = Vec::new();
1867 if per_sample(key) {
1868 statements.push(rust::Statement::Let {
1869 name: Ident::new("_"),
1870 type_: None,
1871 value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
1872 mutable: false,
1873 });
1874 }
1875 statements.push(rust::Statement::Let {
1876 name: Ident::new("bits"),
1877 type_: Some(rust::Type::Primitive(rust::Primitive::U64)),
1878 value: Some(rust::Expr::Literal(Literal::int(0))),
1879 mutable: true,
1880 });
1881 for (field, bits, position) in bitset_fields(ast, type_, &node.name)? {
1882 let value = rust::Expr::Field {
1883 receiver: Box::new(rust::Expr::Path(Path::single("self"))),
1884 name: Ident::new(&field),
1885 };
1886 statements.push(rust::Statement::Semi(rust::Expr::Assign {
1887 target: Box::new(rust::Expr::Path(Path::single("bits"))),
1888 value: Box::new(rust::Expr::Binary {
1889 op: rust::BinaryOp::BitOr,
1890 lhs: Box::new(rust::Expr::Path(Path::single("bits"))),
1891 rhs: Box::new(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
1892 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
1893 method: Ident::new("bitfield"),
1894 args: vec![
1895 widen_to_u64(value, bits),
1896 rust::Expr::Literal(Literal::int(i128::from(bits))),
1897 rust::Expr::Literal(Literal::int(i128::from(position))),
1898 ],
1899 }))),
1900 }),
1901 }));
1902 }
1903 statements.push(rust::Statement::Semi(rust::Expr::Try(Box::new(
1904 rust::Expr::MethodCall {
1905 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
1906 method: Ident::new("write_bitset"),
1907 args: vec![
1908 rust::Expr::Literal(Literal::int(i128::from(anchor))),
1909 rust::Expr::Path(Path::single("bits")),
1910 ],
1911 },
1912 ))));
1913 statements.push(rust::Statement::Tail(rust::Expr::Call {
1914 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1915 args: vec![rust::Expr::Tuple(Vec::new())],
1916 }));
1917 Ok(with_entry(
1918 encode_function(&body_name(codec, key), &statements, per_sample(key)),
1919 encode_entry(ast, type_, key, codec)?,
1920 ))
1921}
1922
1923/// A bitset's `decode`: the anchor, then each field shifted and masked
1924/// back out of it.
1925///
1926/// The mask is written out rather than left to the narrowing cast: the
1927/// anchor is sixty-four bits and the field is as wide as its holder, so
1928/// `bitfield<3> a` in a `u8` would otherwise take five bits that belong
1929/// to the field beside it.
1930fn bitset_decode_fn(
1931 ast: &Ast,
1932 type_: Id<TypeNode>,
1933 layout: &TypeLayout,
1934 codec: &'static str,
1935) -> Result<rust::Item, EncodeError> {
1936 let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
1937 detail: alloc_format(&error),
1938 })?;
1939 let anchor = bitset_anchor(layout, &node.name)?;
1940 let mut statements = vec![rust::Statement::Let {
1941 name: Ident::new("bits"),
1942 type_: None,
1943 value: Some(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
1944 receiver: Box::new(rust::Expr::Path(Path::single("r"))),
1945 method: Ident::new("read_bitset"),
1946 args: vec![rust::Expr::Literal(Literal::int(i128::from(anchor)))],
1947 }))),
1948 mutable: false,
1949 }];
1950 let mut fields = Vec::new();
1951 for (field, bits, position) in bitset_fields(ast, type_, &node.name)? {
1952 let mask: u64 = if bits >= 64 {
1953 u64::MAX
1954 } else {
1955 (1u64 << bits) - 1
1956 };
1957 let masked = rust::Expr::Binary {
1958 op: rust::BinaryOp::BitAnd,
1959 lhs: Box::new(rust::Expr::Binary {
1960 op: rust::BinaryOp::Shr,
1961 lhs: Box::new(rust::Expr::Path(Path::single("bits"))),
1962 rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(position)))),
1963 }),
1964 rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(mask)))),
1965 };
1966 let holder = crate::adapter::cst::bitmask_primitive(bits);
1967 let value = if holder == rust::Primitive::U64 {
1968 masked
1969 } else {
1970 rust::Expr::Cast {
1971 value: Box::new(masked),
1972 type_: Box::new(rust::Type::Primitive(holder)),
1973 }
1974 };
1975 fields.push((Ident::new(&field), value));
1976 }
1977 statements.push(rust::Statement::Tail(rust::Expr::Call {
1978 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
1979 args: vec![rust::Expr::StructLit {
1980 path: Path::single("Self"),
1981 fields,
1982 base: None,
1983 }],
1984 }));
1985 Ok(rust::Item::Function(rust::Function {
1986 doc: Doc::none(),
1987 attrs: Vec::new(),
1988 visibility: Visibility::Public,
1989 name: Ident::new(&format!("decode{codec}")),
1990 generics: Vec::new(),
1991 params: vec![rust::Param {
1992 name: Some(Ident::new("r")),
1993 type_: rust::Type::Ref {
1994 target: Box::new(rust::Type::named(Path::single("Reader"))),
1995 mutable: true,
1996 lifetime: None,
1997 },
1998 receiver: None,
1999 }],
2000 returns: Some(rust::Type::generic(
2001 Path::single("Result"),
2002 vec![
2003 rust::Type::named(Path::single("Self")),
2004 rust::Type::named(Path::single("WireError")),
2005 ],
2006 )),
2007 body: Some(rust::Block { statements }),
2008 }))
2009}
2010
2011/// The encoder **body** for a structure or a valuetype, and — under
2012/// [`per_sample`] — the public entry beside it.
2013///
2014/// The statements follow the plan's member order, which is wire order
2015/// and not necessarily declaration order.
2016///
2017/// Fallible even for a type with nothing fallible in it, so the
2018/// signature is the type's and not its members'. `char` and `wchar`
2019/// refuse a code point they cannot carry, and an `encode` whose
2020/// signature changed with the member list would make that a source
2021/// break for every caller the day someone added one.
2022///
2023/// `steps` names the members whose PL_CDR1 header form the sample
2024/// decides, and the body reads [`EXTENDED_PARAM`] for each of them.
2025fn encode_fns(
2026 ast: &Ast,
2027 type_: Id<TypeNode>,
2028 layout: &TypeLayout,
2029 steps: &StepHeaders,
2030 key: LayoutKey,
2031 codec: &'static str,
2032) -> Result<Vec<rust::Item>, EncodeError> {
2033 let mut statements = Vec::new();
2034 // The flag is read by the members whose header form the sample
2035 // decides, and by nothing else: a `@final` aggregate of primitives
2036 // takes it and never looks at it, which is `unused_variables` in
2037 // every crate that includes the generated text. `let _ =` is valid
2038 // whether or not the body goes on to use it, so it is emitted
2039 // unconditionally rather than from a guess about which bodies read it
2040 // — the same shape `crate::emit::encode`'s `with_extended_used` uses
2041 // for the twenty other languages.
2042 if per_sample(key) {
2043 statements.push(rust::Statement::Let {
2044 name: Ident::new("_"),
2045 type_: None,
2046 value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
2047 mutable: false,
2048 });
2049 }
2050
2051 // The DHEADER the plan asks for, and nothing more: whether an
2052 // appendable type carries one is the plan's decision, made once.
2053 if layout.needs_dheader {
2054 // The length is not known until the members are written, so the
2055 // header is a placeholder the runtime patches. A generator that
2056 // wrote a length it computed itself would be computing the
2057 // layout a second time.
2058 statements.push(rust::Statement::Let {
2059 name: Ident::new("dheader"),
2060 type_: None,
2061 value: Some(rust::Expr::MethodCall {
2062 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
2063 method: Ident::new("write_dheader"),
2064 args: Vec::new(),
2065 }),
2066 mutable: false,
2067 });
2068 }
2069
2070 for plan in &layout.members {
2071 let Some(member) = member_of(ast, type_, plan.member, key, codec) else {
2072 continue;
2073 };
2074 let name = member.0;
2075 let kind = member.1;
2076 let optional = member_node_of(ast, type_, plan.member)
2077 .is_some_and(|member| member.facets.is_optional());
2078
2079 let receiver = holder_of(ast, type_, plan.member);
2080 if optional {
2081 statements.extend(write_optional_member(
2082 receiver,
2083 plan,
2084 &name,
2085 &kind,
2086 steps.member(plan, &name)?,
2087 )?);
2088 continue;
2089 }
2090
2091 // The header the plan chose, including the length code — this
2092 // is where `LengthCodePolicy` becomes visible in the output. A
2093 // member the sample's step decides gets the run-time branch
2094 // instead, because the plan named the short side of a decision
2095 // the sample makes.
2096 statements.extend(open_header(plan, steps.member(plan, &name)?));
2097 statements.push(write_for(receiver, &name, &kind)?);
2098 // The header's length is patched after the value, because that
2099 // is when it is known.
2100 statements.extend(close_header(plan));
2101 }
2102
2103 // The sentinel a PL_CDR1 parameter list ends with. A reader that
2104 // stops at the last member it recognises instead of at the sentinel
2105 // misreads whatever follows.
2106 if layout.needs_sentinel {
2107 statements.push(call_statement("write_sentinel", Vec::new()));
2108 }
2109
2110 if layout.needs_dheader {
2111 statements.push(call_statement(
2112 "finish_dheader",
2113 vec![rust::Expr::Path(Path::single("dheader"))],
2114 ));
2115 }
2116
2117 statements.push(rust::Statement::Tail(rust::Expr::Call {
2118 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
2119 args: vec![rust::Expr::Tuple(Vec::new())],
2120 }));
2121
2122 // An aggregate value is its own alignment block under
2123 // `ElementPadding::Packed`; see `zerodds-wire-codec`'s
2124 // `write_aggregate`.
2125 let statements = wrap_packed_body(
2126 statements,
2127 "w",
2128 key.element_padding == ElementPadding::Packed,
2129 );
2130
2131 Ok(with_entry(
2132 encode_function(&body_name(codec, key), &statements, per_sample(key)),
2133 encode_entry(ast, type_, key, codec)?,
2134 ))
2135}
2136
2137/// A generated `encode`, under either name the per-sample split gives it.
2138///
2139/// `flagged` is the body, which takes [`EXTENDED_PARAM`] after the writer;
2140/// the public entry takes no flag, because the sample whose root this type
2141/// is resolves the decision itself and passes the constant it resolved.
2142fn encode_function(name: &str, statements: &[rust::Statement], flagged: bool) -> rust::Item {
2143 let mut params = vec![
2144 rust::Param {
2145 name: None,
2146 type_: rust::Type::unit(),
2147 receiver: Some(rust::Receiver::Ref),
2148 },
2149 rust::Param {
2150 name: Some(Ident::new("w")),
2151 type_: rust::Type::Ref {
2152 target: Box::new(rust::Type::named(Path::single("Writer"))),
2153 mutable: true,
2154 lifetime: None,
2155 },
2156 receiver: None,
2157 },
2158 ];
2159 // After the writer, so the two the entry passes read in that order:
2160 // the sink, then the sample's decision.
2161 if flagged {
2162 params.push(rust::Param {
2163 name: Some(Ident::new(EXTENDED_PARAM)),
2164 type_: rust::Type::Primitive(rust::Primitive::Bool),
2165 receiver: None,
2166 });
2167 }
2168 rust::Item::Function(rust::Function {
2169 doc: Doc::none(),
2170 attrs: Vec::new(),
2171 visibility: Visibility::Public,
2172 name: Ident::new(name),
2173 generics: Vec::new(),
2174 params,
2175 returns: Some(rust::Type::generic(
2176 Path::single("Result"),
2177 vec![
2178 rust::Type::unit(),
2179 rust::Type::named(Path::single("WireError")),
2180 ],
2181 )),
2182 body: Some(rust::Block {
2183 statements: statements.to_vec(),
2184 }),
2185 })
2186}
2187
2188/// The body, and the public entry that bakes this type's own step
2189/// decision in beside it — or the body alone, for every plan whose header
2190/// form is fixed.
2191fn with_entry(body: rust::Item, entry: Option<rust::Item>) -> Vec<rust::Item> {
2192 match entry {
2193 Some(entry) => vec![body, entry],
2194 None => vec![body],
2195 }
2196}
2197
2198/// The public `encode(&self, w)` of a type under [`per_sample`], and
2199/// `None` without it.
2200///
2201/// RTI decides the PL_CDR1 header form for the **sample's** type, so the
2202/// sample whose root this type is resolves the decision once, here, and
2203/// hands it to the body — which threads it to every type the body calls.
2204/// The same nested `@mutable` type is then framed short inside a small
2205/// sample and extended inside a large one, which a single `encode` per
2206/// type cannot be.
2207///
2208/// The decision itself is [`sample_past_step`]'s, which asks
2209/// [`LayoutKey::for_sample`] — the crossing the reflective encoder makes
2210/// for the same sample, and the one that keeps
2211/// `crates/wire-codec`'s encoder and this codec writing the same octets.
2212///
2213/// # Errors
2214/// [`EncodeError`] when the sample's type cannot be sized, which is what
2215/// [`LayoutKey::for_sample`] reports.
2216fn encode_entry(
2217 ast: &Ast,
2218 type_: Id<TypeNode>,
2219 key: LayoutKey,
2220 codec: &'static str,
2221) -> Result<Option<rust::Item>, EncodeError> {
2222 if !per_sample(key) {
2223 return Ok(None);
2224 }
2225 let past = sample_past_step(ast, type_, key)?;
2226 let statements = vec![rust::Statement::Tail(rust::Expr::MethodCall {
2227 receiver: Box::new(rust::Expr::Path(Path::single("self"))),
2228 method: Ident::new(&body_name(codec, key)),
2229 args: vec![
2230 rust::Expr::Path(Path::single("w")),
2231 rust::Expr::Literal(Literal::Bool(past)),
2232 ],
2233 })];
2234 Ok(Some(encode_function(
2235 &format!("encode{codec}"),
2236 &statements,
2237 false,
2238 )))
2239}
2240
2241/// The statements that open one member's framing, before its value.
2242///
2243/// Split out of the loop because an `@optional` member writes some of
2244/// them inside a `match` arm and some outside it, and a second copy of
2245/// the header shapes would be a second place for a length code to be
2246/// wrong.
2247///
2248/// `chosen` says that the sample, not the plan, picks the PL_CDR1 header
2249/// form, and the statements are then a run-time branch on
2250/// [`EXTENDED_PARAM`] between `write_pid` and `write_pid_extended`. It is
2251/// what [`StepHeaders`] records for the member and what the enclosing
2252/// body threads down from the sample's root.
2253fn open_header(plan: &zerodds_ast_layout::plan::MemberPlan, chosen: bool) -> Vec<rust::Statement> {
2254 match plan.header {
2255 MemberHeader::NoHeader => Vec::new(),
2256 MemberHeader::PidCdr1 {
2257 pid,
2258 extended,
2259 must_understand,
2260 } => vec![bind_pid_header(
2261 chosen,
2262 (pid, plan.member_id),
2263 extended,
2264 "Member",
2265 must_understand,
2266 )],
2267 // Rule (19) and §7.4.1.2. The `Optional` role is what makes
2268 // the declared length follow the optional convention rather
2269 // than the `@mutable` one; the two differ in Cyclone.
2270 MemberHeader::OptionalCdr1 {
2271 pid,
2272 extended,
2273 must_understand,
2274 } => vec![bind_pid_header(
2275 chosen,
2276 (pid, plan.member_id),
2277 extended,
2278 "Optional",
2279 must_understand,
2280 )],
2281 // Rule (20), §7.4.3.5.2: a BOOLEAN `<is_present>`. Written by
2282 // `write_optional_member`, which is the only caller that reaches
2283 // this arm and knows whether the member is there.
2284 MemberHeader::OptionalFlag => Vec::new(),
2285 MemberHeader::EmHeader {
2286 lc,
2287 must_understand,
2288 } => {
2289 // The member id and the length code both come from the
2290 // plan: the id is the completion pass's, the code is the
2291 // policy's, and neither is decided again here.
2292 vec![bind_header(
2293 "emheader",
2294 "write_emheader",
2295 vec![
2296 rust::Expr::Literal(Literal::int(i128::from(plan.member_id))),
2297 rust::Expr::Literal(Literal::int(i128::from(lc as u8))),
2298 rust::Expr::Literal(Literal::Bool(must_understand)),
2299 ],
2300 )]
2301 }
2302 }
2303}
2304
2305/// The `let pid = …` that opens a PL_CDR1 parameter header, in the form
2306/// `plan` names or in the one the sample's flag chooses.
2307///
2308/// Both forms call the same two runtime methods, so the branch is over
2309/// the *call* and not over anything else: `finish_pid` patches whichever
2310/// header was opened and reads its form back off the stream
2311/// (§7.4.1.2.3), and the id, the role and the flag are the plan's in
2312/// either arm. `chosen` with a plan that already names the extended form
2313/// cannot happen: [`StepHeaders`] records a member as chosen only where
2314/// the two sides of the step disagree, and the plan the codec is written
2315/// from is the short side.
2316fn bind_pid_header(
2317 chosen: bool,
2318 (pid, member_id): (u16, u32),
2319 extended: bool,
2320 role: &str,
2321 must_understand: bool,
2322) -> rust::Statement {
2323 // The short header carries the 16-bit parameter id; the extended one
2324 // the member id in 28 bits (XTypes 1.3 §7.4.1.2.2). The plan's `pid`
2325 // saturates at `u16::MAX` for a member id past 16 bits — the extended
2326 // form is then mandatory and takes `member_id`, or a `@hashid` member
2327 // goes out as member 65535.
2328 let writer = |method: &str| {
2329 let id = if method == "write_pid_extended" {
2330 i128::from(member_id)
2331 } else {
2332 i128::from(pid)
2333 };
2334 rust::Expr::Try(Box::new(rust::Expr::MethodCall {
2335 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
2336 method: Ident::new(method),
2337 args: vec![
2338 rust::Expr::Literal(Literal::int(id)),
2339 parameter_role(role),
2340 rust::Expr::Literal(Literal::Bool(must_understand)),
2341 ],
2342 }))
2343 };
2344 let value = if !chosen {
2345 writer(if extended {
2346 "write_pid_extended"
2347 } else {
2348 "write_pid"
2349 })
2350 } else {
2351 // `zd_extended` is the sample's answer, resolved once by
2352 // [`sample_past_step`] in the entry that called this body and
2353 // threaded down to here.
2354 rust::Expr::If {
2355 condition: Box::new(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
2356 then: rust::Block {
2357 statements: vec![rust::Statement::Tail(writer("write_pid_extended"))],
2358 },
2359 otherwise: Some(rust::Block {
2360 statements: vec![rust::Statement::Tail(writer("write_pid"))],
2361 }),
2362 }
2363 };
2364 rust::Statement::Let {
2365 name: Ident::new("pid"),
2366 type_: None,
2367 value: Some(value),
2368 mutable: false,
2369 }
2370}
2371
2372/// The statements that close one member's framing, after its value.
2373fn close_header(plan: &zerodds_ast_layout::plan::MemberPlan) -> Vec<rust::Statement> {
2374 match plan.header {
2375 MemberHeader::NoHeader | MemberHeader::OptionalFlag => Vec::new(),
2376 MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => vec![call_statement(
2377 "finish_pid",
2378 vec![rust::Expr::Path(Path::single("pid"))],
2379 )],
2380 // `?`: under a length code of 0 to 3 the code is the member's
2381 // length, and `finish_emheader` refuses a value of another size
2382 // rather than let the header misdeclare it (§7.4.3.4.2).
2383 MemberHeader::EmHeader { .. } => vec![try_call_statement(
2384 "finish_emheader",
2385 vec![rust::Expr::Path(Path::single("emheader"))],
2386 )],
2387 }
2388}
2389
2390/// One `@optional` member's write, over the `Option<T>` field.
2391///
2392/// Three shapes, one per way the specification marks the absence, and
2393/// the *field* is what says which one this sample wants:
2394///
2395/// - rule (20), XCDR2 outside a `@mutable` type: a BOOLEAN
2396/// `<is_present>` either way, and the value only when it is `true`;
2397/// - rule (19), XCDR1 outside a `@mutable` type: the parameter header
2398/// either way, with a declared length of zero for the absence
2399/// (§7.4.1.2), which is what `finish_pid` computes from the bytes
2400/// that follow;
2401/// - inside a `@mutable` type: no parameter at all. §7.4.1.2.3 —
2402/// a member the stream does not carry is one the reader defaults, and
2403/// for an `@optional` member the default is the absence itself.
2404///
2405/// The match is over `&self.<name>`, so the binding is a borrow and the
2406/// value goes to the runtime the same way a borrowed field does.
2407///
2408/// `chosen` is [`open_header`]'s, passed on: an `@optional` member of a
2409/// `@mutable` type is framed as an `MMEMBER` and takes the same run-time
2410/// branch as every other member of the type.
2411fn write_optional_member(
2412 receiver: rust::Expr,
2413 plan: &zerodds_ast_layout::plan::MemberPlan,
2414 name: &str,
2415 kind: &MemberWrite,
2416 chosen: bool,
2417) -> Result<Vec<rust::Statement>, EncodeError> {
2418 let bound = rust::escape_keyword(name);
2419 let value = rust::Expr::Path(Path::single(&bound));
2420
2421 let (mut present, absent) = match plan.header {
2422 MemberHeader::OptionalFlag => (
2423 vec![call_statement(
2424 "write_bool",
2425 vec![rust::Expr::Literal(Literal::Bool(true))],
2426 )],
2427 vec![call_statement(
2428 "write_bool",
2429 vec![rust::Expr::Literal(Literal::Bool(false))],
2430 )],
2431 ),
2432 // The header is outside the match: it is written whether or not
2433 // the value is, and its declared length is the marker.
2434 MemberHeader::OptionalCdr1 { .. } => (Vec::new(), Vec::new()),
2435 // A parameter list simply omits the parameter, so the header
2436 // goes inside the arm with the value.
2437 _ => (open_header(plan, chosen), Vec::new()),
2438 };
2439 present.push(write_value_to("w", name, kind, value, true)?);
2440 if !matches!(plan.header, MemberHeader::OptionalCdr1 { .. }) {
2441 present.extend(close_header(plan));
2442 }
2443
2444 let matched = rust::Statement::Semi(rust::Expr::Match {
2445 scrutinee: Box::new(rust::Expr::Ref {
2446 target: Box::new(rust::Expr::Field {
2447 receiver: Box::new(receiver),
2448 name: Ident::new(&bound),
2449 }),
2450 mutable: false,
2451 }),
2452 arms: vec![
2453 rust::MatchArm {
2454 pattern: rust::Pattern::TupleStruct {
2455 path: Path::single("Some"),
2456 elements: vec![rust::Pattern::Binding(Ident::new(&bound))],
2457 },
2458 guard: None,
2459 body: rust::Expr::Block(rust::Block {
2460 statements: present,
2461 }),
2462 },
2463 rust::MatchArm {
2464 pattern: rust::Pattern::Path(Path::single("None")),
2465 guard: None,
2466 body: rust::Expr::Block(rust::Block { statements: absent }),
2467 },
2468 ],
2469 });
2470
2471 if matches!(plan.header, MemberHeader::OptionalCdr1 { .. }) {
2472 let mut all = open_header(plan, chosen);
2473 all.push(matched);
2474 all.extend(close_header(plan));
2475 return Ok(all);
2476 }
2477 Ok(vec![matched])
2478}
2479
2480/// `fn key_holder_be(&self) -> Result<Vec<u8>, WireError>`,
2481/// `fn key_from_holder_be(r: &mut Reader) -> Result<Self, WireError>` and
2482/// `fn key_hash(&self) -> Result<[u8; 16], WireError>`, for a keyed type.
2483///
2484/// The two are one function apart — §7.6.8 steps 1 to 4 build the
2485/// holder, step 5 turns it into the hash — and they are emitted together
2486/// so that no caller can reach the hash through a holder written by
2487/// anything but this plan.
2488///
2489/// Empty for a type with no `@key` member: a KeyHash over nothing is
2490/// not a KeyHash, and emitting one would give every instance of that
2491/// type the same handle. Settled in favour of this position after
2492/// `wireprobe-zerodds --emit keyhash` was found answering a keyless type
2493/// with sixteen zero bytes, or with the MD5 of nothing when the type was
2494/// `@mutable`. Cyclone, RTI and Fast DDS all decide on "no keys" before
2495/// any hash is computed and produce none;
2496/// `crates/wire-probe/src/encode.rs::key_hash` carries the measurement
2497/// and now refuses as well.
2498///
2499/// The holder is always big-endian — DDSI-RTPS 2.5 §9.6.3.8 — and that
2500/// fact comes from `key_holder_plan_with`, not from a second reading of
2501/// the model here.
2502///
2503/// What the holder *contains* and in what order does take the dialect.
2504/// This column used to plan the holder under `LayoutKey::key`, whose
2505/// comment said "§7.6.8 leaves no room for a dialect"; three of the
2506/// holder's rules are measured divergences and the other three columns
2507/// have selected them for a while — `KeyHolderExtensibility` (RTI keeps
2508/// the type's own kind under XCDR1), `KeyHolderNesting` (RTI stops
2509/// filtering below a keyless aggregate under XCDR1) and
2510/// `KeyHolderOrder` (RTI under XCDR1 keeps declaration order).
2511/// A column that ignored all three answered `--dialect rti` with
2512/// Cyclone's instance handle.
2513fn key_holder_fns(
2514 ast: &Ast,
2515 type_: Id<TypeNode>,
2516 data_key: LayoutKey,
2517) -> Result<Vec<rust::Item>, EncodeError> {
2518 // The holder's own key, not the data key: big-endian and the key
2519 // purpose, both of which §7.6.8 fixes, over the dialect the data
2520 // plan was built with.
2521 let key = LayoutKey {
2522 purpose: zerodds_ast_layout::Purpose::Key,
2523 endianness: zerodds_ast_layout::Endianness::Big,
2524 ..data_key
2525 };
2526 let keys = zerodds_ast_layout::keyhash::key_members_with(
2527 ast,
2528 type_,
2529 key.key_holder_nesting,
2530 key.key_holder_order,
2531 )
2532 .map_err(|error| EncodeError::NoLayout {
2533 detail: alloc_format(&error),
2534 })?;
2535 if keys.is_empty() {
2536 return Ok(Vec::new());
2537 }
2538
2539 let form =
2540 zerodds_ast_layout::keyhash::key_hash_form_for(ast, type_, key).map_err(|error| {
2541 EncodeError::NoLayout {
2542 detail: alloc_format(&error),
2543 }
2544 })?;
2545
2546 let mut statements = vec![rust::Statement::Let {
2547 name: Ident::new("holder"),
2548 type_: None,
2549 // An associated function, not a method: the constructor takes
2550 // both the order and the alignment cap, and `Writer.with_…()` is
2551 // not Rust.
2552 //
2553 // The cap is four and not the `Writer`'s own default of eight.
2554 // XTypes 1.3 §7.6.8 step 4 serialises the holder as PLAIN_CDR2,
2555 // whose `MAXALIGN` §7.4.1 gives as four, whatever version the
2556 // sample itself goes out in. `Writer::big_endian()` stood here
2557 // and left the cap at eight, so a holder whose plan put a
2558 // `@key double` at offset four had it written at offset eight —
2559 // a KeyHash that disagrees with the plan that sized it, and with
2560 // every peer, for every keyed type with an eight-octet key
2561 // member behind a four-octet one. Nothing reported it: a
2562 // KeyHash is sixteen opaque octets and a wrong one only shows up
2563 // as an instance that never matches.
2564 //
2565 // The order is written as an absolute path rather than through
2566 // an import, because the generated file's import list is built
2567 // by each consumer of this emitter and a new name in it would
2568 // have to be added in each one.
2569 value: Some(rust::Expr::Call {
2570 callee: Box::new(rust::Expr::Path(Path::new(&["Writer", "with_max_align"]))),
2571 args: vec![
2572 rust::Expr::Path(Path::new(&["zerodds_wire", "Endianness", "Big"])),
2573 // PLAIN_CDR2's four, unless the dialect aligns the holder
2574 // to its data encoding or packs it (`KeyHolderAlignment`).
2575 rust::Expr::Literal(Literal::int(i128::from(
2576 key.key_holder_alignment.max_alignment(key.encoding),
2577 ))),
2578 ],
2579 }),
2580 mutable: true,
2581 }];
2582
2583 // A nested key member is written through the **bare** codec, not
2584 // through a versioned one: XTypes 1.3 §7.6.8 step 4 serialises the
2585 // holder as PLAIN_CDR2 whatever version the sample goes out in, so
2586 // there is no XCDR1 key holder for an XCDR1 codec to call.
2587 let codec = "";
2588
2589 // Only the key members, in the order `key_members_with` gives —
2590 // which is the dialect's holder order, and the same order
2591 // `plan_aggregate` laid the holder out in.
2592 for node in &keys {
2593 let Some((name, kind)) = member_of(ast, type_, *node, key, codec) else {
2594 continue;
2595 };
2596 statements.push(write_value_to_sink(
2597 "holder",
2598 SinkShape::Owned,
2599 &name,
2600 &kind,
2601 rust::Expr::Field {
2602 receiver: Box::new(holder_of(ast, type_, *node)),
2603 name: Ident::new(&rust::escape_keyword(&name)),
2604 },
2605 // A key member is never `@optional` — §7.2.2.4.4.4.8, and
2606 // `idl-lower::validate::member_rules` refuses the pair — so
2607 // the field here is the member's own type and not an
2608 // `Option` of it.
2609 false,
2610 )?);
2611 }
2612
2613 // The octets of the holder itself, before step 5 turns them into a
2614 // hash. A `DdsType` impl needs exactly these: the runtime's
2615 // instance path writes the holder into a `PlainCdr2BeKeyHolder` and
2616 // decides the branch of step 5 from the type's declared maximum, so
2617 // handing it a finished hash would be handing it the wrong thing.
2618 let mut holder_statements = statements.clone();
2619 holder_statements.push(rust::Statement::Tail(rust::Expr::Call {
2620 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
2621 args: vec![rust::Expr::MethodCall {
2622 receiver: Box::new(rust::Expr::Path(Path::single("holder"))),
2623 method: Ident::new("finish"),
2624 args: Vec::new(),
2625 }],
2626 }));
2627 let key_holder_be = rust::Item::Function(rust::Function {
2628 doc: Doc::none(),
2629 attrs: Vec::new(),
2630 visibility: Visibility::Public,
2631 name: Ident::new("key_holder_be"),
2632 generics: Vec::new(),
2633 params: vec![rust::Param {
2634 name: None,
2635 type_: rust::Type::unit(),
2636 receiver: Some(rust::Receiver::Ref),
2637 }],
2638 // Fallible for the same reason `key_hash` is.
2639 returns: Some(rust::Type::generic(
2640 Path::single("Result"),
2641 vec![
2642 rust::Type::generic(
2643 Path::single("Vec"),
2644 vec![rust::Type::Primitive(rust::Primitive::U8)],
2645 ),
2646 rust::Type::named(Path::single("WireError")),
2647 ],
2648 )),
2649 body: Some(rust::Block {
2650 statements: holder_statements,
2651 }),
2652 });
2653
2654 // The inverse of `key_holder_be`: the same key members, read in the
2655 // same order the same plan wrote them.
2656 //
2657 // XTypes 1.3 §7.6.8 builds a holder that is not the type's own
2658 // stream — big-endian, `MAXALIGN` four, only the key members, no
2659 // DHEADER and no member headers — so `decode` cannot read one, and a
2660 // type that only had `encode_key_holder_be` could serialise an
2661 // instance's key and never read it back. DDS 1.4 §2.2.2.5.1.13 needs
2662 // exactly that read: on a dispose/unregister sample
2663 // `SampleInfo.valid_data` is `false` and "only the fields that form
2664 // the key" are meaningful, and those fields have to come from
2665 // somewhere.
2666 //
2667 // The members outside the holder are not in the stream at all and
2668 // take their §7.3.1.2.1.10 defaults through `Default::default()` —
2669 // the same values `default_impl` emits, so the two cannot drift.
2670 let mut from_holder = Vec::new();
2671 let mut key_fields = Vec::new();
2672 // A key member this emitter cannot describe is not skipped here. The
2673 // writer above skips it — a judged silent drop, allowlisted in
2674 // `tools/silent-drop-guard.sh` — but a *reader* that skipped one would
2675 // read the following member off the octets of the one it left out and
2676 // hand back a key that is wrong rather than absent. The whole reader
2677 // refuses instead, and says which member it could not read.
2678 let mut undescribed = None;
2679 for node in &keys {
2680 match member_of(ast, type_, *node, key, codec) {
2681 Some((name, kind)) => {
2682 let field = rust::escape_keyword(&name);
2683 from_holder.push(let_statement(
2684 &field,
2685 rust::Expr::Try(Box::new(read_for(&name, &kind)?)),
2686 ));
2687 key_fields.push((
2688 *node,
2689 (Ident::new(&field), rust::Expr::Path(Path::single(&field))),
2690 ));
2691 }
2692 None => undescribed = Some(*node),
2693 }
2694 }
2695 if let Some(node) = undescribed {
2696 from_holder = vec![rust::Statement::Tail(refused_err(
2697 "r",
2698 &format!("a key holder with a member this emitter cannot read ({node:?})"),
2699 ))];
2700 } else {
2701 from_holder.push(rust::Statement::Tail(rust::Expr::Call {
2702 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
2703 args: vec![nested_struct_literal(ast, type_, key_fields, &|| {
2704 Some(Box::new(rust::Expr::Call {
2705 callee: Box::new(rust::Expr::Path(Path::new(&["Default", "default"]))),
2706 args: Vec::new(),
2707 }))
2708 })],
2709 }));
2710 }
2711 let key_from_holder_be = rust::Item::Function(rust::Function {
2712 doc: Doc::none(),
2713 attrs: Vec::new(),
2714 visibility: Visibility::Public,
2715 name: Ident::new("key_from_holder_be"),
2716 generics: Vec::new(),
2717 params: vec![rust::Param {
2718 name: Some(Ident::new("r")),
2719 type_: rust::Type::Ref {
2720 target: Box::new(rust::Type::named(Path::single("Reader"))),
2721 mutable: true,
2722 lifetime: None,
2723 },
2724 receiver: None,
2725 }],
2726 returns: Some(rust::Type::generic(
2727 Path::single("Result"),
2728 vec![
2729 rust::Type::named(Path::single("Self")),
2730 rust::Type::named(Path::single("WireError")),
2731 ],
2732 )),
2733 body: Some(rust::Block {
2734 statements: from_holder,
2735 }),
2736 });
2737
2738 // A declared maximum of sixteen bytes or fewer: zero-pad the holder.
2739 // A larger or unbounded maximum: MD5. Variable length alone does not
2740 // select MD5; the shared maximum-size calculation decides.
2741 let finish = match form {
2742 zerodds_ast_layout::keyhash::KeyHashForm::ZeroPadded => "zero_padded",
2743 zerodds_ast_layout::keyhash::KeyHashForm::Md5 => "md5",
2744 };
2745 statements.push(rust::Statement::Tail(rust::Expr::Call {
2746 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
2747 args: vec![rust::Expr::MethodCall {
2748 receiver: Box::new(rust::Expr::Path(Path::single("holder"))),
2749 method: Ident::new(finish),
2750 args: Vec::new(),
2751 }],
2752 }));
2753
2754 Ok(vec![
2755 key_holder_be,
2756 key_from_holder_be,
2757 rust::Item::Function(rust::Function {
2758 doc: Doc::none(),
2759 attrs: Vec::new(),
2760 visibility: Visibility::Public,
2761 name: Ident::new("key_hash"),
2762 generics: Vec::new(),
2763 params: vec![rust::Param {
2764 name: None,
2765 type_: rust::Type::unit(),
2766 receiver: Some(rust::Receiver::Ref),
2767 }],
2768 // Fallible for the reason `encode` is: a `@key char` refuses
2769 // a code point above U+00FF, and a KeyHash that swallowed
2770 // the refusal would hash a character nobody wrote.
2771 returns: Some(rust::Type::generic(
2772 Path::single("Result"),
2773 vec![
2774 rust::Type::Array {
2775 element: Box::new(rust::Type::Primitive(rust::Primitive::U8)),
2776 length: Box::new(rust::Expr::Literal(Literal::int(16))),
2777 },
2778 rust::Type::named(Path::single("WireError")),
2779 ],
2780 )),
2781 body: Some(rust::Block { statements }),
2782 }),
2783 ])
2784}
2785
2786/// `fn decode(r: &mut Reader) -> Self`.
2787///
2788/// The mirror of the encoder, member for member and header for header,
2789/// built from the same plan — which is what makes the two agree by
2790/// construction rather than by review.
2791fn decode_fn(
2792 ast: &Ast,
2793 type_: Id<TypeNode>,
2794 layout: &TypeLayout,
2795 key: LayoutKey,
2796 codec: &'static str,
2797) -> Result<rust::Item, EncodeError> {
2798 let statements = if ast.effective_extensibility(type_) == zerodds_ast::Extensibility::Mutable {
2799 decode_by_member_id(ast, type_, layout, key, codec)?
2800 } else {
2801 decode_in_plan_order(ast, type_, layout, key, codec)?
2802 };
2803 // The read side of `encode_fns`'s block: an aggregate value's members
2804 // align from its own first octet under `ElementPadding::Packed`.
2805 let statements = wrap_packed_body(
2806 statements,
2807 "r",
2808 key.element_padding == ElementPadding::Packed,
2809 );
2810
2811 Ok(rust::Item::Function(rust::Function {
2812 doc: Doc::none(),
2813 attrs: Vec::new(),
2814 visibility: Visibility::Public,
2815 name: Ident::new(&format!("decode{codec}")),
2816 generics: Vec::new(),
2817 params: vec![rust::Param {
2818 name: Some(Ident::new("r")),
2819 type_: rust::Type::Ref {
2820 target: Box::new(rust::Type::named(Path::single("Reader"))),
2821 mutable: true,
2822 lifetime: None,
2823 },
2824 receiver: None,
2825 }],
2826 returns: Some(rust::Type::generic(
2827 Path::single("Result"),
2828 vec![
2829 rust::Type::named(Path::single("Self")),
2830 rust::Type::named(Path::single("WireError")),
2831 ],
2832 )),
2833 body: Some(rust::Block { statements }),
2834 }))
2835}
2836
2837/// A `@final` or `@appendable` aggregate: the members where the plan
2838/// says, because that is where the writer put them.
2839///
2840/// §7.4.1.2.3 makes only a `@mutable` list unordered. A positional
2841/// aggregate that read by id would be inventing a framing its own
2842/// encoder does not write.
2843fn decode_in_plan_order(
2844 ast: &Ast,
2845 type_: Id<TypeNode>,
2846 layout: &TypeLayout,
2847 key: LayoutKey,
2848 codec: &'static str,
2849) -> Result<Vec<rust::Statement>, EncodeError> {
2850 // A writer with fewer members than this reader stops early, and
2851 // §7.2.4.4.8, Table 19, says "each non-optional member in a T1
2852 // object that is not present in the T2 object takes its default
2853 // value". Only an `@appendable` type may meet one: Table 19 requires
2854 // two final types to have the same set of member IDs.
2855 let appendable = ast.effective_extensibility(type_) == zerodds_ast::Extensibility::Appendable;
2856 let mut statements = Vec::new();
2857 if layout.needs_dheader {
2858 // The DHEADER is what says where the writer's members stopped —
2859 // "skipping the members an older reader does not know is what
2860 // the DHEADER is for". `end` is that offset; a type that cannot
2861 // meet a shorter writer still reads it, to move the cursor.
2862 if appendable {
2863 statements.push(let_statement(
2864 "end",
2865 rust::Expr::Binary {
2866 op: rust::BinaryOp::Add,
2867 lhs: Box::new(rust::Expr::Try(Box::new(call("read_dheader", Vec::new())))),
2868 rhs: Box::new(call("position", Vec::new())),
2869 },
2870 ));
2871 } else {
2872 statements.push(let_statement(
2873 "_dheader",
2874 rust::Expr::Try(Box::new(call("read_dheader", Vec::new()))),
2875 ));
2876 }
2877 }
2878
2879 let mut fields = Vec::new();
2880 for plan in &layout.members {
2881 let Some((name, kind)) = member_of(ast, type_, plan.member, key, codec) else {
2882 continue;
2883 };
2884 // The appendable tail. Under encoding version 2 the delimiter
2885 // says where the writer stopped; under version 1 §7.2.4.2 leaves
2886 // an appendable type undelimited and the payload's end is what
2887 // all three vendors use instead — Cyclone, RTI and Fast DDS each
2888 // default the tail of a short XCDR1 stream, measured 2026-08-09
2889 // as `evo-appendable-tail`.
2890 if appendable && matches!(plan.header, MemberHeader::NoHeader) {
2891 let Some(member) = member_node_of(ast, type_, plan.member) else {
2892 continue;
2893 };
2894 let Some(member_type) = member.type_.resolved() else {
2895 continue;
2896 };
2897 let reached = if layout.needs_dheader {
2898 rust::Expr::Binary {
2899 op: rust::BinaryOp::Ge,
2900 lhs: Box::new(call("position", Vec::new())),
2901 rhs: Box::new(rust::Expr::Path(Path::single("end"))),
2902 }
2903 } else {
2904 rust::Expr::Binary {
2905 op: rust::BinaryOp::Eq,
2906 lhs: Box::new(call("remaining", Vec::new())),
2907 rhs: Box::new(rust::Expr::Literal(Literal::int(0))),
2908 }
2909 };
2910 statements.push(let_statement(
2911 &name,
2912 rust::Expr::If {
2913 condition: Box::new(reached),
2914 then: rust::Block {
2915 statements: vec![rust::Statement::Tail(
2916 crate::default_rust::member_default(
2917 ast,
2918 module_of(ast, type_),
2919 member,
2920 member_type,
2921 )?,
2922 )],
2923 },
2924 otherwise: Some(rust::Block {
2925 statements: vec![rust::Statement::Tail(rust::Expr::Try(Box::new(
2926 read_for(&name, &kind)?,
2927 )))],
2928 }),
2929 },
2930 ));
2931 fields.push((plan.member, field_binding(&name)));
2932 continue;
2933 }
2934 // An `@optional` member reads into an `Option<T>`, so the marker
2935 // the specification puts on the wire decides between `Some` and
2936 // `None` instead of between a value and a refusal.
2937 if member_node_of(ast, type_, plan.member).is_some_and(|member| member.facets.is_optional())
2938 {
2939 statements.extend(read_optional_member(plan, &name, &kind)?);
2940 fields.push((plan.member, field_binding(&name)));
2941 continue;
2942 }
2943 match plan.header {
2944 MemberHeader::NoHeader | MemberHeader::OptionalFlag => {}
2945 MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => {
2946 statements.push(let_statement(
2947 "pid",
2948 rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
2949 ));
2950 }
2951 MemberHeader::EmHeader { .. } => {
2952 statements.push(let_statement(
2953 "emheader",
2954 rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
2955 ));
2956 }
2957 }
2958 // Every read is fallible, so every read is followed by `?`. A
2959 // decoder that could not fail would have to guess at bytes that
2960 // are not there, and a guess is a value nobody sent.
2961 statements.push(let_statement(
2962 &name,
2963 rust::Expr::Try(Box::new(read_for(&name, &kind)?)),
2964 ));
2965 // The mirror of the encoder's patch step. `Reader::read_pid`
2966 // makes the parameter's value its own alignment origin — §7.4.1.2
2967 // calls each parameter "a CDR-encapsulated block of data" — and
2968 // `finish_pid` is the only thing that puts the origin back and
2969 // steps over the padding to the next four-byte boundary.
2970 //
2971 // Without it the origin stayed at the last parameter's value
2972 // start for the rest of the aggregate. A parameter whose value
2973 // begins at an offset that is 4 modulo 8 then aligns an
2974 // eight-byte member after it four bytes late:
2975 // `@final struct S { @optional octet b; double c; };` writes `c`
2976 // at payload offset 8 and read it at 12. It also skipped
2977 // `check_declared_length`, so a peer's length that disagreed
2978 // with the value that followed it was accepted in silence.
2979 match plan.header {
2980 MemberHeader::NoHeader | MemberHeader::OptionalFlag | MemberHeader::EmHeader { .. } => {
2981 }
2982 MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => {
2983 statements.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
2984 "finish_pid",
2985 vec![rust::Expr::Path(Path::single("pid"))],
2986 )))));
2987 }
2988 }
2989 fields.push((plan.member, field_binding(&name)));
2990 }
2991
2992 // Rule (23): a PL_CDR1 parameter list ends at `PID_SENTINEL`, not at
2993 // the last member the reader recognises. The encoder writes one and
2994 // the decoder left it in the stream, which puts every byte after the
2995 // aggregate four positions early for any caller that reads on.
2996 if layout.needs_sentinel {
2997 statements.extend(read_sentinel());
2998 }
2999
3000 statements.push(rust::Statement::Tail(rust::Expr::Call {
3001 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
3002 args: vec![nested_struct_literal(ast, type_, fields, &|| None)],
3003 }));
3004
3005 Ok(statements)
3006}
3007
3008/// A `@mutable` aggregate: the members its headers name, in the order
3009/// the stream names them.
3010///
3011/// XTypes 1.3 §7.4.1.2.3 — "any mutable structure member's value may be
3012/// omitted" — and §7.4.3.4.2, which gives every member an EMHEADER
3013/// carrying its id. A `@mutable` list is unordered by construction and
3014/// the ids are what orders it; this column read it in *plan* order
3015/// instead, so a member the stream omitted took the next member's bytes
3016/// and a member the peer reordered was read as the wrong member. Both
3017/// produce a sample and neither reports anything. The reflective, C and
3018/// Python columns have read by id since the evolution run.
3019///
3020/// Two loops, one per encoding version, because the two have different
3021/// ends: rule (21) bounds the list with the DHEADER's extent and rule
3022/// (23) with `PID_SENTINEL`.
3023///
3024/// A member the stream did not carry takes its default afterwards —
3025/// §7.4.1.2.3, "if the receiver's member is not optional, it logically
3026/// takes its default value" — and an `@optional` one takes the absence,
3027/// which is what its slot already holds.
3028fn decode_by_member_id(
3029 ast: &Ast,
3030 type_: Id<TypeNode>,
3031 layout: &TypeLayout,
3032 key: LayoutKey,
3033 codec: &'static str,
3034) -> Result<Vec<rust::Statement>, EncodeError> {
3035 let mut slots = Vec::new();
3036 for plan in &layout.members {
3037 let Some((name, kind)) = member_of(ast, type_, plan.member, key, codec) else {
3038 continue;
3039 };
3040 slots.push(MutableSlot {
3041 name,
3042 kind,
3043 member_id: plan.member_id,
3044 });
3045 }
3046
3047 let mut statements = Vec::new();
3048 // Rule (21): under encoding version 2 the DHEADER is what says where
3049 // the list stops. Under version 1 rule (23) uses `PID_SENTINEL`
3050 // instead and there is no delimiter to read.
3051 if layout.needs_dheader {
3052 statements.push(let_statement(
3053 "end",
3054 rust::Expr::Binary {
3055 op: rust::BinaryOp::Add,
3056 lhs: Box::new(rust::Expr::Try(Box::new(call("read_dheader", Vec::new())))),
3057 rhs: Box::new(call("position", Vec::new())),
3058 },
3059 ));
3060 }
3061 // One slot per member, empty until a header names it. `Option` is
3062 // also the "seen" flag: a second header for the same id is a stream
3063 // that says two things about one member, and §7.4.3.4.2 gives each
3064 // one header.
3065 for slot in &slots {
3066 statements.push(rust::Statement::Let {
3067 name: Ident::new(&rust::escape_keyword(&slot.name)),
3068 type_: None,
3069 value: Some(rust::Expr::Path(Path::single("None"))),
3070 mutable: true,
3071 });
3072 }
3073
3074 if key.encoding.is_v1() {
3075 statements.extend(parameter_list_loop(&slots)?);
3076 } else {
3077 statements.extend(member_header_loop(&slots)?);
3078 }
3079
3080 // The members no header named. Written after the loop and not
3081 // inside it, because "did not arrive" is only knowable once the
3082 // list has ended.
3083 let mut fields = Vec::new();
3084 for plan in &layout.members {
3085 let Some((name, _)) = member_of(ast, type_, plan.member, key, codec) else {
3086 continue;
3087 };
3088 let bound = rust::escape_keyword(&name);
3089 let optional = member_node_of(ast, type_, plan.member)
3090 .is_some_and(|member| member.facets.is_optional());
3091 if !optional {
3092 let Some(member) = member_node_of(ast, type_, plan.member) else {
3093 continue;
3094 };
3095 let Some(member_type) = member.type_.resolved() else {
3096 continue;
3097 };
3098 statements.push(let_statement(
3099 &name,
3100 or_default(
3101 &bound,
3102 crate::default_rust::member_default(
3103 ast,
3104 module_of(ast, type_),
3105 member,
3106 member_type,
3107 )?,
3108 ),
3109 ));
3110 }
3111 fields.push((
3112 plan.member,
3113 (Ident::new(&bound), rust::Expr::Path(Path::single(&bound))),
3114 ));
3115 }
3116
3117 statements.push(rust::Statement::Tail(rust::Expr::Call {
3118 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
3119 args: vec![nested_struct_literal(ast, type_, fields, &|| None)],
3120 }));
3121 Ok(statements)
3122}
3123
3124/// The value of a member no header named: `bound` if one did, its
3125/// default if not.
3126///
3127/// Spelled the way clippy asks for, so a crate including the generated
3128/// text needs no allowance: `unwrap_or_default()` where Table 9's value
3129/// is also Rust's `Default` (a zero, `false`, `'\0'`, an empty `String`,
3130/// `Vec` or `BTreeMap`), `unwrap_or(value)` where the value is a constant
3131/// expression, and `unwrap_or_else(|| value)` where building it calls
3132/// something. A `match` with a `None` arm was all three, and
3133/// `manual_unwrap_or{,_default}` fired once per member.
3134fn or_default(bound: &str, value: rust::Expr) -> rust::Expr {
3135 let receiver = Box::new(rust::Expr::Path(Path::single(bound)));
3136 if is_rust_default(&value) {
3137 return rust::Expr::MethodCall {
3138 receiver,
3139 method: Ident::new("unwrap_or_default"),
3140 args: Vec::new(),
3141 };
3142 }
3143 if is_constant(&value) {
3144 return rust::Expr::MethodCall {
3145 receiver,
3146 method: Ident::new("unwrap_or"),
3147 args: vec![value],
3148 };
3149 }
3150 rust::Expr::MethodCall {
3151 receiver,
3152 method: Ident::new("unwrap_or_else"),
3153 args: vec![rust::Expr::Closure {
3154 params: Vec::new(),
3155 body: Box::new(value),
3156 }],
3157 }
3158}
3159
3160/// Whether `value` is what `Default::default()` gives for its type.
3161fn is_rust_default(value: &rust::Expr) -> bool {
3162 match value {
3163 rust::Expr::Literal(Literal::Int(text)) => text == "0",
3164 rust::Expr::Literal(Literal::Float(text)) => {
3165 matches!(text.as_str(), "0.0" | "0.0f32" | "0.0f64")
3166 }
3167 rust::Expr::Literal(Literal::Bool(flag)) => !flag,
3168 rust::Expr::Literal(Literal::Char(ch)) => *ch == '\0',
3169 rust::Expr::Call { callee, args } if args.is_empty() => {
3170 let rust::Expr::Path(path) = callee.as_ref() else {
3171 return false;
3172 };
3173 let names: Vec<&str> = path.segments().iter().map(Ident::as_str).collect();
3174 matches!(
3175 names.as_slice(),
3176 ["String", "new"] | ["Vec", "new"] | ["BTreeMap", "new"]
3177 )
3178 }
3179 _ => false,
3180 }
3181}
3182
3183/// Whether `value` is built without calling anything: literals, paths,
3184/// and aggregates of them.
3185///
3186/// zerodds-lint: recursion-depth = the default value's nesting, one level
3187/// per composite literal; `default_rust` bounds it at 64.
3188fn is_constant(value: &rust::Expr) -> bool {
3189 match value {
3190 rust::Expr::Literal(_) | rust::Expr::Path(_) => true,
3191 rust::Expr::Array(elements) | rust::Expr::Tuple(elements) => {
3192 elements.iter().all(is_constant)
3193 }
3194 rust::Expr::Repeat { value, .. } => is_constant(value),
3195 rust::Expr::StructLit { fields, base, .. } => {
3196 base.is_none() && fields.iter().all(|(_, field)| is_constant(field))
3197 }
3198 rust::Expr::Cast { value, .. } => is_constant(value),
3199 _ => false,
3200 }
3201}
3202
3203/// `return Err(WireError::refused(r.position(), "…"));`
3204fn refuse_statement(why: &str) -> rust::Statement {
3205 rust::Statement::Semi(rust::Expr::Return(Some(Box::new(rust::Expr::Call {
3206 callee: Box::new(rust::Expr::Path(Path::single("Err"))),
3207 args: vec![rust::Expr::Call {
3208 callee: Box::new(rust::Expr::Path(Path::new(&["WireError", "refused"]))),
3209 args: vec![
3210 call("position", Vec::new()),
3211 rust::Expr::Literal(Literal::string(why)),
3212 ],
3213 }],
3214 }))))
3215}
3216
3217/// `if <slot>.is_some() { return Err(…); }`
3218fn refuse_a_second_header(slot: &str, member_id: u32) -> rust::Statement {
3219 rust::Statement::Semi(rust::Expr::If {
3220 condition: Box::new(rust::Expr::MethodCall {
3221 receiver: Box::new(rust::Expr::Path(Path::single(slot))),
3222 method: Ident::new("is_some"),
3223 args: Vec::new(),
3224 }),
3225 then: rust::Block {
3226 statements: vec![refuse_statement(&format!(
3227 "one header per member, and member id {member_id} has two"
3228 ))],
3229 },
3230 otherwise: None,
3231 })
3232}
3233
3234/// Rule (21): EMHEADERs until the delimiter's extent is used up.
3235fn member_header_loop(slots: &[MutableSlot]) -> Result<Vec<rust::Statement>, EncodeError> {
3236 let mut body = vec![
3237 let_statement(
3238 "header",
3239 rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
3240 ),
3241 // Where this member ends, whatever it turns out to be. Read
3242 // before the value, because after it the length code's own
3243 // rounding is no longer recoverable.
3244 let_statement(
3245 "ends",
3246 rust::Expr::Binary {
3247 op: rust::BinaryOp::Add,
3248 lhs: Box::new(call("position", Vec::new())),
3249 rhs: Box::new(rust::Expr::Field {
3250 receiver: Box::new(rust::Expr::Path(Path::single("header"))),
3251 name: Ident::new("extent"),
3252 }),
3253 },
3254 ),
3255 ];
3256
3257 // §7.2.2.4.4.4.6: a member this type does not declare is skipped
3258 // when the writer said it may be, and discards the whole sample
3259 // when it said it may not.
3260 let mut chain = Some(rust::Block {
3261 statements: vec![rust::Statement::Semi(rust::Expr::If {
3262 condition: Box::new(rust::Expr::Field {
3263 receiver: Box::new(rust::Expr::Path(Path::single("header"))),
3264 name: Ident::new("must_understand"),
3265 }),
3266 then: rust::Block {
3267 statements: vec![refuse_statement(
3268 "a must-understand member this type does not declare, per §7.2.2.4.4.4.6",
3269 )],
3270 },
3271 otherwise: Some(rust::Block {
3272 statements: vec![rust::Statement::Semi(rust::Expr::Try(Box::new(call(
3273 "skip_to",
3274 vec![rust::Expr::Path(Path::single("ends"))],
3275 ))))],
3276 }),
3277 })],
3278 });
3279
3280 for slot in slots.iter().rev() {
3281 let bound = rust::escape_keyword(&slot.name);
3282 let mut arm = vec![refuse_a_second_header(&bound, slot.member_id)];
3283 arm.push(rust::Statement::Semi(rust::Expr::Assign {
3284 target: Box::new(rust::Expr::Path(Path::single(&bound))),
3285 value: Box::new(rust::Expr::Call {
3286 callee: Box::new(rust::Expr::Path(Path::single("Some"))),
3287 args: vec![rust::Expr::Try(Box::new(read_for(&slot.name, &slot.kind)?))],
3288 }),
3289 }));
3290 // The header said how long the member is; a value that did not
3291 // fill it means the two disagree, and reading on would take the
3292 // next header out of this member's bytes.
3293 arm.push(rust::Statement::Semi(rust::Expr::If {
3294 condition: Box::new(rust::Expr::Binary {
3295 op: rust::BinaryOp::Ne,
3296 lhs: Box::new(call("position", Vec::new())),
3297 rhs: Box::new(rust::Expr::Path(Path::single("ends"))),
3298 }),
3299 then: rust::Block {
3300 statements: vec![refuse_statement(
3301 "a member filling the extent its EMHEADER declared",
3302 )],
3303 },
3304 otherwise: None,
3305 }));
3306 chain = Some(rust::Block {
3307 statements: vec![rust::Statement::Semi(rust::Expr::If {
3308 condition: Box::new(rust::Expr::Binary {
3309 op: rust::BinaryOp::Eq,
3310 lhs: Box::new(rust::Expr::Field {
3311 receiver: Box::new(rust::Expr::Path(Path::single("header"))),
3312 name: Ident::new("member_id"),
3313 }),
3314 rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(
3315 slot.member_id,
3316 )))),
3317 }),
3318 then: rust::Block { statements: arm },
3319 otherwise: chain,
3320 })],
3321 });
3322 }
3323 if let Some(chain) = chain {
3324 body.extend(chain.statements);
3325 }
3326
3327 Ok(vec![rust::Statement::Semi(rust::Expr::While {
3328 condition: Box::new(rust::Expr::Binary {
3329 op: rust::BinaryOp::Lt,
3330 lhs: Box::new(call("position", Vec::new())),
3331 rhs: Box::new(rust::Expr::Path(Path::single("end"))),
3332 }),
3333 body: rust::Block { statements: body },
3334 })])
3335}
3336
3337/// Rule (23): parameters until `PID_SENTINEL`.
3338fn parameter_list_loop(slots: &[MutableSlot]) -> Result<Vec<rust::Statement>, EncodeError> {
3339 // A sentinel is the only thing that ends the list, so the loop
3340 // condition is a flag the sentinel sets rather than a count.
3341 let mut statements = vec![rust::Statement::Let {
3342 name: Ident::new("ended"),
3343 type_: None,
3344 value: Some(rust::Expr::Literal(Literal::Bool(false))),
3345 mutable: true,
3346 }];
3347
3348 // Stepping over a parameter this type has no member for, in the
3349 // one place three different reasons reach it: an id the model does
3350 // not declare, a vendor-private parameter and one inside
3351 // §7.4.1.2.2's reserved range.
3352 let skip_parameter_block = rust::Block {
3353 statements: vec![rust::Statement::Semi(rust::Expr::Try(Box::new(call(
3354 "skip_parameter",
3355 vec![rust::Expr::Path(Path::single("pid"))],
3356 ))))],
3357 };
3358 let unknown = rust::Block {
3359 statements: vec![rust::Statement::Semi(rust::Expr::If {
3360 condition: Box::new(rust::Expr::Field {
3361 receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
3362 name: Ident::new("must_understand"),
3363 }),
3364 then: rust::Block {
3365 statements: vec![refuse_statement(
3366 "a must-understand parameter this type does not declare, per §7.2.2.4.4.4.6",
3367 )],
3368 },
3369 otherwise: Some(skip_parameter_block.clone()),
3370 })],
3371 };
3372 let mut chain = Some(unknown.clone());
3373
3374 for slot in slots.iter().rev() {
3375 let bound = rust::escape_keyword(&slot.name);
3376 let mut arm = vec![refuse_a_second_header(&bound, slot.member_id)];
3377 arm.push(rust::Statement::Semi(rust::Expr::Assign {
3378 target: Box::new(rust::Expr::Path(Path::single(&bound))),
3379 value: Box::new(rust::Expr::Call {
3380 callee: Box::new(rust::Expr::Path(Path::single("Some"))),
3381 args: vec![rust::Expr::Try(Box::new(read_for(&slot.name, &slot.kind)?))],
3382 }),
3383 }));
3384 arm.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
3385 "finish_pid",
3386 vec![rust::Expr::Path(Path::single("pid"))],
3387 )))));
3388 chain = Some(rust::Block {
3389 statements: vec![rust::Statement::Semi(rust::Expr::If {
3390 condition: Box::new(rust::Expr::Binary {
3391 op: rust::BinaryOp::Eq,
3392 lhs: Box::new(rust::Expr::Field {
3393 receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
3394 name: Ident::new("pid"),
3395 }),
3396 rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(
3397 slot.member_id,
3398 )))),
3399 }),
3400 then: rust::Block { statements: arm },
3401 otherwise: chain,
3402 })],
3403 });
3404 }
3405
3406 // Three gates in front of the id chain, in the order the clauses
3407 // rank them.
3408 //
3409 // `PID_IGNORE` first: §7.4.1.2.2 says "All consumers of this Data
3410 // Representation shall ignore parameters with this ID" and states no
3411 // exception, so it outranks the must-understand bit that `unknown`
3412 // would refuse on.
3413 //
3414 // Then `is_user_member`, which is false for a parameter carrying
3415 // `FLAG_IMPL_EXTENSION` and for one inside the reserved range. Both
3416 // are legal for a peer to send and neither is a member of this type,
3417 // so both take the unknown path instead of being matched by their
3418 // low bits against a member that happens to share them.
3419 let matched = rust::Block {
3420 statements: chain.map_or_else(Vec::new, |chain| chain.statements),
3421 };
3422 let tail = rust::Block {
3423 statements: vec![rust::Statement::Semi(rust::Expr::If {
3424 condition: Box::new(rust::Expr::Field {
3425 receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
3426 name: Ident::new("ignore"),
3427 }),
3428 then: skip_parameter_block,
3429 otherwise: Some(rust::Block {
3430 statements: vec![rust::Statement::Semi(rust::Expr::If {
3431 condition: Box::new(rust::Expr::MethodCall {
3432 receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
3433 method: Ident::new("is_user_member"),
3434 args: Vec::new(),
3435 }),
3436 then: matched,
3437 otherwise: Some(unknown),
3438 })],
3439 }),
3440 })],
3441 };
3442 let body = vec![
3443 let_statement(
3444 "pid",
3445 rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
3446 ),
3447 rust::Statement::Semi(rust::Expr::If {
3448 condition: Box::new(rust::Expr::Field {
3449 receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
3450 name: Ident::new("end"),
3451 }),
3452 then: rust::Block {
3453 statements: vec![rust::Statement::Semi(rust::Expr::Assign {
3454 target: Box::new(rust::Expr::Path(Path::single("ended"))),
3455 value: Box::new(rust::Expr::Literal(Literal::Bool(true))),
3456 })],
3457 },
3458 otherwise: Some(tail),
3459 }),
3460 ];
3461
3462 statements.push(rust::Statement::Semi(rust::Expr::While {
3463 condition: Box::new(rust::Expr::Not(Box::new(rust::Expr::Path(Path::single(
3464 "ended",
3465 ))))),
3466 body: rust::Block { statements: body },
3467 }));
3468 Ok(statements)
3469}
3470
3471/// What the two `@mutable` loops need of a member: the id its header
3472/// carries, the name its slot takes, and how to read it.
3473struct MutableSlot {
3474 /// The member's name, as the source wrote it.
3475 name: String,
3476 /// What reading it takes.
3477 kind: MemberWrite,
3478 /// The id its header carries.
3479 member_id: u32,
3480}
3481
3482/// The write call one member takes.
3483///
3484/// One arm per primitive rather than a formatted name: the writer for a
3485/// `u32` and the writer for an `i32` are different functions, and a
3486/// name assembled from a string would hide a wrong one until the bytes
3487/// came out different.
3488fn write_for(
3489 receiver: rust::Expr,
3490 name: &str,
3491 kind: &MemberWrite,
3492) -> Result<rust::Statement, EncodeError> {
3493 let value = rust::Expr::Field {
3494 receiver: Box::new(receiver),
3495 name: Ident::new(&rust::escape_keyword(name)),
3496 };
3497 write_value_to("w", name, kind, value, false)
3498}
3499
3500/// The same, naming the writer it goes to.
3501///
3502/// The KeyHash builds its own holder rather than writing to the
3503/// sample's writer: the holder is big-endian regardless of what the
3504/// sample is, and sharing one writer would make the hash depend on the
3505/// sample's byte order.
3506///
3507/// `borrowed` says the expression is already a `&T` — a binding out of
3508/// `match &self.<name> { Some(v) => … }`, or a closure's element —
3509/// rather than the field itself. It decides one thing per shape and no
3510/// more: a primitive is dereferenced, a string is not re-borrowed.
3511/// Passing the wrong one does not compile, which is the point of
3512/// carrying it rather than guessing from the shape of the expression.
3513///
3514/// `pub(crate)`: `emit::interface_rust` hand-assembles the DDS-RPC 1.0
3515/// §7.5.1.1 topic types the same way this file assembles an aggregate's
3516/// codec, member by member, and calls this rather than re-deriving how
3517/// one value goes onto a `Writer`.
3518pub(crate) fn write_value_to(
3519 sink: &str,
3520 name: &str,
3521 kind: &MemberWrite,
3522 value: rust::Expr,
3523 borrowed: bool,
3524) -> Result<rust::Statement, EncodeError> {
3525 write_value_to_sink(sink, SinkShape::Reference, name, kind, value, borrowed)
3526}
3527
3528/// Whether the name the writes go to is already a `&mut Writer`.
3529///
3530/// Every arm but one calls a method on the sink, and a method call
3531/// auto-references. `MemberWrite::Nested` hands it over as an *argument* —
3532/// `self.k.encode(sink)` — and there Rust does not: a local `Writer` where a
3533/// `&mut Writer` is wanted is `error[E0308]`, in generated code, which is the
3534/// worst place for it. The key-holder path is the one with a local, so it says
3535/// so instead of the emitter guessing from the name.
3536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3537enum SinkShape {
3538 /// A `&mut Writer` parameter — pass it along as it is.
3539 Reference,
3540 /// A local `Writer` binding — take a reference where one is needed.
3541 Owned,
3542}
3543
3544fn write_value_to_sink(
3545 sink: &str,
3546 shape: SinkShape,
3547 name: &str,
3548 kind: &MemberWrite,
3549 value: rust::Expr,
3550 borrowed: bool,
3551) -> Result<rust::Statement, EncodeError> {
3552 let (call, fallible) = write_call(sink, shape, name, kind, value, borrowed)?;
3553 // A write that can refuse is followed by `?`. Dropping the `Result`
3554 // would compile — it is only a warning — and would put the narrowed
3555 // character on the wire, which is the defect the runtime stopped
3556 // committing.
3557 Ok(rust::Statement::Semi(if fallible {
3558 rust::Expr::Try(Box::new(call))
3559 } else {
3560 call
3561 }))
3562}
3563
3564/// The call one value's write is, and whether it returns a `Result`.
3565///
3566/// One arm per shape, and the shape decides three things at once: which
3567/// runtime function, how the value is handed over, and whether the
3568/// result has to be looked at.
3569///
3570/// zerodds-lint: recursion-depth 64 (collection nesting of the owned
3571/// `MemberWrite` tree, via `element_writer`). That tree is built by
3572/// `write_of_at`, whose `MAX_WRITE_DEPTH` bounds its height, and it
3573/// is owned, so it holds no cycle of its own.
3574fn write_call(
3575 sink: &str,
3576 shape: SinkShape,
3577 name: &str,
3578 kind: &MemberWrite,
3579 value: rust::Expr,
3580 borrowed: bool,
3581) -> Result<(rust::Expr, bool), EncodeError> {
3582 let sink_expr = || rust::Expr::Path(Path::single(sink));
3583 let deref = |value: rust::Expr| {
3584 if borrowed {
3585 rust::Expr::Deref(Box::new(value))
3586 } else {
3587 value
3588 }
3589 };
3590 // A `String` field goes in by reference and a `&String` binding is
3591 // one already; both coerce to the `&str` the runtime takes. A
3592 // collection is the same question one type up — `&Vec<T>` and
3593 // `&[T; N]` both coerce to the `&[T]` the runtime takes.
3594 let by_ref = |value: rust::Expr| {
3595 if borrowed {
3596 value
3597 } else {
3598 rust::Expr::Ref {
3599 target: Box::new(value),
3600 mutable: false,
3601 }
3602 }
3603 };
3604 Ok(match kind {
3605 MemberWrite::External(inner) => {
3606 return write_call(
3607 sink,
3608 shape,
3609 name,
3610 inner,
3611 rust::Expr::MethodCall {
3612 receiver: Box::new(value),
3613 method: Ident::new("as_ref"),
3614 args: Vec::new(),
3615 },
3616 true,
3617 );
3618 }
3619 MemberWrite::Primitive(primitive) => (
3620 rust::Expr::MethodCall {
3621 receiver: Box::new(sink_expr()),
3622 method: Ident::new(writer_name(*primitive)),
3623 args: vec![deref(value)],
3624 },
3625 // `char` is one byte and `wchar` two, and a code point that
3626 // fits neither is refused rather than narrowed —
3627 // §7.2.2.2.1.2.2 and §7.2.2.2.1.2.5.
3628 matches!(primitive, PrimitiveKind::Char | PrimitiveKind::WChar),
3629 ),
3630 MemberWrite::String { bound, .. } => {
3631 // A bounded string writes the same bytes as an unbounded one
3632 // of the same length; the bound is a promise about the
3633 // source, not a difference on the wire. The runtime takes it
3634 // as a second argument and not as part of the name — and
3635 // refuses a value that overruns it, which is why the bounded
3636 // form is fallible and the plain one is not.
3637 let (method, mut args, fallible) = match bound {
3638 Some(bound) => (
3639 "write_string_bounded",
3640 vec![rust::Expr::Literal(Literal::int(*bound))],
3641 true,
3642 ),
3643 None => ("write_string", Vec::new(), false),
3644 };
3645 let mut all = vec![by_ref(value)];
3646 all.append(&mut args);
3647 (
3648 rust::Expr::MethodCall {
3649 receiver: Box::new(sink_expr()),
3650 method: Ident::new(method),
3651 args: all,
3652 },
3653 fallible,
3654 )
3655 }
3656 MemberWrite::Fixed {
3657 digits,
3658 scale,
3659 count,
3660 } => (
3661 rust::Expr::MethodCall {
3662 receiver: Box::new(sink_expr()),
3663 method: Ident::new("write_fixed"),
3664 args: vec![
3665 by_ref(value),
3666 rust::Expr::Literal(Literal::int(i128::from(*digits))),
3667 rust::Expr::Literal(Literal::int(i128::from(*scale))),
3668 fixed_digits_expr(*count),
3669 ],
3670 },
3671 // Always fallible: a value the declaration cannot carry is
3672 // refused rather than truncated, and a truncated decimal is the
3673 // wrong number with nothing to notice it by.
3674 true,
3675 ),
3676 MemberWrite::WString {
3677 bound,
3678 policy,
3679 units,
3680 order,
3681 position,
3682 ..
3683 } => {
3684 // Every write here is fallible, bounded or not: the
3685 // `CHARACTER_COUNT` cell refuses a code point outside the
3686 // BMP rather than truncating it to sixteen bits the way
3687 // Fast CDR does, so even an unbounded `wstring` can say no.
3688 //
3689 // `WstringCodeUnits::Utf8` names the runtime's UTF-8 pair,
3690 // which takes no order: UTF-8 octets have no byte order of
3691 // their own, and C1 and C3 are vacuous because there is no
3692 // fixed-width code unit for either to speak about.
3693 let (method, mut args) = match (units, bound) {
3694 (WstringCodeUnits::Utf8, Some(bound)) => (
3695 "write_wstring_utf8_bounded",
3696 vec![rust::Expr::Literal(Literal::int(*bound))],
3697 ),
3698 (WstringCodeUnits::Utf8, None) => ("write_wstring_utf8", Vec::new()),
3699 (WstringCodeUnits::Utf16, Some(bound)) => (
3700 "write_wstring_bounded",
3701 vec![rust::Expr::Literal(Literal::int(*bound))],
3702 ),
3703 (WstringCodeUnits::Utf16, None) => ("write_wstring", Vec::new()),
3704 };
3705 let mut all = vec![by_ref(value)];
3706 all.append(&mut args);
3707 all.push(wstring_policy_expr(*policy));
3708 if *units == WstringCodeUnits::Utf16 {
3709 all.push(wstring_order_expr(*order));
3710 }
3711 all.push(wire_position_expr(name, *position)?);
3712 (
3713 rust::Expr::MethodCall {
3714 receiver: Box::new(sink_expr()),
3715 method: Ident::new(method),
3716 args: all,
3717 },
3718 true,
3719 )
3720 }
3721 MemberWrite::Enumeration { bits } => (
3722 rust::Expr::MethodCall {
3723 receiver: Box::new(sink_expr()),
3724 method: Ident::new(enum_writer(*bits)),
3725 args: vec![deref(value)],
3726 },
3727 false,
3728 ),
3729 // The bound goes into the call and the holder is widened to the
3730 // `u64` the runtime takes. Fallible: a value with a bit set
3731 // above the bound is refused rather than masked, which is what
3732 // `runtime/c/src/writer.c` does with `ZDW_RANGE`.
3733 MemberWrite::Bitmask { bits, .. } => (
3734 rust::Expr::MethodCall {
3735 receiver: Box::new(sink_expr()),
3736 method: Ident::new("write_bitmask"),
3737 args: vec![
3738 rust::Expr::Literal(Literal::int(i128::from(*bits))),
3739 widen_to_u64(deref(value), *bits),
3740 ],
3741 },
3742 true,
3743 ),
3744 // An aggregate encodes itself. There is no `Writer::write_nested`
3745 // and there was never going to be one: the nested type's own
3746 // `encode` is what carries its plan, and a runtime function
3747 // could not have known it.
3748 //
3749 // Under a per-sample plan the call names the nested type's
3750 // **body** and hands it the sample's decision: the same nested
3751 // type is framed short in a sample below RTI's step and extended
3752 // in one past it, so a call through the public entry would frame
3753 // it by the nested type's own size instead of the sample's.
3754 MemberWrite::Nested {
3755 codec, extended, ..
3756 } => {
3757 let mut args = vec![match shape {
3758 SinkShape::Reference => sink_expr(),
3759 SinkShape::Owned => rust::Expr::Ref {
3760 target: Box::new(sink_expr()),
3761 mutable: true,
3762 },
3763 }];
3764 args.extend(extended.argument());
3765 (
3766 rust::Expr::MethodCall {
3767 receiver: Box::new(value),
3768 method: Ident::new(&nested_method(codec, *extended)),
3769 // The one place the sink is an argument and not a
3770 // receiver, so the one place auto-referencing does
3771 // not happen for us.
3772 args,
3773 },
3774 true,
3775 )
3776 }
3777 MemberWrite::Sequence {
3778 element,
3779 bound,
3780 delimited,
3781 packed,
3782 carries_length,
3783 ..
3784 } => {
3785 // `sequence<T, N>`: the count is checked before a byte is
3786 // written. Table 17 makes `O2.length <= T1.length` the
3787 // condition for constructing one, the wire carries no bound
3788 // to compare against, and a sender that wrote more elements
3789 // than its own declaration allows has written a value of a
3790 // type nobody declared. G16, on the write side.
3791 let method = match (bound, delimited) {
3792 (Some(_), true) => "write_delimited_sequence_bounded",
3793 (Some(_), false) => "write_sequence_bounded",
3794 (None, true) => "write_delimited_sequence",
3795 (None, false) => "write_sequence",
3796 };
3797 let mut args = vec![by_ref(value)];
3798 if let Some(bound) = bound {
3799 args.push(rust::Expr::Literal(Literal::int(*bound)));
3800 }
3801 args.push(sequence_element_writer(
3802 name,
3803 element,
3804 *packed,
3805 *carries_length,
3806 )?);
3807 (
3808 rust::Expr::MethodCall {
3809 receiver: Box::new(sink_expr()),
3810 method: Ident::new(method),
3811 args,
3812 },
3813 true,
3814 )
3815 }
3816 MemberWrite::Map {
3817 key,
3818 value: entry_value,
3819 bound,
3820 delimited,
3821 ascending,
3822 ..
3823 } => {
3824 // The count on the wire is the **entry** count and not the
3825 // number of halves — §7.4.1.1.4 defines a `map<K, V>` by
3826 // equivalence to a `sequence` whose element is the pair —
3827 // and the runtime writes it from `BTreeMap::len`, so there
3828 // is nothing to count here. `map<K, V, N>` bounds that same
3829 // number, which is why the bounded form passes it through
3830 // unchanged from the sequence's shape.
3831 let method = match (bound, delimited) {
3832 (Some(_), true) => "write_delimited_map_bounded",
3833 (Some(_), false) => "write_map_bounded",
3834 (None, true) => "write_delimited_map",
3835 (None, false) => "write_map",
3836 };
3837 // Held as its entries, the map goes out in the order it is held; ascending by key
3838 // is a view over them, and a `BTreeMap` is ascending already.
3839 let held = match crate::adapter::cst::rust_map_form() {
3840 crate::adapter::cst::RustMapForm::Entries if *ascending => rust::Expr::Ref {
3841 target: Box::new(rust::Expr::Call {
3842 callee: Box::new(rust::Expr::Path(Path::new(&[
3843 "zerodds_wire",
3844 "AscendingKey",
3845 ]))),
3846 args: vec![by_ref(value)],
3847 }),
3848 mutable: false,
3849 },
3850 _ => by_ref(value),
3851 };
3852 let mut args = vec![held];
3853 if let Some(bound) = bound {
3854 args.push(rust::Expr::Literal(Literal::int(*bound)));
3855 }
3856 args.push(element_writer(name, key)?);
3857 args.push(element_writer(name, entry_value)?);
3858 (
3859 rust::Expr::MethodCall {
3860 receiver: Box::new(sink_expr()),
3861 method: Ident::new(method),
3862 args,
3863 },
3864 true,
3865 )
3866 }
3867 MemberWrite::Array {
3868 element,
3869 dims,
3870 delimited,
3871 packed,
3872 } => {
3873 // No length: an array's element count is in the type, not on
3874 // the wire. XTypes 1.3 §7.4.3.5. A multi-dimensional array is
3875 // one flat run (G4), so the delimiter — where rule (9) asks
3876 // for one — goes round the whole thing and the inner
3877 // dimensions are plain.
3878 (
3879 rust::Expr::MethodCall {
3880 receiver: Box::new(sink_expr()),
3881 method: Ident::new(if *delimited {
3882 "write_delimited_array"
3883 } else {
3884 "write_array"
3885 }),
3886 args: vec![
3887 by_ref(value),
3888 array_element_writer(name, element, dims.len().saturating_sub(1), *packed)?,
3889 ],
3890 },
3891 true,
3892 )
3893 }
3894 MemberWrite::Unsupported(kind) => {
3895 return Err(EncodeError::NoWriter {
3896 member: name.to_string(),
3897 kind,
3898 });
3899 }
3900 })
3901}
3902
3903/// The digit-count axis as the generated code names it.
3904///
3905/// A path and not a boolean: the emitted call says `FixedDigits::Declared`
3906/// or `FixedDigits::ValuePrecision`, so a reader of the generated file sees
3907/// which reading of CORBA 3.3 part 2 §9.3.2.8 it writes without going back
3908/// to the plan that chose it.
3909fn fixed_digits_expr(count: FixedDigitCount) -> rust::Expr {
3910 rust::Expr::Path(Path::new(&[
3911 "zerodds_wire",
3912 "FixedDigits",
3913 match count {
3914 FixedDigitCount::Declared => "Declared",
3915 FixedDigitCount::ValuePrecision => "ValuePrecision",
3916 FixedDigitCount::ValueStringDigits => "ValueStringDigits",
3917 },
3918 ]))
3919}
3920
3921/// `|w, v| <the element's write>`, always returning a `Result`.
3922///
3923/// A closure and not a named path. `Writer::write_i32` takes an `i32` by
3924/// value where an element function takes a `&i32`; a nested aggregate's
3925/// write is a method on the *value*; and a collection's write takes its
3926/// own element function as a second argument, which no path can supply.
3927/// The runtime's element functions were made `Fn`-generic and the CST
3928/// gained `Expr::Closure` for exactly this, and then nothing emitted
3929/// one — `element_writer_name` kept naming paths and refused the two
3930/// shapes that have none. That refusal is `AL-9`, and this is it closed.
3931///
3932/// zerodds-lint: recursion-depth 64 (collection nesting of the owned
3933/// `MemberWrite` tree, via `write_call`). That tree is built by
3934/// `write_of_at`, whose `MAX_WRITE_DEPTH` bounds its height, and it
3935/// is owned, so it holds no cycle of its own.
3936fn element_writer(at: &str, element: &MemberWrite) -> Result<rust::Expr, EncodeError> {
3937 // A `wstring` element is written at the element position, which is
3938 // substituted here for the same reason the `wchar` element's
3939 // framing is: `write_call` was handed a member's shape, and a
3940 // caller that could leave the position alone would take the
3941 // member's answer for an element by not asking.
3942 let substituted = as_element(element);
3943 let element = substituted.as_ref().unwrap_or(element);
3944 // A `wchar` element takes the element framing, which under TAO's
3945 // dialect is not the member's. `write_call` names the member's
3946 // method, so the element's is substituted here rather than passed
3947 // down through five shapes that do not care.
3948 if matches!(element, MemberWrite::Primitive(PrimitiveKind::WChar)) {
3949 return Ok(rust::Expr::Path(Path::new(&[
3950 "Writer",
3951 "write_wchar_element",
3952 ])));
3953 }
3954 let (call, fallible) = write_call(
3955 "w",
3956 SinkShape::Reference,
3957 at,
3958 element,
3959 rust::Expr::Path(Path::single("v")),
3960 true,
3961 )?;
3962 Ok(rust::Expr::Closure {
3963 params: vec![Ident::new("w"), Ident::new("v")],
3964 // An infallible write followed by `Ok(())`: one element-function
3965 // shape rather than two, because three of the four element kinds
3966 // can refuse and a generator choosing between two shapes would
3967 // be choosing per construct. Not `Ok(call)`: that hands the
3968 // write's `()` to a function, which is `clippy::unit_arg` in
3969 // every crate that includes the generated text.
3970 body: Box::new(if fallible {
3971 call
3972 } else {
3973 rust::Expr::Block(rust::Block {
3974 statements: vec![
3975 rust::Statement::Semi(call),
3976 rust::Statement::Tail(rust::Expr::Call {
3977 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
3978 args: vec![rust::Expr::Tuple(Vec::new())],
3979 }),
3980 ],
3981 })
3982 }),
3983 })
3984}
3985
3986/// The element function for an array with `remaining` dimensions still
3987/// to unwrap.
3988///
3989/// `long grid[2][3]` is `[[i32; 3]; 2]`, so the outer `write_array`
3990/// hands each row to a function that writes a row.
3991///
3992/// `packed` wraps each level's element in a
3993/// `Writer::begin_packed_element` / `finish_packed_element` pair, at the
3994/// same site `zerodds-wire-codec`'s `write_array_dimension` writes them:
3995/// the row and every cell inside it each begin their own alignment block.
3996///
3997/// zerodds-lint: recursion-depth 8 (one level per dimension of the array
3998/// declarator: `remaining` falls by one per call and the cell ends it; the
3999/// depth is the number of dimensions the IDL writes, not a property of a
4000/// sample)
4001fn array_element_writer(
4002 at: &str,
4003 element: &MemberWrite,
4004 remaining: usize,
4005 packed: bool,
4006) -> Result<rust::Expr, EncodeError> {
4007 if remaining == 0 {
4008 // A cell: the element position and the packed block, and never an
4009 // element length — only a `sequence` has that axis.
4010 return sequence_element_writer(at, element, packed, false);
4011 }
4012 let inner = array_element_writer(at, element, remaining - 1, packed)?;
4013 let call = rust::Expr::MethodCall {
4014 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
4015 method: Ident::new("write_array"),
4016 args: vec![rust::Expr::Path(Path::single("v")), inner],
4017 };
4018 if !packed {
4019 return Ok(rust::Expr::Closure {
4020 params: vec![Ident::new("w"), Ident::new("v")],
4021 body: Box::new(call),
4022 });
4023 }
4024 Ok(rust::Expr::Closure {
4025 params: vec![Ident::new("w"), Ident::new("v")],
4026 body: Box::new(rust::Expr::Block(rust::Block {
4027 statements: vec![
4028 let_statement("zd_origin", begin_packed("w")),
4029 rust::Statement::Semi(rust::Expr::Try(Box::new(call))),
4030 rust::Statement::Semi(finish_packed("w", "zd_origin")),
4031 rust::Statement::Tail(ok_unit()),
4032 ],
4033 })),
4034 })
4035}
4036
4037/// The element function a `sequence`'s write takes, with the two HDDS
4038/// axes the reference codec puts around each element.
4039///
4040/// With neither axis selected this is [`element_writer`] unchanged, so
4041/// every other dialect's emitted code is the same text it was.
4042fn sequence_element_writer(
4043 at: &str,
4044 element: &MemberWrite,
4045 packed: bool,
4046 carries_length: bool,
4047) -> Result<rust::Expr, EncodeError> {
4048 if !packed && !carries_length {
4049 return element_writer(at, element);
4050 }
4051 let (call, fallible) = element_write_call(at, element)?;
4052 let mut statements = Vec::new();
4053 if packed {
4054 statements.push(let_statement("zd_origin", begin_packed("w")));
4055 }
4056 if carries_length {
4057 statements.push(let_statement(
4058 "zd_length",
4059 rust::Expr::MethodCall {
4060 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
4061 method: Ident::new("write_dheader"),
4062 args: Vec::new(),
4063 },
4064 ));
4065 }
4066 statements.push(rust::Statement::Semi(if fallible {
4067 rust::Expr::Try(Box::new(call))
4068 } else {
4069 call
4070 }));
4071 if carries_length {
4072 statements.push(rust::Statement::Semi(finish_dheader("w", "zd_length")));
4073 }
4074 if packed {
4075 statements.push(rust::Statement::Semi(finish_packed("w", "zd_origin")));
4076 }
4077 statements.push(rust::Statement::Tail(ok_unit()));
4078 Ok(rust::Expr::Closure {
4079 params: vec![Ident::new("w"), Ident::new("v")],
4080 body: Box::new(rust::Expr::Block(rust::Block { statements })),
4081 })
4082}
4083
4084/// The write one collection element is, at the element position, and
4085/// whether it returns a `Result`.
4086///
4087/// [`write_call`] names a `wchar`'s *member* framing; a collection
4088/// element takes the other one, so it is substituted here. A `wstring`
4089/// element has its position rewritten by [`as_element`] first, for the
4090/// same reason.
4091fn element_write_call(at: &str, element: &MemberWrite) -> Result<(rust::Expr, bool), EncodeError> {
4092 let substituted = as_element(element);
4093 let element = substituted.as_ref().unwrap_or(element);
4094 if matches!(element, MemberWrite::Primitive(PrimitiveKind::WChar)) {
4095 return Ok((
4096 rust::Expr::Call {
4097 callee: Box::new(rust::Expr::Path(Path::new(&[
4098 "Writer",
4099 "write_wchar_element",
4100 ]))),
4101 args: vec![
4102 rust::Expr::Path(Path::single("w")),
4103 rust::Expr::Path(Path::single("v")),
4104 ],
4105 },
4106 true,
4107 ));
4108 }
4109 write_call(
4110 "w",
4111 SinkShape::Reference,
4112 at,
4113 element,
4114 rust::Expr::Path(Path::single("v")),
4115 true,
4116 )
4117}
4118
4119/// `w.begin_packed_element()`.
4120fn begin_packed(sink: &str) -> rust::Expr {
4121 rust::Expr::MethodCall {
4122 receiver: Box::new(rust::Expr::Path(Path::single(sink))),
4123 method: Ident::new("begin_packed_element"),
4124 args: Vec::new(),
4125 }
4126}
4127
4128/// `w.finish_packed_element(saved)`.
4129fn finish_packed(sink: &str, saved: &str) -> rust::Expr {
4130 rust::Expr::MethodCall {
4131 receiver: Box::new(rust::Expr::Path(Path::single(sink))),
4132 method: Ident::new("finish_packed_element"),
4133 args: vec![rust::Expr::Path(Path::single(saved))],
4134 }
4135}
4136
4137/// `w.finish_dheader(header)`.
4138fn finish_dheader(sink: &str, header: &str) -> rust::Expr {
4139 rust::Expr::MethodCall {
4140 receiver: Box::new(rust::Expr::Path(Path::single(sink))),
4141 method: Ident::new("finish_dheader"),
4142 args: vec![rust::Expr::Path(Path::single(header))],
4143 }
4144}
4145
4146/// `Ok(())`.
4147fn ok_unit() -> rust::Expr {
4148 rust::Expr::Call {
4149 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
4150 args: vec![rust::Expr::Tuple(Vec::new())],
4151 }
4152}
4153
4154/// Wrap a generated `encode`/`decode` body in a packed-element block.
4155///
4156/// `ElementPadding::Packed` makes every aggregate and union value its own
4157/// alignment block: its members align from its own first octet, not from
4158/// the encapsulation, and the value is not padded up to its own alignment
4159/// before it starts. `sink` is `w` on the write side and `r` on the read
4160/// side.
4161///
4162/// The `finish` goes before the body's final statement — the `Ok(…)` the
4163/// body builds — so the restored origin is in effect for whatever the
4164/// caller writes after the value. A body that returns early on an error
4165/// leaves the origin displaced, which the caller discards with the
4166/// buffer: the same shape `zerodds-wire-codec`'s `?` between begin and
4167/// finish has.
4168fn wrap_packed_body(
4169 mut statements: Vec<rust::Statement>,
4170 sink: &str,
4171 packed: bool,
4172) -> Vec<rust::Statement> {
4173 if !packed {
4174 return statements;
4175 }
4176 let tail = statements.pop();
4177 let mut out = Vec::with_capacity(statements.len() + 3);
4178 out.push(let_statement("zd_origin", begin_packed(sink)));
4179 out.extend(statements);
4180 out.push(rust::Statement::Semi(finish_packed(sink, "zd_origin")));
4181 if let Some(tail) = tail {
4182 out.push(tail);
4183 }
4184 out
4185}
4186
4187/// The same shape, reached as a collection element rather than as a
4188/// member.
4189///
4190/// `None` where the shape carries no position, which is every shape but
4191/// one. A `Some` is a shape whose emitted call names a position and
4192/// therefore has to be rewritten before the call is built — not a
4193/// difference in the octets today, because all nine measured
4194/// implementations answer the two positions alike on all four `wstring`
4195/// axes, and the rewrite is what keeps that a measurement rather than a
4196/// thing nobody asked.
4197fn as_element(element: &MemberWrite) -> Option<MemberWrite> {
4198 match element {
4199 MemberWrite::WString {
4200 bound,
4201 narrow,
4202 policy,
4203 units,
4204 order,
4205 position: _,
4206 } => Some(MemberWrite::WString {
4207 bound: *bound,
4208 narrow: narrow.clone(),
4209 policy: *policy,
4210 units: *units,
4211 order: *order,
4212 position: WirePosition::Element,
4213 }),
4214 _ => None,
4215 }
4216}
4217
4218/// `zerodds_wire::WstringLengthPolicy::new(C1, C2, C3)`.
4219///
4220/// The constructor and not one of the five named constants, for every
4221/// cell alike. A table from cell to name is a table that can be wrong
4222/// about a cell, and the three cells no implementation writes have no
4223/// name to look up — which is exactly the shape that left the ROS cell
4224/// unreachable while the names were the values.
4225///
4226/// Absolute paths rather than imports, because the generated file's
4227/// import list is built by each consumer of this emitter and a new name
4228/// in it would have to be added in each one.
4229fn wstring_policy_expr(policy: WstringLengthPolicy) -> rust::Expr {
4230 let counts = match policy.counts {
4231 WstringLengthCounts::Octets => "Octets",
4232 WstringLengthCounts::Characters => "Characters",
4233 };
4234 let terminator = match policy.terminator {
4235 WstringTerminator::Absent => "Absent",
4236 WstringTerminator::Present => "Present",
4237 };
4238 let unit = match policy.unit {
4239 WstringUnitWidth::Two => "Two",
4240 WstringUnitWidth::Four => "Four",
4241 };
4242 rust::Expr::Call {
4243 callee: Box::new(rust::Expr::Path(Path::new(&[
4244 "zerodds_wire",
4245 "WstringLengthPolicy",
4246 "new",
4247 ]))),
4248 args: vec![
4249 rust::Expr::Path(Path::new(&["zerodds_wire", "WstringLengthCounts", counts])),
4250 rust::Expr::Path(Path::new(&[
4251 "zerodds_wire",
4252 "WstringTerminator",
4253 terminator,
4254 ])),
4255 rust::Expr::Path(Path::new(&["zerodds_wire", "WstringUnitWidth", unit])),
4256 ],
4257 }
4258}
4259
4260/// `zerodds_wire::WstringUnitOrder::<C4>` — the fourth axis.
4261fn wstring_order_expr(order: WstringUnitOrder) -> rust::Expr {
4262 rust::Expr::Path(Path::new(&[
4263 "zerodds_wire",
4264 "WstringUnitOrder",
4265 match order {
4266 WstringUnitOrder::Stream => "Stream",
4267 WstringUnitOrder::BigEndianAlways => "BigEndianAlways",
4268 WstringUnitOrder::ByteOrderMark => "ByteOrderMark",
4269 WstringUnitOrder::HostUnswapped => "HostUnswapped",
4270 },
4271 ]))
4272}
4273
4274/// `zerodds_wire::WirePosition::<position>`.
4275///
4276/// The two positions a `wstring` can be reached at, and an error for
4277/// the four it cannot. Not a fallback to `Member`: a position that
4278/// silently became the member's is precisely how a `wchar` element got
4279/// a member's framing through five emitters, and the runtime's own
4280/// enumeration has no name for a `wstring` in a parameter sentinel to
4281/// map onto.
4282fn wire_position_expr(member: &str, position: WirePosition) -> Result<rust::Expr, EncodeError> {
4283 let name = match position {
4284 WirePosition::Member => "Member",
4285 WirePosition::Element => "Element",
4286 WirePosition::Discriminator
4287 | WirePosition::KeyHolderMember
4288 | WirePosition::Sentinel
4289 | WirePosition::ExtendedMarker => {
4290 return Err(EncodeError::NoWriter {
4291 member: member.to_string(),
4292 kind: "a wstring at a position the wire runtime has no name for",
4293 });
4294 }
4295 };
4296 Ok(rust::Expr::Path(Path::new(&[
4297 "zerodds_wire",
4298 "WirePosition",
4299 name,
4300 ])))
4301}
4302
4303/// The integer writer an enumeration's bit bound calls for.
4304const fn enum_writer(bits: u16) -> &'static str {
4305 match bits {
4306 0..=8 => "write_u8",
4307 9..=16 => "write_u16",
4308 17..=32 => "write_u32",
4309 _ => "write_u64",
4310 }
4311}
4312
4313/// The expression that reads one value, of type `Result<T, WireError>`.
4314///
4315/// The narrowing §7.2.2.7 asks for is part of this expression and not a
4316/// step after it, so a member and a collection element get the same
4317/// treatment from one place — the element position is the one
4318/// §7.3.1.2.1.12.4 names and the one OpenDDS implements separately from
4319/// the collection's own bound.
4320///
4321/// zerodds-lint: recursion-depth 64 — one frame per external-member nesting
4322/// level; the shared lowering bounds that nesting before it reaches here.
4323///
4324/// `pub(crate)`: see [`write_value_to`].
4325pub(crate) fn read_for(name: &str, kind: &MemberWrite) -> Result<rust::Expr, EncodeError> {
4326 let read = read_whole(name, kind)?;
4327 match narrowing_of(kind) {
4328 Some(narrow) => narrowed(read, kind, narrow, name),
4329 None => Ok(read),
4330 }
4331}
4332
4333/// The narrowing of a shape, where the shape has one.
4334const fn narrowing_of(kind: &MemberWrite) -> Option<&Narrowing> {
4335 match kind {
4336 MemberWrite::String { narrow, .. }
4337 | MemberWrite::WString { narrow, .. }
4338 | MemberWrite::Sequence { narrow, .. }
4339 | MemberWrite::Map { narrow, .. } => narrow.as_ref(),
4340 _ => None,
4341 }
4342}
4343
4344/// `{ let v = <read>?; match r.fit_…(v, bound, TryConstruct::X)? { … } }`
4345///
4346/// The whole value comes off the wire first and the bound is applied
4347/// afterwards. A reader that stopped at its own bound would leave the
4348/// rest in the stream and put every following member at the wrong
4349/// offset — OpenDDS reads all twelve characters of `"Hello World!"`
4350/// before it defaults or truncates, and the members after it come back
4351/// correct, which is the property `try_construct_behaviour.md` singles
4352/// out.
4353fn narrowed(
4354 read: rust::Expr,
4355 kind: &MemberWrite,
4356 narrow: &Narrowing,
4357 name: &str,
4358) -> Result<rust::Expr, EncodeError> {
4359 let fit = match kind {
4360 MemberWrite::String { .. } => "fit_string",
4361 // The bound counts UTF-16 code units and not octets, so the
4362 // runtime has its own narrowing rather than `fit_string` with
4363 // another argument: one function taking "octets or units" would
4364 // be a call site deciding which by getting an argument right.
4365 MemberWrite::WString { .. } => "fit_wstring",
4366 MemberWrite::Sequence { .. } => "fit_sequence",
4367 // Table 11 names `map` in the same breath as `sequence`, and
4368 // the bound counts entries, so the runtime has its own `fit_map`
4369 // rather than a `fit_sequence` over the halves.
4370 MemberWrite::Map { .. } => match crate::adapter::cst::rust_map_form() {
4371 crate::adapter::cst::RustMapForm::Entries => "fit_map_entries",
4372 crate::adapter::cst::RustMapForm::BTreeMap => "fit_map",
4373 },
4374 // Table 11: TRIM "only applies to elements or members of type
4375 // string, wide string, sequence, or map", and the front end
4376 // refuses the annotation anywhere else
4377 // (`validate.rs::refuse_trim`). No other shape carries a
4378 // `Narrowing` at all, so this arm is a model that did not come
4379 // through the front end.
4380 _ => {
4381 return Err(EncodeError::NoWriter {
4382 member: name.to_string(),
4383 kind: "a bound on a type that cannot have one",
4384 });
4385 }
4386 };
4387 let bound = rust::escape_keyword(name);
4388 // USE_DEFAULT is the only behaviour that needs a value here; under
4389 // DISCARD `fit_…` has already returned `Err(WireError::Discarded)`
4390 // and under TRIM it has returned the trimmed value.
4391 let default = match (&narrow.default, narrow.behaviour) {
4392 (Some(default), _) => default.clone(),
4393 (None, TryConstruct::UseDefault) => {
4394 return Err(EncodeError::NoWriter {
4395 member: name.to_string(),
4396 kind: "a `@try_construct(USE_DEFAULT)` position whose default this column \
4397 cannot spell",
4398 });
4399 }
4400 // Under DISCARD and TRIM the runtime never answers `Default`,
4401 // so the arm exists only because the match has to be
4402 // exhaustive. A refusal and not a panic: a panic in a decoder is
4403 // a denial of service a peer can send, and this one would mean
4404 // the generated code and the runtime disagree about what the
4405 // behaviour was.
4406 (None, _) => rust::Expr::Path(Path::single("__unreachable")),
4407 };
4408 Ok(rust::Expr::Block(rust::Block {
4409 statements: vec![
4410 let_statement(name, rust::Expr::Try(Box::new(read))),
4411 rust::Statement::Tail(rust::Expr::Match {
4412 scrutinee: Box::new(rust::Expr::Try(Box::new(call(
4413 fit,
4414 vec![
4415 rust::Expr::Path(Path::single(&bound)),
4416 rust::Expr::Literal(Literal::int(narrow.bound)),
4417 rust::Expr::Path(Path::new(&[
4418 "TryConstruct",
4419 try_construct_variant(narrow.behaviour),
4420 ])),
4421 ],
4422 )))),
4423 arms: vec![
4424 rust::MatchArm {
4425 pattern: rust::Pattern::TupleStruct {
4426 path: Path::new(&["Narrowed", "Value"]),
4427 elements: vec![rust::Pattern::Binding(Ident::new(&bound))],
4428 },
4429 guard: None,
4430 body: rust::Expr::Call {
4431 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
4432 args: vec![rust::Expr::Path(Path::single(&bound))],
4433 },
4434 },
4435 rust::MatchArm {
4436 pattern: rust::Pattern::Path(Path::new(&["Narrowed", "Default"])),
4437 guard: None,
4438 body: if narrow.behaviour == TryConstruct::UseDefault {
4439 rust::Expr::Call {
4440 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
4441 args: vec![default],
4442 }
4443 } else {
4444 rust::Expr::Call {
4445 callee: Box::new(rust::Expr::Path(Path::single("Err"))),
4446 args: vec![rust::Expr::Call {
4447 callee: Box::new(rust::Expr::Path(Path::new(&[
4448 "WireError",
4449 "refused",
4450 ]))),
4451 args: vec![
4452 call("position", Vec::new()),
4453 rust::Expr::Literal(Literal::string(
4454 "a narrowing that asked for no default and got one",
4455 )),
4456 ],
4457 }],
4458 }
4459 },
4460 },
4461 ],
4462 }),
4463 ],
4464 }))
4465}
4466
4467/// The runtime's spelling of one behaviour.
4468const fn try_construct_variant(behaviour: TryConstruct) -> &'static str {
4469 match behaviour {
4470 TryConstruct::Discard => "Discard",
4471 TryConstruct::UseDefault => "UseDefault",
4472 TryConstruct::Trim => "Trim",
4473 }
4474}
4475
4476/// The read itself, before any bound is applied.
4477///
4478/// zerodds-lint: recursion-depth 64 — one frame per external-member nesting
4479/// level; the shared lowering bounds that nesting before it reaches here.
4480fn read_whole(name: &str, kind: &MemberWrite) -> Result<rust::Expr, EncodeError> {
4481 Ok(match kind {
4482 MemberWrite::External(inner) => rust::Expr::MethodCall {
4483 receiver: Box::new(read_for(name, inner)?),
4484 method: Ident::new("map"),
4485 args: vec![rust::Expr::Path(Path::new(&["Box", "new"]))],
4486 },
4487 MemberWrite::Primitive(primitive) => call(reader_name(*primitive), Vec::new()),
4488 MemberWrite::String { .. } => call("read_string", Vec::new()),
4489 MemberWrite::Fixed {
4490 digits,
4491 scale,
4492 count,
4493 } => call(
4494 "read_fixed",
4495 vec![
4496 rust::Expr::Literal(Literal::int(i128::from(*digits))),
4497 rust::Expr::Literal(Literal::int(i128::from(*scale))),
4498 fixed_digits_expr(*count),
4499 ],
4500 ),
4501 MemberWrite::WString {
4502 policy,
4503 units,
4504 order,
4505 position,
4506 ..
4507 } => {
4508 // The mirror of `write_call`'s branch: `Utf8` names the
4509 // runtime's UTF-8 reader, which takes no order.
4510 if *units == WstringCodeUnits::Utf8 {
4511 call(
4512 "read_wstring_utf8",
4513 vec![
4514 wstring_policy_expr(*policy),
4515 wire_position_expr(name, *position)?,
4516 ],
4517 )
4518 } else {
4519 call(
4520 "read_wstring",
4521 vec![
4522 wstring_policy_expr(*policy),
4523 wstring_order_expr(*order),
4524 wire_position_expr(name, *position)?,
4525 ],
4526 )
4527 }
4528 }
4529 MemberWrite::Enumeration { bits } => call(enum_reader(*bits), Vec::new()),
4530 // `read_bitmask` answers in the `u64` it reads into, and the
4531 // field is the holder the bound calls for, so the narrowing
4532 // rides on the `Result` rather than after it: `read_for` may
4533 // wrap this expression in a `try_construct` arm, and a cast
4534 // written outside would have been applied to the wrong thing.
4535 MemberWrite::Bitmask { bits, declared } => narrow_from_u64(
4536 call(
4537 "read_bitmask",
4538 vec![
4539 rust::Expr::Literal(Literal::int(i128::from(*bits))),
4540 rust::Expr::Literal(Literal::int(i128::from(*declared))),
4541 ],
4542 ),
4543 *bits,
4544 ),
4545 // The mirror of the encoder's `self.<field>.encode(w)`. The read
4546 // side needs no `extended`: `Reader::read_pid_as` takes the
4547 // header form off the wire, so one decoder serves both forms a
4548 // per-sample plan writes.
4549 MemberWrite::Nested {
4550 type_name, codec, ..
4551 } => rust::Expr::Call {
4552 callee: Box::new(rust::Expr::Path(Path::new(&[
4553 type_name.as_str(),
4554 &format!("decode{codec}"),
4555 ]))),
4556 args: vec![rust::Expr::Path(Path::single("r"))],
4557 },
4558 MemberWrite::Sequence {
4559 element,
4560 delimited,
4561 packed,
4562 carries_length,
4563 ..
4564 } => call(
4565 if *delimited {
4566 "read_delimited_sequence"
4567 } else {
4568 "read_sequence"
4569 },
4570 vec![sequence_element_reader(
4571 name,
4572 element,
4573 *packed,
4574 *carries_length,
4575 )?],
4576 ),
4577 MemberWrite::Map {
4578 key,
4579 value,
4580 delimited,
4581 ..
4582 } => call(
4583 match (crate::adapter::cst::rust_map_form(), *delimited) {
4584 (crate::adapter::cst::RustMapForm::Entries, true) => "read_delimited_map_entries",
4585 (crate::adapter::cst::RustMapForm::Entries, false) => "read_map_entries",
4586 (crate::adapter::cst::RustMapForm::BTreeMap, true) => "read_delimited_map",
4587 (crate::adapter::cst::RustMapForm::BTreeMap, false) => "read_map",
4588 },
4589 vec![element_reader(name, key)?, element_reader(name, value)?],
4590 ),
4591 MemberWrite::Array {
4592 element,
4593 dims,
4594 delimited,
4595 packed,
4596 } => call(
4597 // `_n`, not the `Vec` form: the field an array materialises
4598 // to is `[T; N]`, and `N` is inferred from it.
4599 if *delimited {
4600 "read_delimited_array_n"
4601 } else {
4602 "read_array_n"
4603 },
4604 vec![array_element_reader(
4605 name,
4606 element,
4607 dims.len().saturating_sub(1),
4608 *packed,
4609 )?],
4610 ),
4611 MemberWrite::Unsupported(kind) => {
4612 return Err(EncodeError::NoWriter {
4613 member: name.to_string(),
4614 kind,
4615 });
4616 }
4617 })
4618}
4619
4620/// The element function a collection's read takes.
4621///
4622/// A path where one has the right shape — `Reader::read_i32` and
4623/// `Cell::decode` are both `fn(&mut Reader) -> Result<T, WireError>` —
4624/// and a closure where the read needs an argument of its own.
4625fn element_reader(at: &str, element: &MemberWrite) -> Result<rust::Expr, EncodeError> {
4626 // The element position, substituted for the reason
4627 // `element_writer` gives.
4628 let substituted = as_element(element);
4629 let element = substituted.as_ref().unwrap_or(element);
4630 // An element with a bound of its own takes a closure whatever its
4631 // shape: §7.3.1.2.1.12.4 gives the element position its own
4632 // behaviour, and a named path has nowhere to put it. This is the
4633 // split Table 17 draws and OpenDDS implements — the sequence's
4634 // length overflow and an element's bound overflow fail separately.
4635 if narrowing_of(element).is_some() {
4636 return Ok(rust::Expr::Closure {
4637 params: vec![Ident::new("r")],
4638 body: Box::new(read_for(at, element)?),
4639 });
4640 }
4641 Ok(match element {
4642 // As `element_writer`: the element's `wchar` reader, not the
4643 // member's.
4644 MemberWrite::Primitive(PrimitiveKind::WChar) => {
4645 rust::Expr::Path(Path::new(&["Reader", "read_wchar_element"]))
4646 }
4647 MemberWrite::Primitive(primitive) => {
4648 rust::Expr::Path(Path::new(&["Reader", reader_name(*primitive)]))
4649 }
4650 MemberWrite::String { .. } => rust::Expr::Path(Path::new(&["Reader", "read_string"])),
4651 // A closure and not a path, for the reason `wstring` needs one:
4652 // `read_fixed` takes the declaration and the digit count as
4653 // arguments, so there is no bare
4654 // `fn(&mut Reader) -> Result<T, WireError>` to hand over.
4655 MemberWrite::Fixed { .. } => rust::Expr::Closure {
4656 params: vec![Ident::new("r")],
4657 body: Box::new(read_for(at, element)?),
4658 },
4659 // A closure and not a path: `read_wstring` takes the cell and
4660 // the order as arguments, so there is no bare
4661 // `fn(&mut Reader) -> Result<T, WireError>` to hand over.
4662 MemberWrite::WString { .. } => rust::Expr::Closure {
4663 params: vec![Ident::new("r")],
4664 body: Box::new(read_for(at, element)?),
4665 },
4666 MemberWrite::Enumeration { bits } => {
4667 rust::Expr::Path(Path::new(&["Reader", enum_reader(*bits)]))
4668 }
4669 MemberWrite::Nested {
4670 type_name, codec, ..
4671 } => rust::Expr::Path(Path::new(&[type_name.as_str(), &format!("decode{codec}")])),
4672 // A closure for the reason `read_wstring` needs one: the bound
4673 // and the declared mask are arguments, so there is no bare
4674 // `fn(&mut Reader) -> Result<T, WireError>` to name.
4675 MemberWrite::Bitmask { .. }
4676 | MemberWrite::External(_)
4677 | MemberWrite::Sequence { .. }
4678 | MemberWrite::Array { .. }
4679 | MemberWrite::Map { .. } => rust::Expr::Closure {
4680 params: vec![Ident::new("r")],
4681 body: Box::new(read_for(at, element)?),
4682 },
4683 MemberWrite::Unsupported(kind) => {
4684 return Err(EncodeError::NoWriter {
4685 member: at.to_string(),
4686 kind,
4687 });
4688 }
4689 })
4690}
4691
4692/// A bitmask's holder widened to the `u64` `Writer::write_bitmask`
4693/// takes.
4694///
4695/// `as u64` and not `u64::from`: the 64-bit holder is already a `u64`
4696/// and `u64::from(x)` on one is a type error, while the cast is
4697/// accepted for all four widths. Widening is exact in every one of
4698/// them, so nothing can be lost here.
4699fn widen_to_u64(value: rust::Expr, bits: u16) -> rust::Expr {
4700 if crate::adapter::cst::bitmask_primitive(bits) == rust::Primitive::U64 {
4701 return value;
4702 }
4703 rust::Expr::Cast {
4704 value: Box::new(value),
4705 type_: Box::new(rust::Type::Primitive(rust::Primitive::U64)),
4706 }
4707}
4708
4709/// `Reader::read_bitmask`'s `u64` narrowed back to the holder the field
4710/// is declared as.
4711///
4712/// Exact for the same reason the writer's widening is: the runtime has
4713/// already refused every bit above the bound, so nothing this cast drops
4714/// could have been set.
4715fn narrow_from_u64(read: rust::Expr, bits: u16) -> rust::Expr {
4716 let holder = crate::adapter::cst::bitmask_primitive(bits);
4717 if holder == rust::Primitive::U64 {
4718 return read;
4719 }
4720 rust::Expr::MethodCall {
4721 receiver: Box::new(read),
4722 method: Ident::new("map"),
4723 args: vec![rust::Expr::Closure {
4724 params: vec![Ident::new("v")],
4725 body: Box::new(rust::Expr::Cast {
4726 value: Box::new(rust::Expr::Path(Path::single("v"))),
4727 type_: Box::new(rust::Type::Primitive(holder)),
4728 }),
4729 }],
4730 }
4731}
4732
4733/// The element function for an array with `remaining` dimensions left.
4734///
4735/// `packed` wraps each level's element in a
4736/// `Reader::begin_packed_element` / `finish_packed_element` pair, the
4737/// read side of `array_element_writer`'s: the row and every cell inside
4738/// it each begin their own alignment block.
4739///
4740/// zerodds-lint: recursion-depth 8 (as `array_element_writer`: one level
4741/// per dimension of the array declarator)
4742fn array_element_reader(
4743 at: &str,
4744 element: &MemberWrite,
4745 remaining: usize,
4746 packed: bool,
4747) -> Result<rust::Expr, EncodeError> {
4748 if remaining == 0 {
4749 // A cell: the element position and the packed block, and never an
4750 // element length.
4751 return sequence_element_reader(at, element, packed, false);
4752 }
4753 let inner = array_element_reader(at, element, remaining - 1, packed)?;
4754 let call = rust::Expr::MethodCall {
4755 receiver: Box::new(rust::Expr::Path(Path::single("r"))),
4756 method: Ident::new("read_array_n"),
4757 args: vec![inner],
4758 };
4759 if !packed {
4760 return Ok(rust::Expr::Closure {
4761 params: vec![Ident::new("r")],
4762 body: Box::new(call),
4763 });
4764 }
4765 Ok(rust::Expr::Closure {
4766 params: vec![Ident::new("r")],
4767 body: Box::new(rust::Expr::Block(rust::Block {
4768 statements: vec![
4769 let_statement("zd_origin", begin_packed("r")),
4770 let_statement("value", rust::Expr::Try(Box::new(call))),
4771 rust::Statement::Semi(finish_packed("r", "zd_origin")),
4772 rust::Statement::Tail(rust::Expr::Call {
4773 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
4774 args: vec![rust::Expr::Path(Path::single("value"))],
4775 }),
4776 ],
4777 })),
4778 })
4779}
4780
4781/// The element function a `sequence`'s read takes, with the two HDDS
4782/// axes the reference codec puts around each element.
4783///
4784/// With neither axis selected this is [`element_reader`] unchanged, so
4785/// every other dialect's emitted code is the same text it was.
4786///
4787/// The element's own length is checked against what reading it consumed,
4788/// exactly as `zerodds-wire-codec`'s sequence walk does: the declared
4789/// count is a peer's claim, and a value that did not fill it leaves the
4790/// next element to be read from inside this one.
4791fn sequence_element_reader(
4792 at: &str,
4793 element: &MemberWrite,
4794 packed: bool,
4795 carries_length: bool,
4796) -> Result<rust::Expr, EncodeError> {
4797 if !packed && !carries_length {
4798 return element_reader(at, element);
4799 }
4800 let read = element_read_expr(at, element)?;
4801 let mut statements = Vec::new();
4802 if packed {
4803 statements.push(let_statement("zd_origin", begin_packed("r")));
4804 }
4805 if carries_length {
4806 statements.push(let_statement(
4807 "zd_declared",
4808 rust::Expr::Cast {
4809 value: Box::new(rust::Expr::Try(Box::new(call("read_u32", Vec::new())))),
4810 type_: Box::new(rust::Type::named(Path::single("usize"))),
4811 },
4812 ));
4813 statements.push(let_statement("zd_before", call("position", Vec::new())));
4814 }
4815 statements.push(let_statement("value", rust::Expr::Try(Box::new(read))));
4816 if carries_length {
4817 statements.push(rust::Statement::Semi(rust::Expr::If {
4818 condition: Box::new(rust::Expr::Binary {
4819 op: rust::BinaryOp::Ne,
4820 lhs: Box::new(rust::Expr::Binary {
4821 op: rust::BinaryOp::Sub,
4822 lhs: Box::new(call("position", Vec::new())),
4823 rhs: Box::new(rust::Expr::Path(Path::single("zd_before"))),
4824 }),
4825 rhs: Box::new(rust::Expr::Path(Path::single("zd_declared"))),
4826 }),
4827 then: rust::Block {
4828 statements: vec![rust::Statement::Semi(rust::Expr::Return(Some(Box::new(
4829 refused_err(
4830 "r",
4831 "a sequence element of the octet count its own length declared",
4832 ),
4833 ))))],
4834 },
4835 otherwise: None,
4836 }));
4837 }
4838 if packed {
4839 statements.push(rust::Statement::Semi(finish_packed("r", "zd_origin")));
4840 }
4841 statements.push(rust::Statement::Tail(rust::Expr::Call {
4842 callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
4843 args: vec![rust::Expr::Path(Path::single("value"))],
4844 }));
4845 Ok(rust::Expr::Closure {
4846 params: vec![Ident::new("r")],
4847 body: Box::new(rust::Expr::Block(rust::Block { statements })),
4848 })
4849}
4850
4851/// The read one collection element is, at the element position.
4852///
4853/// [`read_for`] names a `wchar`'s *member* framing; a collection element
4854/// takes the other one, so it is substituted here. A `wstring` element
4855/// has its position rewritten by [`as_element`] first.
4856fn element_read_expr(at: &str, element: &MemberWrite) -> Result<rust::Expr, EncodeError> {
4857 let substituted = as_element(element);
4858 let element = substituted.as_ref().unwrap_or(element);
4859 if matches!(element, MemberWrite::Primitive(PrimitiveKind::WChar)) {
4860 return Ok(call("read_wchar_element", Vec::new()));
4861 }
4862 read_for(at, element)
4863}
4864
4865/// The integer reader an enumeration's bit bound calls for.
4866const fn enum_reader(bits: u16) -> &'static str {
4867 match bits {
4868 0..=8 => "read_u8",
4869 9..=16 => "read_u16",
4870 17..=32 => "read_u32",
4871 _ => "read_u64",
4872 }
4873}
4874
4875/// What kind of write a member needs.
4876///
4877/// `pub(crate)`: see [`write_value_to`]. `emit::interface_rust` never
4878/// constructs a variant beside [`Self::Primitive`] — it gets every other
4879/// shape from [`write_of`] — so it names the type and this one variant
4880/// only.
4881#[derive(Debug, Clone, PartialEq, Eq)]
4882pub(crate) enum MemberWrite {
4883 /// Owning indirection in the Rust binding, with unchanged wire layout.
4884 External(Box<MemberWrite>),
4885 /// A primitive, with its own writer.
4886 Primitive(PrimitiveKind),
4887 /// A string, with its bound when it has one.
4888 ///
4889 /// The bound is written into the call rather than checked at
4890 /// runtime by the writer: a bounded string that overruns is a
4891 /// program error, and the bound is what the IDL said, not a
4892 /// parameter of the wire format.
4893 String {
4894 /// The bound, absent when unbounded.
4895 bound: Option<i128>,
4896 /// What this *position* does with a value the bound cannot hold.
4897 /// Absent when the string is unbounded, because there is then
4898 /// nothing a value can fail to fit.
4899 narrow: Option<Narrowing>,
4900 },
4901 /// A `fixed<P, S>`, with its declaration and the digit-count axis.
4902 ///
4903 /// The value is a decimal **string** in this column — `cst.rs` gives
4904 /// `fixed` the Rust spelling `String` and `default_rust.rs` gives it a
4905 /// zero at the declared scale — for the reason the other twenty columns
4906 /// carry it as text too: Rust has no decimal type in `core`, and a
4907 /// `fixed<31, 2>` does not fit any integer Rust has. A binary float
4908 /// would be the wrong answer rather than an approximate one, because
4909 /// `fixed` exists to be exact.
4910 ///
4911 /// Three fields and not one: `P` and `S` are the declaration, and
4912 /// `count` is the dialect axis over CORBA 3.3 part 2 §9.3.2.8's open
4913 /// question of how many digit nibbles go out. All three go into the
4914 /// emitted call, so the generated codec states the cell it writes.
4915 Fixed {
4916 /// `P` — the total digits the declaration names.
4917 digits: u8,
4918 /// `S` — the digits after the point.
4919 scale: u8,
4920 /// Whether `P` digits go out whatever the value is, or as many as
4921 /// the value has. `zerodds_ast_layout::key::FixedDigitCount`.
4922 count: FixedDigitCount,
4923 },
4924 /// A `wstring`, with its bound and the four axes that decide its
4925 /// octets.
4926 ///
4927 /// Five fields and not one, because the cell is a product: C1, C2
4928 /// and C3 are [`WstringLengthPolicy`]'s three coordinates, C4 is
4929 /// [`WstringUnitOrder`], and the position is what keeps the claim
4930 /// that the two positions agree a measurement rather than an
4931 /// assumption. All four come out of the [`LayoutKey`] and go into
4932 /// the emitted call, so the generated codec states the cell it
4933 /// writes instead of inheriting one from the runtime.
4934 WString {
4935 /// The bound in **UTF-16 code units**, absent when unbounded.
4936 ///
4937 /// Units and not octets: `wstring<N>` is an `N`-unit buffer in
4938 /// every measured mapping, and an octet bound would mean two
4939 /// different capacities under C3's two widths.
4940 bound: Option<i128>,
4941 /// What this *position* does with a value the bound cannot
4942 /// hold. Absent when the `wstring` is unbounded.
4943 narrow: Option<Narrowing>,
4944 /// C1 × C2 × C3 — what the length counts, whether a terminator
4945 /// joins the run, how wide one code unit is.
4946 policy: WstringLengthPolicy,
4947 /// What the content's characters are made of. Under
4948 /// [`WstringCodeUnits::Utf8`] the characters go out as their own
4949 /// UTF-8 octets and C1 and C3 are vacuous, so the emitted call
4950 /// names the runtime's UTF-8 entry point instead.
4951 units: WstringCodeUnits,
4952 /// C4 — in what byte order the units go out, and whether a
4953 /// byte-order mark leads them.
4954 order: WstringUnitOrder,
4955 /// Where the `wstring` is reached from.
4956 position: WirePosition,
4957 },
4958 /// A sequence, with its element and its bound.
4959 Sequence {
4960 /// What each element is.
4961 element: Box<MemberWrite>,
4962 /// The bound, absent when unbounded.
4963 bound: Option<i128>,
4964 /// As [`MemberWrite::String`]'s.
4965 narrow: Option<Narrowing>,
4966 /// Whether a `DHEADER` precedes the length. Rule (12), decided
4967 /// by `zerodds_ast_layout::collection_is_delimited` so that the
4968 /// emitted bytes and the planned layout cannot disagree.
4969 delimited: bool,
4970 /// Whether each element begins its own alignment block.
4971 /// `ElementPadding::Packed`'s writer half: this column wraps the
4972 /// element function in a `Writer::begin_packed_element` /
4973 /// `finish_packed_element` pair, at the same site
4974 /// `zerodds-wire-codec` writes them.
4975 packed: bool,
4976 /// Whether each element is preceded by its own four-octet
4977 /// length. `SequenceElementDelimiter::BeforeEachNonFinalElement`,
4978 /// decided by `zerodds_ast_layout::xcdr::sequence_element_carries_length`
4979 /// so the two walkers ask one crossing.
4980 carries_length: bool,
4981 },
4982 /// A map, with both halves and its bound.
4983 ///
4984 /// Two element functions and not one: a `map<K, V>` is a sequence of
4985 /// pairs (§7.4.1.1.4) and the halves are two different types, so the
4986 /// runtime takes a function per half.
4987 ///
4988 /// # The entry order is the container's and is not decided here
4989 ///
4990 /// `adapter::cst` materialises an IDL `map` as a `BTreeMap<K, V>`,
4991 /// which iterates ascending by key, and
4992 /// `zerodds_ast_layout::MapEntryOrder::AscendingKey` is defined as
4993 /// what a binding that holds a `map` in an ordered container writes.
4994 /// So this column answers the axis by the type it already emits.
4995 /// There is no walk here to bypass it and no second reading to keep
4996 /// in step — which is why this shape carries no ordering field, and
4997 /// why `crates/lang-rules/src/encode.rs` is not one of the sites
4998 /// `wire-probe`'s `map_entry_order.rs` counts crossings in.
4999 Map {
5000 /// What each entry's key is.
5001 key: Box<MemberWrite>,
5002 /// What each entry's value is.
5003 value: Box<MemberWrite>,
5004 /// The bound, in **entries**, absent when unbounded.
5005 bound: Option<i128>,
5006 /// As [`MemberWrite::String`]'s. Table 11 names `map` among the
5007 /// forms a TRIM may be written on.
5008 narrow: Option<Narrowing>,
5009 /// Whether a `DHEADER` precedes the entry count. Rule (14)
5010 /// exempts a map only when both halves are primitive and rule
5011 /// (15) delimits the rest, decided by
5012 /// `zerodds_ast_layout::xcdr::plan` through `MapDelimiter` so
5013 /// that the emitted bytes and the planned layout cannot
5014 /// disagree.
5015 delimited: bool,
5016 /// Whether the entries are written ascending by key: the map is held as its entries
5017 /// (`adapter::cst::RustMapForm::Entries`), which keep the order they were put in, and
5018 /// `MapEntryOrder::AscendingKey` asked for the other one.
5019 ascending: bool,
5020 },
5021 /// An array, with its element and its dimensions outermost first.
5022 Array {
5023 /// What each element is.
5024 element: Box<MemberWrite>,
5025 /// The dimensions, outermost first.
5026 dims: Vec<i128>,
5027 /// Whether a `DHEADER` precedes the elements. Rule (9).
5028 delimited: bool,
5029 /// Whether each element begins its own alignment block, as
5030 /// [`MemberWrite::Sequence`]'s. Arrays never carry an element
5031 /// length: only `sequence` has that axis.
5032 packed: bool,
5033 },
5034 /// An enumeration, written as the integer its bit bound calls for.
5035 Enumeration {
5036 /// The bit bound.
5037 bits: u16,
5038 },
5039 /// A bitmask, written in the holder Table 33 of XTypes 1.3
5040 /// §7.4.1.1.3.2 gives for its `@bit_bound`.
5041 ///
5042 /// # Not `Enumeration`, and not `Nested`
5043 ///
5044 /// Not [`MemberWrite::Enumeration`], although both are an integer in
5045 /// a holder: an enumeration's holder width goes through
5046 /// `EnumHolderWidth`, the axis M1 measured, and a bitmask's does
5047 /// not — `zerodds_ast_layout::xcdr::enumeration_bit_bound` says so
5048 /// in as many words. Sharing the shape would have let a setting
5049 /// meant for one construct move the other's octets.
5050 ///
5051 /// Not [`MemberWrite::Nested`] either, although the other twenty
5052 /// columns give a bitmask its own codec function. `adapter::cst`
5053 /// materialises a bitmask as `pub type Flags = u16;` — a type alias
5054 /// to a primitive, because a Rust `enum` cannot hold two bits at
5055 /// once — and there is no `impl` block to hang an `encode` on.
5056 Bitmask {
5057 /// The bound Table 33 sizes the holder by, from
5058 /// `zerodds_ast_layout::plan::scalar_write`.
5059 bits: u16,
5060 /// The positions the type names, one bit per position. A bit set
5061 /// inside the bound that this mask does not carry is refused on
5062 /// the way in.
5063 declared: u64,
5064 },
5065 /// Another aggregate, which encodes itself.
5066 ///
5067 /// The name is carried because the decode side has to write it:
5068 /// `Cell::decode(r)` names the type and `r.read_nested()` cannot.
5069 Nested {
5070 /// The generated Rust type's name.
5071 type_name: String,
5072 /// The suffix the nested type's codec functions carry: empty
5073 /// for the type's first codec, [`SECONDARY_SUFFIX`] for the
5074 /// second one beside it.
5075 ///
5076 /// Carried rather than recomputed at the call site: `write_call`
5077 /// and `read_whole` are handed a `MemberWrite` and no key, and a
5078 /// nested call that named `encode` while the enclosing body was
5079 /// the XCDR1 one would frame the member under version 2 inside a
5080 /// version 1 stream — a sample that decodes into wrong values
5081 /// with nothing reporting it.
5082 codec: &'static str,
5083 /// How the sample's PL_CDR1 step decision reaches this call, and
5084 /// which of the nested type's two methods it names under
5085 /// [`Extended::Fixed`].
5086 ///
5087 /// Carried for the same reason as `codec`, and read from the same
5088 /// key: `write_call` is handed the shape and nothing else, so a
5089 /// call that could not say whether the flag was threaded would
5090 /// frame every nested type of a per-sample plan with the short
5091 /// header, also inside a sample past RTI's step.
5092 extended: Extended,
5093 },
5094 /// A kind with no writer yet, named so the error can say which.
5095 Unsupported(&'static str),
5096}
5097
5098/// What one position does with a value its bound cannot hold.
5099///
5100/// DDS-XTypes 1.3 §7.2.2.7 attaches the behaviour to the position — a
5101/// member, a collection element, a map key, a union case — and not to
5102/// the type, so it is carried alongside the shape rather than derived
5103/// from it. One `typedef string<5>` may be used at two positions that
5104/// want different answers.
5105#[derive(Debug, Clone, PartialEq, Eq)]
5106pub(crate) struct Narrowing {
5107 /// What the receiving type holds.
5108 bound: i128,
5109 /// Which of the three §7.3.1.2.1.12 behaviours this position asks
5110 /// for.
5111 behaviour: TryConstruct,
5112 /// The value `USE_DEFAULT` substitutes, when that is the behaviour.
5113 ///
5114 /// Computed only for `USE_DEFAULT`: a structure's default is a walk
5115 /// of its own members, and computing one for every bounded string
5116 /// in the model would be a walk per string for a value nothing
5117 /// reads.
5118 default: Option<rust::Expr>,
5119}
5120
5121/// Where a type is being used, for the purpose of `try_construct`.
5122///
5123/// `pub(crate)`: see [`write_value_to`]. `emit::interface_rust` builds
5124/// one per field from `Ast::effective_try_construct` itself — an
5125/// operation parameter has no member declarator, so it always passes
5126/// `written: None`.
5127#[derive(Debug, Clone, Copy)]
5128pub(crate) struct Position {
5129 /// The behaviour this position asks for, already resolved through
5130 /// the alias chain by `Ast::effective_try_construct`.
5131 pub(crate) behaviour: TryConstruct,
5132 /// What the *member declarator* wrote, which `element_try_construct`
5133 /// needs for the one case §7.3.1.2.1.12 lets a member's annotation
5134 /// reach the elements: an array declared on the member.
5135 pub(crate) written: Option<TryConstruct>,
5136}
5137
5138fn member_of(
5139 ast: &Ast,
5140 type_: Id<TypeNode>,
5141 node: zerodds_ast_core::NodeId,
5142 key: LayoutKey,
5143 codec: &'static str,
5144) -> Option<(String, MemberWrite)> {
5145 // `all_members_of`, so a member the aggregate inherited is found
5146 // here instead of being skipped as though the plan had not named it.
5147 let member = ast
5148 .all_members_of(type_)
5149 .into_iter()
5150 .find(|id| ast.member(*id).is_ok_and(|m| m.node_id == node))?;
5151 let member = ast.member(member).ok()?;
5152 // The member's own `@try_construct`, resolved through the alias
5153 // chain — §7.2.2.7 puts the behaviour on the position, and this is
5154 // the member position.
5155 let written = member.facets.xtypes.as_ref().and_then(|x| x.try_construct);
5156 let position = Position {
5157 behaviour: ast.effective_try_construct(written, member.type_.resolved()),
5158 written,
5159 };
5160 // Types this member names are written relative to the module the
5161 // aggregate itself lands in.
5162 let from = module_of(ast, type_);
5163 let mut write = member.type_.resolved().map_or(
5164 MemberWrite::Unsupported("member with an unresolved type"),
5165 |target| write_of(ast, from, target, key, codec, position),
5166 );
5167 // §7.2.2.4.4.4.9 puts an explicit `@default` ahead of Table 9's, and
5168 // only the member position can carry one — a collection element has
5169 // no declarator to write it on.
5170 if let Some(target) = member.type_.resolved() {
5171 if let Some(narrow) = narrowing_of_mut(&mut write) {
5172 if narrow.behaviour == TryConstruct::UseDefault {
5173 narrow.default = crate::default_rust::member_value_default(
5174 ast,
5175 module_of(ast, type_),
5176 member,
5177 target,
5178 )
5179 .ok();
5180 }
5181 }
5182 }
5183 Some((member.name.clone(), external_write(ast, member, write)))
5184}
5185
5186fn external_write(ast: &Ast, member: &zerodds_ast::MemberNode, write: MemberWrite) -> MemberWrite {
5187 if crate::adapter::cst::is_external(ast, member) {
5188 MemberWrite::External(Box::new(write))
5189 } else {
5190 write
5191 }
5192}
5193
5194/// The narrowing of a shape, where the shape has one.
5195fn narrowing_of_mut(write: &mut MemberWrite) -> Option<&mut Narrowing> {
5196 match write {
5197 MemberWrite::String { narrow, .. }
5198 | MemberWrite::WString { narrow, .. }
5199 | MemberWrite::Sequence { narrow, .. }
5200 | MemberWrite::Map { narrow, .. } => narrow.as_mut(),
5201 _ => None,
5202 }
5203}
5204
5205/// The declaration one plan entry names.
5206///
5207/// `member_of` answers with the name and the writer shape; the default
5208/// value needs the declaration itself, for the `@default` it may carry.
5209fn member_node_of(
5210 ast: &Ast,
5211 type_: Id<TypeNode>,
5212 node: zerodds_ast_core::NodeId,
5213) -> Option<&zerodds_ast::MemberNode> {
5214 let member = ast
5215 .all_members_of(type_)
5216 .into_iter()
5217 .find(|id| ast.member(*id).is_ok_and(|m| m.node_id == node))?;
5218 ast.member(member).ok()
5219}
5220
5221/// What writing one type takes.
5222///
5223/// Recursive, because a sequence of sequences is written by writing the
5224/// outer length and then each inner sequence — the shape of the writer
5225/// follows the shape of the type, which is what keeps the two from
5226/// drifting apart.
5227///
5228/// `pub(crate)`: see [`write_value_to`]. Unlike [`member_of`], this
5229/// takes a bare `Id<TypeNode>` and not a member of some aggregate — an
5230/// operation parameter or an exception member is neither, and DDS-RPC
5231/// 1.0 §7.5.1.1.4-7 synthesizes structures for them that never reach the
5232/// model at all.
5233pub(crate) fn write_of(
5234 ast: &Ast,
5235 from: Id<zerodds_ast::ModuleNode>,
5236 target: Id<TypeNode>,
5237 key: LayoutKey,
5238 codec: &'static str,
5239 at: Position,
5240) -> MemberWrite {
5241 write_of_at(ast, from, target, key, codec, at, 0)
5242}
5243
5244/// How deep a member's writer may nest before the walk refuses the type.
5245///
5246/// The same 64 every reference walk in the tree carries;
5247/// `zerodds-wire-codec`'s `default.rs` and `zerodds-types`'
5248/// `dynamic/type_.rs` both stop there.
5249const MAX_WRITE_DEPTH: usize = 64;
5250
5251/// [`write_of`], carrying how many collection and typedef steps the walk
5252/// has already taken.
5253///
5254/// An aggregate is a leaf here — it writes through its own generated
5255/// codec under [`MemberWrite::Nested`] — so only the connectors can come
5256/// back round: a `sequence`'s element, an array's element, a `map`'s two
5257/// halves, a `typedef`'s target. A cycle among those is a finding the
5258/// model's invariant pass reports rather than a shape it rejects, and
5259/// without the counter this function read one forever.
5260///
5261/// zerodds-lint: recursion-depth 64 (sequence, array, map and typedef
5262/// nesting; bounded by `MAX_WRITE_DEPTH`, which returns
5263/// [`MemberWrite::Unsupported`] naming the construct — the same channel
5264/// every other refusal in this walk uses, so the emitter reports it by
5265/// name instead of writing a member it cannot spell)
5266fn write_of_at(
5267 ast: &Ast,
5268 from: Id<zerodds_ast::ModuleNode>,
5269 target: Id<TypeNode>,
5270 key: LayoutKey,
5271 codec: &'static str,
5272 at: Position,
5273 depth: usize,
5274) -> MemberWrite {
5275 if depth > MAX_WRITE_DEPTH {
5276 return MemberWrite::Unsupported("a type whose writer nests past the recursion bound");
5277 }
5278 let Ok(node) = ast.type_(target) else {
5279 return MemberWrite::Unsupported("member with a stale type handle");
5280 };
5281 // The narrowing a bound calls for, computed once for both bounded
5282 // shapes. `None` for an unbounded one: there is nothing a value can
5283 // fail to fit.
5284 let narrowing = |bound: Option<i128>| {
5285 bound.map(|bound| Narrowing {
5286 bound,
5287 behaviour: at.behaviour,
5288 default: if at.behaviour == TryConstruct::UseDefault {
5289 crate::default_rust::default_of(ast, from, target, &node.name).ok()
5290 } else {
5291 None
5292 },
5293 })
5294 };
5295 match &node.kind {
5296 TypeKind::Primitive(kind) => MemberWrite::Primitive(*kind),
5297 // A `wstring`. The default cell is a UInt32 **octet** count and
5298 // UTF-16 code units with no NUL — XTypes 1.3 §7.4.1.1.2 — and
5299 // it is one cell of eight, not one form of three. The three
5300 // binary axes are what a length prefix counts (C1), whether a
5301 // terminating NUL joins the run and is counted (C2), and how
5302 // wide one code unit is (C3); `WstringLengthPolicy` is their
5303 // product, `WstringLengthPolicy::ALL_CELLS` enumerates it, and
5304 // five of the eight cells carry the name of an implementation
5305 // measured writing them. The byte **order** of the units is a
5306 // fourth axis, `WstringUnitOrder`, with four points of its own,
5307 // because it crosses the length convention rather than
5308 // extending it.
5309 //
5310 // All four come out of the key, the way `MapDelimiter` and
5311 // `MapEntryOrder` do, and go into the emitted call as
5312 // arguments. A runtime default would be a second place the wire
5313 // format lives.
5314 //
5315 // The bound counts UTF-16 code units and not octets, which is
5316 // `Reader::fit_wstring` and not `Reader::fit_string`.
5317 TypeKind::String {
5318 width: CharWidth::WChar,
5319 bound,
5320 } => {
5321 let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
5322 MemberWrite::WString {
5323 bound,
5324 narrow: narrowing(bound),
5325 policy: key.wstring_length,
5326 units: key.wstring_code_units,
5327 order: key.wstring_unit_order,
5328 // The member position. A `wstring` reached as a
5329 // collection element is rewritten to
5330 // `WirePosition::Element` by `element_writer` and
5331 // `element_reader`, which is where the `wchar` framing's
5332 // element answer is substituted as well — the nine
5333 // measured implementations answer the two positions
5334 // alike on all four `wstring` axes, and passing the
5335 // position is what keeps that a measurement.
5336 position: WirePosition::Member,
5337 }
5338 }
5339 TypeKind::String { bound, .. } => {
5340 let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
5341 MemberWrite::String {
5342 bound,
5343 narrow: narrowing(bound),
5344 }
5345 }
5346 // The digit count comes out of the key the way `MapEntryOrder` and
5347 // the four `wstring` axes do, and goes into the emitted call as an
5348 // argument. A runtime default would be a second place the wire
5349 // format lives.
5350 TypeKind::Fixed { digits, scale } => MemberWrite::Fixed {
5351 digits: *digits,
5352 scale: *scale,
5353 count: key.fixed_digits,
5354 },
5355 TypeKind::Sequence { element, bound } => element.resolved().map_or_else(
5356 || MemberWrite::Unsupported("sequence with an unresolved element"),
5357 |element| {
5358 let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
5359 // Whether each element carries its own length is the same
5360 // crossing `zerodds-wire-codec`'s sequence walk asks, so
5361 // the emitted bytes and the reference codec cannot part.
5362 let Some(carries_length) =
5363 zerodds_ast_layout::xcdr::sequence_element_carries_length(ast, element, key)
5364 else {
5365 return MemberWrite::Unsupported(
5366 "a sequence element whose own length cannot be told",
5367 );
5368 };
5369 MemberWrite::Sequence {
5370 element: Box::new(write_of_at(
5371 ast,
5372 from,
5373 element,
5374 key,
5375 codec,
5376 inside(ast, target, at),
5377 depth + 1,
5378 )),
5379 bound,
5380 narrow: narrowing(bound),
5381 delimited: zerodds_ast_layout::collection_is_delimited(ast, element, key),
5382 packed: key.element_padding == ElementPadding::Packed,
5383 carries_length,
5384 }
5385 },
5386 ),
5387 TypeKind::Array { element, dims } => element.resolved().map_or_else(
5388 || MemberWrite::Unsupported("array with an unresolved element"),
5389 |element| {
5390 // A dimension that does not evaluate is not a zero-length
5391 // array. Substituting one wrote no elements at all and
5392 // said the sample had been encoded.
5393 let Ok(dims) = dims
5394 .iter()
5395 .map(|dim| ast.eval_integral(*dim))
5396 .collect::<Result<Vec<_>, _>>()
5397 else {
5398 return MemberWrite::Unsupported("array whose length does not evaluate");
5399 };
5400 MemberWrite::Array {
5401 element: Box::new(write_of_at(
5402 ast,
5403 from,
5404 element,
5405 key,
5406 codec,
5407 inside(ast, target, at),
5408 depth + 1,
5409 )),
5410 delimited: zerodds_ast_layout::array_is_delimited(ast, element, key),
5411 dims,
5412 packed: key.element_padding == ElementPadding::Packed,
5413 }
5414 },
5415 ),
5416 // A `map<K, V>` is a sequence of pairs, §7.4.1.1.4, and the two
5417 // halves are two different types — so both are walked, at the
5418 // element position §7.3.1.2.1.12.4 gives them.
5419 TypeKind::Map {
5420 key: k,
5421 value,
5422 bound,
5423 } => {
5424 let (Some(k), Some(value)) = (k.resolved(), value.resolved()) else {
5425 return MemberWrite::Unsupported("map with an unresolved key or value");
5426 };
5427 // The same refusal the C emitter makes, and the plan's own:
5428 // a setting that asked for ascending entries over a key type
5429 // whose values have no order cannot be honoured, and falling
5430 // back to declaration order would be a flag that reported
5431 // success and changed no octet. This column reaches it only
5432 // through the plan — the order it writes is its `BTreeMap`'s
5433 // — which is why the crossing itself is not named here.
5434 if zerodds_ast_layout::plan::map_entry_ordering(ast, target, key).is_err() {
5435 return MemberWrite::Unsupported(
5436 "a map whose key type has no value order, under --map-entries ascending-key",
5437 );
5438 }
5439 let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
5440 let mut deps = zerodds_ast_layout::xcdr::DepCollector::default();
5441 let Ok(layout) = zerodds_ast_layout::xcdr::plan(ast, target, key, &mut deps) else {
5442 return MemberWrite::Unsupported("map whose layout does not plan");
5443 };
5444 let inside = inside(ast, target, at);
5445 let ascending = matches!(
5446 zerodds_ast_layout::plan::map_entry_ordering(ast, target, key),
5447 Ok(Some(_))
5448 );
5449 MemberWrite::Map {
5450 key: Box::new(write_of_at(ast, from, k, key, codec, inside, depth + 1)),
5451 value: Box::new(write_of_at(ast, from, value, key, codec, inside, depth + 1)),
5452 bound,
5453 ascending,
5454 narrow: narrowing(bound),
5455 // Rule (14) exempts a map only when both halves are
5456 // primitive; the plan reads that through `MapDelimiter`,
5457 // and asking it here is what keeps the emitted bytes and
5458 // the planned extent from disagreeing.
5459 delimited: layout.needs_dheader,
5460 }
5461 }
5462 // A bitmask is the unsigned integer Table 33 of §7.4.1.1.3.2
5463 // gives for its `@bit_bound`, and the width is the **plan's**
5464 // answer: `plan::scalar_write` is the crossing every other
5465 // column asks — `crates/lang-rules/src/emit/encode.rs` reaches
5466 // `ScalarWrite::Bitmask` through the same call — so the twenty
5467 // other columns' octets and this one's cannot part company by
5468 // this column computing a width of its own.
5469 //
5470 // Unlike an enumeration this does **not** write through a
5471 // generated codec: `adapter::cst` materialises a bitmask as
5472 // `pub type Flags = u16;`, and a type alias to a primitive has
5473 // no `impl` block for an `encode` to live in.
5474 TypeKind::Bitmask { .. } => {
5475 let Some(ScalarWrite::Bitmask { bits }) =
5476 zerodds_ast_layout::plan::scalar_write(ast, target, key, WcharPosition::Member)
5477 else {
5478 return MemberWrite::Unsupported("a bitmask the layout plan gives no holder for");
5479 };
5480 let Some(declared) = declared_bits(ast, target, bits) else {
5481 return MemberWrite::Unsupported(
5482 "a bitmask with a bit position that does not evaluate",
5483 );
5484 };
5485 MemberWrite::Bitmask { bits, declared }
5486 }
5487 // An enumeration writes through its own generated codec, like any
5488 // other named type. Writing the holder straight from here emitted
5489 // `w.write_u32(self.coord_convention)` — the field is the
5490 // enumeration and not its holder, so the generated file did not
5491 // compile, and no enumeration had ever been a struct member in a
5492 // tree this emitter had been pointed at. `enumeration_encode_fn`
5493 // already produces the `match self { Self::X => 0, .. }` that
5494 // respects `@value`, and it takes the holder's width from the
5495 // same `key`, so nothing about the wire changes.
5496 TypeKind::Enumeration { .. } => match crate::adapter::cst::relative_path(ast, from, node) {
5497 Ok(path) => MemberWrite::Nested {
5498 type_name: path
5499 .segments()
5500 .iter()
5501 .map(|s| s.as_str().to_string())
5502 .collect::<Vec<_>>()
5503 .join("::"),
5504 codec,
5505 extended: Extended::of(key),
5506 },
5507 Err(_) => MemberWrite::Unsupported("an enumeration the model has no module for"),
5508 },
5509 // The behaviour was already resolved through the alias chain by
5510 // `Ast::effective_try_construct`, so the position carries over
5511 // unchanged.
5512 TypeKind::Alias { aliased } => aliased.resolved().map_or_else(
5513 || MemberWrite::Unsupported("typedef with an unresolved target"),
5514 |aliased| write_of_at(ast, from, aliased, key, codec, at, depth + 1),
5515 ),
5516 // A union is written the same way, and for the same reason: it
5517 // has a generated codec of its own, and `union_codec` gives it
5518 // exactly the `encode(&self, w)` and `decode(r)` that
5519 // `MemberWrite::Nested` calls. Leaving it out of this arm did not
5520 // make a union member unwritable in some deeper sense — it made
5521 // the emitter refuse a type it had already generated the codec
5522 // for, which is what `NoWriter { member: "cov", kind: "union" }`
5523 // was: `FramedPose.cov` is a `CovMatrix`, an `@appendable` union
5524 // over an enumerated discriminator, and every one of its own
5525 // cases emitted fine.
5526 // A bitset joins them: `adapter::cst` materialises it as a
5527 // struct with one field per named bitfield, and `bitset_encode_fn`
5528 // gives that struct the `encode(&self, w)` and `decode(r)` this
5529 // shape calls. It was in the `other` arm below, which is what
5530 // `NoWriter { kind: "bitset" }` was.
5531 TypeKind::Structure { .. }
5532 | TypeKind::Valuetype { .. }
5533 | TypeKind::Union { .. }
5534 | TypeKind::Bitset { .. } => {
5535 // The path, not the name: `Nested` becomes `<path>::decode`
5536 // in the reader, and a sibling module's type does not resolve
5537 // by its simple name from here.
5538 match crate::adapter::cst::relative_path(ast, from, node) {
5539 Ok(path) => MemberWrite::Nested {
5540 type_name: path
5541 .segments()
5542 .iter()
5543 .map(|s| s.as_str().to_string())
5544 .collect::<Vec<_>>()
5545 .join("::"),
5546 codec,
5547 extended: Extended::of(key),
5548 },
5549 Err(_) => MemberWrite::Unsupported("a type the model has no module for"),
5550 }
5551 }
5552 other => MemberWrite::Unsupported(other.as_str()),
5553 }
5554}
5555
5556/// The positions a bitmask names, one bit per position.
5557///
5558/// The mask `Reader::read_bitmask` reduces a stream to. §7.4.1.1.3.2
5559/// puts a bit above the bound outside the bitmask, so a position at or
5560/// beyond the bound is not part of the mask; a bit set inside the bound
5561/// that the type does not name is not part of the value either, and the
5562/// reader keeps only the declared positions.
5563///
5564/// The same rule `bitmask_body` in `crates/lang-rules/src/emit/decode.rs`
5565/// emits inline for the other twenty columns — a second copy rather than
5566/// a shared call, because the two are compared against each other and a
5567/// shared helper would make them agree about a mistake.
5568fn declared_bits(ast: &Ast, type_: Id<TypeNode>, bound: u16) -> Option<u64> {
5569 let mut declared = 0u64;
5570 for bit in ast.bits_of(type_) {
5571 // `None` and not `continue`: a bit whose position does not
5572 // evaluate is a bit this mask cannot name, and leaving it out
5573 // would make the reader drop a bit the type does declare, for a
5574 // reason nothing states. The caller
5575 // turns this into `MemberWrite::Unsupported` naming the
5576 // construct.
5577 let position = ast.effective_bit_position(*bit)?;
5578 if position < bound && position < 64 {
5579 declared |= 1u64 << position;
5580 }
5581 }
5582 Some(declared)
5583}
5584
5585/// The position a collection's elements sit in.
5586///
5587/// §7.3.1.2.1.12.4 gives the elements their own behaviour —
5588/// `sequence<@try_construct(TRIM) String5, 10>` — and Table 17 keeps it
5589/// separate from the collection's own: the sequence's length overflow
5590/// and an element's bound overflow are two different failures, which is
5591/// the split OpenDDS's generated element loop implements.
5592fn inside(ast: &Ast, collection: Id<TypeNode>, at: Position) -> Position {
5593 Position {
5594 behaviour: ast.element_try_construct(collection, at.written),
5595 written: None,
5596 }
5597}
5598
5599/// The writer a primitive takes.
5600///
5601/// Exhaustive, so a primitive added to the model without a writer is a
5602/// compile error rather than a member that silently takes another one's.
5603const fn writer_name(kind: PrimitiveKind) -> &'static str {
5604 match kind {
5605 PrimitiveKind::Boolean => "write_bool",
5606 PrimitiveKind::Octet | PrimitiveKind::UInt8 => "write_u8",
5607 PrimitiveKind::Char => "write_char",
5608 // Member and element take different octets under TAO's framing,
5609 // so there is no positionless `write_wchar` to name here. This
5610 // arm is the member's; `element_writer_name` has the element's.
5611 PrimitiveKind::WChar => "write_wchar_member",
5612 PrimitiveKind::Int8 => "write_i8",
5613 PrimitiveKind::Int16 => "write_i16",
5614 PrimitiveKind::UInt16 => "write_u16",
5615 PrimitiveKind::Int32 => "write_i32",
5616 PrimitiveKind::UInt32 => "write_u32",
5617 PrimitiveKind::Int64 => "write_i64",
5618 PrimitiveKind::UInt64 => "write_u64",
5619 PrimitiveKind::Float32 => "write_f32",
5620 PrimitiveKind::Float64 => "write_f64",
5621 PrimitiveKind::Float128 => "write_f128",
5622 }
5623}
5624
5625const fn reader_name(kind: PrimitiveKind) -> &'static str {
5626 match kind {
5627 PrimitiveKind::Boolean => "read_bool",
5628 PrimitiveKind::Octet | PrimitiveKind::UInt8 => "read_u8",
5629 PrimitiveKind::Char => "read_char",
5630 // As `writer_name`: the member's reader.
5631 PrimitiveKind::WChar => "read_wchar_member",
5632 PrimitiveKind::Int8 => "read_i8",
5633 PrimitiveKind::Int16 => "read_i16",
5634 PrimitiveKind::UInt16 => "read_u16",
5635 PrimitiveKind::Int32 => "read_i32",
5636 PrimitiveKind::UInt32 => "read_u32",
5637 PrimitiveKind::Int64 => "read_i64",
5638 PrimitiveKind::UInt64 => "read_u64",
5639 PrimitiveKind::Float32 => "read_f32",
5640 PrimitiveKind::Float64 => "read_f64",
5641 PrimitiveKind::Float128 => "read_f128",
5642 }
5643}
5644
5645fn call(method: &str, args: Vec<rust::Expr>) -> rust::Expr {
5646 rust::Expr::MethodCall {
5647 receiver: Box::new(rust::Expr::Path(Path::single("r"))),
5648 method: Ident::new(method),
5649 args,
5650 }
5651}
5652
5653/// Bind a framing header so it can be patched once its value is written.
5654/// Bind an opened header, propagating the writer's refusal.
5655///
5656/// The three operations this opens — `write_pid`, `write_pid_extended`
5657/// and `write_emheader` — refuse a member id their header's field cannot
5658/// carry (XTypes 1.3 §7.2.2.4.4.4.4 for the 28 bits, §7.4.1.2.2 for the
5659/// reserved short ids), so each returns a `Result` and the `?` carries
5660/// the refusal out of the generated `encode`. Nothing here can trip it
5661/// today, because the plan chose the id and the form together; it is
5662/// there because a generated codec must not be the one place a masked
5663/// id reaches the wire.
5664fn bind_header(name: &str, method: &str, args: Vec<rust::Expr>) -> rust::Statement {
5665 rust::Statement::Let {
5666 name: Ident::new(name),
5667 type_: None,
5668 value: Some(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
5669 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
5670 method: Ident::new(method),
5671 args,
5672 }))),
5673 mutable: false,
5674 }
5675}
5676
5677fn call_statement(method: &str, args: Vec<rust::Expr>) -> rust::Statement {
5678 rust::Statement::Semi(rust::Expr::MethodCall {
5679 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
5680 method: Ident::new(method),
5681 args,
5682 })
5683}
5684
5685/// The same call, with the `?` a fallible one needs.
5686fn try_call_statement(method: &str, args: Vec<rust::Expr>) -> rust::Statement {
5687 rust::Statement::Semi(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
5688 receiver: Box::new(rust::Expr::Path(Path::single("w"))),
5689 method: Ident::new(method),
5690 args,
5691 })))
5692}
5693
5694/// `ParameterRole::Member` or `ParameterRole::Optional`, as a path.
5695fn parameter_role(role: &str) -> rust::Expr {
5696 rust::Expr::Path(Path::new(&["ParameterRole", role]))
5697}
5698
5699/// One `@optional` member's read, into the `Option<T>` field.
5700///
5701/// The mirror of [`write_optional_member`], marker for marker:
5702///
5703/// - rule (20): the BOOLEAN `<is_present>` decides;
5704/// - rule (19): a declared parameter length of zero is the absence
5705/// (§7.4.1.2), and `finish_pid` still runs either way — the parameter
5706/// occupies its place in the list whether or not it carries a value,
5707/// and skipping the close would leave the alignment origin inside it;
5708/// - inside a `@mutable` type: the parameter is present, because a
5709/// parameter list that omitted it would not have brought the reader
5710/// here at all.
5711fn read_optional_member(
5712 plan: &zerodds_ast_layout::plan::MemberPlan,
5713 name: &str,
5714 kind: &MemberWrite,
5715) -> Result<Vec<rust::Statement>, EncodeError> {
5716 let some = |value: rust::Expr| rust::Expr::Call {
5717 callee: Box::new(rust::Expr::Path(Path::single("Some"))),
5718 args: vec![value],
5719 };
5720 let read = rust::Expr::Try(Box::new(read_for(name, kind)?));
5721 match plan.header {
5722 MemberHeader::OptionalFlag => Ok(vec![let_statement(
5723 name,
5724 rust::Expr::If {
5725 condition: Box::new(rust::Expr::Try(Box::new(call("read_bool", Vec::new())))),
5726 then: rust::Block {
5727 statements: vec![rust::Statement::Tail(some(read))],
5728 },
5729 otherwise: Some(rust::Block {
5730 statements: vec![rust::Statement::Tail(rust::Expr::Path(Path::single(
5731 "None",
5732 )))],
5733 }),
5734 },
5735 )]),
5736 MemberHeader::OptionalCdr1 { .. } => Ok(vec![
5737 let_statement(
5738 "pid",
5739 rust::Expr::Try(Box::new(call(
5740 "read_pid_as",
5741 vec![parameter_role("Optional")],
5742 ))),
5743 ),
5744 let_statement(
5745 name,
5746 rust::Expr::If {
5747 condition: Box::new(rust::Expr::Binary {
5748 op: rust::BinaryOp::Eq,
5749 lhs: Box::new(rust::Expr::Field {
5750 receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
5751 name: Ident::new("length"),
5752 }),
5753 rhs: Box::new(rust::Expr::Literal(Literal::int(0))),
5754 }),
5755 then: rust::Block {
5756 statements: vec![rust::Statement::Tail(rust::Expr::Path(Path::single(
5757 "None",
5758 )))],
5759 },
5760 otherwise: Some(rust::Block {
5761 statements: vec![rust::Statement::Tail(some(read))],
5762 }),
5763 },
5764 ),
5765 rust::Statement::Semi(rust::Expr::Try(Box::new(call(
5766 "finish_pid",
5767 vec![rust::Expr::Path(Path::single("pid"))],
5768 )))),
5769 ]),
5770 _ => {
5771 let mut out = match plan.header {
5772 MemberHeader::PidCdr1 { .. } => vec![let_statement(
5773 "pid",
5774 rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
5775 )],
5776 MemberHeader::EmHeader { .. } => vec![let_statement(
5777 "emheader",
5778 rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
5779 )],
5780 _ => Vec::new(),
5781 };
5782 out.push(let_statement(name, some(read)));
5783 if matches!(plan.header, MemberHeader::PidCdr1 { .. }) {
5784 out.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
5785 "finish_pid",
5786 vec![rust::Expr::Path(Path::single("pid"))],
5787 )))));
5788 }
5789 Ok(out)
5790 }
5791 }
5792}
5793
5794/// One `field: local` pair of the struct literal a decoder ends with.
5795///
5796/// Both halves go through `escape_keyword`, and that is the whole point:
5797/// the field is a member named in IDL and the local is the binding
5798/// `let_statement` made for it, so a member called `type` needs `r#type`
5799/// on both sides. Escaping only the field produced `r#type: type`, which
5800/// is a Rust syntax error — the generated file did not compile, and the
5801/// three call sites that built this pair by hand had each got it the
5802/// same way wrong.
5803fn field_binding(name: &str) -> (Ident, rust::Expr) {
5804 let escaped = rust::escape_keyword(name);
5805 (
5806 Ident::new(&escaped),
5807 rust::Expr::Path(Path::single(&escaped)),
5808 )
5809}
5810
5811fn let_statement(name: &str, value: rust::Expr) -> rust::Statement {
5812 rust::Statement::Let {
5813 name: Ident::new(&rust::escape_keyword(name)),
5814 type_: None,
5815 value: Some(value),
5816 mutable: false,
5817 }
5818}
5819
5820/// `#[allow(<lint>)]`.
5821fn allow_lint(lint: &str) -> rust::Attr {
5822 rust::Attr {
5823 path: Path::single("allow"),
5824 args: vec![rust::AttrArg::Word(Ident::new(lint))],
5825 }
5826}
5827
5828/// `impl Default` for an aggregate, from the values XTypes 1.3 Table 9
5829/// and `@default` give its members.
5830///
5831/// Not `#[derive(Default)]`: Rust's default and the specification's are
5832/// two different things wherever a member is an enumeration — Rust has
5833/// none without a `#[default]` attribute, and §7.3.1.2.1.10 names the
5834/// first enumerator or the one `@default_literal` picks. The emitter
5835/// already computes the right value for every member, and this is where
5836/// a caller can reach it.
5837///
5838/// # Errors
5839/// [`EncodeError`] naming the member whose default the model cannot
5840/// give.
5841fn default_impl(ast: &Ast, type_: Id<TypeNode>) -> Result<Option<rust::Item>, EncodeError> {
5842 let Ok(node) = ast.type_(type_) else {
5843 return Ok(None);
5844 };
5845 // A bitset joins them, and for the same reason: it materialises as
5846 // a struct, so `Default::default()` on one is a thing a caller can
5847 // write and used to be `the trait bound `BS: Default` is not
5848 // satisfied`. A valuetype is deliberately not here — it was not
5849 // before — and a bitmask cannot be: `impl Default for u16` is not
5850 // this crate's to write.
5851 if !matches!(
5852 node.kind,
5853 TypeKind::Structure { .. } | TypeKind::Union { .. } | TypeKind::Bitset { .. }
5854 ) {
5855 return Ok(None);
5856 }
5857 let from = node.module;
5858 // Named from inside its own module, which is the plain name.
5859 let self_type = rust::Type::named(Path::single(&rust::escape_keyword(&node.name)));
5860 let value = crate::default_rust::default_of(ast, from, type_, &node.name)?;
5861 Ok(Some(rust::Item::Impl(rust::Impl {
5862 doc: Doc::none(),
5863 // Where every member's Table 9 value happens to be Rust's own
5864 // default, clippy sees a derivable impl. It is written out on
5865 // purpose (see above), so the allowance is the generator's and
5866 // sits on the impl, not in each crate that includes the text.
5867 attrs: vec![allow_lint("clippy::derivable_impls")],
5868 generics: Vec::new(),
5869 trait_: Some(rust::Type::named(Path::single("Default"))),
5870 self_type: self_type.clone(),
5871 items: vec![rust::Item::Function(rust::Function {
5872 doc: Doc::none(),
5873 attrs: Vec::new(),
5874 visibility: Visibility::Private,
5875 name: Ident::new("default"),
5876 generics: Vec::new(),
5877 params: Vec::new(),
5878 returns: Some(rust::Type::named(Path::single("Self"))),
5879 body: Some(rust::Block {
5880 statements: vec![rust::Statement::Tail(value)],
5881 }),
5882 })],
5883 })))
5884}
5885
5886/// The module a type is declared in, for naming other types relative to
5887/// it. A stale handle falls back to the root, where a relative path is
5888/// the plain name — the same thing the emitter wrote before it named
5889/// anything relatively.
5890/// `self`, then `.parent` once per structure between `type_` and the one that declares
5891/// `member`: the base is the first member `parent` of the derived structure (XTypes 1.3
5892/// §7.5.1.4.1.3).
5893fn holder_of(ast: &Ast, type_: Id<TypeNode>, member: NodeId) -> rust::Expr {
5894 let mut place = rust::Expr::Path(Path::single("self"));
5895 for _ in 0..crate::emit::inheritance_depth(ast, type_, member) {
5896 place = rust::Expr::Field {
5897 receiver: Box::new(place),
5898 name: Ident::new(crate::adapter::cst::PARENT),
5899 };
5900 }
5901 place
5902}
5903
5904/// A literal of `type_` from the fields of its own members and those of its bases, each
5905/// level nested in the `parent` of the one above it. `rest` is what a level not every
5906/// member of which is named takes the others from.
5907fn nested_struct_literal(
5908 ast: &Ast,
5909 type_: Id<TypeNode>,
5910 entries: Vec<(NodeId, (Ident, rust::Expr))>,
5911 rest: &impl Fn() -> Option<Box<rust::Expr>>,
5912) -> rust::Expr {
5913 let deepest = entries
5914 .iter()
5915 .map(|(node, _)| crate::emit::inheritance_depth(ast, type_, *node))
5916 .max()
5917 .unwrap_or(0);
5918 let mut inner: Option<rust::Expr> = None;
5919 for depth in (0..=deepest).rev() {
5920 let mut fields: Vec<(Ident, rust::Expr)> = Vec::new();
5921 if let Some(parent) = inner.take() {
5922 fields.push((Ident::new(crate::adapter::cst::PARENT), parent));
5923 }
5924 for (node, field) in &entries {
5925 if crate::emit::inheritance_depth(ast, type_, *node) == depth {
5926 fields.push(field.clone());
5927 }
5928 }
5929 let path = if depth == 0 {
5930 Path::single("Self")
5931 } else {
5932 let owner = crate::emit::base_at(ast, type_, depth);
5933 ast.type_(owner)
5934 .ok()
5935 .and_then(|node| {
5936 crate::adapter::cst::relative_path(ast, module_of(ast, type_), node).ok()
5937 })
5938 .unwrap_or_else(|| Path::single("Self"))
5939 };
5940 inner = Some(rust::Expr::StructLit {
5941 path,
5942 fields,
5943 base: rest(),
5944 });
5945 }
5946 inner.unwrap_or(rust::Expr::StructLit {
5947 path: Path::single("Self"),
5948 fields: Vec::new(),
5949 base: rest(),
5950 })
5951}
5952
5953fn module_of(ast: &Ast, type_: Id<TypeNode>) -> Id<zerodds_ast::ModuleNode> {
5954 ast.type_(type_)
5955 .map_or_else(|_| ast.root_module(), |node| node.module)
5956}
5957
5958fn alloc_format(error: &impl core::fmt::Debug) -> String {
5959 format!("{error:?}")
5960}
5961
5962/// Classify what the planner said: a clause of the specification, or a
5963/// gap of this tree.
5964///
5965/// The planner already carries the distinction in its own type —
5966/// `LayoutError::RecursionOutsideCollection` names IDL 4.2
5967/// §7.4.1.4.4.4.4 — and it was lost here, where every reason became one
5968/// string. Read the variant, never the message: a classification that
5969/// greps its own error text breaks the first time the text is reworded.
5970pub(crate) fn plan_refusal(error: zerodds_ast_layout::plan::LayoutError) -> EncodeError {
5971 match error {
5972 LayoutError::RecursionOutsideCollection { .. } => EncodeError::SpecRefuses {
5973 detail: format!("{error}"),
5974 },
5975 other => EncodeError::NoLayout {
5976 detail: alloc_format(&other),
5977 },
5978 }
5979}
5980
5981/// `_ => Err(WireError::refused(r.position(), "<what>"))`: the arm a
5982/// generated decoder ends a match with when the octets name nothing the
5983/// type declares.
5984fn refusal_arm(what: &str) -> rust::MatchArm {
5985 rust::MatchArm {
5986 pattern: rust::Pattern::Wildcard,
5987 guard: None,
5988 body: refused_err("r", what),
5989 }
5990}
5991
5992/// `Err(WireError::refused(<sink>.position(), "<what>"))`, the value a
5993/// generated codec returns for octets or a sample it cannot carry.
5994fn refused_err(sink: &str, what: &str) -> rust::Expr {
5995 rust::Expr::Call {
5996 callee: Box::new(rust::Expr::Path(Path::single("Err"))),
5997 args: vec![rust::Expr::Call {
5998 callee: Box::new(rust::Expr::Path(Path::new(&["WireError", "refused"]))),
5999 args: vec![
6000 rust::Expr::MethodCall {
6001 receiver: Box::new(rust::Expr::Path(Path::single(sink))),
6002 method: Ident::new("position"),
6003 args: Vec::new(),
6004 },
6005 rust::Expr::Literal(Literal::string(what)),
6006 ],
6007 }],
6008 }
6009}