Skip to main content

zerodds_types/dynamic/
collection.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Collection (`sequence`/`array`) `DynamicType`s that retain a **fully
4//! resolved** element [`DynamicType`].
5//!
6//! The public `TypeDescriptor` keeps only a shallow element descriptor
7//! (`element_type: Box<TypeDescriptor>`) — enough for scalar elements, but it
8//! drops the members of a *composite* element (`sequence<Struct>` etc.), which
9//! the reflective codec needs to recurse. Rather than widen the core type model
10//! (and the TypeObject bridge that builds on it), this module constructs the
11//! collection `DynamicType` with the resolved element kept as its single
12//! synthetic member (index 0). [`resolved_element`] reads it back; the codec
13//! prefers it and falls back to rebuilding a scalar element from the descriptor
14//! when absent (so collections built the plain way still decode).
15
16use alloc::vec;
17
18use super::descriptor::{MemberDescriptor, TypeDescriptor, TypeKind};
19use super::type_::{DynamicType, DynamicTypeInner, DynamicTypeMember};
20
21/// The synthetic member name under which the resolved element type is stored.
22const ELEMENT_MEMBER: &str = "@element";
23
24/// Build a `sequence<Element>` type (bound `0` = unbounded) that retains the
25/// fully-resolved `element` type for the reflective codec.
26#[must_use]
27pub fn sequence_of(element: DynamicType, bound: u32) -> DynamicType {
28    let desc = TypeDescriptor::sequence("seq", element.descriptor().clone(), bound);
29    with_element(desc, element)
30}
31
32/// Build an `Element[dims...]` array type that retains the fully-resolved
33/// `element` type.
34#[must_use]
35pub fn array_of(element: DynamicType, dims: alloc::vec::Vec<u32>, name: &str) -> DynamicType {
36    let desc = TypeDescriptor::array(name, element.descriptor().clone(), dims);
37    with_element(desc, element)
38}
39
40fn with_element(desc: TypeDescriptor, element: DynamicType) -> DynamicType {
41    let mut md = MemberDescriptor::new(ELEMENT_MEMBER, 0, element.descriptor().clone());
42    md.index = 0;
43    DynamicType::from_inner(DynamicTypeInner {
44        descriptor: desc,
45        members: vec![DynamicTypeMember {
46            descriptor: md,
47            member_type: element,
48        }],
49    })
50}
51
52/// The resolved element [`DynamicType`] of a collection, if one was attached via
53/// [`sequence_of`] / [`array_of`]. Returns `None` for collections that carry only
54/// a shallow scalar element descriptor (the codec rebuilds those itself).
55#[must_use]
56pub fn resolved_element(ty: &DynamicType) -> Option<&DynamicType> {
57    if matches!(ty.kind(), TypeKind::Sequence | TypeKind::Array) {
58        ty.member_by_index(0).map(|m| m.dynamic_type())
59    } else {
60        None
61    }
62}
63
64#[cfg(test)]
65#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
66mod tests {
67    use super::*;
68    use crate::dynamic::builder::DynamicTypeBuilderFactory as F;
69
70    #[test]
71    fn sequence_of_struct_retains_element() {
72        let mut b = F::create_struct("E");
73        b.add_struct_member("a", 0, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
74            .unwrap();
75        let elem = b.build().unwrap();
76        let seq = sequence_of(elem, 0);
77        assert_eq!(seq.kind(), TypeKind::Sequence);
78        let re = resolved_element(&seq).expect("resolved element");
79        assert_eq!(re.kind(), TypeKind::Structure);
80        assert_eq!(re.member_count(), 1);
81    }
82
83    #[test]
84    fn scalar_sequence_has_no_resolved_element() {
85        // A plain sequence descriptor (no synthetic member) → None.
86        let desc = TypeDescriptor::sequence(
87            "seq",
88            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
89            0,
90        );
91        let ty = F::create_type(desc).unwrap().build().unwrap();
92        assert!(resolved_element(&ty).is_none());
93    }
94}