Skip to main content

zerodds_ast/
query.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Query and traversal. Spec §6.
4
5use crate::facet::Extensibility;
6use crate::model::Ast;
7use crate::node::AnyId;
8use crate::node::type_::TypeKind;
9use alloc::string::String;
10use alloc::vec::Vec;
11use zerodds_ast_core::{
12    Id, IndexLookup, NodeId, RepositoryId, RosTypeName, ScopedName, TypeIdentifierKey, VisitSet,
13    WalkOrder, walk_graph,
14};
15
16/// How far an inheritance chain is walked before it is taken for a
17/// cycle. Deep enough for any real hierarchy, finite for a broken one.
18const MAX_INHERITANCE_DEPTH: usize = 64;
19
20impl Ast {
21    /// Find any node by its full scoped name.
22    ///
23    /// # Errors
24    /// [`IndexLookup::Missing`] when nothing carries the name,
25    /// [`IndexLookup::Ambiguous`] with every candidate when several do.
26    pub fn find_by_scoped_name(&self, name: &ScopedName) -> Result<AnyId, IndexLookup> {
27        let node = self.by_scoped_name.unique(name)?;
28        self.resolve_node_id(node).ok_or(IndexLookup::Missing)
29    }
30
31    /// Find a type by its XTypes type identifier.
32    ///
33    /// # Errors
34    /// As [`Ast::find_by_scoped_name`].
35    pub fn find_by_type_identifier(
36        &self,
37        key: &TypeIdentifierKey,
38    ) -> Result<Id<crate::TypeNode>, IndexLookup> {
39        let node = self.by_type_identifier.unique(key)?;
40        self.resolve_node_id(node)
41            .and_then(AnyId::as_type)
42            .ok_or(IndexLookup::Missing)
43    }
44
45    /// Find a type by its ROS 2 interface name.
46    ///
47    /// # Errors
48    /// As [`Ast::find_by_scoped_name`].
49    pub fn find_by_ros_name(&self, name: &RosTypeName) -> Result<Id<crate::TypeNode>, IndexLookup> {
50        let node = self.by_ros_name.unique(name)?;
51        self.resolve_node_id(node)
52            .and_then(AnyId::as_type)
53            .ok_or(IndexLookup::Missing)
54    }
55
56    /// Find any node by its CORBA repository identifier.
57    ///
58    /// # Errors
59    /// As [`Ast::find_by_scoped_name`].
60    pub fn find_by_repository_id(&self, id: &RepositoryId) -> Result<AnyId, IndexLookup> {
61        let node = self.by_repository_id.unique(id)?;
62        self.resolve_node_id(node).ok_or(IndexLookup::Missing)
63    }
64
65    /// The full scoped name of a node, if it has one.
66    ///
67    /// Nodes that live inside a type rather than a module — members,
68    /// cases, parameters — have no scoped name of their own.
69    #[must_use]
70    pub fn scoped_name_of(&self, any: AnyId) -> Option<ScopedName> {
71        // A declaration inside an interface or a value type is named
72        // through it: `module CosNaming { interface NamingContext {
73        // exception NotFound …` is `CosNaming::NamingContext::NotFound`.
74        let mut scope: Option<String> = None;
75        let (module, name) = match any {
76            AnyId::Type(id, _) => {
77                let node = self.types.get(id).ok()?;
78                scope.clone_from(&node.scope);
79                (Some(node.module), node.name.clone())
80            }
81            AnyId::Interface(id, _) => {
82                let node = self.interfaces.get(id).ok()?;
83                (Some(node.module), node.name.clone())
84            }
85            AnyId::Exception(id, _) => {
86                let node = self.exceptions.get(id).ok()?;
87                scope.clone_from(&node.scope);
88                (Some(node.module), node.name.clone())
89            }
90            AnyId::Const(id, _) => {
91                let node = self.constants.get(id).ok()?;
92                scope.clone_from(&node.scope);
93                (Some(node.module), node.name.clone())
94            }
95            AnyId::AnnotationDef(id, _) => {
96                let node = self.annotation_defs.get(id).ok()?;
97                (Some(node.module), node.name.clone())
98            }
99            AnyId::Module(id, _) => {
100                let node = self.modules.get(id).ok()?;
101                (node.parent, node.name.clone())
102            }
103            // An enumerator is named from the scope enclosing its
104            // enumeration, not through it. OMG IDL 4.2 §7.4.1.4.2 spells
105            // this out with `module M { enum Size { small, medium, large
106            // }; }; const M::Size MYSIZE = M::medium;` — `M::medium`,
107            // never `M::Size::medium`.
108            AnyId::Enumerator(id, _) => {
109                let node = self.enumerators.get(id).ok()?;
110                let enumeration = self.types.get(node.parent).ok()?;
111                (Some(enumeration.module), node.name.clone())
112            }
113            _ => return None,
114        };
115
116        let mut segments: Vec<String> = Vec::new();
117        let mut current = module;
118        while let Some(id) = current {
119            let node = self.modules.get(id).ok()?;
120            if !node.name.is_empty() {
121                segments.push(node.name.clone());
122            }
123            current = node.parent;
124        }
125        segments.reverse();
126        segments.extend(scope);
127        segments.push(name);
128        Some(ScopedName::absolute(segments))
129    }
130
131    /// The members of an aggregate, empty for every other kind.
132    #[must_use]
133    pub fn members_of(&self, type_: Id<crate::TypeNode>) -> &[Id<crate::MemberNode>] {
134        self.types.get(type_).map_or(&[], |t| t.kind.members())
135    }
136
137    /// The members of an aggregate, its bases' members first.
138    ///
139    /// [`Ast::members_of`] answers what a type declares; this answers
140    /// what it *has*, which is what every wire consumer needs.
141    /// DDS-XTypes 1.3 §7.4.1.1.5.1: "The members defined by the base
142    /// type, if any, shall be serialized before the members of their
143    /// derived types. The representation shall be exactly as if all of
144    /// the members had been defined, in the same order, in the
145    /// most-derived type."
146    ///
147    /// The chain is walked to a fixed depth and a base already seen is
148    /// not walked again: an inheritance cycle is invalid IDL, and
149    /// looping here would hang instead of reporting it.
150    #[must_use]
151    pub fn all_members_of(&self, type_: Id<crate::TypeNode>) -> Vec<Id<crate::MemberNode>> {
152        let mut chain: Vec<Id<crate::TypeNode>> = Vec::new();
153        let mut current = Some(type_);
154        while let Some(id) = current {
155            if chain.contains(&id) || chain.len() >= MAX_INHERITANCE_DEPTH {
156                break;
157            }
158            chain.push(id);
159            current = self.base_of(id);
160        }
161        // Walked derived-to-base, written base-to-derived.
162        let mut out = Vec::new();
163        for id in chain.into_iter().rev() {
164            out.extend_from_slice(self.members_of(id));
165        }
166        out
167    }
168
169    /// The base an aggregate extends, if it has one.
170    ///
171    /// A valuetype may name several; only a single base is a structure
172    /// on the wire, so the first is what the layout follows.
173    ///
174    /// A bitset counts: IDL 4.2 §7.4.13.4.3.2 gives `bitset` "an
175    /// optional single inheritance specification", and the inherited
176    /// bitfields are part of what the derived bitset packs.
177    /// [`Ast::members_of`] is empty for a bitset either way, so
178    /// [`Ast::all_members_of`] is unaffected by the extra arm.
179    #[must_use]
180    pub fn base_of(&self, type_: Id<crate::TypeNode>) -> Option<Id<crate::TypeNode>> {
181        match self.types.get(type_).map(|t| &t.kind) {
182            Ok(TypeKind::Structure { base, .. } | TypeKind::Bitset { base, .. }) => {
183                base.as_ref().and_then(|b| b.resolved())
184            }
185            Ok(TypeKind::Valuetype { bases, .. }) => bases.first().and_then(|b| b.resolved()),
186            _ => None,
187        }
188    }
189
190    /// The bitfields a bitset declares, empty for every other kind.
191    #[must_use]
192    pub fn bitfields_of(&self, type_: Id<crate::TypeNode>) -> &[Id<crate::BitfieldNode>] {
193        match self.types.get(type_).map(|t| &t.kind) {
194            Ok(TypeKind::Bitset { fields, .. }) => fields,
195            _ => &[],
196        }
197    }
198
199    /// The bitfields a bitset *has*, its bases' bitfields first.
200    ///
201    /// The bitset counterpart of [`Ast::all_members_of`], and needed for
202    /// the same reason: a plan that leaves the inherited bitfields out
203    /// sizes the anchor as if they did not exist, and every emitter
204    /// behind it writes short.
205    ///
206    /// # Why base-first, when no specification says so
207    ///
208    /// DDS-XTypes 1.3 has no serialisation rule for a bitset — see the
209    /// note above `wire_probe::encode::write_bitset`. The order is
210    /// therefore taken from the one implementation that serialises the
211    /// construct at all. Measured on 2026-08-09, eProsima Fast DDS
212    /// 3.6.1 flattens `bitset B3 : B2 : B1` with `bitfield<8> a`,
213    /// `bitfield<8> b`, `bitfield<4> c` into a `std::bitset<20>` and
214    /// packs `a` at bits 0..8, `b` at 8..16 and `c` at 16..20 — the
215    /// base's bitfields at the least significant end, in declaration
216    /// order. Cyclone's `idlc` and RTI's `rtiddsgen` do not parse
217    /// `bitset` at all, so Fast DDS is the only oracle there is.
218    ///
219    /// The chain is walked to the same fixed depth as
220    /// [`Ast::all_members_of`], and a base already seen is not walked
221    /// again: an inheritance cycle is invalid IDL, and looping here
222    /// would hang instead of reporting it.
223    #[must_use]
224    pub fn all_bitfields_of(&self, type_: Id<crate::TypeNode>) -> Vec<Id<crate::BitfieldNode>> {
225        let mut chain: Vec<Id<crate::TypeNode>> = Vec::new();
226        let mut current = Some(type_);
227        while let Some(id) = current {
228            if chain.contains(&id) || chain.len() >= MAX_INHERITANCE_DEPTH {
229                break;
230            }
231            chain.push(id);
232            current = self.base_of(id);
233        }
234        // Walked derived-to-base, packed base-to-derived.
235        let mut out = Vec::new();
236        for id in chain.into_iter().rev() {
237            out.extend_from_slice(self.bitfields_of(id));
238        }
239        out
240    }
241
242    /// The enumerators of an enumeration, empty for every other kind.
243    #[must_use]
244    pub fn enumerators_of(&self, type_: Id<crate::TypeNode>) -> &[Id<crate::EnumeratorNode>] {
245        match self.types.get(type_).map(|t| &t.kind) {
246            Ok(TypeKind::Enumeration { enumerators }) => enumerators,
247            _ => &[],
248        }
249    }
250
251    /// The bits of a bitmask, empty for every other kind.
252    #[must_use]
253    pub fn bits_of(&self, type_: Id<crate::TypeNode>) -> &[Id<crate::BitValueNode>] {
254        match self.types.get(type_).map(|t| &t.kind) {
255            Ok(TypeKind::Bitmask { bits }) => bits,
256            _ => &[],
257        }
258    }
259
260    /// The attributes an interface declares directly.
261    #[must_use]
262    pub fn attributes_of(
263        &self,
264        interface: Id<crate::InterfaceNode>,
265    ) -> &[Id<crate::AttributeNode>] {
266        self.interfaces
267            .get(interface)
268            .map_or(&[], |node| node.attributes.as_slice())
269    }
270
271    /// The extensibility a type has, written or defaulted.
272    ///
273    /// The facet keeps `None` for a type whose source said nothing, so
274    /// what was written stays distinguishable from what the rule gives.
275    /// This is the rule: `@final`/`@appendable`/`@mutable`, else
276    /// `@extensibility(...)`, else — for a structure that derives from
277    /// another — its base's, else appendable. DDS-XTypes 1.3 §7.2.4.4,
278    /// §7.2.2.4.5.
279    ///
280    /// Appendable, not final: a consumer that assumed final would emit
281    /// types that cannot evolve, and no DHEADER where one belongs.
282    ///
283    /// The base's, not appendable: §7.2.2.4.5 lets a derived structure
284    /// leave its extensibility unspecified or give the base's, so an
285    /// unspecified one has the base's. Cyclone DDS 11.0.1 frames
286    /// `struct Derived : Base` of a `@mutable` base as mutable
287    /// (`DDS_OP_PLC`, measured); falling back to appendable here framed it
288    /// with a DHEADER and no EMHEADERs.
289    #[must_use]
290    pub fn effective_extensibility(&self, type_: Id<crate::TypeNode>) -> Extensibility {
291        let mut current = type_;
292        // Bounded by the number of types: a base chain visits each at most
293        // once, and a cycle, which a valid model does not have, ends here
294        // rather than looping.
295        for _ in 0..=self.types.len() {
296            let Ok(node) = self.types.get(current) else {
297                break;
298            };
299            if let Some(extensibility) = node.facets.extensibility() {
300                return extensibility;
301            }
302            let TypeKind::Structure {
303                base: Some(base), ..
304            } = &node.kind
305            else {
306                break;
307            };
308            let Some(base) = base.resolved() else {
309                break;
310            };
311            current = base;
312        }
313        crate::DEFAULT_EXTENSIBILITY
314    }
315
316    /// The `@autoid` policy of a type: its own, else the nearest enclosing
317    /// module's.
318    ///
319    /// DDS-XTypes 1.3 §7.3.1.2.1.1 and Table 21 apply `@autoid` to
320    /// "Module declarations, Structure declarations, Union declarations";
321    /// on a module it governs every aggregated type declared in it that
322    /// does not carry its own. `None` is "nothing said", which numbers
323    /// the members sequentially.
324    #[must_use]
325    pub fn effective_autoid(&self, type_: Id<crate::TypeNode>) -> Option<crate::AutoId> {
326        let node = self.types.get(type_).ok()?;
327        if let Some(own) = node.facets.xtypes.as_ref().and_then(|x| x.autoid) {
328            return Some(own);
329        }
330        self.module_chain_value(node.module, |x| x.autoid)
331    }
332
333    /// The `@data_representation` mask of a type: its own, else the
334    /// nearest enclosing module's.
335    ///
336    /// DDS-XTypes 1.3 §7.3.1.2.1.15: the annotation "may be applied to
337    /// modules and to types"; a module's value is the default for the
338    /// types in it. `None` is "nothing said", which §7.6.3.1.1 reads as
339    /// every representation the implementation supports.
340    #[must_use]
341    pub fn effective_data_representation(
342        &self,
343        type_: Id<crate::TypeNode>,
344    ) -> Option<crate::DataRepresentationMask> {
345        let node = self.types.get(type_).ok()?;
346        if let Some(own) = node
347            .facets
348            .xtypes
349            .as_ref()
350            .and_then(|x| x.data_representation)
351        {
352            return Some(own);
353        }
354        self.module_chain_value(node.module, |x| x.data_representation)
355    }
356
357    /// The `DataRepresentationMask` a type has, with the annotation-less
358    /// case resolved: a type and its modules that carry no
359    /// `@data_representation` are "interpreted as if the
360    /// DataRepresentationMask value was set to 0xffffffff" (DDS-XTypes
361    /// 1.3 §7.3.1.2.1.15), which is [`crate::DataRepresentationMask::ALL`].
362    #[must_use]
363    pub fn data_representation_mask(
364        &self,
365        type_: Id<crate::TypeNode>,
366    ) -> crate::DataRepresentationMask {
367        self.effective_data_representation(type_)
368            .unwrap_or(crate::DataRepresentationMask::ALL)
369    }
370
371    /// The first value `read` finds on a module or the modules around it.
372    fn module_chain_value<T>(
373        &self,
374        start: Id<crate::ModuleNode>,
375        read: impl Fn(&crate::XTypesFacet) -> Option<T>,
376    ) -> Option<T> {
377        let mut module = Some(start);
378        for _ in 0..crate::node::constant::MAX_EXPR_DEPTH {
379            let id = module?;
380            let m = self.modules.get(id).ok()?;
381            if let Some(found) = m.facets.xtypes.as_ref().and_then(&read) {
382                return Some(found);
383            }
384            module = m.parent;
385        }
386        None
387    }
388
389    /// The bit bound an enum or bitmask has, written or defaulted.
390    /// DDS-XTypes 1.3 §7.3.1.2.1.9.
391    #[must_use]
392    pub fn effective_bit_bound(&self, type_: Id<crate::TypeNode>) -> u16 {
393        self.types
394            .get(type_)
395            .ok()
396            .and_then(|node| node.facets.xtypes.as_ref())
397            .and_then(|x| x.bit_bound)
398            .unwrap_or(crate::DEFAULT_BIT_BOUND)
399    }
400
401    /// The `try_construct` behaviour that governs one *use* of a type.
402    ///
403    /// `written` is the annotation at the use site — a member's own
404    /// `@try_construct`, or the collection's `element_try_construct` /
405    /// `key_try_construct`, or the union's
406    /// `discriminator_try_construct`. `used` is the type being used
407    /// there.
408    ///
409    /// The order is: what the use site wrote, else what the alias chain
410    /// of the used type declares nearest-first, else
411    /// `TryConstruct::ABSENT` — `DISCARD`, which §7.2.2.7 names as
412    /// the default when the annotation is not used.
413    ///
414    /// # Why an alias may carry one
415    ///
416    /// The clause does not list aliases among the positions
417    /// (§7.3.1.2.1.12 names members, the discriminator, array and
418    /// sequence elements, and map keys and values), and both Cyclone and
419    /// RTI refuse `@try_construct` on a `typedef` by name. Neither of
420    /// those is a statement about what an alias-borne annotation would
421    /// *mean*; they are front ends declining to accept it, and the two
422    /// implementations that do accept it — Fast DDS and OpenDDS — say
423    /// nothing about it either. With the specification silent, the
424    /// reading here is the broader one: an alias states the behaviour
425    /// that uses of it take by default, and any use site that writes its
426    /// own overrides it. Nothing an author wrote can be lost that way,
427    /// and `typedef string<5> String5` — the very declaration
428    /// §7.3.1.2.1.12.4 builds its example on — becomes able to carry the
429    /// behaviour it exists to give its uses.
430    ///
431    /// A peer built by Cyclone or RTI will not compile such a
432    /// declaration at all;
433    /// `crates/ast-layout/tests/fixtures/try_construct_behaviour.md`
434    /// records that.
435    #[must_use]
436    pub fn effective_try_construct(
437        &self,
438        written: Option<crate::TryConstruct>,
439        used: Option<Id<crate::TypeNode>>,
440    ) -> crate::TryConstruct {
441        if let Some(behaviour) = written {
442            return behaviour;
443        }
444        let mut next = used;
445        // Bounded like every other walk over a reference chain: a
446        // `typedef` cycle would otherwise spin here rather than in the
447        // validation rule that reports it.
448        for _ in 0..MAX_INHERITANCE_DEPTH {
449            let Some(id) = next else { break };
450            let Ok(node) = self.types.get(id) else { break };
451            if let Some(behaviour) = node.facets.xtypes.as_ref().and_then(|x| x.try_construct) {
452                return behaviour;
453            }
454            let TypeKind::Alias { aliased } = &node.kind else {
455                break;
456            };
457            next = aliased.resolved();
458        }
459        crate::TryConstruct::ABSENT
460    }
461
462    /// The behaviour that governs the elements of one collection.
463    ///
464    /// `collection` is a sequence, an array or a map — for a map this is
465    /// the **value** slot. `member_wrote` is the `@try_construct` on the
466    /// member whose type this collection is, if any.
467    ///
468    /// # Why a member's annotation can reach an array's elements
469    ///
470    /// An array cannot be trimmed and cannot overflow: Table 17 requires
471    /// `T1.bounds[] == T2.bounds[]`, so the only way an array fails to
472    /// construct is that one of its elements does. Its row accordingly
473    /// speaks of nothing but "the TryConstruct behavior associated with
474    /// T1 element type" — an array has no member-level behaviour to
475    /// carry. There is also no template syntax to annotate an array's
476    /// element with: `@try_construct(TRIM) String5 m[3]` writes the
477    /// annotation in front of the member, which is where OpenDDS reads
478    /// its `array_element_try_construct` from as well.
479    ///
480    /// A sequence or a map has both, and they are different questions —
481    /// the member's governs a length the receiver's bound cannot hold
482    /// (§7.3.1.2.1.12.3), the element's governs one element that will
483    /// not construct (§7.3.1.2.1.12.4) — so a member's annotation never
484    /// reaches their elements.
485    #[must_use]
486    pub fn element_try_construct(
487        &self,
488        collection: Id<crate::TypeNode>,
489        member_wrote: Option<crate::TryConstruct>,
490    ) -> crate::TryConstruct {
491        let Ok(node) = self.types.get(collection) else {
492            return crate::TryConstruct::ABSENT;
493        };
494        let (element, written) = match (&node.kind, node.facets.xtypes.as_ref()) {
495            (TypeKind::Array { element, .. }, xtypes) => (
496                element.resolved(),
497                xtypes
498                    .and_then(|x| x.element_try_construct)
499                    .or(member_wrote),
500            ),
501            (TypeKind::Sequence { element, .. }, xtypes) => (
502                element.resolved(),
503                xtypes.and_then(|x| x.element_try_construct),
504            ),
505            (TypeKind::Map { value, .. }, xtypes) => (
506                value.resolved(),
507                xtypes.and_then(|x| x.element_try_construct),
508            ),
509            _ => return crate::TryConstruct::ABSENT,
510        };
511        self.effective_try_construct(written, element)
512    }
513
514    /// The behaviour that governs a `map`'s keys.
515    ///
516    /// Table 17 gives a key no escape — a key that cannot construct
517    /// fails the whole map — so this is `DISCARD` unless a declaration
518    /// wrote `DISCARD` explicitly, which the front end is the only thing
519    /// that admits. It reads the slot rather than returning a constant
520    /// so that a future revision of the clause has one place to change.
521    #[must_use]
522    pub fn key_try_construct(&self, map: Id<crate::TypeNode>) -> crate::TryConstruct {
523        let Ok(node) = self.types.get(map) else {
524            return crate::TryConstruct::ABSENT;
525        };
526        let TypeKind::Map { key, .. } = &node.kind else {
527            return crate::TryConstruct::ABSENT;
528        };
529        let written = node
530            .facets
531            .xtypes
532            .as_ref()
533            .and_then(|x| x.key_try_construct);
534        self.effective_try_construct(written, key.resolved())
535    }
536
537    /// Whether a type is `@nested` — its own annotation, else the
538    /// `@default_nested` of the nearest enclosing module that carries
539    /// one.
540    ///
541    /// DDS-XTypes 1.3 §7.3.1.2.1.7: "@default_nested ... applied to a
542    /// module ... indicates whether the types defined within it are
543    /// nested by default ... value defaults to that of the enclosing
544    /// module. If a top-level module is not annotated, the default is
545    /// FALSE."
546    ///
547    /// A type's own `@nested` wins over the module's. `@default_nested`
548    /// used to reach nothing at all: the annotation parsed, the model
549    /// dropped it, and a whole module declared nested emitted
550    /// `IS_NESTED`-free `TypeObject`s — which is a topic type per
551    /// endpoint that the author said should be none.
552    ///
553    /// The walk is bounded by the module chain, which the model builds
554    /// acyclically; the counter is there so a handle that somehow points
555    /// at its own ancestor ends the loop rather than the process.
556    #[must_use]
557    pub fn effective_nested(&self, type_: Id<crate::TypeNode>) -> bool {
558        let Ok(node) = self.types.get(type_) else {
559            return false;
560        };
561        if let Some(x) = node.facets.xtypes.as_ref() {
562            if x.is_nested {
563                return true;
564            }
565            // §7.3.1.2.1.16: "The @topic annotation overrides the @nested
566            // and @default_nested annotations." A type that is a topic is
567            // not nested whatever its module says.
568            if x.topic.is_some() {
569                return false;
570            }
571            // `@nested(FALSE)` is the type speaking, and it wins over the
572            // module's `@default_nested(TRUE)`.
573            if x.nested_explicit {
574                return false;
575            }
576        }
577        let mut module = Some(node.module);
578        for _ in 0..crate::node::constant::MAX_EXPR_DEPTH {
579            let Some(id) = module else { break };
580            let Ok(m) = self.modules.get(id) else { break };
581            if let Some(written) = m.facets.xtypes.as_ref().and_then(|x| x.default_nested) {
582                return written;
583            }
584            module = m.parent;
585        }
586        false
587    }
588
589    /// Whether the elements of a collection are `@external` — the
590    /// sequence or array element, or the value of a map
591    /// (DDS-XTypes 1.3 §7.3.1.2.1.4).
592    #[must_use]
593    pub fn element_is_external(&self, collection: Id<crate::TypeNode>) -> bool {
594        self.types
595            .get(collection)
596            .ok()
597            .and_then(|node| node.facets.xtypes.as_ref())
598            .is_some_and(|x| x.element_is_external)
599    }
600
601    /// The user-declared annotations written on the element type of a
602    /// collection (DDS-XTypes 1.3 §7.2.2.6), in the order they were
603    /// written. For a map, the value's.
604    #[must_use]
605    pub fn element_annotations(
606        &self,
607        collection: Id<crate::TypeNode>,
608    ) -> &[crate::AppliedAnnotation] {
609        self.types
610            .get(collection)
611            .ok()
612            .and_then(|node| node.facets.xtypes.as_ref())
613            .map_or(&[], |x| x.element_annotations.as_slice())
614    }
615
616    /// The user-declared annotations written on the key type of a map.
617    #[must_use]
618    pub fn key_annotations(&self, map: Id<crate::TypeNode>) -> &[crate::AppliedAnnotation] {
619        self.types
620            .get(map)
621            .ok()
622            .and_then(|node| node.facets.xtypes.as_ref())
623            .map_or(&[], |x| x.key_annotations.as_slice())
624    }
625
626    /// The user-declared annotations written on the discriminator type of
627    /// a union (DDS-XTypes 1.3 §7.3.1.2.5).
628    #[must_use]
629    pub fn discriminator_annotations(
630        &self,
631        union_: Id<crate::TypeNode>,
632    ) -> &[crate::AppliedAnnotation] {
633        self.types
634            .get(union_)
635            .ok()
636            .and_then(|node| node.facets.xtypes.as_ref())
637            .map_or(&[], |x| x.discriminator_annotations.as_slice())
638    }
639
640    /// Whether the key of a map is `@external`.
641    #[must_use]
642    pub fn key_is_external(&self, map: Id<crate::TypeNode>) -> bool {
643        self.types
644            .get(map)
645            .ok()
646            .and_then(|node| node.facets.xtypes.as_ref())
647            .is_some_and(|x| x.key_is_external)
648    }
649
650    /// The behaviour that governs a union's discriminator.
651    /// DDS-XTypes 1.3 §7.3.1.2.1.12.5, Table 19.
652    #[must_use]
653    pub fn discriminator_try_construct(&self, union: Id<crate::TypeNode>) -> crate::TryConstruct {
654        let Ok(node) = self.types.get(union) else {
655            return crate::TryConstruct::ABSENT;
656        };
657        let TypeKind::Union { discriminator, .. } = &node.kind else {
658            return crate::TryConstruct::ABSENT;
659        };
660        let written = node
661            .facets
662            .xtypes
663            .as_ref()
664            .and_then(|x| x.discriminator_try_construct);
665        self.effective_try_construct(written, discriminator.resolved())
666    }
667
668    /// Whether a union's discriminator carries `@key`.
669    ///
670    /// DDS-XTypes 1.3 Table 21 lists `@key` against "Structure Members,
671    /// Union discriminator" and §7.2.2.4.4.4.8's example is `union
672    /// MyCommand switch (@key CommandKind)`. A union that answers true
673    /// here is a keyed topic type whose key holder is the discriminator
674    /// alone, and §7.2.4.4.4.5 — Table 19's `UNION_TYPE` row, "Either
675    /// the discriminators of both T1 and T2 are keys or neither are
676    /// keys" — makes it unassignable to a union that answers false.
677    ///
678    /// False for anything that is not a union, and false for a union
679    /// whose front end was not asked to carry the annotation — see
680    /// `zerodds_idl_lower::KeyOnUnionDiscriminator`.
681    #[must_use]
682    pub fn discriminator_is_key(&self, union: Id<crate::TypeNode>) -> bool {
683        let Ok(node) = self.types.get(union) else {
684            return false;
685        };
686        if !matches!(node.kind, TypeKind::Union { .. }) {
687            return false;
688        }
689        node.facets
690            .xtypes
691            .as_ref()
692            .is_some_and(|x| x.discriminator_is_key)
693    }
694
695    /// The value an enumerator has, written or positional.
696    ///
697    /// `@value(n)`, else the previous enumerator's value plus one,
698    /// starting at 0. OMG IDL 4.2 §7.4.13.
699    ///
700    /// Returns `None` only when the handle is stale or an explicit
701    /// expression does not evaluate — never a substituted 0, which would
702    /// give two enumerators the same value without saying so.
703    #[must_use]
704    pub fn effective_enumerator_value(&self, id: Id<crate::EnumeratorNode>) -> Option<i128> {
705        let node = self.enumerators.get(id).ok()?;
706        let siblings = self.enumerators_of(node.parent);
707        let mut previous: Option<i128> = None;
708        for sibling in siblings {
709            let current = self.enumerators.get(*sibling).ok()?;
710            let value = match current.value {
711                Some(expr) => self.eval_integral(expr).ok()?,
712                None => previous.map_or(0, |last| last.saturating_add(1)),
713            };
714            if *sibling == id {
715                return Some(value);
716            }
717            previous = Some(value);
718        }
719        None
720    }
721
722    /// The position a bitmask bit has, written or positional.
723    /// DDS-XTypes 1.3 §7.3.1.2.1.10.
724    #[must_use]
725    pub fn effective_bit_position(&self, id: Id<crate::BitValueNode>) -> Option<u16> {
726        let node = self.bit_values.get(id).ok()?;
727        let siblings = self.bits_of(node.parent);
728        let mut previous: Option<u16> = None;
729        for sibling in siblings {
730            let current = self.bit_values.get(*sibling).ok()?;
731            let position = match current.position {
732                Some(written) => written,
733                None => previous.map_or(0, |last| last.saturating_add(1)),
734            };
735            if *sibling == id {
736                return Some(position);
737            }
738            previous = Some(position);
739        }
740        None
741    }
742
743    /// The cases of a union, empty for every other kind.
744    #[must_use]
745    pub fn cases_of(&self, type_: Id<crate::TypeNode>) -> &[Id<crate::CaseNode>] {
746        match self.types.get(type_).map(|t| &t.kind) {
747            Ok(TypeKind::Union { cases, .. }) => cases,
748            _ => &[],
749        }
750    }
751
752    /// The operations an interface declares directly.
753    ///
754    /// **Not the whole answer.** `InterfaceNode.bases` exists and OMG
755    /// IDL 4.2 §7.4.3 gives a derived interface every operation of its
756    /// bases, so this list is short by exactly those. Only a caller that
757    /// writes the base into the target language — a Java `extends`, a
758    /// C++ base clause, a TypeScript `extends` — wants it; every caller
759    /// that emits a flat declaration wants [`Ast::all_operations_of`],
760    /// and one that took this instead emitted a type the source did not
761    /// describe with nothing to report it.
762    #[must_use]
763    pub fn operations_of(
764        &self,
765        interface: Id<crate::InterfaceNode>,
766    ) -> &[Id<crate::OperationNode>] {
767        self.interfaces
768            .get(interface)
769            .map_or(&[], |i| &i.operations)
770    }
771
772    /// Every operation an interface has, its bases' before its own.
773    ///
774    /// [`Ast::operations_of`] answers what an interface *declares*;
775    /// this answers what it *has*. OMG IDL 4.2 §7.4.3: a derived
776    /// interface "inherits all the operations and attributes of its
777    /// base interfaces", and a client holding a `Derived` may call
778    /// every one of them. A generated binding that carries only the
779    /// declared ones is a type the source did not describe, and nothing
780    /// reports it — the operation is simply not there.
781    ///
782    /// Multiple inheritance is a graph and not a chain, so bases are
783    /// walked in declaration order — the order §7.4.3 resolves an
784    /// operation lookup along — and an interface reached twice through
785    /// a diamond contributes its operations once. The depth bound and
786    /// the seen list are [`Ast::all_members_of`]'s, for the same reason:
787    /// an inheritance cycle is invalid IDL and looping here would hang
788    /// instead of reporting it.
789    #[must_use]
790    pub fn all_operations_of(
791        &self,
792        interface: Id<crate::InterfaceNode>,
793    ) -> Vec<Id<crate::OperationNode>> {
794        let mut seen = Vec::new();
795        let mut out = Vec::new();
796        self.collect_operations(interface, &mut seen, &mut out, 0);
797        out
798    }
799
800    fn collect_operations(
801        &self,
802        interface: Id<crate::InterfaceNode>,
803        seen: &mut Vec<Id<crate::InterfaceNode>>,
804        out: &mut Vec<Id<crate::OperationNode>>,
805        depth: usize,
806    ) {
807        if depth >= MAX_INHERITANCE_DEPTH || seen.contains(&interface) {
808            return;
809        }
810        seen.push(interface);
811        let Ok(node) = self.interfaces.get(interface) else {
812            return;
813        };
814        for base in &node.bases {
815            if let Some(base) = base.resolved() {
816                self.collect_operations(base, seen, out, depth + 1);
817            }
818        }
819        for operation in &node.operations {
820            if !out.contains(operation) {
821                out.push(*operation);
822            }
823        }
824    }
825
826    /// Every node reachable from `root`, itself included, in pre-order.
827    ///
828    /// Terminates on recursive types: a struct containing
829    /// `sequence<Self>` appears once. Spec §6.
830    #[must_use]
831    pub fn descendants(&self, root: AnyId) -> Vec<AnyId> {
832        let ids = walk_graph(
833            &[root.node_id()],
834            |node| self.successors_of(node),
835            WalkOrder::PreOrder,
836        );
837        ids.into_iter()
838            .filter_map(|id| self.resolve_node_id(id))
839            .collect()
840    }
841
842    /// Every node the model holds, in identity order.
843    pub fn all_nodes(&self) -> impl Iterator<Item = AnyId> + '_ {
844        self.by_node_id.values().copied()
845    }
846
847    /// The nodes one node points at, for traversal.
848    pub(crate) fn successors_of(&self, node: NodeId) -> Vec<NodeId> {
849        let Some(any) = self.resolve_node_id(node) else {
850            return Vec::new();
851        };
852        let mut out = Vec::new();
853        match any {
854            AnyId::Module(id, _) => {
855                if let Ok(m) = self.modules.get(id) {
856                    out.extend(m.children.iter().map(|c| c.node_id()));
857                }
858            }
859            AnyId::Type(id, _) => {
860                let Ok(t) = self.types.get(id) else {
861                    return out;
862                };
863                for reference in t.kind.type_refs() {
864                    if let Some(target) = reference.resolved() {
865                        if let Ok(node) = self.types.get(target) {
866                            out.push(node.node_id);
867                        }
868                    }
869                }
870                match &t.kind {
871                    TypeKind::Structure { members, .. }
872                    | TypeKind::Valuetype { state: members, .. } => {
873                        for member in members {
874                            if let Ok(m) = self.members.get(*member) {
875                                out.push(m.node_id);
876                            }
877                        }
878                    }
879                    TypeKind::Union { cases, .. } => {
880                        for case in cases {
881                            if let Ok(c) = self.cases.get(*case) {
882                                out.push(c.node_id);
883                            }
884                        }
885                    }
886                    TypeKind::Enumeration { enumerators } => {
887                        for enumerator in enumerators {
888                            if let Ok(e) = self.enumerators.get(*enumerator) {
889                                out.push(e.node_id);
890                            }
891                        }
892                    }
893                    TypeKind::Bitmask { bits } => {
894                        for bit in bits {
895                            if let Ok(b) = self.bit_values.get(*bit) {
896                                out.push(b.node_id);
897                            }
898                        }
899                    }
900                    TypeKind::Bitset { fields, .. } => {
901                        for field in fields {
902                            if let Ok(b) = self.bitfields.get(*field) {
903                                out.push(b.node_id);
904                            }
905                        }
906                    }
907                    _ => {}
908                }
909            }
910            AnyId::Member(id, _) => {
911                if let Ok(m) = self.members.get(id) {
912                    if let Some(target) = m.type_.resolved() {
913                        if let Ok(t) = self.types.get(target) {
914                            out.push(t.node_id);
915                        }
916                    }
917                }
918            }
919            AnyId::Case(id, _) => {
920                if let Ok(c) = self.cases.get(id) {
921                    if let Ok(m) = self.members.get(c.member) {
922                        out.push(m.node_id);
923                    }
924                }
925            }
926            AnyId::Interface(id, _) => {
927                if let Ok(i) = self.interfaces.get(id) {
928                    for operation in &i.operations {
929                        if let Ok(o) = self.operations.get(*operation) {
930                            out.push(o.node_id);
931                        }
932                    }
933                    for attribute in &i.attributes {
934                        if let Ok(a) = self.attributes.get(*attribute) {
935                            out.push(a.node_id);
936                        }
937                    }
938                }
939            }
940            AnyId::Operation(id, _) => {
941                if let Ok(o) = self.operations.get(id) {
942                    for param in &o.params {
943                        if let Ok(p) = self.params.get(*param) {
944                            out.push(p.node_id);
945                        }
946                    }
947                }
948            }
949            AnyId::Exception(id, _) => {
950                if let Ok(e) = self.exceptions.get(id) {
951                    for member in &e.members {
952                        if let Ok(m) = self.members.get(*member) {
953                            out.push(m.node_id);
954                        }
955                    }
956                }
957            }
958            AnyId::Library(id, _) => {
959                if let Ok(l) = self.libraries.get(id) {
960                    out.extend(l.children.iter().map(|c| c.node_id()));
961                }
962            }
963            AnyId::Domain(id, _) => {
964                if let Ok(d) = self.domains.get(id) {
965                    for topic in &d.topics {
966                        if let Ok(t) = self.topics.get(*topic) {
967                            out.push(t.node_id);
968                        }
969                    }
970                }
971            }
972            AnyId::Participant(id, _) => {
973                if let Ok(p) = self.participants.get(id) {
974                    for endpoint in &p.endpoints {
975                        if let Ok(e) = self.endpoints.get(*endpoint) {
976                            out.push(e.node_id);
977                        }
978                    }
979                }
980            }
981            AnyId::AnnotationDef(id, _) => {
982                if let Ok(a) = self.annotation_defs.get(id) {
983                    for member in &a.members {
984                        if let Ok(m) = self.annotation_members.get(*member) {
985                            out.push(m.node_id);
986                        }
987                    }
988                }
989            }
990            _ => {}
991        }
992        out
993    }
994
995    /// Every type reachable from `type_`, itself included.
996    ///
997    /// The layout cache uses this to know which cached plans a change
998    /// invalidates. Spec §7.
999    #[must_use]
1000    pub fn reachable_types(&self, type_: Id<crate::TypeNode>) -> VisitSet {
1001        let Ok(node) = self.types.get(type_) else {
1002            return VisitSet::new();
1003        };
1004        zerodds_ast_core::reachable(&[node.node_id], |id| self.successors_of(id))
1005    }
1006
1007    /// Rebuild every index from the model's current content.
1008    ///
1009    /// Called after loading a snapshot; ordinary editing maintains the
1010    /// indices from the changeset instead. Spec §6, §9.
1011    pub fn reindex(&mut self) {
1012        self.by_scoped_name.clear();
1013        self.by_type_identifier.clear();
1014        self.by_ros_name.clear();
1015        self.by_repository_id.clear();
1016
1017        let nodes: Vec<AnyId> = self.by_node_id.values().copied().collect();
1018        for any in nodes {
1019            self.index_node(any);
1020        }
1021    }
1022
1023    /// Add one node to every index it belongs in.
1024    pub(crate) fn index_node(&mut self, any: AnyId) {
1025        let node = any.node_id();
1026        // The object-reference type derived from an interface carries the
1027        // interface's name so a backend can render it, but the name in
1028        // the scope belongs to the interface. Indexing both would make
1029        // every use of that name ambiguous.
1030        let derived_object_ref = match any {
1031            AnyId::Type(id, _) => self.types.get(id).is_ok_and(|node| {
1032                matches!(node.kind, crate::TypeKind::ObjectRef { interface: Some(_) })
1033            }),
1034            _ => false,
1035        };
1036        if let Some(name) = self.scoped_name_of(any).filter(|_| !derived_object_ref) {
1037            self.by_scoped_name.insert(name, node);
1038        }
1039        let Some(facets) = self.facets(any) else {
1040            return;
1041        };
1042        let ros_name = facets.ros2.as_ref().and_then(|r| r.ros_name.clone());
1043        let repository_id = facets.corba.as_ref().and_then(|c| c.repository_id.clone());
1044        if let Some(name) = ros_name {
1045            self.by_ros_name.insert(name, node);
1046        }
1047        if let Some(id) = repository_id {
1048            self.by_repository_id.insert(id, node);
1049        }
1050    }
1051
1052    /// Remove one node from every index.
1053    pub(crate) fn unindex_node(&mut self, node: NodeId) {
1054        self.by_scoped_name.remove_node(node);
1055        self.by_type_identifier.remove_node(node);
1056        self.by_ros_name.remove_node(node);
1057        self.by_repository_id.remove_node(node);
1058    }
1059
1060    /// Register a type identifier for a type.
1061    ///
1062    /// The identifier is computed by the XTypes runtime, not here; the
1063    /// model only indexes it. Spec §1.2.
1064    ///
1065    /// # Errors
1066    /// [`crate::AstError::Core`] with a stale handle.
1067    pub fn set_type_identifier(
1068        &mut self,
1069        type_: Id<crate::TypeNode>,
1070        key: TypeIdentifierKey,
1071    ) -> Result<(), crate::AstError> {
1072        let node = self.types.get(type_)?.node_id;
1073        self.by_type_identifier.insert(key, node);
1074        Ok(())
1075    }
1076}