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}