Skip to main content

zerodds_xml_compose/
lower.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The DDS-XML `<types>` tree lowered into the common AST.
4//!
5//! Two passes over every document, then two completion passes over the
6//! model.
7//!
8//! **Declare** creates one node per named type, so that a member written
9//! before its type is declared has something to point at. Enumerations
10//! and bitmasks are finished here — neither names another type — and
11//! structs, unions, aliases and bitsets are created forward and given
12//! their bodies in the second pass.
13//!
14//! **Define** fills those bodies. Every type reference is resolved
15//! against the names the first pass recorded, innermost scope first, so
16//! an anonymous collection built at the use site holds a resolved
17//! element rather than a pending name that no enclosing-scope walk could
18//! later reach — the anonymous node sits in the root module and its
19//! scope is not the writing member's.
20//!
21//! **Resolve** then runs `Ast::resolve_all_by_name` for whatever is
22//! left, and **complete** fills what the document left implicit: the
23//! caller's default extensibility where the document said nothing, and
24//! every member id, by the same rule and with the same hash the IDL
25//! front end uses. Two declarations of one type, one in `.idl` and one
26//! in `.xml`, have to reach the same `TypeIdentifier`, and they cannot
27//! if the two paths number members differently.
28
29use std::collections::{BTreeMap, BTreeSet};
30
31use zerodds_ast::node::ModuleNode;
32use zerodds_ast::{
33    AnyId, AppliedAnnotation, AppliedArgument, Ast, AutoId, Bound, CaseLabel, CharWidth, ConstExpr,
34    ConstExprNode, EvalError, Extensibility, Facets, Literal, MemberIdAssignment, MemberIdOrigin,
35    MemberSpec, PrimitiveKind, SourceFacet, TryConstruct, TypeKind, TypeNode, VerbatimDirective,
36    VerbatimPlacement, XTypesFacet, XmlFacet,
37};
38use zerodds_ast_core::{Id, PendingRef, Ref, ScopedName};
39use zerodds_xml::{DdsXmlDocument, XmlElement, parse_xml_tree};
40
41use crate::ComposeOptions;
42use crate::expr::{self, Expression};
43
44/// How bad one finding is.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Severity {
47    /// The document says something this loader cannot carry, and the
48    /// model is missing it.
49    Error,
50    /// The model is whole; something was noted on the way.
51    Warning,
52}
53
54/// One thing wrong with a document.
55///
56/// Not `zerodds_idl_parse::Diagnostic`: that one carries a byte span
57/// into an IDL token stream and a `DiagnosticCode` naming IDL grammar
58/// productions. An XML finding points at an element path, and saying so
59/// is better than a span of `0..0` that pretends to be a source
60/// location.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Diagnostic {
63    /// How bad it is.
64    pub severity: Severity,
65    /// What is wrong, in a sentence.
66    pub message: String,
67    /// The document, and the element path inside it —
68    /// `types/extensibility.xml: dds/types/module[Test]/struct[struct_m1]`.
69    pub at: String,
70    /// The clause the rule comes from.
71    pub spec_reference: &'static str,
72}
73
74impl std::fmt::Display for Diagnostic {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        write!(f, "{}: {} [{}]", self.at, self.message, self.spec_reference)
77    }
78}
79
80/// Lower every unit into one model, and report what went wrong.
81pub(crate) fn lower_all(
82    ast: &mut Ast,
83    sources: &[(String, String)],
84    options: &ComposeOptions,
85) -> Vec<Diagnostic> {
86    let mut diagnostics = Vec::new();
87    let units = load_units(sources, &options.include_paths, &mut diagnostics);
88
89    let mut lowering = Lowering {
90        ast,
91        options: options.clone(),
92        unit: String::new(),
93        diagnostics,
94        declared: BTreeMap::new(),
95        builtin: BTreeMap::new(),
96    };
97
98    // Every named type in every unit exists before any body is filled,
99    // so a forward reference within a file and one across two files are
100    // the same case.
101    for (name, doc) in &units {
102        lowering.unit = name.clone();
103        lowering.walk_units(doc, Pass::Declare);
104    }
105    for (name, doc) in &units {
106        lowering.unit = name.clone();
107        lowering.walk_units(doc, Pass::Define);
108    }
109    lowering.unit = units
110        .first()
111        .map_or_else(|| String::from("<bundle>"), |(name, _)| name.clone());
112    lowering.resolve();
113    lowering.complete();
114    lowering.diagnostics
115}
116
117/// The documents to lower, each ahead of the ones that include it.
118///
119/// DDS-XTypes 1.3 §7.3.2.1.1: "A file inclusion specified as in this Type
120/// Representation shall be considered equivalent to an IDL #include of the
121/// same file", and "Conformant Type Representation compilers need not
122/// support the inclusion of files of other Type Representations from
123/// within an XML Type Representation document". So an `<include
124/// file="x.xml"/>` brings that document into the model, once however many
125/// documents include it, and an `<include>` of a file of another
126/// representation is reported. The file is looked for beside the document
127/// that names it.
128fn load_units(
129    sources: &[(String, String)],
130    include_paths: &[std::path::PathBuf],
131    diagnostics: &mut Vec<Diagnostic>,
132) -> Vec<(String, DdsXmlDocument)> {
133    let mut units = Vec::with_capacity(sources.len());
134    // What the caller names is loaded in its own turn, so an include of
135    // it is not a second copy.
136    let mut seen: BTreeSet<String> = sources.iter().map(|(name, _)| document_key(name)).collect();
137    for (name, text) in sources {
138        load_one(
139            name,
140            text,
141            &mut units,
142            &mut Loading {
143                seen: &mut seen,
144                include_paths,
145            },
146            diagnostics,
147            0,
148        );
149    }
150    units
151}
152
153/// How deeply includes nest before the chain is taken for a runaway.
154const MAX_INCLUDE_DEPTH: usize = 32;
155
156/// What a load carries from document to document.
157struct Loading<'a> {
158    /// The documents already taken, so that one included twice is one.
159    seen: &'a mut BTreeSet<String>,
160    /// The directories searched after the including document's own
161    /// (DDS-XTypes 1.3 §7.5.2.2.13).
162    include_paths: &'a [std::path::PathBuf],
163}
164
165/// Where an included file is: beside the document that names it, then in
166/// each include path; the first that exists, or the first candidate when
167/// none does (the read then names the file that was expected).
168fn locate_include(
169    including: &str,
170    file: &str,
171    include_paths: &[std::path::PathBuf],
172) -> std::path::PathBuf {
173    let beside = std::path::Path::new(including)
174        .parent()
175        .map_or_else(|| std::path::PathBuf::from(file), |dir| dir.join(file));
176    let mut candidates = vec![beside];
177    candidates.extend(include_paths.iter().map(|dir| dir.join(file)));
178    candidates
179        .iter()
180        .find(|candidate| candidate.is_file())
181        .cloned()
182        .unwrap_or_else(|| candidates.swap_remove(0))
183}
184
185/// zerodds-lint: recursion-depth 32 (an include nests at most
186/// `MAX_INCLUDE_DEPTH` levels; a deeper one is an error diagnostic)
187fn load_one(
188    name: &str,
189    text: &str,
190    units: &mut Vec<(String, DdsXmlDocument)>,
191    loading: &mut Loading<'_>,
192    diagnostics: &mut Vec<Diagnostic>,
193    depth: usize,
194) {
195    let document = match parse_xml_tree(text) {
196        Ok(document) => document,
197        Err(error) => {
198            diagnostics.push(Diagnostic {
199                severity: Severity::Error,
200                message: format!("the document is not well-formed DDS-XML: {error:?}"),
201                at: name.to_owned(),
202                spec_reference: "OMG DDS-XML 1.0 §7.1",
203            });
204            return;
205        }
206    };
207    let mut files = Vec::new();
208    let mut deeper = false;
209    if let Ok(libraries) = document.library_elements("types") {
210        for library in libraries {
211            collect_includes(library, &mut files, 0, &mut deeper);
212            check_identifier_names(library, name, diagnostics);
213        }
214    }
215    if deeper {
216        diagnostics.push(Diagnostic {
217            severity: Severity::Error,
218            message: format!("modules nest deeper than {MAX_INCLUDE_DEPTH} levels; the includes below are not read"),
219            at: name.to_owned(),
220            spec_reference: "OMG DDS-XTypes 1.3 §7.3.2.1.1",
221        });
222    }
223    for file in files {
224        let path = locate_include(name, &file, loading.include_paths);
225        let included = path.to_string_lossy().into_owned();
226        if !std::path::Path::new(&file)
227            .extension()
228            .is_some_and(|extension| extension.eq_ignore_ascii_case("xml"))
229        {
230            diagnostics.push(Diagnostic {
231                severity: Severity::Error,
232                message: format!(
233                    "`{file}` is not an XML document; including a file of another Type \
234                     Representation is not supported, and §7.3.2.1.1 does not require it"
235                ),
236                at: format!("{name}: include[{file}]"),
237                spec_reference: "OMG DDS-XTypes 1.3 §7.3.2.1.1",
238            });
239            continue;
240        }
241        if !loading.seen.insert(document_key(&included)) {
242            continue;
243        }
244        if depth >= MAX_INCLUDE_DEPTH {
245            diagnostics.push(Diagnostic {
246                severity: Severity::Error,
247                message: format!("includes nest deeper than {MAX_INCLUDE_DEPTH} levels"),
248                at: format!("{name}: include[{file}]"),
249                spec_reference: "OMG DDS-XTypes 1.3 §7.3.2.1.1",
250            });
251            continue;
252        }
253        match std::fs::read_to_string(&path) {
254            Ok(included_text) => {
255                load_one(
256                    &included,
257                    &included_text,
258                    units,
259                    loading,
260                    diagnostics,
261                    depth + 1,
262                );
263            }
264            Err(error) => diagnostics.push(Diagnostic {
265                severity: Severity::Error,
266                message: format!("the included file cannot be read: {error}"),
267                at: format!("{name}: include[{file}]"),
268                spec_reference: "OMG DDS-XTypes 1.3 §7.3.2.1.1",
269            }),
270        }
271    }
272    units.push((name.to_owned(), document));
273}
274
275/// The attributes whose type in the schema of Annex A is `identifierName`
276/// (`([a-zA-Z]|::)([a-zA-Z_0-9]|::)*`, S. 243): the `name` of every
277/// declaration, member, enumerator, flag and application, and the three
278/// attributes that name a type.
279const IDENTIFIER_ATTRIBUTES: [&str; 4] = [
280    "name",
281    "baseType",
282    "nonBasicTypeName",
283    "mapKeyNonBasicTypeName",
284];
285
286/// Reports every `identifierName` attribute of a `<types>` library that does
287/// not match the pattern of Annex A, an error each, with the path of the
288/// element.
289///
290/// The library's own `name` is no `identifierName` in the schema (`typeLibrary`
291/// has no attributes) and is left alone.
292fn check_identifier_names(library: &XmlElement, document: &str, diagnostics: &mut Vec<Diagnostic>) {
293    let mut stack: Vec<&XmlElement> = library.children.iter().collect();
294    while let Some(element) = stack.pop() {
295        for attribute in IDENTIFIER_ATTRIBUTES {
296            if let Some(value) = element.attribute(attribute) {
297                if !zerodds_xml::is_identifier_name(value) {
298                    diagnostics.push(Diagnostic {
299                        severity: Severity::Error,
300                        message: format!(
301                            "{}@{attribute}=\"{value}\" is no identifierName (a letter or `::` \
302                             first, then letters, digits, `_` and `::`)",
303                            element.path
304                        ),
305                        at: document.to_owned(),
306                        spec_reference: "OMG DDS-XTypes 1.3 Annex A, identifierName",
307                    });
308                }
309            }
310        }
311        stack.extend(element.children.iter());
312    }
313}
314
315/// The `file` of every `<include>` in a library, inside modules too.
316///
317/// zerodds-lint: recursion-depth 32 (module nesting; past `MAX_INCLUDE_DEPTH`
318/// modules the walk stops and says so in `deeper`)
319fn collect_includes(element: &XmlElement, out: &mut Vec<String>, depth: usize, deeper: &mut bool) {
320    if depth > MAX_INCLUDE_DEPTH {
321        *deeper = true;
322        return;
323    }
324    for child in &element.children {
325        match child.name.as_str() {
326            "include" => {
327                if let Some(file) = child.attribute("file") {
328                    out.push(file.to_owned());
329                }
330            }
331            "module" => collect_includes(child, out, depth + 1, deeper),
332            _ => {}
333        }
334    }
335}
336
337/// A name one document is known by, however a path to it is spelled.
338fn document_key(name: &str) -> String {
339    std::fs::canonicalize(name).map_or_else(
340        |_| name.to_owned(),
341        |path| path.to_string_lossy().into_owned(),
342    )
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346enum Pass {
347    Declare,
348    Define,
349}
350
351struct Lowering<'a> {
352    ast: &'a mut Ast,
353    options: ComposeOptions,
354    unit: String,
355    diagnostics: Vec<Diagnostic>,
356    /// Every named type, by its absolute path from the root.
357    declared: BTreeMap<Vec<String>, Id<TypeNode>>,
358    /// One node per primitive per model, as the IDL front end does it: a
359    /// document with a hundred `int32` members must not hold a hundred
360    /// primitive types.
361    builtin: BTreeMap<PrimitiveKind, Id<TypeNode>>,
362}
363
364/// Where in the document a node sits, for both name resolution and
365/// diagnostics.
366#[derive(Clone)]
367struct Scope {
368    /// The module path from the root, outermost first.
369    path: Vec<String>,
370    /// The module node itself.
371    module: Id<ModuleNode>,
372    /// The element path, for a diagnostic.
373    trail: String,
374}
375
376impl<'a> Lowering<'a> {
377    // -- walking ---------------------------------------------------------
378
379    fn walk_units(&mut self, doc: &DdsXmlDocument, pass: Pass) {
380        let root = self.ast.root_module();
381        let scope = Scope {
382            path: Vec::new(),
383            module: root,
384            trail: format!("{}: {}", self.unit, doc.root.name),
385        };
386        match doc.library_elements("types") {
387            Ok(libraries) => {
388                for library in libraries {
389                    let scope = Scope {
390                        trail: format!("{}/types", scope.trail),
391                        ..scope.clone()
392                    };
393                    if pass == Pass::Declare {
394                        self.constants_in_order(&library.children, &scope, None);
395                    }
396                    for child in &library.children {
397                        self.declaration(child, &scope, pass);
398                    }
399                }
400            }
401            Err(error) => self.error(
402                &scope.trail,
403                format!("no <types> to read: {error:?}"),
404                "OMG DDS-XML 1.0 §7.3.3.3",
405            ),
406        }
407    }
408
409    /// The `<const>` children of one scope, each after the constants its
410    /// value names (§7.3.2.1.3: a `<const_exp>` may name a constant; the
411    /// document may write them in any order). A constant that waits on
412    /// itself or on a cycle is lowered anyway, in document order, and the
413    /// resolve pass reports the name that does not resolve.
414    fn constants_in_order(&mut self, children: &[XmlElement], scope: &Scope, within: Option<&str>) {
415        let mut remaining: Vec<&XmlElement> =
416            children.iter().filter(|c| c.name == "const").collect();
417        while !remaining.is_empty() {
418            let declared: Vec<String> = remaining
419                .iter()
420                .filter_map(|c| c.attribute("name").map(str::to_owned))
421                .collect();
422            let ready = remaining.iter().position(|c| {
423                let own = c.attribute("name");
424                let mut named = Vec::new();
425                if let Some(Ok(parsed)) = c.attribute("value").map(expr::parse) {
426                    names_in(&parsed, &mut named);
427                }
428                !named
429                    .iter()
430                    .any(|name| Some(name.as_str()) != own && declared.contains(name))
431            });
432            let at = ready.unwrap_or(0);
433            let el = remaining.remove(at);
434            self.constant(el, scope, within);
435        }
436    }
437
438    /// One `<types>` child, in whichever pass is running.
439    fn declaration(&mut self, el: &XmlElement, scope: &Scope, pass: Pass) {
440        match el.name.as_str() {
441            "module" => self.module(el, scope, pass),
442            "struct" => self.structure(el, scope, pass),
443            "union" => self.union(el, scope, pass),
444            "enum" => match pass {
445                Pass::Declare => self.enumeration(el, scope),
446                // Annotations are applied once every `<annotation>` of the
447                // bundle is declared.
448                Pass::Define => self.annotate_enumerated(el, scope, "enumerator"),
449            },
450            "bitmask" => match pass {
451                Pass::Declare => self.bitmask(el, scope),
452                Pass::Define => self.annotate_enumerated(el, scope, "flag"),
453            },
454            "typedef" => self.alias(el, scope, pass),
455            "forward_dcl" | "forwardDcl" => {
456                if pass == Pass::Declare {
457                    self.forward_declaration(el, scope);
458                }
459            }
460            // Read before the passes (`load_units`): the included document
461            // is one of the units, ahead of this one.
462            "include" => {}
463            // Declared ahead of the other children of their scope, in the
464            // order their values need (`constants_in_order`).
465            "const" => {}
466            "bitset" => self.bitset(el, scope, pass),
467            "annotation" => {
468                if pass == Pass::Declare {
469                    self.annotation_declaration(el, scope);
470                }
471            }
472            other => {
473                if pass == Pass::Declare {
474                    self.error(
475                        &format!("{}/{other}", scope.trail),
476                        format!("<{other}> is not a type declaration"),
477                        "OMG DDS-XTypes 1.3 Annex A",
478                    );
479                }
480            }
481        }
482    }
483
484    fn module(&mut self, el: &XmlElement, scope: &Scope, pass: Pass) {
485        let Some(name) = self.named(el, scope) else {
486            return;
487        };
488        let Ok(module) = self.ast.ensure_module(scope.module, &name) else {
489            self.error(
490                &scope.trail,
491                format!("the model refused a module named `{name}`"),
492                "zerodds-ast-1.0 §3.1",
493            );
494            return;
495        };
496        let mut path = scope.path.clone();
497        path.push(name.clone());
498        let inner = Scope {
499            trail: format!("{}/module[{name}]", scope.trail),
500            path,
501            module,
502        };
503        if pass == Pass::Declare {
504            if let Ok(node_id) = self.ast.module(module).map(|node| node.node_id) {
505                self.set_xml_facet(AnyId::Module(module, node_id), el);
506                // Annex A `moduleDecl/@autoid`: "Module declarations" are
507                // among the targets of `@autoid` (§7.3.1.2.1.1, Table 21),
508                // and the value governs the aggregated types declared in the
509                // module that carry none of their own.
510                if let Some(autoid) = self.autoid_of(el, &inner.trail) {
511                    if let Some(facets) = self.ast.facets_mut(AnyId::Module(module, node_id)) {
512                        facets.xtypes_mut().autoid = Some(autoid);
513                    }
514                }
515            }
516        }
517        if pass == Pass::Declare {
518            self.constants_in_order(&el.children, &inner, None);
519        }
520        for child in &el.children {
521            self.declaration(child, &inner, pass);
522        }
523    }
524
525    // -- declarations ----------------------------------------------------
526
527    fn structure(&mut self, el: &XmlElement, scope: &Scope, pass: Pass) {
528        let Some(name) = self.named(el, scope) else {
529            return;
530        };
531        let trail = format!("{}/struct[{name}]", scope.trail);
532        if pass == Pass::Declare {
533            let placeholder = TypeKind::Structure {
534                base: None,
535                members: Vec::new(),
536            };
537            self.declare(&name, scope, placeholder, el, &trail);
538            return;
539        }
540        let Some(id) = self.declared_here(&name, scope) else {
541            return;
542        };
543        let base = el
544            .attribute("baseType")
545            .or_else(|| el.attribute("base_type"))
546            .map(|written| self.named_ref(written, scope, &trail));
547        if self
548            .ast
549            .complete_type(
550                id,
551                TypeKind::Structure {
552                    base,
553                    members: Vec::new(),
554                },
555            )
556            .is_err()
557        {
558            self.error(
559                &trail,
560                format!("`{name}` is declared twice"),
561                "OMG IDL 4.2 §7.2.3",
562            );
563            return;
564        }
565        // The names a constant or a bound inside the structure resolves
566        // against start at the structure: `MyStructure::MY_CONSTANT`.
567        let mut inner_path = scope.path.clone();
568        inner_path.push(name.clone());
569        let inner = Scope {
570            path: inner_path,
571            ..scope.clone()
572        };
573        let annotations = self.applied_annotations(el, &inner, &trail);
574        let verbatim = self.verbatim_directives(el, &trail);
575        self.attach_to_type(id, annotations, verbatim);
576        // Constants first: a member's bound may name one declared after it.
577        self.constants_in_order(&el.children, &inner, Some(&name));
578        for child in &el.children {
579            match child.name.as_str() {
580                "member" => {
581                    let spec = self.member_spec(child, &inner, &trail);
582                    let Some(spec) = spec else { continue };
583                    if self.ast.add_member(id, spec).is_err() {
584                        self.error(
585                            &trail,
586                            String::from("the model refused a member"),
587                            "zerodds-ast-1.0 §3.3",
588                        );
589                    }
590                }
591                // Read above: the constants, and the annotations and
592                // verbatim text of the structure as a whole.
593                "const" | "annotate" | "verbatim" => {}
594                other => self.warning(
595                    &trail,
596                    format!("<{other}> is no child of a <struct> and is not read"),
597                    "OMG DDS-XTypes 1.3 Annex A",
598                ),
599            }
600        }
601    }
602
603    fn union(&mut self, el: &XmlElement, scope: &Scope, pass: Pass) {
604        let Some(name) = self.named(el, scope) else {
605            return;
606        };
607        let trail = format!("{}/union[{name}]", scope.trail);
608        if pass == Pass::Declare {
609            let placeholder = TypeKind::Union {
610                discriminator: Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(
611                    Vec::new(),
612                ))),
613                cases: Vec::new(),
614                default_case: None,
615            };
616            self.declare(&name, scope, placeholder, el, &trail);
617            return;
618        }
619        let Some(id) = self.declared_here(&name, scope) else {
620            return;
621        };
622        let Some(discriminator_el) = el.child("discriminator") else {
623            self.error(
624                &trail,
625                String::from("a union needs a <discriminator>"),
626                "OMG DDS-XTypes 1.3 Annex A",
627            );
628            return;
629        };
630        // `key="true"` on a union discriminator: the one construct this
631        // loader answers on an axis rather than by itself. Table 21
632        // permits it, so `KeyOnUnionDiscriminator::Carried` — the
633        // default — records it; `Refused`, for a tool that has to stay
634        // within what the measured toolchains accept, reports it here,
635        // exactly as the IDL front end does for `@key` in the same
636        // position. The two paths have to
637        // answer alike: a type declared once in IDL and once in XML must
638        // reach the same `TypeIdentifier`, and the key bit is part of
639        // it.
640        let discriminator_is_key = truthy(discriminator_el.attribute("key"));
641        if discriminator_is_key
642            && self.options.key_on_union_discriminator == crate::KeyOnUnionDiscriminator::Refused
643        {
644            self.error(
645                &trail,
646                String::from(
647                    "`key=\"true\"` on a union discriminator is not carried under \
648                     `KeyOnUnionDiscriminator::Refused`: the clause permits it and no measured \
649                     implementation accepts it, so this setting reports it rather than \
650                     computing a KeyHash no peer computes",
651                ),
652                "DDS-XTypes 1.3 §7.2.2.4.4.4.8, Table 21",
653            );
654            // The node stays in the model with a broken discriminator
655            // rather than as a forward declaration nobody completes: a
656            // reference left pending would be reported a second time by
657            // the resolve pass, under a clause that has nothing to do
658            // with the refusal, and the reader would chase the wrong
659            // rule.
660            let _ = self.ast.complete_type(
661                id,
662                TypeKind::Union {
663                    discriminator: broken("key discriminator"),
664                    cases: Vec::new(),
665                    default_case: None,
666                },
667            );
668            return;
669        }
670        let discriminator = self.type_reference(
671            discriminator_el,
672            scope,
673            &trail,
674            "type",
675            "nonBasicTypeName",
676            "stringMaxLength",
677        );
678        if self
679            .ast
680            .complete_type(
681                id,
682                TypeKind::Union {
683                    discriminator: discriminator.clone(),
684                    cases: Vec::new(),
685                    default_case: None,
686                },
687            )
688            .is_err()
689        {
690            self.error(
691                &trail,
692                format!("`{name}` is declared twice"),
693                "OMG IDL 4.2 §7.2.3",
694            );
695            return;
696        }
697        if let Some(behaviour) = self.try_construct(discriminator_el, &trail) {
698            self.set_type_xtypes(id, |x| x.discriminator_try_construct = Some(behaviour));
699        }
700        // §7.3.1.2.5: annotations "can be applied to the implicit
701        // discriminator member of a union type"; the schema's
702        // `discriminatorDecl` holds `annotate` for them.
703        let on_discriminator = self.applied_annotations(discriminator_el, scope, &trail);
704        if !on_discriminator.is_empty() {
705            self.set_type_xtypes(id, |x| x.discriminator_annotations.extend(on_discriminator));
706        }
707        let annotations = self.applied_annotations(el, scope, &trail);
708        let verbatim = self.verbatim_directives(el, &trail);
709        self.attach_to_type(id, annotations, verbatim);
710        if discriminator_is_key {
711            self.set_type_xtypes(id, |x| x.discriminator_is_key = true);
712        }
713
714        let discriminator_type = discriminator.resolved();
715        for case in el.children_named("case") {
716            let mut labels = Vec::new();
717            for label in case.children_named("caseDiscriminator") {
718                let Some(written) = label.attribute("value") else {
719                    self.error(
720                        &trail,
721                        String::from("a <caseDiscriminator> needs a `value`"),
722                        "OMG DDS-XTypes 1.3 Annex A",
723                    );
724                    continue;
725                };
726                match self.case_label(written, discriminator_type, &trail) {
727                    Some(parsed) => labels.push(parsed),
728                    // `case 1: case 2: long x;` reached by one label is
729                    // a different union: the value the lost label
730                    // selected now falls to `default:`, or to nothing.
731                    // The whole case goes rather than half of it, and
732                    // `case_label` has already said which label and why.
733                    None => {
734                        labels.clear();
735                        break;
736                    }
737                }
738            }
739            if labels.is_empty() {
740                self.error(
741                    &trail,
742                    String::from("a <case> needs at least one <caseDiscriminator>"),
743                    "OMG DDS-XTypes 1.3 Annex A",
744                );
745                continue;
746            }
747            let Some(member_el) = case.child("member") else {
748                self.error(
749                    &trail,
750                    String::from("a <case> needs a <member>"),
751                    "OMG DDS-XTypes 1.3 Annex A",
752                );
753                continue;
754            };
755            let Some(spec) = self.member_spec(member_el, scope, &trail) else {
756                continue;
757            };
758            if self.ast.add_case(id, labels, spec).is_err() {
759                self.error(
760                    &trail,
761                    String::from("the model refused a case"),
762                    "zerodds-ast-1.0 §3.3",
763                );
764            }
765        }
766    }
767
768    fn enumeration(&mut self, el: &XmlElement, scope: &Scope) {
769        let Some(name) = self.named(el, scope) else {
770            return;
771        };
772        let trail = format!("{}/enum[{name}]", scope.trail);
773        let kind = TypeKind::Enumeration {
774            enumerators: Vec::new(),
775        };
776        let Some(id) = self.declare_now(&name, scope, kind, el, &trail) else {
777            return;
778        };
779        for literal in el.children_named("enumerator") {
780            let Some(literal_name) = literal.attribute("name") else {
781                self.error(
782                    &trail,
783                    String::from("an <enumerator> needs a `name`"),
784                    "OMG DDS-XTypes 1.3 Annex A",
785                );
786                continue;
787            };
788            let literal_name = literal_name.to_owned();
789            // Absent, not zero: an enumerator without a value is
790            // positional, and `Ast::effective_enumerator_value` applies
791            // the rule. Writing a value in would erase the difference
792            // between `value="0"` and silence.
793            let value = match literal.attribute("value") {
794                // §7.3.2.7.1: "The syntax of the value shall be defined by
795                // the <const_exp> production in the IDL grammar". A value
796                // that does not parse must not leave the enumerator
797                // looking positional: `value="7"` silently becoming
798                // "previous plus one" changes the ordinal on the wire.
799                Some(written) => {
800                    match self.const_exp(written, scope, &trail, "OMG DDS-XTypes 1.3 §7.3.2.7.1") {
801                        Some(expr) => Some(expr),
802                        None => continue,
803                    }
804                }
805                None => None,
806            };
807            let Ok(enumerator) = self.ast.add_enumerator(id, &literal_name, value) else {
808                self.error(
809                    &trail,
810                    format!("the model refused enumerator `{literal_name}`"),
811                    "zerodds-ast-1.0 §3.3",
812                );
813                continue;
814            };
815            if truthy(literal.attribute("defaultLiteral"))
816                || truthy(literal.attribute("default_literal"))
817            {
818                if let Ok(node_id) = self.ast.enumerator(enumerator).map(|node| node.node_id) {
819                    if let Some(facets) =
820                        self.ast.facets_mut(AnyId::Enumerator(enumerator, node_id))
821                    {
822                        facets.xtypes_mut().is_default_literal = true;
823                    }
824                }
825            }
826        }
827    }
828
829    fn bitmask(&mut self, el: &XmlElement, scope: &Scope) {
830        let Some(name) = self.named(el, scope) else {
831            return;
832        };
833        let trail = format!("{}/bitmask[{name}]", scope.trail);
834        let kind = TypeKind::Bitmask { bits: Vec::new() };
835        let Some(id) = self.declare_now(&name, scope, kind, el, &trail) else {
836            return;
837        };
838        // The OMG suite writes `<flag name="…" value="…"/>`; DDS-XML's
839        // own Types building block and `zerodds-xml`'s parser write
840        // `<bit_value name="…" position="…"/>`. Both spellings name the
841        // same construct, and a loader that took only one would read
842        // half the corpus.
843        let bits: Vec<&XmlElement> = el
844            .children
845            .iter()
846            .filter(|c| c.name == "flag" || c.name == "bit_value" || c.name == "bit")
847            .collect();
848        for bit in bits {
849            let Some(bit_name) = bit.attribute("name") else {
850                self.error(
851                    &trail,
852                    format!("a <{}> needs a `name`", bit.name),
853                    "OMG DDS-XTypes 1.3 Annex A",
854                );
855                continue;
856            };
857            let bit_name = bit_name.to_owned();
858            let written = bit.attribute("position").or_else(|| bit.attribute("value"));
859            let position = match written {
860                Some(written) => match integer(written).and_then(|v| u16::try_from(v).ok()) {
861                    Some(position) => Some(position),
862                    None => {
863                        self.error(
864                            &trail,
865                            format!("`{written}` is not a bit position"),
866                            "OMG DDS-XTypes 1.3 §7.3.1.2.1.9",
867                        );
868                        continue;
869                    }
870                },
871                None => None,
872            };
873            if self.ast.add_bit_value(id, &bit_name, position).is_err() {
874                self.error(
875                    &trail,
876                    format!("the model refused bit `{bit_name}`"),
877                    "zerodds-ast-1.0 §3.3",
878                );
879            }
880        }
881    }
882
883    fn alias(&mut self, el: &XmlElement, scope: &Scope, pass: Pass) {
884        let Some(name) = self.named(el, scope) else {
885            return;
886        };
887        let trail = format!("{}/typedef[{name}]", scope.trail);
888        if pass == Pass::Declare {
889            let placeholder = TypeKind::Alias {
890                aliased: Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(Vec::new()))),
891            };
892            self.declare(&name, scope, placeholder, el, &trail);
893            return;
894        }
895        let Some(id) = self.declared_here(&name, scope) else {
896            return;
897        };
898        let aliased = self.type_reference(
899            el,
900            scope,
901            &trail,
902            "type",
903            "nonBasicTypeName",
904            "stringMaxLength",
905        );
906        let aliased = self.collections_around(el, aliased, scope, &trail);
907        if self
908            .ast
909            .complete_type(id, TypeKind::Alias { aliased })
910            .is_err()
911        {
912            self.error(
913                &trail,
914                format!("`{name}` is declared twice"),
915                "OMG IDL 4.2 §7.2.3",
916            );
917        }
918    }
919
920    /// `<forward_dcl name="T" kind="STRUCT"/>` — a name with no body.
921    ///
922    /// The node stays `Completeness::Forward` until a `<struct>` or
923    /// `<union>` of the same name completes it, which is what makes two
924    /// mutually recursive types expressible.
925    fn forward_declaration(&mut self, el: &XmlElement, scope: &Scope) {
926        let Some(name) = self.named(el, scope) else {
927            return;
928        };
929        let trail = format!("{}/forward_dcl[{name}]", scope.trail);
930        let kind = match el.attribute("kind").unwrap_or("STRUCT") {
931            "UNION" | "union" => TypeKind::Union {
932                discriminator: Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(
933                    Vec::new(),
934                ))),
935                cases: Vec::new(),
936                default_case: None,
937            },
938            _ => TypeKind::Structure {
939                base: None,
940                members: Vec::new(),
941            },
942        };
943        self.declare(&name, scope, kind, el, &trail);
944    }
945
946    // -- members ---------------------------------------------------------
947
948    /// A `<member>` of a struct or a union case.
949    fn member_spec(&mut self, el: &XmlElement, scope: &Scope, trail: &str) -> Option<MemberSpec> {
950        let name = match el.attribute("name") {
951            Some(name) => name.to_owned(),
952            None => {
953                self.error(
954                    trail,
955                    String::from("a <member> needs a `name`"),
956                    "OMG DDS-XTypes 1.3 Annex A",
957                );
958                return None;
959            }
960        };
961        let trail = format!("{trail}/member[{name}]");
962        let element = self.type_reference(
963            el,
964            scope,
965            &trail,
966            "type",
967            "nonBasicTypeName",
968            "stringMaxLength",
969        );
970        let type_ = self.collections_around(el, element, scope, &trail);
971
972        // DDS-XTypes 1.3 §7.3.2.4: "The element_external property of the
973        // Type System Model shall be represented by an attribute
974        // external." For a member that is a collection — a sequence, a
975        // map or an array — the attribute is the elements', as
976        // `sequence<@external Foo>` is (§7.3.1.2.1.6); for any other
977        // member it is the member's.
978        let collection = el.attribute("sequenceMaxLength").is_some()
979            || el.attribute("mapKeyType").is_some()
980            || el.attribute("arrayDimensions").is_some();
981        let external = truthy(el.attribute("external"));
982        let mut xtypes = XTypesFacet {
983            is_key: truthy(el.attribute("key")),
984            is_optional: truthy(el.attribute("optional")),
985            is_external: external && !collection,
986            element_is_external: external && collection,
987            is_must_understand: truthy(el.attribute("mustUnderstand"))
988                || truthy(el.attribute("must_understand")),
989            is_non_serialized: truthy(el.attribute("nonSerialized")),
990            ..XTypesFacet::default()
991        };
992        // Table 17's `ARRAY_TYPE` row knows no member-level TryConstruct
993        // behaviour — only the element's — so an array member's
994        // annotation goes to `element_try_construct`, exactly where the
995        // IDL front end puts it.
996        if let Some(behaviour) = self.try_construct(el, &trail) {
997            if el
998                .attribute("arrayDimensions")
999                .is_some_and(|written| !written.trim().is_empty())
1000            {
1001                xtypes.element_try_construct = Some(behaviour);
1002            } else {
1003                xtypes.try_construct = Some(behaviour);
1004            }
1005        }
1006        // `@id(n)` first, then `@hashid` — the same order §7.3.1.2.1.1
1007        // fixes. The number a hash produces is filled in by the
1008        // completion pass, so that both front ends compute it once, in
1009        // one place, from `zerodds_idl_lower::hashed_member_id`.
1010        if let Some(written) = el.attribute("id") {
1011            match integer(written).and_then(|v| u32::try_from(v).ok()) {
1012                Some(id) => {
1013                    xtypes.member_id = Some(MemberIdAssignment {
1014                        effective: id,
1015                        origin: MemberIdOrigin::Declared,
1016                    });
1017                }
1018                None => self.error(
1019                    &trail,
1020                    format!("`{written}` is not a member id"),
1021                    "DDS-XTypes 1.3 §7.3.1.2.1.1",
1022                ),
1023            }
1024        } else if let Some(written) = el.attribute("hashid") {
1025            // `hashid=""` and `hashid="true"` both mean "hash this
1026            // member's own name"; anything else is the name to hash,
1027            // which is `@hashid("x1")` in IDL.
1028            let source_name = if written.is_empty() || written.eq_ignore_ascii_case("true") {
1029                name.clone()
1030            } else {
1031                written.to_owned()
1032            };
1033            xtypes.member_id = Some(MemberIdAssignment {
1034                effective: 0,
1035                origin: MemberIdOrigin::Hashed { source_name },
1036            });
1037        }
1038
1039        // §7.3.2.5.1.2 and Annex A `memberDecl`: a member holds applied
1040        // annotations of its own.
1041        let applied = self.applied_annotations(el, scope, &trail);
1042        let mut facets = self.facets(el, Some(xtypes));
1043        facets.annotations.extend(applied);
1044        Some(MemberSpec {
1045            name,
1046            type_,
1047            facets,
1048        })
1049    }
1050
1051    // -- type references -------------------------------------------------
1052
1053    /// The element type an attribute triple names.
1054    fn type_reference(
1055        &mut self,
1056        el: &XmlElement,
1057        scope: &Scope,
1058        trail: &str,
1059        type_attribute: &str,
1060        nonbasic_attribute: &str,
1061        bound_attribute: &str,
1062    ) -> Ref<TypeNode> {
1063        let Some(written) = el.attribute(type_attribute) else {
1064            self.error(
1065                trail,
1066                format!("no `{type_attribute}` attribute to read a type from"),
1067                "OMG DDS-XTypes 1.3 Annex A",
1068            );
1069            return broken(type_attribute);
1070        };
1071        // DDS-XTypes 1.3 §7.3.2.2: "It is an error to include this attribute
1072        // if the type attribute does not indicate a non-basic type."
1073        if !matches!(written, "nonBasic" | "nonbasic") && el.attribute(nonbasic_attribute).is_some()
1074        {
1075            self.error(
1076                trail,
1077                format!(
1078                    "`{nonbasic_attribute}` on an element whose `{type_attribute}` is \
1079                     `{written}`, which is not `nonBasic`"
1080                ),
1081                "OMG DDS-XTypes 1.3 §7.3.2.2",
1082            );
1083            return broken(nonbasic_attribute);
1084        }
1085        match written {
1086            "nonBasic" | "nonbasic" => match el.attribute(nonbasic_attribute) {
1087                Some(name) => self.named_ref(name, scope, trail),
1088                None => {
1089                    self.error(
1090                        trail,
1091                        format!(
1092                            "`{type_attribute}=\"nonBasic\"` without a `{nonbasic_attribute}` \
1093                             names nothing"
1094                        ),
1095                        "OMG DDS-XTypes 1.3 Annex A",
1096                    );
1097                    broken(nonbasic_attribute)
1098                }
1099            },
1100            "string" | "wstring" => {
1101                let width = if written == "string" {
1102                    CharWidth::Char
1103                } else {
1104                    CharWidth::WChar
1105                };
1106                let bound = self.bound(el.attribute(bound_attribute), scope, trail);
1107                self.anonymous(TypeKind::String { width, bound })
1108            }
1109            other => match primitive_of(other) {
1110                Some(kind) => self.primitive_ref(kind),
1111                None => {
1112                    // A `type=` that is neither a primitive nor
1113                    // `nonBasic` is still a name in every DDS-XML
1114                    // dialect that writes one, and refusing it outright
1115                    // would lose a type that resolves perfectly well.
1116                    self.named_ref(other, scope, trail)
1117                }
1118            },
1119        }
1120    }
1121
1122    /// The sequence, array and map wrappers a member's attributes put
1123    /// around its element type.
1124    ///
1125    /// DDS-XTypes 1.3 §7.3.2.4.4 orders them "from most-tightly-binding
1126    /// to least-tightly-binding": the sequence designations, then the
1127    /// array designations, then the map designations. "A member
1128    /// specifying all of these would define a map whose values are
1129    /// arrays of sequences of strings", and "to indicate a type composed
1130    /// in a different order [...] it is necessary to interpose an alias
1131    /// definition". `stringMaxLength="10" arrayDimensions="10"` is
1132    /// `string<10> x1[10]`, an array of bounded strings, as the suite's
1133    /// own `arrays.idl` writes it.
1134    fn collections_around(
1135        &mut self,
1136        el: &XmlElement,
1137        element: Ref<TypeNode>,
1138        scope: &Scope,
1139        trail: &str,
1140    ) -> Ref<TypeNode> {
1141        let mut current = element;
1142        if let Some(written) = el.attribute("sequenceMaxLength") {
1143            let bound = self.bound(Some(written), scope, trail);
1144            current = self.anonymous(TypeKind::Sequence {
1145                element: current,
1146                bound,
1147            });
1148        }
1149        if let Some(written) = el.attribute("arrayDimensions") {
1150            match self.dimensions(written, scope, trail) {
1151                Some(dims) if !dims.is_empty() => {
1152                    current = self.anonymous(TypeKind::Array {
1153                        element: current,
1154                        dims,
1155                    });
1156                }
1157                Some(_) => {}
1158                None => return broken("arrayDimensions"),
1159            }
1160        }
1161        if let Some(key_written) = el.attribute("mapKeyType") {
1162            let key = self.type_reference(
1163                el,
1164                scope,
1165                trail,
1166                "mapKeyType",
1167                "mapKeyNonBasicTypeName",
1168                "mapKeyStringMaxLength",
1169            );
1170            // §7.3.2.4.3: "If the "key" type is basic, the
1171            // mapKeyNonBasicTypeName attribute is not allowed", and
1172            // mapKeyStringMaxLength only goes with a string key. A
1173            // non-basic key may be an alias of a string, so that one is
1174            // not judged here.
1175            let key_non_basic = matches!(key_written, "nonBasic" | "nonbasic");
1176            if !key_non_basic && el.attribute("mapKeyNonBasicTypeName").is_some() {
1177                self.error(
1178                    trail,
1179                    format!(
1180                        "`mapKeyNonBasicTypeName` is not allowed beside the basic \
1181                         `mapKeyType=\"{key_written}\"`"
1182                    ),
1183                    "OMG DDS-XTypes 1.3 §7.3.2.4.3",
1184                );
1185            }
1186            if !key_non_basic
1187                && !matches!(key_written, "string" | "wstring")
1188                && el.attribute("mapKeyStringMaxLength").is_some()
1189            {
1190                self.error(
1191                    trail,
1192                    format!(
1193                        "`mapKeyStringMaxLength` is not allowed beside the key type \
1194                         `{key_written}`, which is no string"
1195                    ),
1196                    "OMG DDS-XTypes 1.3 §7.3.2.4.3",
1197                );
1198            }
1199            // §7.3.2.4.3: "This attribute is required for all map types."
1200            if el.attribute("mapMaxLength").is_none() {
1201                self.error(
1202                    trail,
1203                    String::from("a map member needs `mapMaxLength`; \"-1\" is unbounded"),
1204                    "OMG DDS-XTypes 1.3 §7.3.2.4.3",
1205                );
1206            }
1207            let bound = self.bound(el.attribute("mapMaxLength"), scope, trail);
1208            current = self.anonymous(TypeKind::Map {
1209                key,
1210                value: current,
1211                bound,
1212            });
1213        } else if el.attribute("mapMaxLength").is_some() {
1214            self.error(
1215                trail,
1216                String::from("`mapMaxLength` without `mapKeyType` names no key type"),
1217                "OMG DDS-XTypes 1.3 §7.3.2.4.3",
1218            );
1219        }
1220        current
1221    }
1222
1223    /// `arrayDimensions`: a comma-delimited list of `<const_exp>`,
1224    /// "in the same order in which those bounds would be given in IDL"
1225    /// (§7.3.2.4.1). `None` when the list does not parse or a dimension
1226    /// is not a positive integer; the diagnostic is given here.
1227    fn dimensions(
1228        &mut self,
1229        written: &str,
1230        scope: &Scope,
1231        trail: &str,
1232    ) -> Option<Vec<Id<ConstExprNode>>> {
1233        let parsed = match expr::parse_list(written) {
1234            Ok(parsed) => parsed,
1235            Err(why) => {
1236                self.error(
1237                    trail,
1238                    format!("`arrayDimensions=\"{written}\"` is not a list of bounds: {why}"),
1239                    "OMG DDS-XTypes 1.3 §7.3.2.4.1",
1240                );
1241                return None;
1242            }
1243        };
1244        let mut out = Vec::with_capacity(parsed.len());
1245        for one in &parsed {
1246            let id = self.expression(one, scope, trail)?;
1247            match self.ast.eval_integral(id) {
1248                Ok(value) if value <= 0 => {
1249                    self.error(
1250                        trail,
1251                        format!("an array dimension of {value} is no size"),
1252                        "OMG DDS-XTypes 1.3 §7.3.2.4.1",
1253                    );
1254                    return None;
1255                }
1256                Ok(_) => {}
1257                // A constant declared further on: the model evaluates the
1258                // expression when it needs the number.
1259                Err(EvalError::Unresolved { .. }) => {}
1260                Err(why) => {
1261                    self.error(
1262                        trail,
1263                        format!("an array dimension cannot be evaluated: {why:?}"),
1264                        "OMG DDS-XTypes 1.3 §7.3.2.4.1",
1265                    );
1266                    return None;
1267                }
1268            }
1269            out.push(id);
1270        }
1271        Some(out)
1272    }
1273
1274    /// A named type, resolved against the names the declare pass
1275    /// recorded, innermost enclosing scope first.
1276    fn named_ref(&mut self, written: &str, scope: &Scope, trail: &str) -> Ref<TypeNode> {
1277        let absolute = written.starts_with("::");
1278        let segments: Vec<String> = written
1279            .split("::")
1280            .filter(|s| !s.is_empty())
1281            .map(str::to_owned)
1282            .collect();
1283        if segments.is_empty() {
1284            self.error(
1285                trail,
1286                String::from("an empty type name names nothing"),
1287                "OMG IDL 4.2 §7.5",
1288            );
1289            return broken("");
1290        }
1291        if absolute {
1292            if let Some(id) = self.declared.get(&segments) {
1293                return Ref::Resolved(*id);
1294            }
1295            return Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(segments)));
1296        }
1297        for depth in (0..=scope.path.len()).rev() {
1298            let mut candidate: Vec<String> = scope.path.iter().take(depth).cloned().collect();
1299            candidate.extend(segments.iter().cloned());
1300            if let Some(id) = self.declared.get(&candidate) {
1301                return Ref::Resolved(*id);
1302            }
1303        }
1304        // Not declared in this bundle. Left pending rather than broken:
1305        // `Ast::resolve_all_by_name` gets its own attempt against the
1306        // model's name index, and only then is it reported.
1307        Ref::Pending(PendingRef::ScopedName(ScopedName::relative(segments)))
1308    }
1309
1310    fn primitive_ref(&mut self, kind: PrimitiveKind) -> Ref<TypeNode> {
1311        if let Some(id) = self.builtin.get(&kind) {
1312            return Ref::Resolved(*id);
1313        }
1314        let root = self.ast.root_module();
1315        match self
1316            .ast
1317            .add_type(root, kind.as_str(), TypeKind::Primitive(kind))
1318        {
1319            Ok(id) => {
1320                self.builtin.insert(kind, id);
1321                Ref::Resolved(id)
1322            }
1323            Err(_) => broken(kind.as_str()),
1324        }
1325    }
1326
1327    /// An unnamed type, which a collection attribute introduces at the
1328    /// use site.
1329    fn anonymous(&mut self, kind: TypeKind) -> Ref<TypeNode> {
1330        let root = self.ast.root_module();
1331        let what = kind.as_str();
1332        self.ast
1333            .add_type(root, "", kind)
1334            .map_or_else(|_| broken(what), Ref::Resolved)
1335    }
1336
1337    /// A collection bound.
1338    ///
1339    /// DDS-XTypes 1.3 §7.3.2.4: the value "may be any constant
1340    /// expression as defined by the <const_exp> production in the IDL
1341    /// grammar [IDL]. The literal expression "-1" shall indicate an
1342    /// unbounded collection; no other "negative" value is permitted."
1343    /// `0` stays unbounded as well — DDS-XML 1.0 §7.2.2 and the suite's
1344    /// own `sequences.xml`, which comments `-1` as "unlimited (0 or -1)".
1345    ///
1346    /// An expression that names a constant declared further on is kept
1347    /// as written and evaluated by whoever needs the number.
1348    fn bound(&mut self, written: Option<&str>, scope: &Scope, trail: &str) -> Bound {
1349        let Some(written) = written else {
1350            return Bound::Unbounded;
1351        };
1352        let Some(id) = self.const_exp(written, scope, trail, "OMG DDS-XTypes 1.3 §7.3.2.4") else {
1353            return Bound::Unbounded;
1354        };
1355        match self.ast.eval_integral(id) {
1356            Ok(value) if value < -1 => {
1357                self.error(
1358                    trail,
1359                    format!("`{written}` is a negative bound other than -1"),
1360                    "OMG DDS-XTypes 1.3 §7.3.2.4",
1361                );
1362                Bound::Unbounded
1363            }
1364            Ok(-1) => Bound::Unbounded,
1365            Ok(0) if self.options.zero_bound_is_unbounded => Bound::Unbounded,
1366            Ok(0) => {
1367                self.error(
1368                    trail,
1369                    format!(
1370                        "`{written}` is a bound of zero; \"-1\" is unbounded, and a bound is \
1371                         a positive integer"
1372                    ),
1373                    "OMG DDS-XTypes 1.3 §7.3.2.4",
1374                );
1375                Bound::Unbounded
1376            }
1377            Ok(_) | Err(EvalError::Unresolved { .. }) => Bound::Const(id),
1378            Err(why) => {
1379                self.error(
1380                    trail,
1381                    format!("`{written}` cannot be evaluated as a bound: {why:?}"),
1382                    "OMG DDS-XTypes 1.3 §7.3.2.4",
1383                );
1384                Bound::Unbounded
1385            }
1386        }
1387    }
1388
1389    fn try_construct(&mut self, el: &XmlElement, trail: &str) -> Option<TryConstruct> {
1390        let written = el
1391            .attribute("tryConstruct")
1392            .or_else(|| el.attribute("try_construct"))?;
1393        match written.to_ascii_lowercase().as_str() {
1394            "trim" => Some(TryConstruct::Trim),
1395            "discard" => Some(TryConstruct::Discard),
1396            "use_default" | "usedefault" | "default" => Some(TryConstruct::UseDefault),
1397            other => {
1398                self.error(
1399                    trail,
1400                    format!("`{other}` is not a try-construct behaviour"),
1401                    "DDS-XTypes 1.3 §7.3.1.2.1.12",
1402                );
1403                None
1404            }
1405        }
1406    }
1407
1408    /// One `<caseDiscriminator value="…">`.
1409    fn case_label(
1410        &mut self,
1411        written: &str,
1412        discriminator: Option<Id<TypeNode>>,
1413        trail: &str,
1414    ) -> Option<CaseLabel> {
1415        if written.eq_ignore_ascii_case("default") {
1416            return Some(CaseLabel::Default);
1417        }
1418        // An enumerator name, when the discriminator is an enumeration.
1419        // Tried before the numeric reading so that an enumerator called
1420        // `1` — which no file writes — could still only be a number.
1421        if let Some(discriminator) = discriminator {
1422            let enumerators = self.ast.enumerators_of(discriminator).to_vec();
1423            for enumerator in enumerators {
1424                if self
1425                    .ast
1426                    .enumerator(enumerator)
1427                    .is_ok_and(|node| node.name == written)
1428                {
1429                    return match self.ast.add_expr(ConstExpr::Literal(Literal::Enumerator(
1430                        Ref::Resolved(enumerator),
1431                    ))) {
1432                        Ok(expr) => Some(CaseLabel::Value(expr)),
1433                        Err(_) => {
1434                            self.error(
1435                                trail,
1436                                format!("the model refused the case label `{written}`"),
1437                                "zerodds-ast-1.0 §3.5",
1438                            );
1439                            None
1440                        }
1441                    };
1442                }
1443            }
1444        }
1445        if let Some(value) = integer(written) {
1446            return match self.ast.add_int_expr(value) {
1447                Ok(expr) => Some(CaseLabel::Value(expr)),
1448                Err(_) => {
1449                    self.error(
1450                        trail,
1451                        format!("the model refused the case label `{written}`"),
1452                        "zerodds-ast-1.0 §3.5",
1453                    );
1454                    None
1455                }
1456            };
1457        }
1458        let literal = match written {
1459            "TRUE" | "true" => Some(Literal::Boolean(true)),
1460            "FALSE" | "false" => Some(Literal::Boolean(false)),
1461            _ => None,
1462        };
1463        if let Some(literal) = literal {
1464            return match self.ast.add_expr(ConstExpr::Literal(literal)) {
1465                Ok(expr) => Some(CaseLabel::Value(expr)),
1466                Err(_) => {
1467                    self.error(
1468                        trail,
1469                        format!("the model refused the case label `{written}`"),
1470                        "zerodds-ast-1.0 §3.5",
1471                    );
1472                    None
1473                }
1474            };
1475        }
1476        self.error(
1477            trail,
1478            format!("`{written}` is not a case label the discriminator can take"),
1479            "OMG IDL 4.2 §7.4.1.4.4.3",
1480        );
1481        None
1482    }
1483
1484    // -- model plumbing --------------------------------------------------
1485
1486    fn named(&mut self, el: &XmlElement, scope: &Scope) -> Option<String> {
1487        match el.attribute("name") {
1488            Some(name) if !name.is_empty() => Some(name.to_owned()),
1489            _ => {
1490                let what = el.name.clone();
1491                self.error(
1492                    &scope.trail,
1493                    format!("<{what}> needs a `name`"),
1494                    "OMG DDS-XML 1.0 §7.1.4",
1495                );
1496                None
1497            }
1498        }
1499    }
1500
1501    /// Declare a type forward, to be completed in the second pass.
1502    fn declare(&mut self, name: &str, scope: &Scope, kind: TypeKind, el: &XmlElement, trail: &str) {
1503        let mut path = scope.path.clone();
1504        path.push(name.to_owned());
1505        if self.declared.contains_key(&path) {
1506            // A `<forward_dcl>` followed by the definition is the
1507            // ordinary case and not a duplicate: the second pass
1508            // completes the node the first one made.
1509            return;
1510        }
1511        match self.ast.add_forward_type(scope.module, name, kind) {
1512            Ok(id) => {
1513                self.declared.insert(path, id);
1514                self.record(id, el, trail);
1515            }
1516            Err(_) => self.error(
1517                trail,
1518                format!("the model refused a type named `{name}`"),
1519                "zerodds-ast-1.0 §3.2",
1520            ),
1521        }
1522    }
1523
1524    /// Declare a type that is complete as soon as it is created — an
1525    /// enumeration or a bitmask, neither of which names another type.
1526    fn declare_now(
1527        &mut self,
1528        name: &str,
1529        scope: &Scope,
1530        kind: TypeKind,
1531        el: &XmlElement,
1532        trail: &str,
1533    ) -> Option<Id<TypeNode>> {
1534        let mut path = scope.path.clone();
1535        path.push(name.to_owned());
1536        match self.ast.add_type(scope.module, name, kind) {
1537            Ok(id) => {
1538                self.declared.insert(path, id);
1539                self.record(id, el, trail);
1540                Some(id)
1541            }
1542            Err(_) => {
1543                self.error(
1544                    trail,
1545                    format!("the model refused a type named `{name}`"),
1546                    "zerodds-ast-1.0 §3.2",
1547                );
1548                None
1549            }
1550        }
1551    }
1552
1553    fn declared_here(&mut self, name: &str, scope: &Scope) -> Option<Id<TypeNode>> {
1554        let mut path = scope.path.clone();
1555        path.push(name.to_owned());
1556        self.declared.get(&path).copied()
1557    }
1558
1559    /// Everything a type node carries from its element: provenance, the
1560    /// attributes as written, and the XTypes properties an aggregate or
1561    /// an enumerated type declares on itself.
1562    fn record(&mut self, id: Id<TypeNode>, el: &XmlElement, trail: &str) {
1563        let Ok(node_id) = self.ast.type_(id).map(|node| node.node_id) else {
1564            return;
1565        };
1566        let any = AnyId::Type(id, node_id);
1567        // Each of the three below is a value whose *absence* has a
1568        // specified meaning — appendable, bit bound 32, sequential ids.
1569        // A spelling this loader cannot read is therefore reported and
1570        // not folded into the absent case: `extensibility="fnial"`
1571        // quietly producing an appendable type is a wire format nobody
1572        // asked for and nothing names.
1573        let mut xtypes = XTypesFacet {
1574            is_nested: truthy(el.attribute("nested")),
1575            nested_explicit: el.attribute("nested").is_some(),
1576            ignore_literal_names: truthy(el.attribute("ignoreLiteralNames")),
1577            ..XTypesFacet::default()
1578        };
1579        if let Some(written) = el.attribute("extensibility") {
1580            match extensibility_of(written) {
1581                Some(extensibility) => xtypes.extensibility = Some(extensibility),
1582                None => self.error(
1583                    trail,
1584                    format!("`{written}` is not final, appendable or mutable"),
1585                    "DDS-XTypes 1.3 §7.3.1.2.1.8",
1586                ),
1587            }
1588        }
1589        if let Some(written) = el
1590            .attribute("bitBound")
1591            .or_else(|| el.attribute("bit_bound"))
1592        {
1593            match integer(written).and_then(|v| u16::try_from(v).ok()) {
1594                Some(bound) => xtypes.bit_bound = Some(bound),
1595                None => self.error(
1596                    trail,
1597                    format!("`{written}` is not a bit bound"),
1598                    "DDS-XTypes 1.3 §7.3.1.2.1.9",
1599                ),
1600            }
1601        }
1602        xtypes.autoid = self.autoid_of(el, trail);
1603        let facets = self.facets(el, Some(xtypes));
1604        if let Some(slot) = self.ast.facets_mut(any) {
1605            *slot = facets;
1606        }
1607    }
1608
1609    /// The `autoid` of a module, a structure or a union (Annex A `autoIdKind`).
1610    fn autoid_of(&mut self, el: &XmlElement, trail: &str) -> Option<AutoId> {
1611        let written = el.attribute("autoid")?;
1612        match written.to_ascii_lowercase().as_str() {
1613            "hash" => Some(AutoId::Hash),
1614            // Annex A spells the enumeration `sequencial` (S. 265);
1615            // IDL and the rest of the specification spell it
1616            // `sequential`. Both are read.
1617            "sequential" | "sequencial" => Some(AutoId::Sequential),
1618            other => {
1619                self.error(
1620                    trail,
1621                    format!("`{other}` is not HASH or SEQUENTIAL"),
1622                    "DDS-XTypes 1.3 §7.3.1.2.1.1",
1623                );
1624                None
1625            }
1626        }
1627    }
1628
1629    fn facets(&self, el: &XmlElement, xtypes: Option<XTypesFacet>) -> Facets {
1630        Facets {
1631            source: SourceFacet::file(self.unit.clone(), None),
1632            xtypes,
1633            xml: Some(XmlFacet {
1634                element: el.name.clone(),
1635                attributes: el
1636                    .attributes
1637                    .iter()
1638                    .map(|(k, v)| (k.clone(), v.clone()))
1639                    .collect(),
1640                ..XmlFacet::default()
1641            }),
1642            ..Facets::default()
1643        }
1644    }
1645
1646    fn set_xml_facet(&mut self, any: AnyId, el: &XmlElement) {
1647        let facets = self.facets(el, None);
1648        if let Some(slot) = self.ast.facets_mut(any) {
1649            *slot = facets;
1650        }
1651    }
1652
1653    fn set_type_xtypes(&mut self, id: Id<TypeNode>, apply: impl FnOnce(&mut XTypesFacet)) {
1654        let Ok(node_id) = self.ast.type_(id).map(|node| node.node_id) else {
1655            return;
1656        };
1657        if let Some(facets) = self.ast.facets_mut(AnyId::Type(id, node_id)) {
1658            apply(facets.xtypes_mut());
1659        }
1660    }
1661
1662    // -- after the walk --------------------------------------------------
1663
1664    /// Whatever the declare pass could not bind, against the model's own
1665    /// name index. A name that still does not resolve is an error.
1666    fn resolve(&mut self) {
1667        let Ok(report) = self.ast.resolve_all_by_name() else {
1668            self.error(
1669                &self.unit.clone(),
1670                String::from("the model could not run a resolve pass"),
1671                "OMG IDL 4.2 §7.5",
1672            );
1673            return;
1674        };
1675        let broken: Vec<String> = report
1676            .broken
1677            .iter()
1678            .map(|(node, cause)| format!("{cause:?} (node {node:?})"))
1679            .collect();
1680        for one in broken {
1681            self.error(
1682                &self.unit.clone(),
1683                format!("a name does not resolve: {one}"),
1684                "OMG IDL 4.2 §7.5",
1685            );
1686        }
1687        let pending = self.ast.pending_count();
1688        if pending > 0 {
1689            self.error(
1690                &self.unit.clone(),
1691                format!("{pending} type reference(s) never resolved"),
1692                "OMG IDL 4.2 §7.5",
1693            );
1694        }
1695    }
1696
1697    /// What the document left implicit.
1698    ///
1699    /// The same two rules `zerodds_idl_lower::complete` applies, in the
1700    /// same order and over the same facets: the caller's default
1701    /// extensibility where the document said nothing, then member ids
1702    /// base-first.
1703    fn complete(&mut self) {
1704        let types: Vec<Id<TypeNode>> = self.ast.types().map(|(id, _)| id).collect();
1705        for id in self.base_first(&types) {
1706            self.complete_one(id);
1707        }
1708    }
1709
1710    fn complete_one(&mut self, id: Id<TypeNode>) {
1711        let Ok(node) = self.ast.type_(id) else {
1712            return;
1713        };
1714        let node_id = node.node_id;
1715        let any = AnyId::Type(id, node_id);
1716        let is_aggregate = matches!(
1717            node.kind,
1718            TypeKind::Structure { .. } | TypeKind::Union { .. } | TypeKind::Valuetype { .. }
1719        );
1720        if !is_aggregate {
1721            return;
1722        }
1723        // The caller's default fills silence only. What the document
1724        // said stays what it said, so "appendable because the file says
1725        // so" and "appendable because nobody said otherwise" remain two
1726        // different facts. A structure with a base is left out: silent, it
1727        // has its base's extensibility (DDS-XTypes 1.3 §7.2.2.4.5), which
1728        // `Ast::effective_extensibility` follows.
1729        let derives = matches!(node.kind, TypeKind::Structure { base: Some(_), .. });
1730        if let (Some(extensibility), false) = (self.options.default_extensibility, derives) {
1731            let declared = self
1732                .ast
1733                .facets(any)
1734                .and_then(|facets| facets.xtypes.as_ref())
1735                .and_then(|x| x.extensibility);
1736            if declared.is_none() {
1737                if let Some(facets) = self.ast.facets_mut(any) {
1738                    facets.xtypes_mut().extensibility = Some(extensibility);
1739                }
1740            }
1741        }
1742        self.member_ids(id);
1743    }
1744
1745    /// Member ids: an `id` attribute, else a `hashid`, else the
1746    /// aggregate's `autoid`, else the previous member plus one, starting
1747    /// at zero — or, for a derived type, at the base's last id plus one.
1748    /// DDS-XTypes 1.3 §7.3.1.2.1.1.
1749    fn member_ids(&mut self, id: Id<TypeNode>) {
1750        let autoid = self.ast.effective_autoid(id);
1751        let members: Vec<_> = self.ast.members_of(id).to_vec();
1752        let mut previous: Option<u32> = self.last_base_member_id(id);
1753        for member in members {
1754            let Ok(node) = self.ast.member(member) else {
1755                continue;
1756            };
1757            let member_any = AnyId::Member(member, node.node_id);
1758            let name = node.name.clone();
1759            let declared = self
1760                .ast
1761                .facets(member_any)
1762                .and_then(|facets| facets.xtypes.as_ref())
1763                .and_then(|x| x.member_id.clone());
1764            let assignment = match declared {
1765                Some(assignment) if assignment.origin == MemberIdOrigin::Declared => assignment,
1766                Some(MemberIdAssignment {
1767                    origin: MemberIdOrigin::Hashed { source_name },
1768                    ..
1769                }) => MemberIdAssignment {
1770                    effective: zerodds_idl_lower::hashed_member_id(&source_name),
1771                    origin: MemberIdOrigin::Hashed { source_name },
1772                },
1773                _ => match autoid {
1774                    Some(AutoId::Hash) => MemberIdAssignment {
1775                        effective: zerodds_idl_lower::hashed_member_id(&name),
1776                        origin: MemberIdOrigin::AutoHash,
1777                    },
1778                    Some(AutoId::Sequential) => MemberIdAssignment {
1779                        effective: next_id(previous),
1780                        origin: MemberIdOrigin::AutoSequential,
1781                    },
1782                    None => MemberIdAssignment {
1783                        effective: next_id(previous),
1784                        origin: MemberIdOrigin::PreviousPlusOne,
1785                    },
1786                },
1787            };
1788            let hashed = matches!(
1789                assignment.origin,
1790                MemberIdOrigin::Hashed { .. } | MemberIdOrigin::AutoHash
1791            );
1792            if !hashed
1793                || self.options.hashed_id_advances_the_counter
1794                    == zerodds_idl_lower::HashedIdCounter::Advances
1795            {
1796                previous = Some(assignment.effective);
1797            }
1798            if let Some(facets) = self.ast.facets_mut(member_any) {
1799                facets.xtypes_mut().member_id = Some(assignment);
1800            }
1801        }
1802    }
1803
1804    fn last_base_member_id(&self, id: Id<TypeNode>) -> Option<u32> {
1805        let base = self.ast.base_of(id)?;
1806        let last = *self.ast.all_members_of(base).last()?;
1807        let node_id = self.ast.member(last).ok()?.node_id;
1808        self.ast
1809            .facets(AnyId::Member(last, node_id))
1810            .and_then(|facets| facets.xtypes.as_ref())
1811            .and_then(|x| x.member_id.as_ref())
1812            .map(|assignment| assignment.effective)
1813    }
1814
1815    /// The types, every base ahead of what derives from it: sequential
1816    /// member ids continue across an inheritance boundary.
1817    fn base_first(&self, types: &[Id<TypeNode>]) -> Vec<Id<TypeNode>> {
1818        let mut out: Vec<Id<TypeNode>> = Vec::with_capacity(types.len());
1819        for id in types.iter().copied() {
1820            self.push_base_first(id, &mut out, 0);
1821        }
1822        out
1823    }
1824
1825    fn push_base_first(&self, id: Id<TypeNode>, out: &mut Vec<Id<TypeNode>>, depth: usize) {
1826        if depth > MAX_INHERITANCE_DEPTH || out.contains(&id) {
1827            return;
1828        }
1829        if let Some(base) = self.ast.base_of(id) {
1830            self.push_base_first(base, out, depth + 1);
1831        }
1832        out.push(id);
1833    }
1834
1835    fn warning(&mut self, at: &str, message: String, spec_reference: &'static str) {
1836        self.diagnostics.push(Diagnostic {
1837            severity: Severity::Warning,
1838            message,
1839            at: at.to_owned(),
1840            spec_reference,
1841        });
1842    }
1843
1844    // -- expressions -----------------------------------------------------
1845
1846    /// Parse a `<const_exp>` attribute and put it into the model.
1847    fn const_exp(
1848        &mut self,
1849        written: &str,
1850        scope: &Scope,
1851        trail: &str,
1852        spec_reference: &'static str,
1853    ) -> Option<Id<ConstExprNode>> {
1854        match expr::parse(written) {
1855            Ok(parsed) => self.expression(&parsed, scope, trail),
1856            Err(why) => {
1857                self.error(
1858                    trail,
1859                    format!("`{written}` is not a <const_exp>: {why}"),
1860                    spec_reference,
1861                );
1862                None
1863            }
1864        }
1865    }
1866
1867    /// One parsed expression as model nodes. A name is resolved against
1868    /// the scopes of the document, innermost first (IDL 4.2 §7.5); one
1869    /// that names nothing yet stays a pending reference, which the
1870    /// resolve pass reports when it is still unresolved.
1871    fn expression(
1872        &mut self,
1873        parsed: &Expression,
1874        scope: &Scope,
1875        trail: &str,
1876    ) -> Option<Id<ConstExprNode>> {
1877        let node = match parsed {
1878            Expression::Integer(value) => ConstExpr::Literal(Literal::Integer(*value)),
1879            Expression::Float { mantissa, exponent } => ConstExpr::Literal(Literal::Float {
1880                mantissa: mantissa.clone(),
1881                exponent: exponent.clone(),
1882            }),
1883            Expression::String(text) => ConstExpr::Literal(Literal::String(text.clone())),
1884            Expression::Char(c) => ConstExpr::Literal(Literal::Char(*c)),
1885            Expression::Boolean(value) => ConstExpr::Literal(Literal::Boolean(*value)),
1886            Expression::Name { absolute, segments } => {
1887                match self.lookup(*absolute, segments, scope, |any| {
1888                    matches!(any, AnyId::Const(..) | AnyId::Enumerator(..))
1889                }) {
1890                    Some(AnyId::Const(id, _)) => ConstExpr::NameRef(Ref::Resolved(id)),
1891                    Some(AnyId::Enumerator(id, _)) => {
1892                        ConstExpr::Literal(Literal::Enumerator(Ref::Resolved(id)))
1893                    }
1894                    _ => ConstExpr::NameRef(Ref::Pending(PendingRef::ScopedName(scoped(
1895                        *absolute, segments,
1896                    )))),
1897                }
1898            }
1899            Expression::Unary(op, inner) => ConstExpr::Unary {
1900                op: *op,
1901                operand: self.expression(inner, scope, trail)?,
1902            },
1903            Expression::Binary(op, left, right) => {
1904                let lhs = self.expression(left, scope, trail)?;
1905                let rhs = self.expression(right, scope, trail)?;
1906                ConstExpr::Binary { op: *op, lhs, rhs }
1907            }
1908        };
1909        match self.ast.add_expr(node) {
1910            Ok(id) => Some(id),
1911            Err(_) => {
1912                self.error(
1913                    trail,
1914                    String::from("the model refused an expression"),
1915                    "zerodds-ast-1.0 §3.5",
1916                );
1917                None
1918            }
1919        }
1920    }
1921
1922    /// The node a written name denotes: the name is tried from the
1923    /// innermost enclosing scope outwards, or from the root when it
1924    /// starts with `::`.
1925    fn lookup(
1926        &self,
1927        absolute: bool,
1928        segments: &[String],
1929        scope: &Scope,
1930        wanted: impl Fn(&AnyId) -> bool,
1931    ) -> Option<AnyId> {
1932        let depths: Vec<usize> = if absolute {
1933            vec![0]
1934        } else {
1935            (0..=scope.path.len()).rev().collect()
1936        };
1937        for depth in depths {
1938            let mut candidate: Vec<String> = scope.path.iter().take(depth).cloned().collect();
1939            candidate.extend(segments.iter().cloned());
1940            if let Ok(any) = self
1941                .ast
1942                .find_by_scoped_name(&ScopedName::absolute(candidate))
1943            {
1944                if wanted(&any) {
1945                    return Some(any);
1946                }
1947            }
1948        }
1949        None
1950    }
1951
1952    // -- constants, bitsets, annotations ---------------------------------
1953
1954    /// `<const name type value>`; DDS-XTypes 1.3 §7.3.2.1.3. "Constants
1955    /// are not reflected directly in the Type System" — the model holds
1956    /// the constant and the expressions that name it, and the type
1957    /// identifiers see only the evaluated numbers.
1958    ///
1959    /// `within` is the structure the constant is declared in, which is a
1960    /// segment of its scoped name.
1961    fn constant(&mut self, el: &XmlElement, scope: &Scope, within: Option<&str>) {
1962        let Some(name) = self.named(el, scope) else {
1963            return;
1964        };
1965        let trail = format!("{}/const[{name}]", scope.trail);
1966        let type_ = self.type_reference(
1967            el,
1968            scope,
1969            &trail,
1970            "type",
1971            "nonBasicTypeName",
1972            "stringMaxLength",
1973        );
1974        let Some(written) = el.attribute("value") else {
1975            self.error(
1976                &trail,
1977                String::from("a <const> needs a `value`"),
1978                "OMG DDS-XTypes 1.3 Annex A",
1979            );
1980            return;
1981        };
1982        let Some(expression) =
1983            self.const_exp(written, scope, &trail, "OMG DDS-XTypes 1.3 §7.3.2.1.3")
1984        else {
1985            return;
1986        };
1987        let Ok(id) = self.ast.add_const(scope.module, &name, type_, expression) else {
1988            self.error(
1989                &trail,
1990                format!("the model refused a constant named `{name}`"),
1991                "zerodds-ast-1.0 §3.5",
1992            );
1993            return;
1994        };
1995        let Ok(node_id) = self.ast.constant(id).map(|c| c.node_id) else {
1996            return;
1997        };
1998        let any = AnyId::Const(id, node_id);
1999        if let Some(owner) = within {
2000            if self.ast.set_declaration_scope(any, owner).is_err() {
2001                self.error(
2002                    &trail,
2003                    format!("the model refused the scope `{owner}` for `{name}`"),
2004                    "zerodds-ast-1.0 §3.5",
2005                );
2006            }
2007        }
2008        self.set_xml_facet(any, el);
2009    }
2010
2011    /// `<bitset>` with `<bitfield name type bits|mask>`, the form
2012    /// `zerodds-xml` reads: a field is as wide as `bits` says or as the
2013    /// mask has bits set; a field without a `name` is padding.
2014    fn bitset(&mut self, el: &XmlElement, scope: &Scope, pass: Pass) {
2015        let Some(name) = self.named(el, scope) else {
2016            return;
2017        };
2018        let trail = format!("{}/bitset[{name}]", scope.trail);
2019        if pass == Pass::Declare {
2020            let placeholder = TypeKind::Bitset {
2021                base: None,
2022                fields: Vec::new(),
2023            };
2024            self.declare(&name, scope, placeholder, el, &trail);
2025            return;
2026        }
2027        let Some(id) = self.declared_here(&name, scope) else {
2028            return;
2029        };
2030        let base = el
2031            .attribute("baseType")
2032            .or_else(|| el.attribute("base_type"))
2033            .map(|written| self.named_ref(written, scope, &trail));
2034        if self
2035            .ast
2036            .complete_type(
2037                id,
2038                TypeKind::Bitset {
2039                    base,
2040                    fields: Vec::new(),
2041                },
2042            )
2043            .is_err()
2044        {
2045            self.error(
2046                &trail,
2047                format!("`{name}` is declared twice"),
2048                "OMG IDL 4.2 §7.2.3",
2049            );
2050            return;
2051        }
2052        let annotations = self.applied_annotations(el, scope, &trail);
2053        let verbatim = self.verbatim_directives(el, &trail);
2054        self.attach_to_type(id, annotations, verbatim);
2055        for field in &el.children {
2056            if field.name != "bitfield" {
2057                if !matches!(field.name.as_str(), "annotate" | "verbatim") {
2058                    self.warning(
2059                        &trail,
2060                        format!("<{}> is no child of a <bitset> and is not read", field.name),
2061                        "OMG IDL 4.2 §7.4.13.4.3",
2062                    );
2063                }
2064                continue;
2065            }
2066            let field_name = field.attribute("name").map(str::to_owned);
2067            let label = field_name
2068                .clone()
2069                .unwrap_or_else(|| String::from("<padding>"));
2070            let width = match (field.attribute("bits"), field.attribute("mask")) {
2071                (Some(written), _) => integer(written).and_then(|v| u16::try_from(v).ok()),
2072                (None, Some(mask)) => integer(mask)
2073                    .and_then(|v| u64::try_from(v).ok())
2074                    .and_then(|v| u16::try_from(v.count_ones()).ok()),
2075                (None, None) => None,
2076            };
2077            let Some(width) = width.filter(|w| (1..=64).contains(w)) else {
2078                self.error(
2079                    &trail,
2080                    format!("bitfield `{label}` needs a `bits` or a `mask` of 1 to 64 bits"),
2081                    "OMG IDL 4.2 §7.4.13.4.3",
2082                );
2083                continue;
2084            };
2085            let holder = field.attribute("type").is_some().then(|| {
2086                self.type_reference(
2087                    field,
2088                    scope,
2089                    &trail,
2090                    "type",
2091                    "nonBasicTypeName",
2092                    "stringMaxLength",
2093                )
2094            });
2095            match self
2096                .ast
2097                .add_bitfield(id, field_name.as_deref(), width, holder)
2098            {
2099                Ok(bitfield) => {
2100                    if let Ok(node_id) = self.ast.bitfield(bitfield).map(|b| b.node_id) {
2101                        self.set_xml_facet(AnyId::Bitfield(bitfield, node_id), field);
2102                    }
2103                }
2104                Err(_) => self.error(
2105                    &trail,
2106                    format!("the model refused bitfield `{label}`"),
2107                    "zerodds-ast-1.0 §3.3",
2108                ),
2109            }
2110        }
2111    }
2112
2113    /// `<annotation name><member name type/></annotation>`;
2114    /// DDS-XTypes 1.3 §7.3.2.9 and Annex A `annotationTypeDecl`.
2115    fn annotation_declaration(&mut self, el: &XmlElement, scope: &Scope) {
2116        let Some(name) = self.named(el, scope) else {
2117            return;
2118        };
2119        let trail = format!("{}/annotation[{name}]", scope.trail);
2120        let Ok(def) = self.ast.add_annotation_def(scope.module, &name) else {
2121            self.error(
2122                &trail,
2123                format!("the model refused an annotation named `{name}`"),
2124                "zerodds-ast-1.0 §3.6",
2125            );
2126            return;
2127        };
2128        if let Ok(node_id) = self.ast.annotation_def(def).map(|d| d.node_id) {
2129            self.set_xml_facet(AnyId::AnnotationDef(def, node_id), el);
2130        }
2131        // Annex A `annotationTypeDecl/@baseType`. The model's declaration has
2132        // no base, so the members of the base are the first members of this
2133        // declaration, in the base's order, as an inheriting annotation type
2134        // has them (IDL 4.2 §7.4.15.3, `<annotation_inheritance_spec>`).
2135        if let Some(written) = el.attribute("baseType") {
2136            let absolute = written.starts_with("::");
2137            let segments: Vec<String> = written
2138                .split("::")
2139                .filter(|s| !s.is_empty())
2140                .map(str::to_owned)
2141                .collect();
2142            let base = self.lookup(absolute, &segments, scope, |any| {
2143                matches!(any, AnyId::AnnotationDef(..))
2144            });
2145            let Some(AnyId::AnnotationDef(base, _)) = base else {
2146                self.error(
2147                    &trail,
2148                    format!(
2149                        "`baseType=\"{written}\"` names no annotation declared before this one"
2150                    ),
2151                    "OMG DDS-XTypes 1.3 Annex A",
2152                );
2153                return;
2154            };
2155            let inherited: Vec<_> = match self.ast.annotation_def(base) {
2156                Ok(node) => node.members.clone(),
2157                Err(_) => {
2158                    self.error(
2159                        &trail,
2160                        format!("the model does not hold the base annotation `{written}`"),
2161                        "zerodds-ast-1.0 §3.6",
2162                    );
2163                    return;
2164                }
2165            };
2166            for member in inherited {
2167                let Ok(node) = self.ast.annotation_member(member) else {
2168                    self.error(
2169                        &trail,
2170                        format!("a member of the base annotation `{written}` is not in the model"),
2171                        "zerodds-ast-1.0 §3.6",
2172                    );
2173                    continue;
2174                };
2175                let (member_name, member_type, member_default) =
2176                    (node.name.clone(), node.type_.clone(), node.default);
2177                if self
2178                    .ast
2179                    .add_annotation_member(def, &member_name, member_type, member_default)
2180                    .is_err()
2181                {
2182                    self.error(
2183                        &trail,
2184                        format!(
2185                            "the model refused the inherited annotation member `{member_name}`"
2186                        ),
2187                        "zerodds-ast-1.0 §3.6",
2188                    );
2189                }
2190            }
2191        }
2192        for child in &el.children {
2193            if child.name != "member" {
2194                self.warning(
2195                    &trail,
2196                    format!(
2197                        "<{}> is no child of an <annotation> and is not read",
2198                        child.name
2199                    ),
2200                    "OMG DDS-XTypes 1.3 Annex A",
2201                );
2202                continue;
2203            }
2204            let Some(member) = child.attribute("name") else {
2205                self.error(
2206                    &trail,
2207                    String::from("a <member> needs a `name`"),
2208                    "OMG DDS-XTypes 1.3 Annex A",
2209                );
2210                continue;
2211            };
2212            let member = member.to_owned();
2213            let type_ = self.type_reference(
2214                child,
2215                scope,
2216                &format!("{trail}/member[{member}]"),
2217                "type",
2218                "nonBasicTypeName",
2219                "stringMaxLength",
2220            );
2221            if self
2222                .ast
2223                .add_annotation_member(def, &member, type_, None)
2224                .is_err()
2225            {
2226                self.error(
2227                    &trail,
2228                    format!("the model refused annotation member `{member}`"),
2229                    "zerodds-ast-1.0 §3.6",
2230                );
2231            }
2232        }
2233    }
2234
2235    /// The `<annotate name><member name value/></annotate>` children of
2236    /// an element, as applied annotations (§7.3.2.9). A built-in
2237    /// annotation is an attribute of the element in this representation
2238    /// (§7.3.2.5) and is reported when it is written as `<annotate>`.
2239    fn applied_annotations(
2240        &mut self,
2241        el: &XmlElement,
2242        scope: &Scope,
2243        trail: &str,
2244    ) -> Vec<AppliedAnnotation> {
2245        let mut out = Vec::new();
2246        for note in el.children_named("annotate") {
2247            let Some(written) = note.attribute("name") else {
2248                self.error(
2249                    trail,
2250                    String::from("an <annotate> needs a `name`"),
2251                    "OMG DDS-XTypes 1.3 Annex A",
2252                );
2253                continue;
2254            };
2255            let at = format!("{trail}/annotate[{written}]");
2256            let absolute = written.starts_with("::");
2257            let segments: Vec<String> = written
2258                .split("::")
2259                .filter(|s| !s.is_empty())
2260                .map(str::to_owned)
2261                .collect();
2262            let user_declared = self.lookup(absolute, &segments, scope, |any| {
2263                matches!(any, AnyId::AnnotationDef(..))
2264            });
2265            let definition = match user_declared {
2266                Some(AnyId::AnnotationDef(id, _)) => Ref::Resolved(id),
2267                _ => {
2268                    if segments.len() == 1
2269                        && zerodds_ast::node::annotation::BUILTIN_ANNOTATIONS.contains(&written)
2270                    {
2271                        self.error(
2272                            &at,
2273                            format!(
2274                                "`{written}` is a built-in annotation; this representation \
2275                                 writes it as an attribute of the element"
2276                            ),
2277                            "OMG DDS-XTypes 1.3 §7.3.2.5",
2278                        );
2279                        continue;
2280                    }
2281                    // Every `<annotation>` of the bundle was declared in the
2282                    // first pass, so a name that is not found now is not
2283                    // declared anywhere.
2284                    self.error(
2285                        &at,
2286                        format!("no <annotation> named `{written}` is declared"),
2287                        "OMG DDS-XTypes 1.3 §7.3.2.9",
2288                    );
2289                    continue;
2290                }
2291            };
2292            let mut arguments = Vec::new();
2293            for argument in note.children_named("member") {
2294                let (Some(member), Some(value)) =
2295                    (argument.attribute("name"), argument.attribute("value"))
2296                else {
2297                    self.error(
2298                        &at,
2299                        String::from("an annotation <member> needs a `name` and a `value`"),
2300                        "OMG DDS-XTypes 1.3 Annex A",
2301                    );
2302                    continue;
2303                };
2304                let member = member.to_owned();
2305                if let Some(expression) =
2306                    self.const_exp(value, scope, &at, "OMG DDS-XTypes 1.3 §7.3.2.9")
2307                {
2308                    arguments.push(AppliedArgument {
2309                        name: Some(member),
2310                        value: expression,
2311                    });
2312                }
2313            }
2314            out.push(AppliedAnnotation {
2315                name: written.to_owned(),
2316                definition,
2317                arguments,
2318            });
2319        }
2320        out
2321    }
2322
2323    /// The `<verbatim language placement>text</verbatim>` children of an
2324    /// element (§7.3.2.5.1.1). The text is the `<text>` child of Annex A's
2325    /// `verbatimDecl` or, as the clause's example writes it, the element's
2326    /// own content; the placement defaults to `before-declaration` and
2327    /// the language to `*`, as `@verbatim` does in IDL 4.2 §8.3.5.1.
2328    fn verbatim_directives(&mut self, el: &XmlElement, trail: &str) -> Vec<VerbatimDirective> {
2329        let mut out = Vec::new();
2330        for verbatim in el.children_named("verbatim") {
2331            let placement = match verbatim.attribute("placement") {
2332                Some(written) => match zerodds_idl_lower::verbatim_placement(written) {
2333                    Some(placement) => placement,
2334                    None => {
2335                        self.error(
2336                            trail,
2337                            format!("`{written}` is none of the six `@verbatim` placements"),
2338                            "OMG IDL 4.2 §8.3.5.1",
2339                        );
2340                        continue;
2341                    }
2342                },
2343                None => VerbatimPlacement::BeforeDeclaration,
2344            };
2345            let text = verbatim
2346                .child("text")
2347                .map_or_else(|| verbatim.text.clone(), |t| t.text.clone());
2348            out.push(VerbatimDirective {
2349                language: verbatim
2350                    .attribute("language")
2351                    .map_or_else(|| String::from("*"), str::to_owned),
2352                placement,
2353                text,
2354            });
2355        }
2356        out
2357    }
2358
2359    /// Annotations and verbatim text of a type declaration, onto its node.
2360    fn attach_to_type(
2361        &mut self,
2362        id: Id<TypeNode>,
2363        annotations: Vec<AppliedAnnotation>,
2364        verbatim: Vec<VerbatimDirective>,
2365    ) {
2366        let Ok(node_id) = self.ast.type_(id).map(|node| node.node_id) else {
2367            return;
2368        };
2369        if let Some(facets) = self.ast.facets_mut(AnyId::Type(id, node_id)) {
2370            facets.annotations.extend(annotations);
2371            if !verbatim.is_empty() {
2372                facets.xtypes_mut().verbatim.extend(verbatim);
2373            }
2374        }
2375    }
2376
2377    /// Annotations of an enumeration or a bitmask and of its literals,
2378    /// applied once every `<annotation>` of the bundle is declared.
2379    /// `literal` is the element name of a literal (`enumerator`, `flag`).
2380    fn annotate_enumerated(&mut self, el: &XmlElement, scope: &Scope, literal: &str) {
2381        let Some(name) = el.attribute("name") else {
2382            return;
2383        };
2384        let name = name.to_owned();
2385        let trail = format!("{}/{}[{name}]", scope.trail, el.name);
2386        let Some(id) = self.declared_here(&name, scope) else {
2387            return;
2388        };
2389        let annotations = self.applied_annotations(el, scope, &trail);
2390        let verbatim = self.verbatim_directives(el, &trail);
2391        self.attach_to_type(id, annotations, verbatim);
2392        let Ok(node) = self.ast.type_(id) else {
2393            return;
2394        };
2395        let literals: Vec<(String, AnyId)> = match &node.kind {
2396            TypeKind::Enumeration { enumerators } => enumerators
2397                .iter()
2398                .filter_map(|e| {
2399                    let node = self.ast.enumerator(*e).ok()?;
2400                    Some((node.name.clone(), AnyId::Enumerator(*e, node.node_id)))
2401                })
2402                .collect(),
2403            TypeKind::Bitmask { bits } => bits
2404                .iter()
2405                .filter_map(|b| {
2406                    let node = self.ast.bit_value(*b).ok()?;
2407                    Some((node.name.clone(), AnyId::BitValue(*b, node.node_id)))
2408                })
2409                .collect(),
2410            _ => Vec::new(),
2411        };
2412        for child in el.children_named(literal) {
2413            let Some(literal_name) = child.attribute("name") else {
2414                continue;
2415            };
2416            let applied = self.applied_annotations(child, scope, &trail);
2417            if applied.is_empty() {
2418                continue;
2419            }
2420            if let Some((_, any)) = literals.iter().find(|(n, _)| n == literal_name) {
2421                if let Some(facets) = self.ast.facets_mut(*any) {
2422                    facets.annotations.extend(applied);
2423                }
2424            }
2425        }
2426    }
2427
2428    fn error(&mut self, at: &str, message: String, spec_reference: &'static str) {
2429        self.diagnostics.push(Diagnostic {
2430            severity: Severity::Error,
2431            message,
2432            at: at.to_owned(),
2433            spec_reference,
2434        });
2435    }
2436}
2437
2438/// A written name as a scoped name: from the root when it started with
2439/// `::`, otherwise relative to the node that names it.
2440/// The last segment of every name an expression writes.
2441///
2442/// zerodds-lint: recursion-depth 64 (the parser refuses an expression
2443/// nested deeper than its `MAX_DEPTH`)
2444fn names_in(parsed: &Expression, out: &mut Vec<String>) {
2445    match parsed {
2446        Expression::Name { segments, .. } => {
2447            if let Some(last) = segments.last() {
2448                out.push(last.clone());
2449            }
2450        }
2451        Expression::Unary(_, inner) => names_in(inner, out),
2452        Expression::Binary(_, left, right) => {
2453            names_in(left, out);
2454            names_in(right, out);
2455        }
2456        _ => {}
2457    }
2458}
2459
2460fn scoped(absolute: bool, segments: &[String]) -> ScopedName {
2461    if absolute {
2462        ScopedName::absolute(segments.to_vec())
2463    } else {
2464        ScopedName::relative(segments.to_vec())
2465    }
2466}
2467
2468/// How far an inheritance chain is walked before it is taken for a
2469/// cycle, matching `Ast::all_members_of`.
2470const MAX_INHERITANCE_DEPTH: usize = 64;
2471
2472fn next_id(previous: Option<u32>) -> u32 {
2473    previous.map_or(0, |last| last.saturating_add(1))
2474}
2475
2476/// A reference that will not be retried, naming what was wanted.
2477///
2478/// `BreakCause` carries the key rather than a sentence so that a
2479/// consumer can print the name the document wrote; a broken reference
2480/// with no name in it is a diagnostic nobody can act on.
2481fn broken(what: &str) -> Ref<TypeNode> {
2482    Ref::Broken(zerodds_ast_core::BreakCause::NotFound {
2483        what: PendingRef::ScopedName(ScopedName::absolute(vec![what.to_owned()])),
2484    })
2485}
2486
2487/// The XTypes primitive a `type=` attribute names.
2488///
2489/// Both vocabularies, because both are written: DDS-XTypes 1.3 Annex A's
2490/// width-carrying spellings (`int32`, `float64`, `char8`) and OMG IDL
2491/// 4.2 §7.4.1.4's keywords (`long`, `double`, `char`), which DDS-XML
2492/// documents in the field use interchangeably. `zerodds-xml`'s own
2493/// `PrimitiveType::from_xml` carries most of this table and is not
2494/// reused only because it has no `int8` and no `uint8`, which the OMG
2495/// suite's `primitives.xml` writes fourteen times.
2496fn primitive_of(written: &str) -> Option<PrimitiveKind> {
2497    Some(match written {
2498        "boolean" | "bool" => PrimitiveKind::Boolean,
2499        "byte" | "octet" => PrimitiveKind::Octet,
2500        "uint8" => PrimitiveKind::UInt8,
2501        "int8" => PrimitiveKind::Int8,
2502        "char8" | "char" => PrimitiveKind::Char,
2503        "char16" | "wchar" => PrimitiveKind::WChar,
2504        "int16" | "short" => PrimitiveKind::Int16,
2505        "uint16" | "ushort" | "unsignedShort" => PrimitiveKind::UInt16,
2506        "int32" | "long" => PrimitiveKind::Int32,
2507        "uint32" | "ulong" | "unsignedLong" => PrimitiveKind::UInt32,
2508        "int64" | "longlong" | "longLong" => PrimitiveKind::Int64,
2509        "uint64" | "ulonglong" | "unsignedLongLong" => PrimitiveKind::UInt64,
2510        "float32" | "float" => PrimitiveKind::Float32,
2511        "float64" | "double" => PrimitiveKind::Float64,
2512        "float128" | "longdouble" | "longDouble" => PrimitiveKind::Float128,
2513        _ => return None,
2514    })
2515}
2516
2517/// `byte`/`octet` and `uint8` are two type kinds, not two spellings of
2518/// one: XTypes 1.3 §7.3.4.2 gives `TK_BYTE` (0x02) and `TK_UINT8`
2519/// (0x0D), and §7.2.4.4.4.4 Table 15 makes a primitive assignable only
2520/// to its own kind. The DDS-XML vocabulary writes `byte` for the first
2521/// and `uint8` for the second, which is the split above; the IDL front
2522/// end splits them the same way — `octet` reaches
2523/// `PrimitiveKind::Octet` and `uint8` reaches `PrimitiveKind::UInt8` —
2524/// so the two loaders agree.
2525const _: () = ();
2526
2527fn extensibility_of(written: &str) -> Option<Extensibility> {
2528    match written.to_ascii_lowercase().as_str() {
2529        "final" => Some(Extensibility::Final),
2530        "appendable" | "extensible" => Some(Extensibility::Appendable),
2531        "mutable" => Some(Extensibility::Mutable),
2532        _ => None,
2533    }
2534}
2535
2536fn truthy(written: Option<&str>) -> bool {
2537    matches!(
2538        written.map(str::trim),
2539        Some("true" | "TRUE" | "True" | "1" | "yes")
2540    )
2541}
2542
2543/// An integer as DDS-XML writes one: decimal, `0x`-hexadecimal, or
2544/// either with a sign.
2545fn integer(written: &str) -> Option<i128> {
2546    let text = written.trim();
2547    let (negative, rest) = match text.strip_prefix('-') {
2548        Some(rest) => (true, rest),
2549        None => (false, text.strip_prefix('+').unwrap_or(text)),
2550    };
2551    let value = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
2552        i128::from_str_radix(hex, 16).ok()?
2553    } else if let Some(bin) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
2554        i128::from_str_radix(bin, 2).ok()?
2555    } else {
2556        rest.parse::<i128>().ok()?
2557    };
2558    Some(if negative { -value } else { value })
2559}
2560
2561#[cfg(test)]
2562#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2563mod tests {
2564    use super::*;
2565
2566    #[test]
2567    fn an_integer_reads_decimal_hexadecimal_and_a_sign() {
2568        assert_eq!(integer("10"), Some(10));
2569        assert_eq!(integer("0x0e"), Some(14));
2570        assert_eq!(integer("0X1F"), Some(31));
2571        assert_eq!(integer("-1"), Some(-1));
2572        assert_eq!(integer("VAL0"), None);
2573    }
2574
2575    #[test]
2576    fn the_two_primitive_vocabularies_agree() {
2577        assert_eq!(primitive_of("int32"), primitive_of("long"));
2578        assert_eq!(primitive_of("byte"), primitive_of("octet"));
2579        assert_eq!(primitive_of("float128"), primitive_of("longdouble"));
2580        assert_eq!(primitive_of("int8"), Some(PrimitiveKind::Int8));
2581        assert_eq!(primitive_of("nonBasic"), None);
2582    }
2583
2584    /// XTypes 1.3 §7.3.4.2 gives `octet` and `uint8` the distinct type
2585    /// kinds `TK_BYTE` and `TK_UINT8`. A loader that merged them
2586    /// announced `TK_BYTE` for a `uint8` member, which made the two
2587    /// mutually assignable under §7.2.4.4.4.4 Table 15 — the two OMG
2588    /// `dds-xtypes` cases `struct_uint8_byte` and `struct_byte_uint8`
2589    /// expect `INCONSISTENT_TOPIC` and got a match.
2590    #[test]
2591    fn octet_and_uint8_are_two_kinds() {
2592        assert_eq!(primitive_of("octet"), Some(PrimitiveKind::Octet));
2593        assert_eq!(primitive_of("byte"), Some(PrimitiveKind::Octet));
2594        assert_eq!(primitive_of("uint8"), Some(PrimitiveKind::UInt8));
2595        assert_ne!(primitive_of("octet"), primitive_of("uint8"));
2596    }
2597}