Skip to main content

zerodds_lang_rules/adapter/
c.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The C CST as the other end of a rule. Spec §3.2.
4//!
5//! The counterpart of [`crate::adapter::cst`], which does the same for
6//! Rust. Reading the two side by side is the point: where they differ,
7//! the difference is C's, and it is written down here rather than
8//! spread through a renderer.
9//!
10//! Values are resolved from the model, never from the overlay — the
11//! same rule the Rust materialiser follows and for the same reason
12//! (spec §2.3).
13//!
14//! ## The three things C forces that Rust does not
15//!
16//! 1. **A name for every type.** Rust writes `Vec<i32>` where a member
17//!    needs it; C has no generics, so `sequence<long>` has to become a
18//!    declared type with a name of its own. Those names are structural —
19//!    `zd_seq_int32_t` — so two identical anonymous collections collapse
20//!    onto one declaration instead of two that differ only in a counter.
21//! 2. **Declaration before use.** The Rust materialiser walks the model
22//!    in declaration order and stops. Here the walk is a post-order over
23//!    the type graph, because a C translation unit that names a struct
24//!    before declaring it does not compile.
25//! 3. **No enumeration wide enough.** A C enumeration constant is an
26//!    `int` (C11 §6.7.2.2), and `@bit_bound(32)` admits enumerator
27//!    values up to `2^32 - 1`, which an `int` cannot hold. So an IDL
28//!    enumeration becomes a fixed-width integer typedef plus one
29//!    constant per enumerator, and the C `enum` is used only where every
30//!    value fits. Emitting the `enum` unconditionally would make the
31//!    holder width implementation-defined for exactly the cases the
32//!    interop corpus tests.
33//!
34//! ## The memory shapes
35//!
36//! | IDL | C |
37//! |---|---|
38//! | `string` | `const char *` |
39//! | `wstring` | `const char *`, holding UTF-8 |
40//! | `fixed<P,S>` | `const char *`, holding the decimal |
41//! | `sequence<T>` | `struct { uint32_t _maximum; uint32_t _length; T *_buffer; bool _release; }` |
42//! | array | a typedef of the C array |
43//! | `map<K,V>` | `struct { uint32_t _maximum; uint32_t _length; Entry *_buffer; bool _release; }` of `struct { K key; V value; }`, named `[key][element]Map`, with the operations of Table 47 (see [`c_map`](super::c_map)) |
44//! | `union` | `struct { D _d; union { ... } _u; }` |
45//! | `@optional T m` | `T *m;`, NULL for an absent member (a `string` stays `const char *`) |
46//! | `@external T m` | `T *m;` (a `string` stays `const char *`), with the functions of [`c_lifecycle`](crate::adapter::c_lifecycle) |
47//!
48//! A `wstring` holds UTF-8 rather than `uint16_t` units on purpose: it
49//! puts the §7.4.1.1.2 conversion — the surrogate rule the byte count
50//! depends on — inside the C runtime, where it is a second
51//! implementation of the clause and not a copy of the Rust one's answer.
52//!
53//! ## Where the IDL spelling of a member is kept
54//!
55//! C has no keyword the generated identifier could keep, so
56//! [`c::escape_keyword`] moves a colliding name — `long static;` becomes
57//! `int32_t static_;`. That rename is not only a readability question:
58//! XTypes 1.3 §7.3.1.2.1.1 computes a member's ID under `@autoid(HASH)`
59//! and under `@hashid` from the first four octets of the MD5 of its
60//! **IDL** name, so a header that only knows `static_` cannot compute
61//! the ID for `static` and the member it writes lands under an ID the
62//! peer does not have.
63//!
64//! Every declaration that has named parts therefore carries them in one
65//! macro of its own, beside the declaration:
66//!
67//! ```c
68//! #define ZD_IDL_MEMBERS_package_Sample { { "impl", "impl" }, \
69//!     { "static_", "static" } }
70//! ```
71//!
72//! and [`MEMBER_NAME`] is the record the entries fit, so a caller writes
73//!
74//! ```c
75//! static const zd_member_name names[] = ZD_IDL_MEMBERS_package_Sample;
76//! ```
77//!
78//! and has the mapping, in IDL declaration order — which is the order
79//! the member IDs are assigned in — with the C spelling on the left and
80//! the IDL spelling on the right.
81//!
82//! Three forms were measured against the flag set the probe compiles
83//! with (`-std=c99 -Wall -Wextra -Wpedantic -Wconversion
84//! -Wsign-conversion -Werror`); this one is what is left:
85//!
86//! - **A table as an object** — `static const zd_member_name
87//!   zd_names_T[] = { … };` — is what C# and Python effectively have,
88//!   and is the form this tree cannot write at all: [`c::Variable`]
89//!   carries no array declarator, so adding one would be a change to
90//!   `zerodds-lang-cst` made for this. The reason not to make it is the
91//!   one the constants above are `#define`s for: the object is a
92//!   separate object in every translation unit that includes the header.
93//!   Whether an unused one is also diagnosed depends on the compiler —
94//!   GCC documents `-Wunused-const-variable` as enabled by
95//!   `-Wunused-variable` for C, and Apple clang 21 was silent about a
96//!   header-provided table under the flag set above — so the diagnostic
97//!   is a hazard here and not the measurement.
98//! - **An X-macro block** — `#define ZD_MEMBERS_T(X) X(0, static_,
99//!   "static") …` — enumerates and costs nothing, and its replacement
100//!   list is a run of juxtaposed invocations, which is not a C
101//!   expression at all. It could only reach the output as raw text
102//!   smuggled through an [`Ident`], in a tree whose whole point is that
103//!   it is not text.
104//! - **One `#define` per member** — `#define ZD_IDL_NAME_T_static_
105//!   "static"` — is expressible and costs nothing, and its name is
106//!   ambiguous to anything reading the header: `ZD_IDL_NAME_a_b_c` does
107//!   not say where the type name ends and the field name begins, and IDL
108//!   scopes flatten with the same underscore.
109//!
110//! The braced initialiser is a C expression this tree already carries
111//! ([`c::Expr::Initializer`]), it puts both spellings of a member in one
112//! entry rather than in two lists that can disagree, it is enumerable —
113//! by the preprocessor for a C caller, by a line reader for a tool — and
114//! a macro that nothing expands occupies no storage and is not
115//! diagnosable. A second declaration reaching the same macro name is a
116//! redefinition and fails loudly, which is the property a silent
117//! collision would not have.
118
119use crate::adapter::constant;
120use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
121use crate::apply::Applied;
122use crate::verbatim;
123use seesaw_tgg::ident::GhostId;
124use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TypeKind, TypeNode};
125use zerodds_ast_core::{Id, NodeId};
126use zerodds_lang_cst::c;
127use zerodds_lang_cst::common::{Doc, Ident, Literal};
128
129/// The support typedefs every generated translation unit needs.
130///
131/// Emitted once, ahead of everything the model contributes: a `string`
132/// has the same C shape wherever it appears, and repeating the typedef
133/// per use would not compile.
134#[must_use]
135pub fn prelude() -> Vec<c::Decl> {
136    let mut out = vec![
137        c::Decl::Typedef(c::Typedef {
138            doc: Doc::lines(&[
139                "A `string`. The bytes are the caller's and are not owned here:",
140                "nothing in the write path allocates.",
141            ]),
142            type_: c::Type::Pointer(Box::new(c::Type::Const(Box::new(c::Type::Primitive(
143                c::Primitive::Char,
144            ))))),
145            name: Ident::new(STRING),
146            dims: Vec::new(),
147        }),
148        c::Decl::Typedef(c::Typedef {
149            doc: Doc::lines(&[
150                "A `wstring`, held as UTF-8. The runtime converts to UTF-16 on",
151                "the way out, so the surrogate rule of XTypes 1.3 7.4.1.1.2 is",
152                "implemented in C and not taken from the generator.",
153            ]),
154            type_: c::Type::Pointer(Box::new(c::Type::Const(Box::new(c::Type::Primitive(
155                c::Primitive::Char,
156            ))))),
157            name: Ident::new(WSTRING),
158            dims: Vec::new(),
159        }),
160    ];
161    // The record the `ZD_IDL_MEMBERS_…` macros are written to fit. A
162    // type and not an object: a `typedef` at file scope that nothing
163    // uses is not diagnosable, so every generated translation unit can
164    // carry the shape of the mapping whether or not it reads it.
165    out.extend(struct_typedef(
166        MEMBER_NAME,
167        vec![
168            c::Member {
169                doc: Doc::line("The identifier as this header spells it."),
170                type_: c::Type::Named(Ident::new(STRING)),
171                name: Ident::new("field"),
172                dims: Vec::new(),
173                bits: None,
174            },
175            c::Member {
176                doc: Doc::line("The identifier as the IDL source spelled it."),
177                type_: c::Type::Named(Ident::new(STRING)),
178                name: Ident::new("idl"),
179                dims: Vec::new(),
180                bits: None,
181            },
182        ],
183        Doc::lines(&[
184            "One member's two spellings.",
185            "",
186            "Load-bearing, not documentation. A member's ID under @autoid(HASH)",
187            "and under @hashid is the first four octets of the MD5 of its IDL",
188            "name (XTypes 1.3 7.3.1.2.1.1), so a caller holding only the C",
189            "spelling — which a keyword collision moved — cannot compute the ID",
190            "of the member it is writing.",
191            "",
192            "The entries come out of the `ZD_IDL_MEMBERS_<type>` macro beside",
193            "each declaration, in IDL declaration order:",
194            "",
195            "    static const zd_member_name names[] = ZD_IDL_MEMBERS_a_Sample;",
196        ]),
197    ));
198    out
199}
200
201/// The C name of the `string` support type.
202pub const STRING: &str = "zd_string";
203/// The C name of the `wstring` support type.
204pub const WSTRING: &str = "zd_wstring";
205/// The C name of the record one entry of an IDL-name table fits.
206pub const MEMBER_NAME: &str = "zd_member_name";
207/// The prefix of the macro carrying a declaration's own IDL name.
208///
209/// The same escape that moves a member moves a type — `struct class`
210/// becomes `class_` — and a type's name reaches the wire twice over: as
211/// `TypeObject`'s `type_name` and as the topic type name a peer matches
212/// on. The C spelling cannot be inverted back to it, because
213/// [`c::escape_keyword`] leaves a name that was already spelled `class_`
214/// alone and both arrive at the same identifier.
215pub const IDL_NAME_PREFIX: &str = "ZD_IDL_NAME_";
216/// The prefix of the macro carrying a declaration's IDL member names.
217///
218/// The rest of the name is the declaration's own C name, which is
219/// already the flattened scoped name — so the macro is unique wherever
220/// the type is, and two that were not would be a redefinition the
221/// preprocessor reports rather than a mapping one of them lost.
222pub const IDL_MEMBERS_PREFIX: &str = "ZD_IDL_MEMBERS_";
223/// The C name of the `long double` support type.
224///
225/// The runtime's `zdw_f128` and never the compiler's `long double`: that
226/// type is x87 80-bit extended on x86-64, binary128 on aarch64 and a
227/// plain `double` on macOS arm64, so a generated structure declaring it
228/// would have a different wire format on each. `runtime/c/include/
229/// zerodds_wire.h` carries the measurements.
230pub const FLOAT128: &str = "zdw_f128";
231
232/// Build the C declarations a rule application produced.
233///
234/// # Errors
235/// [`MaterialiseError`] when a value the output has to state does not
236/// evaluate, or a type has no C shape — never a silently dropped
237/// declaration.
238pub fn c_items(ast: &Ast, applied: &Applied) -> Result<Vec<c::Decl>, MaterialiseError> {
239    let mut out = prelude();
240    // `@verbatim(placement = BEGIN_FILE)`, wherever it was written,
241    // opens the file — OMG IDL 4.2 §8.3.5.1. After the prelude, which
242    // is the earliest position this declaration list controls; the
243    // caller's own includes stand above the list either way.
244    let (begin_file, end_file) = verbatim::file_slots(ast, verbatim::aliases::C);
245    out.extend(begin_file.into_iter().map(c::Decl::Verbatim));
246    let order = ordered_types(ast);
247    no_bits_name_collision(ast, &order)?;
248    // Every named aggregate as an incomplete type first. The post-order
249    // walk puts a dependency before its user, except where the graph
250    // has a cycle — `struct A { sequence<B> bs; }; struct B {
251    // sequence<A> as; }` came out with `m_A *_buffer` above the
252    // `typedef` that names `m_A`, and the header did not compile. A
253    // pointer to an incomplete type is what C offers for exactly this,
254    // and a legal cycle always crosses one (a by-value cycle has no
255    // layout and is refused before it gets here). The duplicate
256    // typedefs the walk then produces are removed below, because
257    // `-std=c99` calls a typedef redefinition an error.
258    for id in &order {
259        let Ok(node) = ast.type_(*id) else { continue };
260        if matches!(
261            node.kind,
262            TypeKind::Structure { .. } | TypeKind::Valuetype { .. } | TypeKind::Union { .. }
263        ) {
264            let name = c_type_name(ast, *id);
265            out.push(c::Decl::Typedef(c::Typedef {
266                doc: Doc::none(),
267                type_: c::Type::StructTag(Ident::new(&name)),
268                name: Ident::new(&name),
269                dims: Vec::new(),
270            }));
271        }
272    }
273    let mut emitted: Vec<String> = Vec::new();
274    // A map's entry struct embeds its key and value by value, and a
275    // value on a cycle (`struct Node { map<long, Node> children; }`)
276    // is incomplete at the map's position in the walk. Nothing holds
277    // an entry except the map's `_buffer` pointer, so every entry
278    // definition can wait until all named types are complete.
279    let mut deferred: Vec<c::Decl> = Vec::new();
280    let lifecycle_order = order.clone();
281    for id in order {
282        let start = out.len();
283        declare(ast, applied, id, &mut out, &mut deferred, &mut emitted)?;
284        weave_verbatim(ast, id, start, &mut out);
285    }
286    out.append(&mut deferred);
287    // The walk's own `typedef struct N N;` lines repeat the forwards;
288    // keep the first of each and drop the rest.
289    let mut seen_typedefs: Vec<String> = Vec::new();
290    out.retain(|decl| {
291        if let c::Decl::Typedef(typedef) = decl {
292            if let c::Type::StructTag(tag) = &typedef.type_ {
293                if tag.as_str() == typedef.name.as_str() {
294                    let name = typedef.name.as_str().to_string();
295                    if seen_typedefs.contains(&name) {
296                        return false;
297                    }
298                    seen_typedefs.push(name);
299                }
300            }
301        }
302        true
303    });
304
305    // Constants, exceptions and natives are not types, so they are
306    // walked separately — the model keeps them in arenas of their own,
307    // and the Rust materialiser does the same.
308    for (id, node) in ast.constants() {
309        if ghost_created_for(applied, node.node_id, "CConst").is_none() {
310            continue;
311        }
312        let _ = id;
313        // `eval_const` and not `eval_integral`. Every constant IDL 4.2
314        // admits was asked the integral question here, so
315        // `const string MODULE_ID = "…";` — a constant that evaluates
316        // perfectly well and is not a number — came back as
317        // `Unevaluated` and was not emitted at all. The integral
318        // question is simply the wrong one for four of the eight forms.
319        let value = ast
320            .eval_const(node.expr)
321            .map_err(|_| MaterialiseError::Unevaluated {
322                construct: "constant's value".to_string(),
323                at: node.name.clone(),
324            })?;
325        // A `#define` and not a `static const`: a `static const` in a
326        // header is a separate object in every translation unit that
327        // includes it, and every one that does not use it warns.
328        let type_ = node
329            .type_
330            .resolved()
331            .map_or(c::Type::Primitive(c::Primitive::Int32), |target| {
332                c_value_type(ast, target)
333            });
334        let literal = c_constant_literal(&value, &type_, &node.name)?;
335        out.push(c::Decl::Define(c::Define {
336            doc: Doc::none(),
337            name: Ident::new(&c::escape_keyword(&node.name)),
338            value: Some(c::Expr::Paren(Box::new(c::Expr::Cast {
339                type_: Box::new(type_),
340                value: Box::new(c::Expr::Literal(literal)),
341            }))),
342        }));
343    }
344
345    for (id, node) in ast.exceptions() {
346        if ghost_created_for(applied, node.node_id, "CError").is_none() {
347            continue;
348        }
349        let _ = id;
350        let mut members = Vec::new();
351        let mut names = Vec::new();
352        for member in &node.members {
353            let Ok(member_node) = ast.member(*member) else {
354                continue;
355            };
356            let Some(target) = member_node.type_.resolved() else {
357                return Err(MaterialiseError::Unevaluated {
358                    construct: "exception member type".to_string(),
359                    at: member_node.name.clone(),
360                });
361            };
362            let field = c::escape_keyword(&member_node.name);
363            names.push((field.clone(), member_node.name.clone()));
364            members.push(c::Member {
365                doc: Doc::none(),
366                type_: c_value_type(ast, target),
367                name: Ident::new(&field),
368                dims: Vec::new(),
369                bits: None,
370            });
371        }
372        let exception_name = c::escape_keyword(&node.name);
373        out.extend(struct_typedef(
374            &exception_name,
375            members,
376            Doc::line("An exception, as the structure its members make."),
377        ));
378        out.extend(idl_members(&exception_name, &names)?);
379        out.push(idl_name(&exception_name, &node.name));
380    }
381
382    // The initialiser, destructor and copy functions of the aggregates
383    // that hold an external member (XTypes 1.3 §7.5.1.2.3.1), after every
384    // type they name and before the text that closes the file.
385    out.extend(super::c_lifecycle::lifecycle(ast, &lifecycle_order)?);
386
387    // The operations of Table 47 on every map, and the maps of every
388    // type (XTypes 1.3 §7.5.1.3.2), after every type they name.
389    out.extend(super::c_map::maps(ast, &lifecycle_order, &emitted)?);
390
391    // `@verbatim(placement = END_FILE)` closes the file, after even the
392    // constants and exceptions. OMG IDL 4.2 §8.3.5.1.
393    out.extend(end_file.into_iter().map(c::Decl::Verbatim));
394
395    Ok(out)
396}
397
398/// Weave one type's `@verbatim` text around what `declare` just pushed.
399/// OMG IDL 4.2 §8.3.5.1; the slot semantics are [`crate::verbatim`]'s.
400///
401/// `start` is where the type's declarations begin in `out`.
402/// `BEGIN_DECLARATION` and `END_DECLARATION` open and close the member
403/// list of the type's `struct`; an enumeration and a typedef have no body
404/// to hold raw text, so for them the text lands adjacent to the
405/// declaration — the nearest position the tree can express, never a
406/// dropped directive.
407fn weave_verbatim(ast: &Ast, id: Id<TypeNode>, start: usize, out: &mut Vec<c::Decl>) {
408    verbatim::weave(
409        verbatim::declaration_slots(ast, id, verbatim::aliases::C),
410        &c_type_name(ast, id),
411        start,
412        out,
413    );
414}
415
416impl verbatim::Carrier for c::Decl {
417    fn verbatim(text: String) -> Self {
418        Self::Verbatim(text)
419    }
420
421    fn take_into_body(
422        &mut self,
423        name: &str,
424        begin: &mut Vec<String>,
425        end: &mut Vec<String>,
426    ) -> bool {
427        let Self::Struct(node) = self else {
428            return false;
429        };
430        if node.name.as_ref().map(Ident::as_str) != Some(name) {
431            return false;
432        }
433        node.body_text.begin.append(begin);
434        node.body_text.end.append(end);
435        true
436    }
437}
438
439/// Every type in the model, dependencies first.
440///
441/// Post-order over the type graph. A type already visited is not
442/// revisited, which also means a cycle stops rather than recurses — a
443/// recursive IDL type has no C declaration order at all, and
444/// [`c_codec`](crate::encode_c::c_codec) is where that is reported.
445#[must_use]
446pub fn ordered_types(ast: &Ast) -> Vec<Id<TypeNode>> {
447    let all: Vec<Id<TypeNode>> = ast.types().map(|(id, _)| id).collect();
448    let order = ordered_types_from(ast, &all);
449    // The node every `any` points at stays in the model when the types
450    // that held one have been left out (`Ast::drop_outside_profile`).
451    // Nothing reaches it then, and a codec for it is a refusal of a
452    // construct no declaration uses. One that something still reaches is
453    // kept, so that the refusal by name stays where it is needed.
454    order
455        .iter()
456        .copied()
457        .filter(|id| {
458            let no_data_form = ast
459                .type_(*id)
460                .is_ok_and(|node| matches!(node.kind, TypeKind::Any | TypeKind::ValueBase));
461            !no_data_form
462                || order
463                    .iter()
464                    .any(|other| other != id && dependencies(ast, *other).contains(id))
465        })
466        .collect()
467}
468
469/// The same walk, from named roots instead of from the whole model.
470///
471/// What a translation unit for one topic type needs, and no more. The
472/// distinction matters in C and not in Rust: a `static` function nothing
473/// calls is a warning, so emitting a codec for a type the root cannot
474/// reach would make every generated file noisy — and a generator whose
475/// output warns trains its users to stop reading warnings.
476#[must_use]
477pub fn ordered_types_from(ast: &Ast, roots: &[Id<TypeNode>]) -> Vec<Id<TypeNode>> {
478    ordered_types_from_pruning(ast, roots, &[])
479}
480
481/// [`ordered_types_from`] that neither lists a type in `pruned` nor follows
482/// its dependencies: what a type reaches only through a pruned type is not
483/// needed, as the pruned type's code is written elsewhere.
484#[must_use]
485pub fn ordered_types_from_pruning(
486    ast: &Ast,
487    roots: &[Id<TypeNode>],
488    pruned: &[Id<TypeNode>],
489) -> Vec<Id<TypeNode>> {
490    let mut order = Vec::new();
491    let mut seen: Vec<Id<TypeNode>> = pruned.to_vec();
492    for root in roots {
493        visit(ast, *root, &mut order, &mut seen, 0);
494    }
495    order
496}
497
498/// zerodds-lint: recursion-depth 64 (dependency edges of the type
499/// graph; bounded by `MAX_DEPTH` and by `seen`, which admits each
500/// type once, so a cycle stops on the second visit)
501fn visit(
502    ast: &Ast,
503    id: Id<TypeNode>,
504    order: &mut Vec<Id<TypeNode>>,
505    seen: &mut Vec<Id<TypeNode>>,
506    depth: usize,
507) {
508    if depth > MAX_DEPTH || seen.contains(&id) {
509        return;
510    }
511    seen.push(id);
512    for dependency in dependencies(ast, id) {
513        visit(ast, dependency, order, seen, depth + 1);
514    }
515    order.push(id);
516}
517
518/// How deep the walk follows the type graph, matching the layout
519/// planner's own bound.
520pub(crate) const MAX_DEPTH: usize = 64;
521
522/// The types a type's C declaration has to be preceded by.
523///
524/// Also the types whose codec a type's codec calls, which is what
525/// [`crate::emit::encode::called_only_in_bulk`] reads it for.
526pub(crate) fn dependencies(ast: &Ast, id: Id<TypeNode>) -> Vec<Id<TypeNode>> {
527    let mut out = Vec::new();
528    let Ok(node) = ast.type_(id) else {
529        return out;
530    };
531    match &node.kind {
532        TypeKind::Sequence { element, .. } | TypeKind::Array { element, .. } => {
533            out.extend(element.resolved());
534        }
535        TypeKind::Map { key, value, .. } => {
536            out.extend(key.resolved());
537            out.extend(value.resolved());
538        }
539        TypeKind::Alias { aliased } => out.extend(aliased.resolved()),
540        TypeKind::ValueBox { boxed } => out.extend(boxed.resolved()),
541        TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
542            // A base is a type the derived one is declared in terms of
543            // (XTypes 1.3 §7.5.1.4.1: a base class, or the member
544            // `parent`), so it comes first.
545            if let TypeKind::Structure {
546                base: Some(base), ..
547            } = &node.kind
548            {
549                out.extend(base.resolved());
550            }
551            for member in ast.all_members_of(id) {
552                if let Ok(member_node) = ast.member(member) {
553                    out.extend(member_node.type_.resolved());
554                }
555            }
556        }
557        TypeKind::Union { discriminator, .. } => {
558            out.extend(discriminator.resolved());
559            for case in ast.cases_of(id) {
560                if let Ok(case_node) = ast.case(*case) {
561                    if let Ok(member) = ast.member(case_node.member) {
562                        out.extend(member.type_.resolved());
563                    }
564                }
565            }
566        }
567        TypeKind::Primitive(_)
568        | TypeKind::String { .. }
569        | TypeKind::Fixed { .. }
570        | TypeKind::Enumeration { .. }
571        | TypeKind::Bitmask { .. }
572        | TypeKind::Bitset { .. }
573        | TypeKind::Native
574        | TypeKind::Any
575        | TypeKind::ValueBase
576        | TypeKind::ObjectRef { .. }
577        | TypeKind::AnnotationType { .. } => {}
578    }
579    out
580}
581
582/// The C spelling of a type in a position that has to hold a value,
583/// refusing the constructs C cannot hold.
584///
585/// `any` is the first of those. It is in the model because Building
586/// Block Any is one of IDL 4.2's fifteen, and what it holds is decided
587/// at run time — there is no C type for it and no wire form to generate
588/// a codec against. Emitting `any a;` into a header and reporting
589/// success produced a file no compiler reads, from a generator that said
590/// nothing was wrong.
591fn c_member_type(ast: &Ast, target: Id<TypeNode>, at: &str) -> Result<c::Type, MaterialiseError> {
592    let construct = match ast.type_(target).map(|node| &node.kind) {
593        Ok(TypeKind::Any) => Some("any"),
594        Ok(TypeKind::ValueBase) => Some("ValueBase"),
595        // An object reference is a handle into the RPC arc and
596        // `coverage.rs` says so; there is no C data type for it, and
597        // writing the IDL spelling into the header produced a file no
598        // compiler reads.
599        Ok(TypeKind::ObjectRef { .. }) => Some("Object"),
600        _ => None,
601    };
602    if let Some(construct) = construct {
603        return Err(MaterialiseError::NoLanguageForm {
604            construct: construct.to_string(),
605            language: "C",
606            at: at.to_string(),
607        });
608    }
609    Ok(c_value_type(ast, target))
610}
611
612/// The C storage of one member of `enclosing`, which is
613/// [`c_member_type`] except where the value has no room there.
614///
615/// A C structure holds its members as storage, so a member whose type
616/// reaches the structure that declares it back — over members, union
617/// branches, array elements and aliases, none of which is a pointer in
618/// C — is a structure that contains itself, and the compiler answers
619/// `field 'next' has incomplete type`. The pointer is what gives such a
620/// type a size, and it is what DDS-XTypes 1.3 Table 21 already calls the
621/// case: a member "stored externally to the containing type".
622///
623/// [`zerodds_ast_layout::member_held_inline_closes_a_cycle`] is the
624/// question, asked of the *pair* — a member's type alone does not answer
625/// it, because the same type is inline in one aggregate and an
626/// indirection in another. The cycle is one reason: `@optional Node next`
627/// without `@external` closes it. `@external` (§7.5.1.2.3.1) and
628/// `@optional` (§7.5.1.2.4.1) are the other, and each makes the member a
629/// pointer whether or not it closes a cycle: `@optional long a` is
630/// `int32_t *a`.
631///
632/// The codec answers the same question at the same seam —
633/// [`crate::emit::CodecTarget::behind_indirection`] — so what the header
634/// declares and what the encoder reaches through cannot part.
635fn c_held_member_type(
636    ast: &Ast,
637    enclosing: Id<TypeNode>,
638    member: &zerodds_ast::MemberNode,
639    target: Id<TypeNode>,
640    at: &str,
641) -> Result<c::Type, MaterialiseError> {
642    let held = c_member_type(ast, target, at)?;
643    if member_held_behind_pointer(ast, enclosing, member, target) {
644        return Ok(c::Type::Pointer(Box::new(held)));
645    }
646    Ok(held)
647}
648
649/// Whether the C value of a type is a pointer already: a `string`, a
650/// `wstring` and a `fixed`, which are `const char *`, and an alias of
651/// one. XTypes 1.3 §7.5.1.2.3.1: "String and wide string members are
652/// already represented using pointers, so the mappings for these members
653/// do not change. The same applies to aliases to string and wide string
654/// types."
655#[must_use]
656pub fn value_is_pointer(ast: &Ast, id: Id<TypeNode>) -> bool {
657    let mut current = id;
658    for _ in 0..MAX_DEPTH {
659        match ast.type_(current).map(|node| &node.kind) {
660            Ok(TypeKind::String { .. } | TypeKind::Fixed { .. }) => return true,
661            Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
662                Some(target) => current = target,
663                None => return false,
664            },
665            _ => return false,
666        }
667    }
668    false
669}
670
671/// Whether C holds a member behind a pointer, given whether the member
672/// is annotated `@external` or `@optional` (`annotated`, see
673/// [`pointer_annotated`]).
674///
675/// Two reasons, and either is enough. A member that closes a cycle has no
676/// other way to have a size (`c_held_member_type`). An `@external`
677/// member "shall be represented using pointers […] a member of type X
678/// shall instead be mapped as type pointer-to-X" (XTypes 1.3
679/// §7.5.1.2.3.1), unless the value is a pointer already
680/// ([`value_is_pointer`]); an `@optional` member is held the same way
681/// (§7.5.1.2.4.1: "Optional members shall be represented using
682/// pointers", "A NULL pointer shall indicate an omitted value"). One
683/// pointer for all of them: `@external Node next` inside `Node` is
684/// `Node *next`.
685#[must_use]
686pub fn pointer_member(
687    ast: &Ast,
688    enclosing: Id<TypeNode>,
689    target: Id<TypeNode>,
690    annotated: bool,
691) -> bool {
692    zerodds_ast_layout::member_held_inline_closes_a_cycle(ast, enclosing, target)
693        || (annotated && !value_is_pointer(ast, target))
694}
695
696/// Whether the annotations of `member` alone make C hold it behind a
697/// pointer: `@external` (XTypes 1.3 §7.5.1.2.3.1) or `@optional`
698/// (§7.5.1.2.4.1). The `external` argument of [`pointer_member`].
699#[must_use]
700pub fn pointer_annotated(member: &zerodds_ast::MemberNode) -> bool {
701    crate::adapter::annotated_external(member) || member.facets.is_optional()
702}
703
704/// [`pointer_member`] for the member of `enclosing` that is called
705/// `member_name`: a structure's member or a union's branch. For a caller
706/// that holds the name and the type and not the node, as the wire probe's
707/// generator does.
708///
709/// # Errors
710/// A message naming the aggregate and the name, when `enclosing` declares
711/// no such member: the answer would otherwise be the cycle rule's, for a
712/// member that may well be `@external`.
713pub fn named_member_held_behind_pointer(
714    ast: &Ast,
715    enclosing: Id<TypeNode>,
716    member_name: &str,
717    target: Id<TypeNode>,
718) -> Result<bool, String> {
719    let structure_member = ast
720        .all_members_of(enclosing)
721        .into_iter()
722        .filter_map(|member| ast.member(member).ok())
723        .find(|member| member.name == member_name);
724    let branch_member = ast
725        .cases_of(enclosing)
726        .iter()
727        .filter_map(|case| ast.case(*case).ok())
728        .filter_map(|case| ast.member(case.member).ok())
729        .find(|member| member.name == member_name);
730    let member = structure_member.or(branch_member).ok_or_else(|| {
731        format!(
732            "`{}` declares no member or branch `{member_name}`",
733            c_type_name(ast, enclosing)
734        )
735    })?;
736    Ok(pointer_member(
737        ast,
738        enclosing,
739        target,
740        pointer_annotated(member),
741    ))
742}
743
744/// [`pointer_member`] for a member as declared.
745#[must_use]
746pub fn member_held_behind_pointer(
747    ast: &Ast,
748    enclosing: Id<TypeNode>,
749    member: &zerodds_ast::MemberNode,
750    target: Id<TypeNode>,
751) -> bool {
752    pointer_member(ast, enclosing, target, pointer_annotated(member))
753}
754
755/// The member that holds the base of a derived structure.
756/// XTypes 1.3 §7.5.1.4.1.3.
757pub const PARENT: &str = "parent";
758
759/// The structure `id` derives from, if it does.
760#[must_use]
761pub fn struct_base(ast: &Ast, id: Id<TypeNode>) -> Option<Id<TypeNode>> {
762    match ast.type_(id).map(|node| &node.kind) {
763        Ok(TypeKind::Structure {
764            base: Some(base), ..
765        }) => base.resolved(),
766        _ => None,
767    }
768}
769
770/// One member of a structure as C reaches it.
771#[derive(Debug, Clone)]
772pub struct CMember {
773    /// The member.
774    pub handle: Id<zerodds_ast::MemberNode>,
775    /// The path to it from the structure: empty for its own, `parent.` for the base's,
776    /// `parent.parent.` for the base's base.
777    pub path: String,
778    /// The structure that declares it.
779    pub owner: Id<TypeNode>,
780}
781
782/// Every member `id` has, the base's first. Same order as [`Ast::all_members_of`].
783#[must_use]
784pub fn c_members(ast: &Ast, id: Id<TypeNode>) -> Vec<CMember> {
785    let mut chain = vec![id];
786    while chain.len() < 64 {
787        match struct_base(ast, *chain.last().unwrap_or(&id)) {
788            Some(base) if !chain.contains(&base) => chain.push(base),
789            _ => break,
790        }
791    }
792    let mut out = Vec::new();
793    for (depth, owner) in chain.iter().enumerate().rev() {
794        let path = format!("{PARENT}.").repeat(depth);
795        for member in ast.members_of(*owner) {
796            out.push(CMember {
797                handle: *member,
798                path: path.clone(),
799                owner: *owner,
800            });
801        }
802    }
803    out
804}
805
806/// The bound a `string`, a `sequence` or a `map` carries, as the suffix
807/// that keeps two of them apart.
808///
809/// One codec body is emitted per routine name, and the name is built
810/// from the type's C name. With the bound left out, `sequence<long>`
811/// and `sequence<long, 4>` were one name and whichever was declared
812/// first decided: measured on
813/// `@final struct S { sequence<long> b; sequence<long, 4> a; };`, the
814/// generated C held no `zd_over` check at all, so `a` accepted a stream
815/// of any length; with the members the other way round both got the
816/// bounded body, so `b` refused — and truncated — a length the IDL puts
817/// no limit on. `string<4>` beside `string` and `wstring<4>` beside
818/// `wstring` collapsed the same way, onto `zdw_utf8_octets` and
819/// `zdw_utf16_units`. Either way the reader answered with a value the
820/// IDL does not describe and said nothing.
821///
822/// An `array` needs nothing here: its `dims` are already in the name.
823pub(crate) fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
824    match bound.expr() {
825        None => String::new(),
826        // `_b` and not a bare `_n`: `zd_seq_long_4` is what the `Array`
827        // arm answers for an array of four `long`, and the two are
828        // different wire forms.
829        Some(expr) => match ast.eval_integral(expr) {
830            Ok(limit) => format!("_b{limit}"),
831            // As the array's `_x`: a bound that does not evaluate is
832            // still not the unbounded form, and `c_items` reports the
833            // expression that would not evaluate.
834            Err(_) => "_bx".to_string(),
835        },
836    }
837}
838
839/// The C spelling of a type where a declarator needs one.
840///
841/// A primitive is its own spelling; everything else is a declared name,
842/// because C has no way to write an anonymous aggregate twice and have
843/// the two be the same type.
844///
845/// A `sequence` and a `map` carry their bound here and not only in
846/// [`c_codec_tag`], because in this column the name is a *declaration*
847/// as well as a routine name: `zd_seq_long` is a struct `declare`
848/// emits, and two codecs over one struct would be two functions whose
849/// parameter type says nothing about which of the two IDL types they
850/// read. `zd_seq_long` and `zd_seq_b4_long` are two structs of the same
851/// shape, one per IDL type, and the bounded member is declared at the
852/// bounded one — which is what makes the split reach the header and not
853/// just the codec.
854///
855/// A `string` is the exception and takes its bound in [`type_tag`]
856/// alone: both shapes are the prelude's [`STRING`] and [`WSTRING`],
857/// there is one C type per width whatever the bound, and `declare`
858/// emits nothing for either. A declarator with a bound in it would name
859/// a type no header defines.
860#[must_use]
861pub fn c_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
862    type_name_at(ast, id, 0)
863}
864
865/// [`c_type_name`], carrying how many connector steps the walk has
866/// already taken.
867///
868/// zerodds-lint: recursion-depth 64 (unnamed-typedef chains and the
869/// element, key and value tags; bounded by `MAX_DEPTH`, which names the
870/// type by its arena slot rather than descending). The slot and not a
871/// fixed word: two types that both nest past the bound would otherwise
872/// take one C name, and the second declaration would silently stand for
873/// both.
874pub(crate) fn type_name_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
875    if depth > MAX_DEPTH {
876        return format!("zd_deep_{}", id.slot());
877    }
878    let Ok(node) = ast.type_(id) else {
879        return "void".to_string();
880    };
881    match &node.kind {
882        // `long double` is the one primitive with no C built-in that
883        // carries it: the spelling is `zdw_f128`, which is binary128
884        // whatever the compiler's `long double` happens to be on this
885        // target. Matched ahead of the arm below, which would reach
886        // `c_primitive`'s `double`. See `runtime/c/include/zerodds_wire.h`
887        // and `docs/float128-column.md` §2 for the three formats that
888        // name covers.
889        TypeKind::Primitive(PrimitiveKind::Float128) => FLOAT128.to_string(),
890        TypeKind::Primitive(kind) => c_primitive(*kind).as_str().to_string(),
891        TypeKind::String {
892            width: CharWidth::Char,
893            ..
894        } => STRING.to_string(),
895        TypeKind::String {
896            width: CharWidth::WChar,
897            ..
898        } => WSTRING.to_string(),
899        TypeKind::Fixed { digits, scale } => format!("zd_fixed_{digits}_{scale}"),
900        TypeKind::Sequence { element, bound } => match element.resolved() {
901            Some(element) => format!(
902                "zd_seq{}_{}",
903                bound_tag(ast, bound),
904                type_tag_at(ast, element, depth + 1)
905            ),
906            None => "void".to_string(),
907        },
908        TypeKind::Array { element, dims } => match element.resolved() {
909            Some(element) => {
910                let mut name = format!("zd_arr_{}", type_tag_at(ast, element, depth + 1));
911                for dim in dims {
912                    match ast.eval_integral(*dim) {
913                        Ok(length) => name.push_str(&format!("_{length}")),
914                        // A dimension that does not evaluate gets no
915                        // number rather than a zero: the name has to
916                        // differ from a `[0]` array's, and
917                        // `c_items` reports the failure by name.
918                        Err(_) => name.push_str("_x"),
919                    }
920                }
921                name
922            }
923            None => "void".to_string(),
924        },
925        // XTypes 1.3 §7.5.1.3.2.1: `[key_type][fully_qualified_element_type]Map`
926        // where the key is a primitive or a string; any other key keeps the
927        // structural name, because the clause gives none.
928        TypeKind::Map { key, value, bound } => match (key.resolved(), value.resolved()) {
929            (Some(key), Some(value)) => super::c_map::map_type_name(ast, key, value, bound, depth)
930                .unwrap_or_else(|| {
931                    format!(
932                        "zd_map{}_{}_{}",
933                        bound_tag(ast, bound),
934                        type_tag_at(ast, key, depth + 1),
935                        type_tag_at(ast, value, depth + 1)
936                    )
937                }),
938            _ => "void".to_string(),
939        },
940        // A typedef with no name is one the frontend made for an inline
941        // type; it is transparent, so the C name is the target's.
942        TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
943            Some(target) => type_name_at(ast, target, depth + 1),
944            None => "void".to_string(),
945        },
946        _ => flattened(ast, id),
947    }
948}
949
950/// The name component a collection takes from its element.
951///
952/// The IDL kind and not the C spelling, and this is not cosmetic:
953/// `double` and `long double` are both a C `double` here — Rust has no
954/// 128-bit float either — and they are eight and sixteen bytes on the
955/// wire. A structural name built from the C type would give
956/// `sequence<double>` and `sequence<long double>` one declaration and
957/// one encoder, and the second would go out at the first's width with
958/// nothing to say it had. The same collapse would hit `octet` against
959/// `uint8` and `unsigned short` against `wchar`, where it happens to be
960/// harmless.
961///
962/// A `string`'s bound rides here and not in [`c_type_name`], for the
963/// reason [`c_codec_tag`] states: both bounds are one declarator type
964/// and two wire forms, so the name has to keep them apart where the
965/// declarator cannot.
966///
967#[must_use]
968pub fn type_tag(ast: &Ast, id: Id<TypeNode>) -> String {
969    type_tag_at(ast, id, 0)
970}
971
972/// [`type_tag`], carrying how many connector steps the walk has already
973/// taken.
974///
975/// The depth is threaded through [`type_name_at`] and back rather than
976/// started over at either door: the two reach each other, so a counter
977/// reset at one of them would leave the pair with no bound at all. The
978/// marker this function used to carry — `recursion-depth 64 (a chain of
979/// unnamed typedefs)` — named a bound nothing enforced.
980///
981/// zerodds-lint: recursion-depth 64 (unnamed-typedef chains, via
982/// `type_name_at`; bounded by `MAX_DEPTH`, which names the type by its
983/// arena slot rather than descending)
984pub(crate) fn type_tag_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
985    if depth > MAX_DEPTH {
986        return format!("zd_deep_{}", id.slot());
987    }
988    let Ok(node) = ast.type_(id) else {
989        return type_name_at(ast, id, depth + 1);
990    };
991    match &node.kind {
992        TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
993        TypeKind::String {
994            width: CharWidth::Char,
995            bound,
996        } => format!("{STRING}{}", bound_tag(ast, bound)),
997        TypeKind::String {
998            width: CharWidth::WChar,
999            bound,
1000        } => format!("{WSTRING}{}", bound_tag(ast, bound)),
1001        // A typedef with no name is one the frontend made for an inline
1002        // type; it is transparent, so the tag is the target's — which
1003        // is what carries a bounded string's bound through one.
1004        TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
1005            Some(target) => type_tag_at(ast, target, depth + 1),
1006            None => type_name_at(ast, id, depth + 1),
1007        },
1008        _ => type_name_at(ast, id, depth + 1),
1009    }
1010}
1011
1012/// The name a generated codec function is called after.
1013///
1014/// [`type_tag`], which is the *name component* function and not the
1015/// declarator one: the two part on a string, and only on a string. A
1016/// `string<4>` and a `string` are both the prelude's [`STRING`] — one C
1017/// type per width whatever the bound, and `declare` emits nothing for
1018/// either — so `c_type_name` has to keep answering `zd_string` for both
1019/// or a member would be declared at a type no header defines. The tag
1020/// carries the bound instead, and `zd_dec_zd_string` beside
1021/// `zd_dec_zd_string_b4` is the same arrangement the nineteen other
1022/// columns have for a string: two routines over one type.
1023///
1024/// Everything else is its declaration's own name, which already carries
1025/// the bound — see [`c_type_name`] — so the routine and the struct it
1026/// reads split together.
1027#[must_use]
1028pub fn c_codec_tag(ast: &Ast, id: Id<TypeNode>) -> String {
1029    type_tag(ast, id)
1030}
1031
1032/// The map entry struct a `map<K,V>` needs a name for.
1033#[must_use]
1034pub fn c_map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
1035    super::c_map::entry_name(&c_type_name(ast, id))
1036}
1037
1038/// A named type's C identifier: the scoped name with the module
1039/// separators flattened onto it, which is what both C generators in the
1040/// field do and what a `#include` of two modules requires.
1041#[must_use]
1042pub fn flattened(ast: &Ast, id: Id<TypeNode>) -> String {
1043    let Ok(any) = ast.any_of_type(id) else {
1044        return "zd_unnamed".to_string();
1045    };
1046    match ast.scoped_name_of(any) {
1047        Some(scoped) => c::escape_keyword(&scoped.segments().join("_")),
1048        None => "zd_unnamed".to_string(),
1049    }
1050}
1051
1052/// The flattened module path a file-scope constant carries.
1053#[must_use]
1054pub fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
1055    let Ok(any) = ast.any_of_type(id) else {
1056        return String::new();
1057    };
1058    let Some(scoped) = ast.scoped_name_of(any) else {
1059        return String::new();
1060    };
1061    let segments = scoped.segments();
1062    if segments.len() <= 1 {
1063        return String::new();
1064    }
1065    let mut prefix = segments[..segments.len() - 1].join("_");
1066    prefix.push('_');
1067    prefix
1068}
1069
1070/// The C primitive an IDL primitive becomes.
1071///
1072/// Exhaustive, so a primitive added to the model without a C spelling is
1073/// a compile error and not a member that silently takes another's.
1074///
1075/// `char` is one byte and `wchar` two, which is what the wire form is;
1076/// XTypes 1.3 §7.5.1.1.1 leaves the C spelling of `Char16` to the
1077/// binding, and a fixed-width integer is the only choice that does not
1078/// depend on the platform's `wchar_t`.
1079///
1080/// `long double` is **not** routed here: it takes [`FLOAT128`], which is
1081/// a real binary128. The `Float128` arm below exists so the match is
1082/// total and answers for a caller that wants the nearest built-in; both
1083/// callers in this module — [`c_type_name`] and [`c_value_type`] — match
1084/// `Float128` before they reach it. A caller that does not is the defect
1085/// the three-column gate found: `double` is eight bytes where the member
1086/// is sixteen.
1087#[must_use]
1088pub const fn c_primitive(kind: PrimitiveKind) -> c::Primitive {
1089    match kind {
1090        PrimitiveKind::Boolean => c::Primitive::Bool,
1091        PrimitiveKind::Octet | PrimitiveKind::UInt8 => c::Primitive::UInt8,
1092        PrimitiveKind::Int8 => c::Primitive::Int8,
1093        PrimitiveKind::Char => c::Primitive::Char,
1094        PrimitiveKind::WChar => c::Primitive::UInt16,
1095        PrimitiveKind::Int16 => c::Primitive::Int16,
1096        PrimitiveKind::UInt16 => c::Primitive::UInt16,
1097        PrimitiveKind::Int32 => c::Primitive::Int32,
1098        PrimitiveKind::UInt32 => c::Primitive::UInt32,
1099        PrimitiveKind::Int64 => c::Primitive::Int64,
1100        PrimitiveKind::UInt64 => c::Primitive::UInt64,
1101        PrimitiveKind::Float32 => c::Primitive::Float,
1102        PrimitiveKind::Float64 | PrimitiveKind::Float128 => c::Primitive::Double,
1103    }
1104}
1105
1106/// The unsigned integer a bit width calls for.
1107///
1108/// Public because the decoder emitter has to cast into exactly this
1109/// type: a bitfield is unpacked out of a 64-bit anchor and assigned to
1110/// a member whose width is decided here, and a second copy of the rule
1111/// would narrow into a type this one had moved on from.
1112#[must_use]
1113pub const fn c_holder(bits: u16) -> c::Primitive {
1114    match bits {
1115        0..=8 => c::Primitive::UInt8,
1116        9..=16 => c::Primitive::UInt16,
1117        17..=32 => c::Primitive::UInt32,
1118        _ => c::Primitive::UInt64,
1119    }
1120}
1121
1122fn type_by_name(name: &str) -> c::Type {
1123    c::Type::Named(Ident::new(name))
1124}
1125
1126/// The C declarator type a value of `id` is held in.
1127///
1128/// The one answer, for every emitter. A member of a generated structure,
1129/// a codec's `v` parameter, a defaulter's destination, the element type a
1130/// sequence's buffer is allocated at — all four have to name the same C
1131/// type or the translation unit does not compile, and where it happens to
1132/// compile anyway the two names have to be the same *width* or the
1133/// allocation is short.
1134///
1135/// It was three private copies in `encode_c`, `decode_c` and `default_c`
1136/// beside this one, and that is what let `long double` diverge: this
1137/// function answers [`FLOAT128`] for it and the copies asked
1138/// [`c_primitive`], which answers `double` — sixteen bytes against eight,
1139/// with the codec's `const double *` pointing at a `zdw_f128` member.
1140#[must_use]
1141pub fn c_value_type(ast: &Ast, id: Id<TypeNode>) -> c::Type {
1142    let Ok(node) = ast.type_(id) else {
1143        return c::Type::Void;
1144    };
1145    match &node.kind {
1146        TypeKind::Primitive(PrimitiveKind::Float128) => type_by_name(FLOAT128),
1147        TypeKind::Primitive(kind) => c::Type::Primitive(c_primitive(*kind)),
1148        _ => type_by_name(&c_type_name(ast, id)),
1149    }
1150}
1151
1152/// A type the model declares under the name `<TypeName>Bits` of a
1153/// bitmask's flag type.
1154///
1155/// DDS-XTypes 1.3 §7.5.1.2.2 names that type as part of the binding, so
1156/// a user type of the same C name cannot be given another name here
1157/// without the header no longer being the one the clause describes. The
1158/// two would be one identifier in the header, the second declaration
1159/// would be skipped as a repeat (`declare`) or rejected by the C
1160/// compiler, and either way the source would not mean what it says; the
1161/// refusal names both declarations instead.
1162///
1163/// # Errors
1164/// [`MaterialiseError::SpellingCollision`] when two declarations reach
1165/// one C name, the second being a bitmask's `<TypeName>Bits`.
1166fn no_bits_name_collision(ast: &Ast, order: &[Id<TypeNode>]) -> Result<(), MaterialiseError> {
1167    let mut spelled: Vec<(String, String)> = Vec::new();
1168    let mut primitives: Vec<String> = Vec::new();
1169    for id in order {
1170        let Ok(node) = ast.type_(*id) else { continue };
1171        // Anonymous collections share a C name by shape, on purpose.
1172        if node.name.is_empty() {
1173            continue;
1174        }
1175        let name = c_type_name(ast, *id);
1176        // Two IDL primitives may have one C type (`octet` and `uint8` are
1177        // both `uint8_t`); that is not a collision of declarations. A
1178        // primitive stands in the list once per spelling, so that a
1179        // declared name equal to one is still found.
1180        if matches!(node.kind, TypeKind::Primitive(_)) {
1181            if !primitives.contains(&name) {
1182                primitives.push(name.clone());
1183                spelled.push((name, node.name.clone()));
1184            }
1185            continue;
1186        }
1187        if matches!(node.kind, TypeKind::Bitmask { .. }) {
1188            spelled.push((name.clone(), node.name.clone()));
1189            spelled.push((
1190                format!("{name}Bits"),
1191                format!("{}Bits (the flag type of bitmask {})", node.name, node.name),
1192            ));
1193        } else {
1194            spelled.push((name, node.name.clone()));
1195        }
1196    }
1197    no_spelling_collision(
1198        spelled
1199            .iter()
1200            .map(|(spelled, declared)| (spelled.as_str(), declared.as_str())),
1201        "C",
1202        "the types of one header",
1203    )
1204}
1205
1206#[allow(clippy::too_many_lines)]
1207fn declare(
1208    ast: &Ast,
1209    applied: &Applied,
1210    id: Id<TypeNode>,
1211    out: &mut Vec<c::Decl>,
1212    deferred: &mut Vec<c::Decl>,
1213    emitted: &mut Vec<String>,
1214) -> Result<(), MaterialiseError> {
1215    let Ok(node) = ast.type_(id) else {
1216        return Ok(());
1217    };
1218    let name = c_type_name(ast, id);
1219    // Two anonymous collections of the same shape are one C type. The
1220    // check is by name because the name is structural.
1221    if emitted.contains(&name) {
1222        return Ok(());
1223    }
1224
1225    match &node.kind {
1226        // A primitive needs no declaration; its C spelling is built in.
1227        TypeKind::Primitive(_) => return Ok(()),
1228        // The two string shapes are in the prelude.
1229        TypeKind::String { .. } => return Ok(()),
1230
1231        TypeKind::Fixed { digits, scale } => {
1232            emitted.push(name.clone());
1233            out.push(c::Decl::Typedef(c::Typedef {
1234                doc: Doc::line(&format!(
1235                    "`fixed<{digits},{scale}>`, held as the decimal it was written as. \
1236                     A double cannot carry one, which is the point of the type."
1237                )),
1238                type_: c::Type::Pointer(Box::new(c::Type::Const(Box::new(c::Type::Primitive(
1239                    c::Primitive::Char,
1240                ))))),
1241                name: Ident::new(&name),
1242                dims: Vec::new(),
1243            }));
1244        }
1245
1246        TypeKind::Sequence { element, .. } => {
1247            let Some(element) = element.resolved() else {
1248                return Err(MaterialiseError::Unevaluated {
1249                    construct: "sequence element".to_string(),
1250                    at: name,
1251                });
1252            };
1253            emitted.push(name.clone());
1254            out.extend(sequence_decl(&name, c_member_type(ast, element, &name)?));
1255        }
1256
1257        TypeKind::Array { element, dims } => {
1258            let Some(element) = element.resolved() else {
1259                return Err(MaterialiseError::Unevaluated {
1260                    construct: "array element".to_string(),
1261                    at: name,
1262                });
1263            };
1264            let mut lengths = Vec::new();
1265            for dim in dims {
1266                let length =
1267                    ast.eval_integral(*dim)
1268                        .map_err(|_| MaterialiseError::Unevaluated {
1269                            construct: "array length".to_string(),
1270                            at: name.clone(),
1271                        })?;
1272                lengths.push(c::Expr::Literal(Literal::int(length)));
1273            }
1274            emitted.push(name.clone());
1275            out.push(c::Decl::Typedef(c::Typedef {
1276                doc: Doc::line("An array. The element count is in the type and not on the wire."),
1277                type_: c_member_type(ast, element, &name)?,
1278                name: Ident::new(&name),
1279                dims: lengths,
1280            }));
1281        }
1282
1283        TypeKind::Map { key, value, .. } => {
1284            let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
1285                return Err(MaterialiseError::Unevaluated {
1286                    construct: "map key or value".to_string(),
1287                    at: name,
1288                });
1289            };
1290            let entry = c_map_entry_name(ast, id);
1291            emitted.push(name.clone());
1292            // The typedef here, the definition at the end of the file:
1293            // the map's `_buffer` is a pointer, so an incomplete entry
1294            // suffices at this position — and has to, when the value
1295            // type is an enclosing one and not yet complete.
1296            out.push(c::Decl::Typedef(c::Typedef {
1297                doc: Doc::none(),
1298                type_: c::Type::StructTag(Ident::new(&entry)),
1299                name: Ident::new(&entry),
1300                dims: Vec::new(),
1301            }));
1302            deferred.push(c::Decl::Struct(c::Struct {
1303                body_text: zerodds_lang_cst::common::BodyText::none(),
1304                doc: Doc::line(
1305                    "One entry of a map. XTypes 1.3 7.4.1.1.4 defines a map as a \
1306                     sequence of these, and the rules write the two halves with no \
1307                     framing between them.",
1308                ),
1309                name: Some(Ident::new(&entry)),
1310                members: vec![
1311                    c::Member {
1312                        doc: Doc::none(),
1313                        type_: c_member_type(ast, key, &name)?,
1314                        name: Ident::new("key"),
1315                        dims: Vec::new(),
1316                        bits: None,
1317                    },
1318                    c::Member {
1319                        doc: Doc::none(),
1320                        type_: c_member_type(ast, value, &name)?,
1321                        name: Ident::new("value"),
1322                        dims: Vec::new(),
1323                        bits: None,
1324                    },
1325                ],
1326            }));
1327            out.extend(super::c_map::map_decl(&name, &entry));
1328        }
1329
1330        TypeKind::Alias { aliased } => {
1331            if node.name.is_empty() {
1332                return Ok(());
1333            }
1334            if ghost_created_for(applied, node.node_id, "CAlias").is_none() {
1335                return Ok(());
1336            }
1337            let Some(target) = aliased.resolved() else {
1338                return Err(MaterialiseError::Unevaluated {
1339                    construct: "typedef target".to_string(),
1340                    at: name,
1341                });
1342            };
1343            emitted.push(name.clone());
1344            // `typedef Row Grid;` where `Row` is itself an array typedef
1345            // is legal C and keeps the dimensions where they were
1346            // declared, so the alias never restates them.
1347            out.push(c::Decl::Typedef(c::Typedef {
1348                doc: Doc::none(),
1349                type_: c_value_type(ast, target),
1350                name: Ident::new(&name),
1351                dims: Vec::new(),
1352            }));
1353        }
1354
1355        TypeKind::Enumeration { enumerators } => {
1356            if ghost_created_for(applied, node.node_id, "CEnum").is_none() {
1357                return Ok(());
1358            }
1359            emitted.push(name.clone());
1360            let prefix = module_prefix(ast, id);
1361            let mut values = Vec::new();
1362            let mut names = Vec::new();
1363            for enumerator in enumerators {
1364                let Ok(enum_node) = ast.enumerator(*enumerator) else {
1365                    continue;
1366                };
1367                if ghost_created_for(applied, enum_node.node_id, "CEnumerator").is_none() {
1368                    continue;
1369                }
1370                let value = ast.effective_enumerator_value(*enumerator).ok_or_else(|| {
1371                    MaterialiseError::Unevaluated {
1372                        construct: "enumerator value".to_string(),
1373                        at: enum_node.name.clone(),
1374                    }
1375                })?;
1376                // The constant's whole spelling on the left, prefix and
1377                // all: that is the name a caller writes, and half of it
1378                // would be a mapping onto something the header does not
1379                // declare.
1380                let constant = format!("{prefix}{}", c::escape_keyword(&enum_node.name));
1381                names.push((constant.clone(), enum_node.name.clone()));
1382                values.push((constant, value));
1383            }
1384            // The storage is a fixed-width integer even when a C `enum`
1385            // would do: an enumeration constant is an `int`, and the
1386            // holder the wire wants is the `@bit_bound`'s, which is not
1387            // the same question.
1388            out.push(c::Decl::Typedef(c::Typedef {
1389                doc: Doc::line(
1390                    "An enumeration. The holder is signed because XTypes 1.3 \
1391                     7.4.3.5.3 makes it so; the width on the wire is the \
1392                     @bit_bound's and is applied by the runtime.",
1393                ),
1394                type_: c::Type::Primitive(c::Primitive::Int32),
1395                name: Ident::new(&name),
1396                dims: Vec::new(),
1397            }));
1398            for (constant, value) in values {
1399                // Parenthesised: a macro replacement list that is not
1400                // self-contained changes meaning at its use site.
1401                out.push(c::Decl::Define(c::Define {
1402                    doc: Doc::none(),
1403                    name: Ident::new(&constant),
1404                    value: Some(c::Expr::Paren(Box::new(c::Expr::Cast {
1405                        type_: Box::new(type_by_name(&name)),
1406                        value: Box::new(c::Expr::Literal(Literal::int(value))),
1407                    }))),
1408                }));
1409            }
1410            out.extend(idl_members(&name, &names)?);
1411        }
1412
1413        TypeKind::Bitmask { bits } => {
1414            if ghost_created_for(applied, node.node_id, "CBitmask").is_none() {
1415                return Ok(());
1416            }
1417            emitted.push(name.clone());
1418            let bound = ast.effective_bit_bound(id);
1419            let prefix = module_prefix(ast, id);
1420            let mut names = Vec::new();
1421            // XTypes 1.3 §7.5.1.2.2: "the language binding shall be as if
1422            // there was an enumeration definition like the following:
1423            // @bit_bound(n) enum <TypeName>Bits { @value(1 << p) FLAG, … }",
1424            // and a typedef of the unsigned integer Table 44 gives for the
1425            // bound for "collections of flags from the previously defined
1426            // enumeration". The enumeration is written as C writes one
1427            // here (see the enumeration arm): an integer typedef with a
1428            // constant per flag. Its holder is the unsigned integer of the
1429            // bound, not a signed 32-bit one: a flag of a 64-bit bitmask
1430            // has a value an `int` cannot hold.
1431            let bits_name = format!("{name}Bits");
1432            out.push(c::Decl::Typedef(c::Typedef {
1433                doc: Doc::line(
1434                    "The enumeration of the flags of a bitmask (XTypes 1.3 \
1435                     7.5.1.2.2): one constant per flag, valued 1 << position.",
1436                ),
1437                type_: c::Type::Primitive(c_holder(bound)),
1438                name: Ident::new(&bits_name),
1439                dims: Vec::new(),
1440            }));
1441            out.push(c::Decl::Typedef(c::Typedef {
1442                doc: Doc::line(
1443                    "A bitmask: a collection of flags, as the unsigned integer \
1444                     Table 44 of XTypes 1.3 7.5.1.2.2 gives for its bound.",
1445                ),
1446                type_: c::Type::Primitive(c_holder(bound)),
1447                name: Ident::new(&name),
1448                dims: Vec::new(),
1449            }));
1450            for bit in bits {
1451                let Ok(bit_node) = ast.bit_value(*bit) else {
1452                    continue;
1453                };
1454                if ghost_created_for(applied, bit_node.node_id, "CBitConst").is_none() {
1455                    continue;
1456                }
1457                let position = ast.effective_bit_position(*bit).ok_or_else(|| {
1458                    MaterialiseError::Unevaluated {
1459                        construct: "bit position".to_string(),
1460                        at: bit_node.name.clone(),
1461                    }
1462                })?;
1463                // `#define MOVING (FlagsBits)1 << 4` expands inside
1464                // `x & MOVING` as `(x & (FlagsBits)1) << 4`. The brackets
1465                // are the difference between a bitmask constant and a
1466                // number nobody wrote.
1467                let constant = format!("{prefix}{}", c::escape_keyword(&bit_node.name));
1468                names.push((constant.clone(), bit_node.name.clone()));
1469                out.push(c::Decl::Define(c::Define {
1470                    doc: Doc::none(),
1471                    name: Ident::new(&constant),
1472                    value: Some(c::Expr::Paren(Box::new(c::Expr::Binary {
1473                        op: "<<",
1474                        lhs: Box::new(c::Expr::Cast {
1475                            type_: Box::new(type_by_name(&bits_name)),
1476                            value: Box::new(c::Expr::Literal(Literal::int(1))),
1477                        }),
1478                        rhs: Box::new(c::Expr::Literal(Literal::int(i128::from(position)))),
1479                    }))),
1480                }));
1481            }
1482            out.extend(idl_members(&name, &names)?);
1483        }
1484
1485        TypeKind::Bitset { .. } => {
1486            if ghost_created_for(applied, node.node_id, "CBitset").is_none() {
1487                return Ok(());
1488            }
1489            emitted.push(name.clone());
1490            let mut members = Vec::new();
1491            let mut names = Vec::new();
1492            // The base's bitfields are members of the derived struct
1493            // too: an inherited bitset is one flat run.
1494            for field in &ast.all_bitfields_of(id) {
1495                let Ok(field_node) = ast.bitfield(*field) else {
1496                    continue;
1497                };
1498                // An anonymous field is padding: it moves the next field
1499                // along and carries no value of its own.
1500                let Some(field_name) = field_node.name.as_ref() else {
1501                    continue;
1502                };
1503                let escaped = c::escape_keyword(field_name);
1504                names.push((escaped.clone(), field_name.clone()));
1505                members.push(c::Member {
1506                    doc: Doc::none(),
1507                    type_: c::Type::Primitive(c_holder(field_node.bits)),
1508                    name: Ident::new(&escaped),
1509                    dims: Vec::new(),
1510                    // Not a C bitfield: C leaves the allocation order of
1511                    // bitfields implementation-defined, and the order on
1512                    // the wire is the declaration's. Packing is the
1513                    // codec's, where it can be seen.
1514                    bits: None,
1515                });
1516            }
1517            out.extend(struct_typedef(
1518                &name,
1519                members,
1520                Doc::line(
1521                    "A bitset. The fields are separate integers here; the packing into \
1522                 the anchor is the codec's, because C leaves bitfield allocation \
1523                 order to the implementation.",
1524                ),
1525            ));
1526            out.extend(idl_members(&name, &names)?);
1527        }
1528
1529        TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
1530            let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
1531                "CValue"
1532            } else {
1533                "CStruct"
1534            };
1535            if ghost_created_for(applied, node.node_id, created).is_none() {
1536                return Ok(());
1537            }
1538            emitted.push(name.clone());
1539            // XTypes 1.3 §7.5.1.4.1.3: "The language binding shall be
1540            // generated as if an instance of the base type were the first
1541            // member of the subtype with the name 'parent'". The struct
1542            // declares that member, then its own; the plan still puts the
1543            // base's members on the wire first (§7.4.1.1.5.1), and the codec
1544            // reaches them as `parent.m`. The table of IDL names lists every
1545            // member the struct has, the base's first, under that path.
1546            let mut members = Vec::new();
1547            let mut names = Vec::new();
1548            if let Some(base) = struct_base(ast, id) {
1549                members.push(c::Member {
1550                    doc: Doc::line(
1551                        "The base: an instance of the base type is the first member \
1552                         (XTypes 1.3 7.5.1.4.1.3).",
1553                    ),
1554                    type_: type_by_name(&c_type_name(ast, base)),
1555                    name: Ident::new(PARENT),
1556                    dims: Vec::new(),
1557                    bits: None,
1558                });
1559            }
1560            let own = ast.members_of(id).to_vec();
1561            for CMember {
1562                handle: member,
1563                path,
1564                ..
1565            } in c_members(ast, id)
1566            {
1567                let Ok(member_node) = ast.member(member) else {
1568                    continue;
1569                };
1570                let field = c::escape_keyword(&member_node.name);
1571                // XTypes 1.3 §7.5.1.2.4.1: an optional member is a
1572                // pointer and "A NULL pointer shall indicate an omitted
1573                // value", so there is no flag beside it. The path is the one
1574                // a member of the base takes (`parent.m`).
1575                names.push((format!("{path}{field}"), member_node.name.clone()));
1576                if !own.contains(&member) {
1577                    continue;
1578                }
1579                let Some(target) = member_node.type_.resolved() else {
1580                    return Err(MaterialiseError::Unevaluated {
1581                        construct: "member type".to_string(),
1582                        at: member_node.name.clone(),
1583                    });
1584                };
1585                members.push(c::Member {
1586                    doc: if member_node.facets.is_optional() {
1587                        Doc::line(
1588                            "An @optional member: NULL when it is absent. In a `@mutable` \
1589                             type an absent member is not written at all; outside one its \
1590                             absence is written — a zero `<is_present>` byte under XCDR2 \
1591                             (rule 20) or a parameter header declaring a length of zero \
1592                             under XCDR1 (§7.4.1.2).",
1593                        )
1594                    } else {
1595                        Doc::none()
1596                    },
1597                    type_: c_held_member_type(ast, id, member_node, target, &member_node.name)?,
1598                    name: Ident::new(&field),
1599                    dims: Vec::new(),
1600                    bits: None,
1601                });
1602            }
1603            // C has no empty structure. C23 §6.7.2.1p8: "If the
1604            // struct-declaration-list does not contain any named
1605            // members … the behavior is undefined" — gcc and clang
1606            // accept `struct S { };` only as an extension, and the
1607            // probe builds with `-std=c11 -pedantic-errors`, where it
1608            // is an error.
1609            //
1610            // Refused by name rather than emitted: the generated header
1611            // did compile nowhere and the failure arrived as
1612            // "does not compile" against a build directory, 840 slots
1613            // at a time, with nothing naming the construct. C++ has no
1614            // such rule and its column carries the case.
1615            if members.is_empty() {
1616                return Err(MaterialiseError::NoLanguageForm {
1617                    construct: format!(
1618                        "`{name}`, an aggregate with no members — C23 §6.7.2.1p8 \
1619                         leaves a structure with no named member undefined"
1620                    ),
1621                    language: "C",
1622                    at: name,
1623                });
1624            }
1625            out.extend(struct_typedef(&name, members, Doc::none()));
1626            out.extend(idl_members(&name, &names)?);
1627        }
1628
1629        TypeKind::Union { discriminator, .. } => {
1630            if ghost_created_for(applied, node.node_id, "CUnion").is_none() {
1631                return Ok(());
1632            }
1633            emitted.push(name.clone());
1634            let Some(disc) = discriminator.resolved() else {
1635                return Err(MaterialiseError::Unevaluated {
1636                    construct: "union discriminator".to_string(),
1637                    at: name,
1638                });
1639            };
1640            let mut arms = Vec::new();
1641            let mut names = Vec::new();
1642            for case in ast.cases_of(id) {
1643                let Ok(case_node) = ast.case(*case) else {
1644                    continue;
1645                };
1646                if ghost_created_for(applied, case_node.node_id, "CCase").is_none() {
1647                    continue;
1648                }
1649                let Ok(member) = ast.member(case_node.member) else {
1650                    continue;
1651                };
1652                let Some(target) = member.type_.resolved() else {
1653                    return Err(MaterialiseError::Unevaluated {
1654                        construct: "union case type".to_string(),
1655                        at: member.name.clone(),
1656                    });
1657                };
1658                let field = c::escape_keyword(&member.name);
1659                // The branches and not `_d`: the discriminator is this
1660                // binding's accessor name, and IDL 4.2 §7.4.1.4.4.1
1661                // gives it no member name of its own to record.
1662                names.push((field.clone(), member.name.clone()));
1663                arms.push(c::Member {
1664                    doc: Doc::none(),
1665                    type_: c_held_member_type(ast, id, member, target, &member.name)?,
1666                    name: Ident::new(&field),
1667                    dims: Vec::new(),
1668                    bits: None,
1669                });
1670            }
1671            let inner = format!("{name}_u");
1672            out.push(c::Decl::Union(c::Union {
1673                doc: Doc::line("The branches. Which one is live is what `_d` says."),
1674                name: Some(Ident::new(&inner)),
1675                members: arms,
1676            }));
1677            out.extend(struct_typedef(
1678                &name,
1679                vec![
1680                    c::Member {
1681                        doc: Doc::none(),
1682                        type_: c_member_type(ast, disc, &name)?,
1683                        name: Ident::new("_d"),
1684                        dims: Vec::new(),
1685                        bits: None,
1686                    },
1687                    c::Member {
1688                        doc: Doc::none(),
1689                        type_: c::Type::UnionTag(Ident::new(&inner)),
1690                        name: Ident::new("_u"),
1691                        dims: Vec::new(),
1692                        bits: None,
1693                    },
1694                ],
1695                Doc::none(),
1696            ));
1697            out.extend(idl_members(&name, &names)?);
1698        }
1699
1700        TypeKind::Native => {
1701            if ghost_created_for(applied, node.node_id, "CNative").is_none() {
1702                return Ok(());
1703            }
1704            emitted.push(name.clone());
1705            // Opaque, never a guess at what the name might mean: the
1706            // concrete type comes from the facet a `#pragma` or an
1707            // annotation set, and a `void *` when there is none.
1708            let mapping = ast
1709                .any_of_type(id)
1710                .ok()
1711                .and_then(|any| ast.facets(any))
1712                .and_then(|facets| facets.corba.as_ref())
1713                .and_then(|corba| corba.native_mapping.clone());
1714            out.push(c::Decl::Typedef(c::Typedef {
1715                doc: Doc::line("A native type. Opaque unless a facet named its mapping."),
1716                type_: match mapping {
1717                    Some(spelling) => c::Type::Named(Ident::new(&spelling)),
1718                    None => c::Type::Pointer(Box::new(c::Type::Void)),
1719                },
1720                name: Ident::new(&name),
1721                dims: Vec::new(),
1722            }));
1723        }
1724        // An object reference belongs to the RPC arc and an annotation
1725        // type has no runtime form at all — `zerodds_ast_layout` refuses
1726        // to plan either — so a C declaration would be a shape nothing
1727        // could serialise. Named here rather than passed over, so the
1728        // coverage table and this file say the same thing.
1729        TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
1730            return Ok(());
1731        }
1732        // `any` for the same reason and a different one: it is in the
1733        // model because Building Block Any is one of the fifteen, and
1734        // its content is decided at run time, so there is no C type to
1735        // declare for it. The codec refuses it by name — `body_of` in
1736        // `encode_c`, `decode_c` and `default_c` — rather than emitting
1737        // nothing and reporting success.
1738        TypeKind::Any | TypeKind::ValueBase => {
1739            return Ok(());
1740        }
1741        TypeKind::ValueBox { boxed } => {
1742            let Some(target) = boxed.resolved() else {
1743                return Ok(());
1744            };
1745            emitted.push(name.clone());
1746            out.push(c::Decl::Typedef(c::Typedef {
1747                doc: Doc::line("A boxed value: a pointer, absent when null."),
1748                type_: c::Type::Pointer(Box::new(c_member_type(ast, target, &name)?)),
1749                name: Ident::new(&name),
1750                dims: Vec::new(),
1751            }));
1752        }
1753    }
1754    // The declaration's own IDL name, in one place rather than in every
1755    // arm above: an arm that emitted nothing has already returned, so
1756    // reaching here is what "this declaration was written" means.
1757    if has_idl_name(&node.kind) && !node.name.is_empty() {
1758        out.push(idl_name(&name, &node.name));
1759    }
1760    Ok(())
1761}
1762
1763/// The `ZD_IDL_MEMBERS_<name>` macro for a declaration's named parts.
1764///
1765/// `pairs` is `(the C spelling, the IDL spelling)` in IDL declaration
1766/// order — the order the member IDs are assigned in
1767/// (XTypes 1.3 §7.3.1.2.1.1) — and the entries are written even where
1768/// the two spellings are the same, because a caller computing a member
1769/// ID needs every member's IDL name and not only the ones a keyword
1770/// moved.
1771///
1772/// [`None`] for a declaration with no named parts: an empty braced
1773/// initialiser is not C, and the only declarations that reach it are an
1774/// enumeration with no enumerators and a bitset that is all padding.
1775/// The file's module documentation carries why this is a macro and not
1776/// an object.
1777/// The table only exists where the escape is invertible.
1778///
1779/// Two members whose IDL names differ and whose spellings do not are
1780/// two entries with the same left half — the table would record the
1781/// collision rather than refuse it, and a reader recovering the IDL
1782/// name would get whichever entry it hit first. The check is here
1783/// because this is the function that holds both spellings for a whole
1784/// scope; nothing upstream sees more than one member at a time.
1785///
1786/// # Errors
1787/// [`MaterialiseError::SpellingCollision`] when two members reach one
1788/// spelling.
1789fn idl_members(
1790    name: &str,
1791    pairs: &[(String, String)],
1792) -> Result<Option<c::Decl>, MaterialiseError> {
1793    no_spelling_collision(
1794        pairs
1795            .iter()
1796            .map(|(spelled, declared)| (spelled.as_str(), declared.as_str())),
1797        "C",
1798        name,
1799    )?;
1800    if pairs.is_empty() {
1801        return Ok(None);
1802    }
1803    let entries = pairs
1804        .iter()
1805        .map(|(field, idl)| {
1806            c::Expr::Initializer(vec![
1807                c::Expr::Literal(Literal::Str(field.clone())),
1808                c::Expr::Literal(Literal::Str(idl.clone())),
1809            ])
1810        })
1811        .collect();
1812    Ok(Some(c::Decl::Define(c::Define {
1813        doc: Doc::lines(&[
1814            "The IDL spelling of each member, beside the spelling this header",
1815            "uses. `zd_member_name` is the record an entry fits.",
1816        ]),
1817        name: Ident::new(&format!("{IDL_MEMBERS_PREFIX}{name}")),
1818        value: Some(c::Expr::Initializer(entries)),
1819    })))
1820}
1821
1822/// The `ZD_IDL_NAME_<name>` macro for a declaration's own IDL name.
1823fn idl_name(name: &str, idl: &str) -> c::Decl {
1824    c::Decl::Define(c::Define {
1825        doc: Doc::line("The IDL spelling of the declaration above."),
1826        name: Ident::new(&format!("{IDL_NAME_PREFIX}{name}")),
1827        value: Some(c::Expr::Literal(Literal::Str(idl.to_string()))),
1828    })
1829}
1830
1831/// Whether a type's C name was made from an IDL identifier.
1832///
1833/// The collections are the reason this is asked: `zd_seq_int32_t` is a
1834/// name this file invented for a type IDL spells `sequence<long>` and
1835/// never named, so there is no IDL spelling to record for it. The named
1836/// kinds below all take their C name from
1837/// [`c::escape_keyword`] applied to an identifier the source wrote.
1838const fn has_idl_name(kind: &TypeKind) -> bool {
1839    matches!(
1840        kind,
1841        TypeKind::Structure { .. }
1842            | TypeKind::Valuetype { .. }
1843            | TypeKind::Union { .. }
1844            | TypeKind::Enumeration { .. }
1845            | TypeKind::Bitmask { .. }
1846            | TypeKind::Bitset { .. }
1847            | TypeKind::Alias { .. }
1848            | TypeKind::Native
1849            | TypeKind::ValueBox { .. }
1850    )
1851}
1852
1853/// `typedef struct <name> <name>;` followed by the definition.
1854///
1855/// The typedef comes first and names an incomplete type, which is what
1856/// lets a member refer to the aggregate before its body is closed —
1857/// C's answer to a self-referential declaration.
1858fn struct_typedef(name: &str, members: Vec<c::Member>, doc: Doc) -> Vec<c::Decl> {
1859    vec![
1860        c::Decl::Typedef(c::Typedef {
1861            doc: Doc::none(),
1862            type_: c::Type::StructTag(Ident::new(name)),
1863            name: Ident::new(name),
1864            dims: Vec::new(),
1865        }),
1866        c::Decl::Struct(c::Struct {
1867            body_text: zerodds_lang_cst::common::BodyText::none(),
1868            doc,
1869            name: Some(Ident::new(name)),
1870            members,
1871        }),
1872    ]
1873}
1874
1875/// `struct { uint32_t _maximum; uint32_t _length; T *_buffer; bool _release; }`, with its typedef
1876/// (the sequence of [C-MAP], CORBA C mapping §1.14, as XTypes 1.3 §7.5.1 reuses it).
1877///
1878/// A length beside a pointer and not a NUL-terminated run: a sequence of
1879/// `octet` may contain a zero, and a terminator would cut the sample
1880/// short without saying so. `_maximum` is the capacity of the buffer and `_release` says
1881/// whether the sequence owns it, as the map's fields do.
1882fn sequence_decl(name: &str, element: c::Type) -> Vec<c::Decl> {
1883    struct_typedef(
1884        name,
1885        vec![
1886            c::Member {
1887                doc: Doc::none(),
1888                type_: c::Type::Primitive(c::Primitive::UInt32),
1889                name: Ident::new("_maximum"),
1890                dims: Vec::new(),
1891                bits: None,
1892            },
1893            c::Member {
1894                doc: Doc::none(),
1895                type_: c::Type::Primitive(c::Primitive::UInt32),
1896                name: Ident::new("_length"),
1897                dims: Vec::new(),
1898                bits: None,
1899            },
1900            c::Member {
1901                doc: Doc::none(),
1902                type_: c::Type::Pointer(Box::new(element)),
1903                name: Ident::new("_buffer"),
1904                dims: Vec::new(),
1905                bits: None,
1906            },
1907            c::Member {
1908                doc: Doc::none(),
1909                type_: c::Type::Primitive(c::Primitive::Bool),
1910                name: Ident::new("_release"),
1911                dims: Vec::new(),
1912                bits: None,
1913            },
1914        ],
1915        Doc::line(
1916            "A sequence: a count and the elements. The buffer is the caller's — \
1917             nothing in the write path allocates.",
1918        ),
1919    )
1920}
1921
1922/// Whether the rules created a node of `type_name` for `node`.
1923///
1924/// The same two-hop walk the Rust materialiser does: a correspondence is
1925/// an ordinary node with a link to the anchor and a link to what was
1926/// created.
1927fn ghost_created_for(applied: &Applied, node: NodeId, type_name: &str) -> Option<GhostId> {
1928    let anchor = applied
1929        .origin
1930        .iter()
1931        .find(|(_, model)| **model == node)
1932        .map(|(ghost, _)| *ghost)?;
1933    let typ = applied.graph.types.lookup(type_name)?;
1934    applied
1935        .graph
1936        .nodes_of_type(typ)
1937        .map(|created| created.id)
1938        .find(|created| {
1939            applied.graph.connected(&anchor, created)
1940                || applied
1941                    .graph
1942                    .parts(&anchor)
1943                    .any(|part| applied.graph.connected(&part.other, created))
1944        })
1945}
1946
1947/// The C literal a constant's value takes, behind the cast its declared
1948/// type puts in front of it.
1949///
1950/// The four string-shaped forms are all `const char *` in this binding —
1951/// `zd_string`, `zd_wstring` and `zd_fixed_P_S` are the same typedef —
1952/// so each becomes a string literal and the cast names which of the
1953/// three it is. That is not a widening: a `fixed` is a decimal and a
1954/// `wstring` is UTF-8 because that is how `runtime/c` takes them, and a
1955/// constant that spelled either differently from a member of the same
1956/// type would be a second answer to what the value is.
1957///
1958/// `long double` is refused by name. CORBA gives a `long double` literal
1959/// no spelling C can carry: the compiler's own `long double` is x87
1960/// 80-bit extended on x86-64, binary128 on aarch64 and a plain `double`
1961/// on macOS arm64, which is why [`FLOAT128`] is a struct and not that
1962/// type — and a struct has no literal. Emitting `((zdw_f128)1.5)` would
1963/// not compile; emitting a `double` would put a different number in the
1964/// header than the IDL wrote.
1965fn c_constant_literal(
1966    value: &zerodds_ast::ConstValue,
1967    type_: &c::Type,
1968    at: &str,
1969) -> Result<Literal, MaterialiseError> {
1970    use zerodds_ast::ConstValue;
1971    let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
1972        construct: construct.to_string(),
1973        language: "C",
1974        at: at.to_string(),
1975    };
1976    Ok(match value {
1977        ConstValue::Boolean(flag) => Literal::Bool(*flag),
1978        // Through `integer_text` and not `Literal::int`: −2^63 has no
1979        // decimal in C, and a `const long long` carrying it is the one
1980        // value where the digits are read unsigned before the minus
1981        // applies. Every other value is its own decimal, which is what
1982        // `Literal::int` wrote.
1983        ConstValue::Integer(number) => Literal::Int(zerodds_lang_render::c::integer_text(*number)),
1984        ConstValue::Char(character) => Literal::Char(*character),
1985        // The code point as a number, because `wchar` is a `uint16_t`
1986        // here: a `'ä'` character literal in C is one byte of the
1987        // execution character set and not a code unit.
1988        ConstValue::WChar(point) => Literal::int(i128::from(*point)),
1989        ConstValue::Float { mantissa, exponent } => {
1990            if matches!(type_, c::Type::Named(name) if name.as_str() == FLOAT128) {
1991                return Err(refuse("a `long double` constant, which has no C literal"));
1992            }
1993            let mut text = constant::float_text(mantissa, exponent.as_deref());
1994            // `-Wconversion` is on for every generated file, and an
1995            // initialiser whose value is a `double` narrowing into a
1996            // `float` is exactly what it reports.
1997            if matches!(type_, c::Type::Primitive(c::Primitive::Float)) {
1998                text.push('f');
1999            }
2000            Literal::Float(text)
2001        }
2002        ConstValue::Fixed { digits, scale } => {
2003            Literal::Str(constant::fixed_decimal(digits, *scale))
2004        }
2005        ConstValue::String(text) => Literal::Str(text.clone()),
2006        ConstValue::WString(points) => Literal::Str(
2007            constant::wstring_utf8(points)
2008                .ok_or_else(|| refuse("a `wstring` constant holding a lone surrogate"))?,
2009        ),
2010        // `ConstValue` is `#[non_exhaustive]`, so a form added to the
2011        // model reaches here. Refused by name rather than given a
2012        // plausible default: a constant emitted as the wrong literal is
2013        // a header that disagrees with the IDL and compiles.
2014        _ => return Err(refuse("a constant form this binding has no literal for")),
2015    })
2016}