Skip to main content

zerodds_types/dynamic/
bridge.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DynamicType ↔ TypeObject Bridge (XTypes 1.3 §7.6.3 + §7.6.4).
4//!
5//! This bridge is the bracket between the **wire representation**
6//! (`TypeObject`, with `MinimalTypeObject` + `CompleteTypeObject` as the
7//! discrimination) and the **runtime API** (`DynamicType`).
8//!
9//! Implements all 10 TypeObject kinds: struct + union + enum + bitmask +
10//! bitset + annotation + alias plus the collection kinds sequence/array/map
11//! (XTypes §7.3.4.4: `CompleteSequenceType` / `CompleteArrayType` /
12//! `CompleteMapType`). Anonymous plain collections are additionally
13//! referenced inline via `TypeIdentifier` (PlainCollection); `to_type_object`
14//! returns the full collection `CompleteTypeObject` variant here.
15//!
16//! The reverse direction (`create_type_w_type_object`) resolves a top-level
17//! Complete TypeObject of any of the 10 kinds; the registry-aware
18//! `create_type_w_type_object_in` additionally follows composite member
19//! `EquivalenceHash` references through a [`crate::resolve::TypeRegistry`] so a
20//! struct/union with nested struct/union/enum/bitmask/bitset MEMBERS yields a
21//! fully-populated `DynamicType`. Collections whose element/key/value is itself a
22//! composite (`sequence<Struct>`, `Struct[N]`, `map<K,Struct>`, and arbitrary
23//! nesting) are also fully resolved — retained via [`super::collection`] so the
24//! reflective codec walks them. `map<K,V>` is codec-complete (byte-identical to
25//! the compiled `BTreeMap`); composite `typedef` targets resolve transparently.
26//!
27//! **Minimal TypeObjects** resolve too (`resolve_minimal`): a Minimal object
28//! carries the full structure — member ids, types, flags, nesting — in its shared
29//! `common` fields, so a Minimal-resolved type encodes/decodes wire data
30//! byte-identically to its Complete twin. The one intrinsic difference is that a
31//! Minimal object holds only 4-byte name *hashes* (by XTypes design), so member /
32//! type names are synthetic (`m_<hash>`) unless the Complete form is co-published.
33//! (The bare, registry-less `create_type_w_type_object` still declines Minimal,
34//! since nested minimal-hash members need the registry.)
35
36use alloc::format;
37use alloc::string::{String, ToString};
38use alloc::vec::Vec;
39
40use crate::type_identifier::{PrimitiveKind, TypeIdentifier};
41use crate::type_object::common::NameHash;
42use crate::type_object::common::{CommonStructMember, CommonUnionMember};
43use crate::type_object::complete::{
44    CompleteAliasBody, CompleteAliasHeader, CompleteAliasType, CompleteAnnotationParameter,
45    CompleteAnnotationType, CompleteArrayType, CompleteBitfield, CompleteBitflag,
46    CompleteBitmaskType, CompleteBitsetType, CompleteCollectionElement,
47    CompleteDiscriminatorMember, CompleteEnumeratedHeader, CompleteEnumeratedLiteral,
48    CompleteEnumeratedType, CompleteMapType, CompleteSequenceType, CompleteStructHeader,
49    CompleteStructMember, CompleteStructType, CompleteUnionHeader, CompleteUnionMember,
50    CompleteUnionType,
51};
52use crate::type_object::flags::{
53    AliasMemberFlag, AliasTypeFlag, AnnotationParameterFlag, AnnotationTypeFlag, BitfieldFlag,
54    BitflagFlag, BitmaskTypeFlag, BitsetTypeFlag, CollectionElementFlag, CollectionTypeFlag,
55    EnumLiteralFlag, EnumTypeFlag, StructMemberFlag, StructTypeFlag, UnionDiscriminatorFlag,
56    UnionMemberFlag, UnionTypeFlag,
57};
58use crate::type_object::minimal::CommonDiscriminatorMember;
59use crate::type_object::minimal::{
60    CommonBitfield, CommonBitflag, CommonCollectionElement, CommonEnumeratedHeader,
61    CommonEnumeratedLiteral, MinimalTypeObject,
62};
63use crate::type_object::{CompleteTypeObject, TypeObject};
64
65use super::builder::{DynamicTypeBuilder, DynamicTypeBuilderFactory};
66use super::collection;
67use super::descriptor::{ExtensibilityKind, MemberDescriptor, TypeDescriptor, TypeKind};
68use super::error::DynamicError;
69use super::type_::DynamicType;
70
71// ----------------------------------------------------------------------
72// DynamicType → TypeObject (spec §7.6.3 — for discovery + TypeLookup)
73// ----------------------------------------------------------------------
74
75impl DynamicType {
76    /// Spec §7.6.3 — converts this DynamicType into a
77    /// `CompleteTypeObject`. Always returns complete (not minimal),
78    /// because DynamicType carries the names + annotations.
79    ///
80    /// Scope:
81    /// - Struct with primitive + string + sequence/array members.
82    /// - Union, enum, alias as their own helper methods (see module doc).
83    ///
84    /// # Errors
85    /// `Unsupported` for not-yet-implemented kinds,
86    /// `Inconsistent` if the type is malformed.
87    pub fn to_type_object(&self) -> Result<TypeObject, DynamicError> {
88        match self.kind() {
89            TypeKind::Structure => Ok(TypeObject::Complete(CompleteTypeObject::Struct(
90                self.to_complete_struct()?,
91            ))),
92            TypeKind::Union => Ok(TypeObject::Complete(CompleteTypeObject::Union(
93                self.to_complete_union()?,
94            ))),
95            TypeKind::Enumeration => Ok(TypeObject::Complete(CompleteTypeObject::Enumerated(
96                self.to_complete_enum()?,
97            ))),
98            TypeKind::Bitmask => Ok(TypeObject::Complete(CompleteTypeObject::Bitmask(
99                self.to_complete_bitmask()?,
100            ))),
101            TypeKind::Alias => Ok(TypeObject::Complete(CompleteTypeObject::Alias(
102                self.to_complete_alias()?,
103            ))),
104            TypeKind::Sequence => Ok(TypeObject::Complete(CompleteTypeObject::Sequence(
105                self.to_complete_sequence()?,
106            ))),
107            TypeKind::Array => Ok(TypeObject::Complete(CompleteTypeObject::Array(
108                self.to_complete_array()?,
109            ))),
110            TypeKind::Map => Ok(TypeObject::Complete(CompleteTypeObject::Map(
111                self.to_complete_map()?,
112            ))),
113            TypeKind::Bitset => Ok(TypeObject::Complete(CompleteTypeObject::Bitset(
114                self.to_complete_bitset()?,
115            ))),
116            TypeKind::Annotation => Ok(TypeObject::Complete(CompleteTypeObject::Annotation(
117                self.to_complete_annotation()?,
118            ))),
119            other => Err(DynamicError::unsupported(format!(
120                "to_type_object: {other:?} is a primitive/no-type kind without TypeObject",
121            ))),
122        }
123    }
124
125    /// XTypes §7.3.4.4 — `sequence<T>` → `CompleteSequenceType`.
126    /// `bound = 0` bedeutet unbounded.
127    fn to_complete_sequence(&self) -> Result<CompleteSequenceType, DynamicError> {
128        use crate::type_object::common::{
129            AppliedBuiltinTypeAnnotations, CompleteTypeDetail, OptionalAppliedAnnotationSeq,
130        };
131        let element = self
132            .descriptor()
133            .element_type
134            .as_ref()
135            .ok_or_else(|| DynamicError::inconsistent("sequence type missing element_type"))?;
136        let bound = self.descriptor().bound.first().copied().unwrap_or(0);
137        Ok(CompleteSequenceType {
138            collection_flag: CollectionTypeFlag(0),
139            bound,
140            detail: CompleteTypeDetail {
141                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
142                ann_custom: OptionalAppliedAnnotationSeq::default(),
143                type_name: self.descriptor().name.clone(),
144            },
145            element: complete_collection_element(element)?,
146        })
147    }
148
149    /// XTypes §7.3.4.4 — `T[N]` → `CompleteArrayType`.
150    /// `bound_seq` traegt alle Array-Dimensionen.
151    fn to_complete_array(&self) -> Result<CompleteArrayType, DynamicError> {
152        use crate::type_object::common::{
153            AppliedBuiltinTypeAnnotations, CompleteTypeDetail, OptionalAppliedAnnotationSeq,
154        };
155        let element = self
156            .descriptor()
157            .element_type
158            .as_ref()
159            .ok_or_else(|| DynamicError::inconsistent("array type missing element_type"))?;
160        let bound_seq = self.descriptor().bound.clone();
161        if bound_seq.is_empty() {
162            return Err(DynamicError::inconsistent(
163                "array type missing dimensions (bound)",
164            ));
165        }
166        Ok(CompleteArrayType {
167            collection_flag: CollectionTypeFlag(0),
168            bound_seq,
169            detail: CompleteTypeDetail {
170                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
171                ann_custom: OptionalAppliedAnnotationSeq::default(),
172                type_name: self.descriptor().name.clone(),
173            },
174            element: complete_collection_element(element)?,
175        })
176    }
177
178    /// XTypes §7.3.4.4 — `map<K, V>` → `CompleteMapType`.
179    /// `element_type` traegt den Value-, `key_element_type` den Key-Typ.
180    fn to_complete_map(&self) -> Result<CompleteMapType, DynamicError> {
181        use crate::type_object::common::{
182            AppliedBuiltinTypeAnnotations, CompleteTypeDetail, OptionalAppliedAnnotationSeq,
183        };
184        let value =
185            self.descriptor().element_type.as_ref().ok_or_else(|| {
186                DynamicError::inconsistent("map type missing element_type (value)")
187            })?;
188        let key = self
189            .descriptor()
190            .key_element_type
191            .as_ref()
192            .ok_or_else(|| DynamicError::inconsistent("map type missing key_element_type"))?;
193        let bound = self.descriptor().bound.first().copied().unwrap_or(0);
194        Ok(CompleteMapType {
195            collection_flag: CollectionTypeFlag(0),
196            bound,
197            detail: CompleteTypeDetail {
198                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
199                ann_custom: OptionalAppliedAnnotationSeq::default(),
200                type_name: self.descriptor().name.clone(),
201            },
202            key: complete_collection_element(key)?,
203            element: complete_collection_element(value)?,
204        })
205    }
206
207    fn to_complete_alias(&self) -> Result<CompleteAliasType, DynamicError> {
208        use crate::type_object::common::{
209            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteTypeDetail,
210            OptionalAppliedAnnotationSeq,
211        };
212        let header = CompleteAliasHeader {
213            detail: CompleteTypeDetail {
214                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
215                ann_custom: OptionalAppliedAnnotationSeq::default(),
216                type_name: self.descriptor().name.clone(),
217            },
218        };
219        let element = self.descriptor().element_type.as_ref().ok_or_else(|| {
220            DynamicError::inconsistent("alias type missing element_type (target)")
221        })?;
222        let related_type = descriptor_to_type_identifier(element)?;
223        let body = CompleteAliasBody {
224            related_flags: AliasMemberFlag(0),
225            related_type,
226            ann_builtin: AppliedBuiltinMemberAnnotations::default(),
227            ann_custom: OptionalAppliedAnnotationSeq::default(),
228        };
229        Ok(CompleteAliasType {
230            alias_flags: AliasTypeFlag(0),
231            header,
232            body,
233        })
234    }
235
236    fn to_complete_enum(&self) -> Result<CompleteEnumeratedType, DynamicError> {
237        use crate::type_object::common::{
238            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
239            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
240        };
241        let bit_bound = self.descriptor().bound.first().copied().unwrap_or(32);
242        let bit_bound_u16 = u16::try_from(bit_bound).unwrap_or(32);
243        let header = CompleteEnumeratedHeader {
244            common: CommonEnumeratedHeader {
245                bit_bound: bit_bound_u16,
246            },
247            detail: CompleteTypeDetail {
248                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
249                ann_custom: OptionalAppliedAnnotationSeq::default(),
250                type_name: self.descriptor().name.clone(),
251            },
252        };
253        let mut literal_seq: Vec<CompleteEnumeratedLiteral> =
254            Vec::with_capacity(self.member_count() as usize);
255        for m in self.members() {
256            let mut flags_bits: u16 = 0;
257            if m.descriptor().is_default_label {
258                flags_bits |= EnumLiteralFlag::IS_DEFAULT_LITERAL;
259            }
260            // Enum-Ordinalwert = MemberId (Convention).
261            let value = i32::try_from(m.id()).map_err(|_| {
262                DynamicError::inconsistent(format!("enum literal id {} exceeds i32 range", m.id()))
263            })?;
264            literal_seq.push(CompleteEnumeratedLiteral {
265                common: CommonEnumeratedLiteral {
266                    value,
267                    flags: EnumLiteralFlag(flags_bits),
268                },
269                detail: CompleteMemberDetail {
270                    name: m.name().to_string(),
271                    ann_builtin: AppliedBuiltinMemberAnnotations::default(),
272                    ann_custom: OptionalAppliedAnnotationSeq::default(),
273                },
274            });
275        }
276        Ok(CompleteEnumeratedType {
277            enum_flags: EnumTypeFlag(0),
278            header,
279            literal_seq,
280        })
281    }
282
283    fn to_complete_bitmask(&self) -> Result<CompleteBitmaskType, DynamicError> {
284        use crate::type_object::common::{
285            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
286            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
287        };
288        let bit_bound = self.descriptor().bound.first().copied().unwrap_or(32);
289        let bit_bound_u16 = u16::try_from(bit_bound).unwrap_or(32);
290        let detail = CompleteTypeDetail {
291            ann_builtin: AppliedBuiltinTypeAnnotations::default(),
292            ann_custom: OptionalAppliedAnnotationSeq::default(),
293            type_name: self.descriptor().name.clone(),
294        };
295        let mut flag_seq: Vec<CompleteBitflag> = Vec::with_capacity(self.member_count() as usize);
296        for m in self.members() {
297            let position = u16::try_from(m.id()).map_err(|_| {
298                DynamicError::inconsistent(format!(
299                    "bitmask flag position {} exceeds u16 range",
300                    m.id()
301                ))
302            })?;
303            flag_seq.push(CompleteBitflag {
304                common: CommonBitflag {
305                    position,
306                    flags: BitflagFlag(0),
307                },
308                detail: CompleteMemberDetail {
309                    name: m.name().to_string(),
310                    ann_builtin: AppliedBuiltinMemberAnnotations::default(),
311                    ann_custom: OptionalAppliedAnnotationSeq::default(),
312                },
313            });
314        }
315        Ok(CompleteBitmaskType {
316            bitmask_flags: BitmaskTypeFlag(0),
317            bit_bound: bit_bound_u16,
318            detail,
319            flag_seq,
320        })
321    }
322
323    /// Spec §7.3.4.4 — Bitset: ein `CompleteBitfield` je Member, mit
324    /// Bit-Startposition (`MemberDescriptor.id`), Breite
325    /// (`MemberDescriptor.bit_bound`) und Holding-Type (TypeKind-Byte des
326    /// Member-Typs, der ein primitiver Integer-Typ sein muss).
327    fn to_complete_bitset(&self) -> Result<CompleteBitsetType, DynamicError> {
328        use crate::type_object::common::{
329            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
330            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
331        };
332        let detail = CompleteTypeDetail {
333            ann_builtin: AppliedBuiltinTypeAnnotations::default(),
334            ann_custom: OptionalAppliedAnnotationSeq::default(),
335            type_name: self.descriptor().name.clone(),
336        };
337        let mut field_seq: Vec<CompleteBitfield> = Vec::with_capacity(self.member_count() as usize);
338        for m in self.members() {
339            let position = u16::try_from(m.id()).map_err(|_| {
340                DynamicError::inconsistent(format!(
341                    "bitset field position {} exceeds u16 range",
342                    m.id()
343                ))
344            })?;
345            let holder_type = match descriptor_to_type_identifier(
346                m.descriptor().member_type.as_ref(),
347            )? {
348                TypeIdentifier::Primitive(pk) => pk.to_u8(),
349                other => {
350                    return Err(DynamicError::inconsistent(format!(
351                        "bitset field {:?} holder must be a primitive integer type, got {other:?}",
352                        m.name()
353                    )));
354                }
355            };
356            let bitcount = m.descriptor().bit_bound.ok_or_else(|| {
357                DynamicError::inconsistent(format!(
358                    "bitset field {:?} missing bit_bound (bitfield width)",
359                    m.name()
360                ))
361            })?;
362            field_seq.push(CompleteBitfield {
363                common: CommonBitfield {
364                    position,
365                    flags: BitfieldFlag(0),
366                    bitcount,
367                    holder_type,
368                },
369                detail: CompleteMemberDetail {
370                    name: m.name().to_string(),
371                    ann_builtin: AppliedBuiltinMemberAnnotations::default(),
372                    ann_custom: OptionalAppliedAnnotationSeq::default(),
373                },
374            });
375        }
376        Ok(CompleteBitsetType {
377            bitset_flags: BitsetTypeFlag(0),
378            detail,
379            field_seq,
380        })
381    }
382
383    /// Spec §7.3.4.4 — Annotation: ein `CompleteAnnotationParameter` je
384    /// Member (Parameter), mit Member-Id, Parameter-Typ, Name und dem
385    /// (opaque als UTF-8-Bytes abgelegten) Default-Value aus
386    /// `MemberDescriptor.default_value`.
387    fn to_complete_annotation(&self) -> Result<CompleteAnnotationType, DynamicError> {
388        use crate::type_object::common::{
389            AppliedBuiltinTypeAnnotations, CompleteTypeDetail, OptionalAppliedAnnotationSeq,
390        };
391        let detail = CompleteTypeDetail {
392            ann_builtin: AppliedBuiltinTypeAnnotations::default(),
393            ann_custom: OptionalAppliedAnnotationSeq::default(),
394            type_name: self.descriptor().name.clone(),
395        };
396        let mut member_seq: Vec<CompleteAnnotationParameter> =
397            Vec::with_capacity(self.member_count() as usize);
398        for m in self.members() {
399            let member_type_id =
400                descriptor_to_type_identifier(m.descriptor().member_type.as_ref())?;
401            let default_value = m
402                .descriptor()
403                .default_value
404                .as_deref()
405                .map(|s| s.as_bytes().to_vec())
406                .unwrap_or_default();
407            member_seq.push(CompleteAnnotationParameter {
408                member_id: m.id(),
409                member_flags: AnnotationParameterFlag(0),
410                member_type_id,
411                name: m.name().to_string(),
412                default_value,
413            });
414        }
415        Ok(CompleteAnnotationType {
416            annotation_flag: AnnotationTypeFlag(0),
417            detail,
418            member_seq,
419        })
420    }
421
422    fn to_complete_union(&self) -> Result<CompleteUnionType, DynamicError> {
423        use crate::type_object::common::{
424            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
425            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
426        };
427        let union_flags = UnionTypeFlag(extensibility_to_flag_bits(
428            self.descriptor().extensibility_kind,
429        ));
430        let header = CompleteUnionHeader {
431            detail: CompleteTypeDetail {
432                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
433                ann_custom: OptionalAppliedAnnotationSeq::default(),
434                type_name: self.descriptor().name.clone(),
435            },
436        };
437        let disc_descriptor = self
438            .descriptor()
439            .discriminator_type
440            .as_ref()
441            .ok_or_else(|| DynamicError::inconsistent("union type missing discriminator_type"))?;
442        let disc_type = descriptor_to_type_identifier(disc_descriptor)?;
443        let discriminator = CompleteDiscriminatorMember {
444            common: CommonDiscriminatorMember {
445                member_flags: UnionDiscriminatorFlag(0),
446                type_id: disc_type,
447            },
448            ann_builtin: AppliedBuiltinTypeAnnotations::default(),
449            ann_custom: OptionalAppliedAnnotationSeq::default(),
450        };
451        let mut member_seq: Vec<CompleteUnionMember> =
452            Vec::with_capacity(self.member_count() as usize);
453        for m in self.members() {
454            let mut flags_bits: u16 = 0;
455            if m.descriptor().is_default_label {
456                flags_bits |= UnionMemberFlag::IS_DEFAULT;
457            }
458            // Spec §7.3.4.5.3.2 — case labels are `long` (i32).
459            let label_seq: Vec<i32> = m
460                .descriptor()
461                .label
462                .iter()
463                .map(|&v| i32::try_from(v).unwrap_or_default())
464                .collect();
465            let common = CommonUnionMember {
466                member_id: m.id(),
467                member_flags: UnionMemberFlag(flags_bits),
468                type_id: descriptor_to_type_identifier(m.descriptor().member_type.as_ref())?,
469                label_seq,
470            };
471            let detail = CompleteMemberDetail {
472                name: m.name().to_string(),
473                ann_builtin: AppliedBuiltinMemberAnnotations::default(),
474                ann_custom: OptionalAppliedAnnotationSeq::default(),
475            };
476            member_seq.push(CompleteUnionMember { common, detail });
477        }
478        Ok(CompleteUnionType {
479            union_flags,
480            header,
481            discriminator,
482            member_seq,
483        })
484    }
485
486    fn to_complete_struct(&self) -> Result<CompleteStructType, DynamicError> {
487        use crate::type_object::common::{
488            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
489            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
490        };
491        let struct_flags = StructTypeFlag(extensibility_to_flag_bits(
492            self.descriptor().extensibility_kind,
493        ));
494        let header = CompleteStructHeader {
495            base_type: TypeIdentifier::None,
496            detail: CompleteTypeDetail {
497                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
498                ann_custom: OptionalAppliedAnnotationSeq::default(),
499                type_name: self.descriptor().name.clone(),
500            },
501        };
502        let mut member_seq: Vec<CompleteStructMember> =
503            Vec::with_capacity(self.member_count() as usize);
504        for m in self.members() {
505            let mut flags_bits: u16 = 0;
506            if m.descriptor().is_key {
507                flags_bits |= StructMemberFlag::IS_KEY;
508            }
509            if m.descriptor().is_optional {
510                flags_bits |= StructMemberFlag::IS_OPTIONAL;
511            }
512            if m.descriptor().is_must_understand {
513                flags_bits |= StructMemberFlag::IS_MUST_UNDERSTAND;
514            }
515            if m.descriptor().is_shared {
516                flags_bits |= StructMemberFlag::IS_EXTERNAL;
517            }
518            let common = CommonStructMember {
519                member_id: m.id(),
520                member_flags: StructMemberFlag(flags_bits),
521                member_type_id: descriptor_to_type_identifier(m.descriptor().member_type.as_ref())?,
522            };
523            let detail = CompleteMemberDetail {
524                name: m.name().to_string(),
525                ann_builtin: AppliedBuiltinMemberAnnotations::default(),
526                ann_custom: OptionalAppliedAnnotationSeq::default(),
527            };
528            member_seq.push(CompleteStructMember { common, detail });
529        }
530        Ok(CompleteStructType {
531            struct_flags,
532            header,
533            member_seq,
534        })
535    }
536}
537
538const fn extensibility_to_flag_bits(ext: ExtensibilityKind) -> u16 {
539    match ext {
540        ExtensibilityKind::Final => StructTypeFlag::IS_FINAL,
541        ExtensibilityKind::Appendable => StructTypeFlag::IS_APPENDABLE,
542        ExtensibilityKind::Mutable => StructTypeFlag::IS_MUTABLE,
543    }
544}
545
546const fn flag_bits_to_extensibility(flags: u16) -> ExtensibilityKind {
547    if flags & StructTypeFlag::IS_FINAL != 0 {
548        ExtensibilityKind::Final
549    } else if flags & StructTypeFlag::IS_MUTABLE != 0 {
550        ExtensibilityKind::Mutable
551    } else {
552        ExtensibilityKind::Appendable
553    }
554}
555
556/// XTypes §7.3.4.4 — builds a `CompleteCollectionElement` (element or key
557/// description) from a `TypeDescriptor`.
558fn complete_collection_element(
559    desc: &TypeDescriptor,
560) -> Result<CompleteCollectionElement, DynamicError> {
561    use crate::type_object::common::{
562        AppliedBuiltinMemberAnnotations, OptionalAppliedAnnotationSeq,
563    };
564    Ok(CompleteCollectionElement {
565        common: CommonCollectionElement {
566            element_flags: CollectionElementFlag(0),
567            type_id: descriptor_to_type_identifier(desc)?,
568        },
569        ann_builtin: AppliedBuiltinMemberAnnotations::default(),
570        ann_custom: OptionalAppliedAnnotationSeq::default(),
571    })
572}
573
574/// Maps a member `TypeDescriptor` to a matching `TypeIdentifier`. Only
575/// primitive + string + sequence + array are encoded inline; composite
576/// members require a TypeRegistry lookup strategy via
577/// `zerodds_types::resolve::TypeRegistry`.
578fn descriptor_to_type_identifier(desc: &TypeDescriptor) -> Result<TypeIdentifier, DynamicError> {
579    match desc.kind {
580        TypeKind::Boolean => Ok(TypeIdentifier::Primitive(PrimitiveKind::Boolean)),
581        TypeKind::Byte => Ok(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
582        TypeKind::Int8 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Int8)),
583        TypeKind::UInt8 => Ok(TypeIdentifier::Primitive(PrimitiveKind::UInt8)),
584        TypeKind::Int16 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Int16)),
585        TypeKind::UInt16 => Ok(TypeIdentifier::Primitive(PrimitiveKind::UInt16)),
586        TypeKind::Int32 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
587        TypeKind::UInt32 => Ok(TypeIdentifier::Primitive(PrimitiveKind::UInt32)),
588        TypeKind::Int64 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Int64)),
589        TypeKind::UInt64 => Ok(TypeIdentifier::Primitive(PrimitiveKind::UInt64)),
590        TypeKind::Float32 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Float32)),
591        TypeKind::Float64 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Float64)),
592        TypeKind::Float128 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Float128)),
593        TypeKind::Char8 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Char8)),
594        TypeKind::Char16 => Ok(TypeIdentifier::Primitive(PrimitiveKind::Char16)),
595        TypeKind::String8 => {
596            let bound = desc.bound.first().copied().unwrap_or(0);
597            if bound <= u32::from(u8::MAX) {
598                Ok(TypeIdentifier::String8Small { bound: bound as u8 })
599            } else {
600                Ok(TypeIdentifier::String8Large { bound })
601            }
602        }
603        TypeKind::String16 => {
604            let bound = desc.bound.first().copied().unwrap_or(0);
605            if bound <= u32::from(u8::MAX) {
606                Ok(TypeIdentifier::String16Small { bound: bound as u8 })
607            } else {
608                Ok(TypeIdentifier::String16Large { bound })
609            }
610        }
611        TypeKind::Structure | TypeKind::Union | TypeKind::Enumeration | TypeKind::Alias => {
612            // Bridge phase 1: composite members carry `TypeIdentifier::None`
613            // — the TypeRegistry (C4.2) later resolves via the name
614            // against an EquivalenceHash. Roundtrip tests set this
615            // information separately.
616            Ok(TypeIdentifier::None)
617        }
618        kind => Err(DynamicError::unsupported(format!(
619            "descriptor_to_type_identifier: {kind:?} not yet covered"
620        ))),
621    }
622}
623
624// ----------------------------------------------------------------------
625// TypeObject → DynamicType (Spec §7.6.4)
626// ----------------------------------------------------------------------
627
628impl DynamicTypeBuilderFactory {
629    /// Spec §7.6.4 / §7.5.5.1.4 `create_type_w_type_object(type_object)`.
630    ///
631    /// Scope:
632    /// - `CompleteStructType` with primitive + string members.
633    /// - `MinimalStructType` as read-only with hash names.
634    /// - Other kinds: `Unsupported`.
635    ///
636    /// # Errors
637    /// `Unsupported` for kinds outside the current implementation scope.
638    pub fn create_type_w_type_object(
639        type_obj: &TypeObject,
640    ) -> Result<DynamicTypeBuilder, DynamicError> {
641        match type_obj {
642            TypeObject::Complete(c) => match c {
643                CompleteTypeObject::Struct(s) => complete_struct_to_builder(s),
644                other => Err(DynamicError::unsupported(format!(
645                    "complete-typeobject kind {} not yet supported",
646                    other_kind_name(other)
647                ))),
648            },
649            TypeObject::Minimal(_) => Err(DynamicError::unsupported(
650                "minimal-typeobject → dynamic-type pending C4.2 TypeRegistry",
651            )),
652        }
653    }
654
655    /// Registry-aware `create_type_w_type_object` (XTypes §7.6.4 with a
656    /// TypeLookupService). Unlike the bare overload, composite member types
657    /// referenced by `EquivalenceHash` are resolved recursively against
658    /// `registry`, so a struct/union with nested struct/union/enum/bitmask/
659    /// bitset MEMBERS yields a fully-populated `DynamicType` (members carry
660    /// their own members). Covers all 10 top-level Complete kinds.
661    ///
662    /// Collections whose element/key/value is composite (`sequence<NestedStruct>`,
663    /// `NestedStruct[N]`, `map<K,Struct>`, and arbitrary nesting) are fully
664    /// resolved too, retained via [`super::collection`] so the codec walks the
665    /// nested members. Composite `typedef` targets resolve transparently, and
666    /// `TypeObject::Minimal` resolves via [`resolve_minimal`] (wire-identical to
667    /// Complete; only the names are synthetic hashes).
668    ///
669    /// # Errors
670    /// `Unsupported` only for unresolvable registry hashes and minimal annotation
671    /// types (annotations are metadata, not data members).
672    pub fn create_type_w_type_object_in(
673        type_obj: &TypeObject,
674        registry: &crate::resolve::TypeRegistry,
675    ) -> Result<DynamicType, DynamicError> {
676        match type_obj {
677            TypeObject::Complete(c) => resolve_complete(c, registry),
678            TypeObject::Minimal(m) => resolve_minimal(m, registry, "MinimalType"),
679        }
680    }
681}
682
683/// Resolves a `TypeIdentifier` to a full `DynamicType`, following composite
684/// `EquivalenceHash` references through `registry` recursively.
685///
686/// zerodds-lint: recursion-depth 16 (capped by TypeIdentifier decode depth +
687/// registry acyclicity; a missing/cyclic hash returns `Unsupported`).
688fn resolve_type_id(
689    ti: &TypeIdentifier,
690    registry: &crate::resolve::TypeRegistry,
691) -> Result<DynamicType, DynamicError> {
692    use super::builder::DynamicTypeBuilderFactory as F;
693    match ti {
694        TypeIdentifier::Primitive(p) => {
695            let kind = primitive_kind_to_type_kind(*p);
696            F::get_primitive_type(kind)
697        }
698        TypeIdentifier::String8Small { bound } => Ok(F::create_string_type(u32::from(*bound))),
699        TypeIdentifier::String8Large { bound } => Ok(F::create_string_type(*bound)),
700        TypeIdentifier::String16Small { bound } => Ok(F::create_wstring_type(u32::from(*bound))),
701        TypeIdentifier::String16Large { bound } => Ok(F::create_wstring_type(*bound)),
702        // Plain (anonymous) collections: resolve the element FULLY and retain it
703        // via `collection::{sequence_of,array_of}` so a composite element
704        // (`sequence<Struct>`, `Struct[N]`) — and arbitrary nesting like
705        // `sequence<sequence<Struct>>` — carries its members for the reflective
706        // codec. Scalar / string / nested-collection elements resolve through the
707        // same recursion.
708        TypeIdentifier::PlainSequenceSmall { bound, element, .. } => {
709            let elem = resolve_type_id(element, registry)?;
710            Ok(collection::sequence_of(elem, u32::from(*bound)))
711        }
712        TypeIdentifier::PlainSequenceLarge { bound, element, .. } => {
713            let elem = resolve_type_id(element, registry)?;
714            Ok(collection::sequence_of(elem, *bound))
715        }
716        TypeIdentifier::PlainArraySmall {
717            array_bounds,
718            element,
719            ..
720        } => {
721            let elem = resolve_type_id(element, registry)?;
722            let dims = array_bounds.iter().map(|b| u32::from(*b)).collect();
723            Ok(collection::array_of(elem, dims, "<arr>"))
724        }
725        TypeIdentifier::PlainArrayLarge {
726            array_bounds,
727            element,
728            ..
729        } => {
730            let elem = resolve_type_id(element, registry)?;
731            Ok(collection::array_of(elem, array_bounds.clone(), "<arr>"))
732        }
733        // Plain map: resolve BOTH key and value fully and retain them via
734        // `collection::map_of` so a composite key/value carries its members.
735        TypeIdentifier::PlainMapSmall {
736            bound,
737            key,
738            element,
739            ..
740        } => {
741            let k = resolve_type_id(key, registry)?;
742            let v = resolve_type_id(element, registry)?;
743            Ok(collection::map_of(k, v, u32::from(*bound), "<map>"))
744        }
745        TypeIdentifier::PlainMapLarge {
746            bound,
747            key,
748            element,
749            ..
750        } => {
751            let k = resolve_type_id(key, registry)?;
752            let v = resolve_type_id(element, registry)?;
753            Ok(collection::map_of(k, v, *bound, "<map>"))
754        }
755        TypeIdentifier::EquivalenceHashComplete(h) => {
756            let c = registry.get_complete(h).ok_or_else(|| {
757                DynamicError::unsupported("composite member: EquivalenceHash not in TypeRegistry")
758            })?;
759            resolve_complete(c, registry)
760        }
761        TypeIdentifier::EquivalenceHashMinimal(h) => {
762            let m = registry.get_minimal(h).ok_or_else(|| {
763                DynamicError::unsupported(
764                    "minimal composite member: EquivalenceHash not in registry",
765                )
766            })?;
767            let name = format!(
768                "T_{:08x}",
769                u32::from_be_bytes([h.0[0], h.0[1], h.0[2], h.0[3]])
770            );
771            resolve_minimal(m, registry, &name)
772        }
773        other => Err(DynamicError::unsupported(format!(
774            "resolve_type_id: {other:?} not yet supported"
775        ))),
776    }
777}
778
779/// Builds a full `DynamicType` from a `CompleteTypeObject`, resolving composite
780/// member types through `registry`. Handles all 10 Complete kinds.
781/// zerodds-lint: recursion-depth 64 (runtime DynamicData codec; bounded by type nesting).
782fn resolve_complete(
783    c: &CompleteTypeObject,
784    registry: &crate::resolve::TypeRegistry,
785) -> Result<DynamicType, DynamicError> {
786    use super::builder::DynamicTypeBuilderFactory as F;
787    match c {
788        CompleteTypeObject::Struct(s) => complete_struct_to_builder_in(s, registry)?.build(),
789        CompleteTypeObject::Union(u) => {
790            let disc_kind = type_id_to_kind(&u.discriminator.common.type_id)?;
791            let disc_desc = type_id_to_descriptor(&u.discriminator.common.type_id, disc_kind)?;
792            let mut b = F::create_type(TypeDescriptor::union(
793                u.header.detail.type_name.clone(),
794                disc_desc,
795            ))?;
796            for m in &u.member_seq {
797                let mt = resolve_type_id(&m.common.type_id, registry)?;
798                let mut md = MemberDescriptor::new(
799                    m.detail.name.clone(),
800                    m.common.member_id,
801                    mt.descriptor().clone(),
802                );
803                md.label = m.common.label_seq.iter().map(|&v| i64::from(v)).collect();
804                md.is_default_label = (m.common.member_flags.0 & UnionMemberFlag::IS_DEFAULT) != 0;
805                b.add_member_resolved(md, mt)?;
806            }
807            b.build()
808        }
809        CompleteTypeObject::Enumerated(e) => {
810            let mut desc = TypeDescriptor::enumeration(e.header.detail.type_name.clone());
811            desc.bound = alloc::vec![u32::from(e.header.common.bit_bound)];
812            let mut b = F::create_type(desc)?;
813            for lit in &e.literal_seq {
814                let id = u32::try_from(lit.common.value).unwrap_or(0);
815                let mut md = MemberDescriptor::new(
816                    lit.detail.name.clone(),
817                    id,
818                    TypeDescriptor::primitive(TypeKind::Int32, "int32"),
819                );
820                md.is_default_label =
821                    (lit.common.flags.0 & EnumLiteralFlag::IS_DEFAULT_LITERAL) != 0;
822                b.add_member(md)?;
823            }
824            b.build()
825        }
826        CompleteTypeObject::Bitmask(bm) => {
827            let mut desc = bare_descriptor(TypeKind::Bitmask, bm.detail.type_name.clone());
828            desc.bound = alloc::vec![u32::from(bm.bit_bound)];
829            let mut b = F::create_type(desc)?;
830            for f in &bm.flag_seq {
831                let md = MemberDescriptor::new(
832                    f.detail.name.clone(),
833                    u32::from(f.common.position),
834                    TypeDescriptor::primitive(TypeKind::Boolean, "boolean"),
835                );
836                b.add_member(md)?;
837            }
838            b.build()
839        }
840        CompleteTypeObject::Bitset(bs) => {
841            let desc = bare_descriptor(TypeKind::Bitset, bs.detail.type_name.clone());
842            let mut b = F::create_type(desc)?;
843            for f in &bs.field_seq {
844                let holder = PrimitiveKind::from_u8(f.common.holder_type).ok_or_else(|| {
845                    DynamicError::inconsistent(format!(
846                        "bitset field holder_type {} is not a primitive",
847                        f.common.holder_type
848                    ))
849                })?;
850                let mut md = MemberDescriptor::new(
851                    f.detail.name.clone(),
852                    u32::from(f.common.position),
853                    TypeDescriptor::primitive(primitive_kind_to_type_kind(holder), "holder"),
854                );
855                md.bit_bound = Some(f.common.bitcount);
856                b.add_member(md)?;
857            }
858            b.build()
859        }
860        CompleteTypeObject::Alias(a) => {
861            let target_kind = type_id_to_kind(&a.body.related_type)?;
862            if matches!(
863                target_kind,
864                TypeKind::Structure
865                    | TypeKind::Union
866                    | TypeKind::Sequence
867                    | TypeKind::Array
868                    | TypeKind::Map
869            ) {
870                // A typedef has the same wire form as its target, so a composite /
871                // collection target is resolved TRANSPARENTLY (fully, members
872                // carried) rather than kept as a shallow alias node.
873                resolve_type_id(&a.body.related_type, registry)
874            } else {
875                // Scalar / enum / bitmask target: a shallow alias node suffices
876                // (the codec rebuilds the scalar target from the descriptor).
877                let target = type_id_to_descriptor(&a.body.related_type, target_kind)?;
878                let mut desc = bare_descriptor(TypeKind::Alias, a.header.detail.type_name.clone());
879                desc.element_type = Some(alloc::boxed::Box::new(target));
880                F::create_type(desc)?.build()
881            }
882        }
883        CompleteTypeObject::Sequence(_)
884        | CompleteTypeObject::Array(_)
885        | CompleteTypeObject::Map(_) => {
886            // Named (non-anonymous) collection TypeObject: resolve the element
887            // fully through the registry so composite elements carry their members.
888            resolve_named_collection(c, registry)
889        }
890        CompleteTypeObject::Annotation(an) => {
891            let desc = bare_descriptor(TypeKind::Annotation, an.detail.type_name.clone());
892            let mut b = F::create_type(desc)?;
893            for m in &an.member_seq {
894                let mk = type_id_to_kind(&m.member_type_id)?;
895                let mt = type_id_to_descriptor(&m.member_type_id, mk)?;
896                let mut md = MemberDescriptor::new(m.name.clone(), m.member_id, mt);
897                if !m.default_value.is_empty() {
898                    md.default_value = Some(String::from_utf8_lossy(&m.default_value).into_owned());
899                }
900                b.add_member(md)?;
901            }
902            b.build()
903        }
904    }
905}
906
907/// A stable, non-empty synthetic name for a Minimal member/type: its 4-byte
908/// `NameHash` rendered as hex. A Minimal TypeObject carries no readable names by
909/// XTypes design — only these hashes — so this is the best name recoverable from
910/// a bare Minimal form (the Complete form, if co-published, has the real names).
911fn hash_name(prefix: &str, h: &NameHash) -> String {
912    format!("{prefix}_{:08x}", u32::from_be_bytes(h.0))
913}
914
915/// Builds a full `DynamicType` from a `MinimalTypeObject`. A Minimal object
916/// carries the FULL structure — member ids, types, flags, nesting — in its
917/// shared `common` fields (identical to Complete); only the readable member/type
918/// NAMES are absent (4-byte hashes). Because the reflective codec keys on member
919/// ids + types, a Minimal-resolved type encodes/decodes wire data byte-identically
920/// to its Complete twin; only reflection shows hash-based names.
921/// zerodds-lint: recursion-depth 64 (runtime DynamicData codec; bounded by type nesting).
922fn resolve_minimal(
923    m: &MinimalTypeObject,
924    registry: &crate::resolve::TypeRegistry,
925    type_name: &str,
926) -> Result<DynamicType, DynamicError> {
927    use super::builder::DynamicTypeBuilderFactory as F;
928    match m {
929        MinimalTypeObject::Struct(s) => {
930            let mut desc = TypeDescriptor::structure(type_name.to_string());
931            desc.extensibility_kind = flag_bits_to_extensibility(s.struct_flags.0);
932            if (s.struct_flags.0 & StructTypeFlag::IS_NESTED) != 0 {
933                desc.is_nested = true;
934            }
935            let mut b = F::create_type(desc)?;
936            for (idx, mem) in s.member_seq.iter().enumerate() {
937                let mt = resolve_type_id(&mem.common.member_type_id, registry)?;
938                let mut md = MemberDescriptor::new(
939                    hash_name("m", &mem.detail),
940                    mem.common.member_id,
941                    mt.descriptor().clone(),
942                );
943                md.index = u32::try_from(idx).unwrap_or(u32::MAX);
944                md.is_key = (mem.common.member_flags.0 & StructMemberFlag::IS_KEY) != 0;
945                md.is_optional = (mem.common.member_flags.0 & StructMemberFlag::IS_OPTIONAL) != 0;
946                md.is_must_understand =
947                    (mem.common.member_flags.0 & StructMemberFlag::IS_MUST_UNDERSTAND) != 0;
948                md.is_shared = (mem.common.member_flags.0 & StructMemberFlag::IS_EXTERNAL) != 0;
949                b.add_member_resolved(md, mt)?;
950            }
951            b.build()
952        }
953        MinimalTypeObject::Union(u) => {
954            let disc_kind = type_id_to_kind(&u.discriminator.common.type_id)?;
955            let disc_desc = type_id_to_descriptor(&u.discriminator.common.type_id, disc_kind)?;
956            let mut b = F::create_type(TypeDescriptor::union(type_name.to_string(), disc_desc))?;
957            for mem in &u.member_seq {
958                let mt = resolve_type_id(&mem.common.type_id, registry)?;
959                let mut md = MemberDescriptor::new(
960                    hash_name("m", &mem.detail),
961                    mem.common.member_id,
962                    mt.descriptor().clone(),
963                );
964                md.label = mem.common.label_seq.iter().map(|&v| i64::from(v)).collect();
965                md.is_default_label =
966                    (mem.common.member_flags.0 & UnionMemberFlag::IS_DEFAULT) != 0;
967                b.add_member_resolved(md, mt)?;
968            }
969            b.build()
970        }
971        MinimalTypeObject::Enumerated(e) => {
972            let mut desc = TypeDescriptor::enumeration(type_name.to_string());
973            desc.bound = alloc::vec![u32::from(e.header.common.bit_bound)];
974            let mut b = F::create_type(desc)?;
975            for lit in &e.literal_seq {
976                let id = u32::try_from(lit.common.value).unwrap_or(0);
977                let mut md = MemberDescriptor::new(
978                    hash_name("e", &lit.detail),
979                    id,
980                    TypeDescriptor::primitive(TypeKind::Int32, "int32"),
981                );
982                md.is_default_label =
983                    (lit.common.flags.0 & EnumLiteralFlag::IS_DEFAULT_LITERAL) != 0;
984                b.add_member(md)?;
985            }
986            b.build()
987        }
988        MinimalTypeObject::Bitmask(bm) => {
989            let mut desc = bare_descriptor(TypeKind::Bitmask, type_name.to_string());
990            desc.bound = alloc::vec![u32::from(bm.bit_bound)];
991            let mut b = F::create_type(desc)?;
992            for f in &bm.flag_seq {
993                let md = MemberDescriptor::new(
994                    hash_name("f", &f.detail),
995                    u32::from(f.common.position),
996                    TypeDescriptor::primitive(TypeKind::Boolean, "boolean"),
997                );
998                b.add_member(md)?;
999            }
1000            b.build()
1001        }
1002        MinimalTypeObject::Bitset(bs) => {
1003            let desc = bare_descriptor(TypeKind::Bitset, type_name.to_string());
1004            let mut b = F::create_type(desc)?;
1005            for f in &bs.field_seq {
1006                let holder = PrimitiveKind::from_u8(f.common.holder_type).ok_or_else(|| {
1007                    DynamicError::inconsistent(format!(
1008                        "bitset field holder_type {} is not a primitive",
1009                        f.common.holder_type
1010                    ))
1011                })?;
1012                let mut md = MemberDescriptor::new(
1013                    hash_name("f", &f.name_hash),
1014                    u32::from(f.common.position),
1015                    TypeDescriptor::primitive(primitive_kind_to_type_kind(holder), "holder"),
1016                );
1017                md.bit_bound = Some(f.common.bitcount);
1018                b.add_member(md)?;
1019            }
1020            b.build()
1021        }
1022        MinimalTypeObject::Alias(a) => {
1023            // Typedefs are transparent (same wire form as their target).
1024            resolve_type_id(&a.body.common.related_type, registry)
1025        }
1026        MinimalTypeObject::Sequence(s) => {
1027            let elem = resolve_type_id(&s.element.common.type_id, registry)?;
1028            Ok(collection::sequence_named(type_name, elem, s.bound))
1029        }
1030        MinimalTypeObject::Array(a) => {
1031            let elem = resolve_type_id(&a.element.common.type_id, registry)?;
1032            Ok(collection::array_of(elem, a.bound_seq.clone(), type_name))
1033        }
1034        MinimalTypeObject::Map(mp) => {
1035            let key = resolve_type_id(&mp.key.common.type_id, registry)?;
1036            let value = resolve_type_id(&mp.element.common.type_id, registry)?;
1037            Ok(collection::map_of(key, value, mp.bound, type_name))
1038        }
1039        MinimalTypeObject::Annotation(_) => Err(DynamicError::unsupported(
1040            "minimal annotation type → dynamic-type (annotations are metadata, not data members)",
1041        )),
1042    }
1043}
1044
1045/// Like [`complete_struct_to_builder`] but resolves each member's type fully
1046/// (recursively through `registry`) and attaches it via `add_member_resolved`,
1047/// so nested composite members carry their own members.
1048fn complete_struct_to_builder_in(
1049    s: &CompleteStructType,
1050    registry: &crate::resolve::TypeRegistry,
1051) -> Result<DynamicTypeBuilder, DynamicError> {
1052    let mut desc = TypeDescriptor::structure(s.header.detail.type_name.clone());
1053    desc.extensibility_kind = flag_bits_to_extensibility(s.struct_flags.0);
1054    if (s.struct_flags.0 & StructTypeFlag::IS_NESTED) != 0 {
1055        desc.is_nested = true;
1056    }
1057    let mut b = DynamicTypeBuilderFactory::create_type(desc)?;
1058    for (idx, m) in s.member_seq.iter().enumerate() {
1059        let mt = resolve_type_id(&m.common.member_type_id, registry)?;
1060        let mut md = MemberDescriptor::new(
1061            m.detail.name.clone(),
1062            m.common.member_id,
1063            mt.descriptor().clone(),
1064        );
1065        md.index = u32::try_from(idx).unwrap_or(u32::MAX);
1066        md.is_key = (m.common.member_flags.0 & StructMemberFlag::IS_KEY) != 0;
1067        md.is_optional = (m.common.member_flags.0 & StructMemberFlag::IS_OPTIONAL) != 0;
1068        md.is_must_understand =
1069            (m.common.member_flags.0 & StructMemberFlag::IS_MUST_UNDERSTAND) != 0;
1070        md.is_shared = (m.common.member_flags.0 & StructMemberFlag::IS_EXTERNAL) != 0;
1071        b.add_member_resolved(md, mt)?;
1072    }
1073    Ok(b)
1074}
1075
1076/// Builds a top-level named collection (Sequence/Array) `CompleteTypeObject`
1077/// into a `DynamicType`, resolving the element fully through `registry` (so a
1078/// composite element carries its members via `collection::{sequence_named,
1079/// array_of,map_of}`), so composite keys/values/elements carry their members.
1080fn resolve_named_collection(
1081    c: &CompleteTypeObject,
1082    registry: &crate::resolve::TypeRegistry,
1083) -> Result<DynamicType, DynamicError> {
1084    match c {
1085        CompleteTypeObject::Sequence(s) => {
1086            let elem = resolve_type_id(&s.element.common.type_id, registry)?;
1087            Ok(collection::sequence_named(
1088                &s.detail.type_name,
1089                elem,
1090                s.bound,
1091            ))
1092        }
1093        CompleteTypeObject::Array(a) => {
1094            let elem = resolve_type_id(&a.element.common.type_id, registry)?;
1095            Ok(collection::array_of(
1096                elem,
1097                a.bound_seq.clone(),
1098                &a.detail.type_name,
1099            ))
1100        }
1101        CompleteTypeObject::Map(m) => {
1102            let key = resolve_type_id(&m.key.common.type_id, registry)?;
1103            let value = resolve_type_id(&m.element.common.type_id, registry)?;
1104            Ok(collection::map_of(key, value, m.bound, &m.detail.type_name))
1105        }
1106        other => Err(DynamicError::unsupported(format!(
1107            "resolve_named_collection called on non-collection: {other:?}"
1108        ))),
1109    }
1110}
1111
1112/// A bare `TypeDescriptor` carrying just `kind` + `name` (all structural fields
1113/// empty) — for the non-collection composite kinds whose body lives in their
1114/// members (Bitmask/Bitset/Annotation) or in `element_type` (Alias), set by the
1115/// caller after construction.
1116fn bare_descriptor(kind: TypeKind, name: impl Into<String>) -> TypeDescriptor {
1117    TypeDescriptor {
1118        kind,
1119        name: name.into(),
1120        base_type: None,
1121        discriminator_type: None,
1122        bound: Vec::new(),
1123        element_type: None,
1124        key_element_type: None,
1125        extensibility_kind: ExtensibilityKind::default(),
1126        is_nested: false,
1127    }
1128}
1129
1130fn complete_struct_to_builder(s: &CompleteStructType) -> Result<DynamicTypeBuilder, DynamicError> {
1131    let mut desc = TypeDescriptor::structure(s.header.detail.type_name.clone());
1132    desc.extensibility_kind = flag_bits_to_extensibility(s.struct_flags.0);
1133    if (s.struct_flags.0 & StructTypeFlag::IS_NESTED) != 0 {
1134        desc.is_nested = true;
1135    }
1136    let mut b = DynamicTypeBuilderFactory::create_type(desc)?;
1137    for (idx, m) in s.member_seq.iter().enumerate() {
1138        let kind = type_id_to_kind(&m.common.member_type_id)?;
1139        let member_type = type_id_to_descriptor(&m.common.member_type_id, kind)?;
1140        let mut md = MemberDescriptor::new(m.detail.name.clone(), m.common.member_id, member_type);
1141        md.index = u32::try_from(idx).unwrap_or(u32::MAX);
1142        md.is_key = (m.common.member_flags.0 & StructMemberFlag::IS_KEY) != 0;
1143        md.is_optional = (m.common.member_flags.0 & StructMemberFlag::IS_OPTIONAL) != 0;
1144        md.is_must_understand =
1145            (m.common.member_flags.0 & StructMemberFlag::IS_MUST_UNDERSTAND) != 0;
1146        md.is_shared = (m.common.member_flags.0 & StructMemberFlag::IS_EXTERNAL) != 0;
1147        b.add_member(md)?;
1148    }
1149    Ok(b)
1150}
1151
1152const fn other_kind_name(c: &CompleteTypeObject) -> &'static str {
1153    match c {
1154        CompleteTypeObject::Alias(_) => "alias",
1155        CompleteTypeObject::Annotation(_) => "annotation",
1156        CompleteTypeObject::Struct(_) => "struct",
1157        CompleteTypeObject::Union(_) => "union",
1158        CompleteTypeObject::Bitset(_) => "bitset",
1159        CompleteTypeObject::Sequence(_) => "sequence",
1160        CompleteTypeObject::Array(_) => "array",
1161        CompleteTypeObject::Map(_) => "map",
1162        CompleteTypeObject::Enumerated(_) => "enum",
1163        CompleteTypeObject::Bitmask(_) => "bitmask",
1164    }
1165}
1166
1167fn type_id_to_kind(ti: &TypeIdentifier) -> Result<TypeKind, DynamicError> {
1168    Ok(match ti {
1169        TypeIdentifier::None => TypeKind::NoType,
1170        TypeIdentifier::Primitive(p) => primitive_kind_to_type_kind(*p),
1171        TypeIdentifier::String8Small { .. } | TypeIdentifier::String8Large { .. } => {
1172            TypeKind::String8
1173        }
1174        TypeIdentifier::String16Small { .. } | TypeIdentifier::String16Large { .. } => {
1175            TypeKind::String16
1176        }
1177        TypeIdentifier::PlainSequenceSmall { .. } | TypeIdentifier::PlainSequenceLarge { .. } => {
1178            TypeKind::Sequence
1179        }
1180        TypeIdentifier::PlainArraySmall { .. } | TypeIdentifier::PlainArrayLarge { .. } => {
1181            TypeKind::Array
1182        }
1183        TypeIdentifier::PlainMapSmall { .. } | TypeIdentifier::PlainMapLarge { .. } => {
1184            TypeKind::Map
1185        }
1186        TypeIdentifier::EquivalenceHashMinimal(_) | TypeIdentifier::EquivalenceHashComplete(_) => {
1187            // Composite via TypeRegistry lookup — mark as Structure
1188            // by default; resolving it correctly is C4.2.
1189            TypeKind::Structure
1190        }
1191        other => {
1192            return Err(DynamicError::unsupported(format!(
1193                "type_id_to_kind: {other:?} not yet supported"
1194            )));
1195        }
1196    })
1197}
1198
1199const fn primitive_kind_to_type_kind(p: PrimitiveKind) -> TypeKind {
1200    match p {
1201        PrimitiveKind::Boolean => TypeKind::Boolean,
1202        PrimitiveKind::Byte => TypeKind::Byte,
1203        PrimitiveKind::Int8 => TypeKind::Int8,
1204        PrimitiveKind::UInt8 => TypeKind::UInt8,
1205        PrimitiveKind::Int16 => TypeKind::Int16,
1206        PrimitiveKind::UInt16 => TypeKind::UInt16,
1207        PrimitiveKind::Int32 => TypeKind::Int32,
1208        PrimitiveKind::UInt32 => TypeKind::UInt32,
1209        PrimitiveKind::Int64 => TypeKind::Int64,
1210        PrimitiveKind::UInt64 => TypeKind::UInt64,
1211        PrimitiveKind::Float32 => TypeKind::Float32,
1212        PrimitiveKind::Float64 => TypeKind::Float64,
1213        PrimitiveKind::Float128 => TypeKind::Float128,
1214        PrimitiveKind::Char8 => TypeKind::Char8,
1215        PrimitiveKind::Char16 => TypeKind::Char16,
1216    }
1217}
1218
1219/// zerodds-lint: recursion-depth 16
1220///
1221/// Recursive calls via the PlainSequence/PlainArray element. Depth is
1222/// implicitly capped by [`TypeIdentifier::MAX_DECODE_DEPTH`] — the
1223/// TypeIdentifier can be at most 16 layers deep on wire-decode, after
1224/// which decoding fails with `LengthExceeded`.
1225fn type_id_to_descriptor(
1226    ti: &TypeIdentifier,
1227    kind: TypeKind,
1228) -> Result<TypeDescriptor, DynamicError> {
1229    if kind.is_primitive() {
1230        return Ok(TypeDescriptor::primitive(
1231            kind,
1232            super::type_::primitive_name(kind).to_string(),
1233        ));
1234    }
1235    Ok(match ti {
1236        TypeIdentifier::String8Small { bound } => TypeDescriptor::string8(u32::from(*bound)),
1237        TypeIdentifier::String8Large { bound } => TypeDescriptor::string8(*bound),
1238        TypeIdentifier::String16Small { bound } => TypeDescriptor::string16(u32::from(*bound)),
1239        TypeIdentifier::String16Large { bound } => TypeDescriptor::string16(*bound),
1240        TypeIdentifier::None => TypeDescriptor {
1241            kind: TypeKind::Structure,
1242            name: String::from("<unresolved>"),
1243            base_type: None,
1244            discriminator_type: None,
1245            bound: Vec::new(),
1246            element_type: None,
1247            key_element_type: None,
1248            extensibility_kind: ExtensibilityKind::default(),
1249            is_nested: false,
1250        },
1251        TypeIdentifier::EquivalenceHashMinimal(_) | TypeIdentifier::EquivalenceHashComplete(_) => {
1252            // We only know the name after a TypeRegistry lookup
1253            // (C4.2). Until then: placeholder descriptor with Kind=Structure.
1254            TypeDescriptor {
1255                kind: TypeKind::Structure,
1256                name: String::from("<typeref>"),
1257                base_type: None,
1258                discriminator_type: None,
1259                bound: Vec::new(),
1260                element_type: None,
1261                key_element_type: None,
1262                extensibility_kind: ExtensibilityKind::default(),
1263                is_nested: false,
1264            }
1265        }
1266        TypeIdentifier::PlainSequenceSmall { bound, element, .. } => {
1267            let elem_kind = type_id_to_kind(element)?;
1268            TypeDescriptor::sequence(
1269                "<seq>".to_string(),
1270                type_id_to_descriptor(element, elem_kind)?,
1271                u32::from(*bound),
1272            )
1273        }
1274        TypeIdentifier::PlainSequenceLarge { bound, element, .. } => {
1275            let elem_kind = type_id_to_kind(element)?;
1276            TypeDescriptor::sequence(
1277                "<seq>".to_string(),
1278                type_id_to_descriptor(element, elem_kind)?,
1279                *bound,
1280            )
1281        }
1282        TypeIdentifier::PlainArraySmall {
1283            array_bounds,
1284            element,
1285            ..
1286        } => {
1287            let elem_kind = type_id_to_kind(element)?;
1288            TypeDescriptor::array(
1289                "<arr>".to_string(),
1290                type_id_to_descriptor(element, elem_kind)?,
1291                array_bounds.iter().map(|b| u32::from(*b)).collect(),
1292            )
1293        }
1294        TypeIdentifier::PlainArrayLarge {
1295            array_bounds,
1296            element,
1297            ..
1298        } => {
1299            let elem_kind = type_id_to_kind(element)?;
1300            TypeDescriptor::array(
1301                "<arr>".to_string(),
1302                type_id_to_descriptor(element, elem_kind)?,
1303                array_bounds.clone(),
1304            )
1305        }
1306        other => {
1307            return Err(DynamicError::unsupported(format!(
1308                "type_id_to_descriptor: {other:?} not yet supported"
1309            )));
1310        }
1311    })
1312}
1313
1314#[cfg(test)]
1315#[allow(
1316    clippy::unwrap_used,
1317    clippy::expect_used,
1318    clippy::panic,
1319    clippy::unreachable
1320)]
1321mod tests {
1322    use super::*;
1323    use crate::dynamic::DynamicTypeBuilderFactory;
1324
1325    fn build_int_struct() -> DynamicType {
1326        let mut b = DynamicTypeBuilderFactory::create_struct("::Sensor");
1327        b.add_struct_member("id", 1, TypeDescriptor::primitive(TypeKind::Int64, "int64"))
1328            .unwrap();
1329        b.add_struct_member(
1330            "temp",
1331            2,
1332            TypeDescriptor::primitive(TypeKind::Float64, "double"),
1333        )
1334        .unwrap();
1335        b.add_struct_member("name", 3, TypeDescriptor::string8(64))
1336            .unwrap();
1337        b.build().unwrap()
1338    }
1339
1340    #[test]
1341    fn dynamic_struct_to_typeobject_complete() {
1342        let t = build_int_struct();
1343        let to = t.to_type_object().unwrap();
1344        match to {
1345            TypeObject::Complete(CompleteTypeObject::Struct(s)) => {
1346                assert_eq!(s.header.detail.type_name, "::Sensor");
1347                assert_eq!(s.member_seq.len(), 3);
1348                assert_eq!(s.member_seq[0].detail.name, "id");
1349            }
1350            _ => panic!("to_type_object on Structure must return Complete::Struct"),
1351        }
1352    }
1353
1354    #[test]
1355    fn typeobject_complete_struct_back_to_dynamic_type() {
1356        let t = build_int_struct();
1357        let to = t.to_type_object().unwrap();
1358        let b = DynamicTypeBuilderFactory::create_type_w_type_object(&to).unwrap();
1359        let t2 = b.build().unwrap();
1360        assert_eq!(t2.name(), "::Sensor");
1361        assert_eq!(t2.member_count(), 3);
1362        assert_eq!(t2.member_by_id(1).unwrap().name(), "id");
1363        assert_eq!(t2.member_by_id(2).unwrap().name(), "temp");
1364        assert_eq!(t2.member_by_id(3).unwrap().name(), "name");
1365    }
1366
1367    #[test]
1368    fn roundtrip_dynamic_to_typeobject_to_dynamic_equals_logically() {
1369        let t = build_int_struct();
1370        let to = t.to_type_object().unwrap();
1371        let b = DynamicTypeBuilderFactory::create_type_w_type_object(&to).unwrap();
1372        let t2 = b.build().unwrap();
1373        // Deep equality on the type built by the bridge.
1374        assert!(t.equals(&t2), "roundtrip failed: {t:?} vs {t2:?}");
1375    }
1376
1377    #[test]
1378    fn unsupported_kind_to_typeobject_yields_unsupported_error() {
1379        let prim = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
1380        let err = prim.to_type_object().unwrap_err();
1381        assert!(matches!(err, DynamicError::Unsupported(_)));
1382    }
1383
1384    #[test]
1385    fn dynamic_alias_to_typeobject_complete() {
1386        // alias with element_type = int32.
1387        let mut desc = TypeDescriptor::primitive(TypeKind::Int32, "int32");
1388        desc.kind = TypeKind::Alias;
1389        desc.name = "::SensorId".to_string();
1390        desc.element_type = Some(alloc::boxed::Box::new(TypeDescriptor::primitive(
1391            TypeKind::Int32,
1392            "int32",
1393        )));
1394        let t = DynamicTypeBuilderFactory::create_type(desc)
1395            .unwrap()
1396            .build()
1397            .unwrap();
1398        let to = t.to_type_object().expect("alias bridge ok");
1399        match to {
1400            TypeObject::Complete(CompleteTypeObject::Alias(a)) => {
1401                assert_eq!(a.header.detail.type_name, "::SensorId");
1402                assert!(matches!(a.body.related_type, TypeIdentifier::Primitive(_)));
1403            }
1404            other => panic!("expected Alias, got {other:?}"),
1405        }
1406    }
1407
1408    #[test]
1409    fn dynamic_enum_to_typeobject_complete() {
1410        let mut desc = TypeDescriptor::primitive(TypeKind::Enumeration, "::Color");
1411        desc.kind = TypeKind::Enumeration;
1412        desc.bound = alloc::vec![32];
1413        let mut b = DynamicTypeBuilderFactory::create_type(desc).unwrap();
1414        for (id, name) in [(0u32, "RED"), (1u32, "GREEN"), (2u32, "BLUE")] {
1415            let m = MemberDescriptor::new(
1416                name,
1417                id,
1418                TypeDescriptor::primitive(TypeKind::Int32, "int32"),
1419            );
1420            b.add_member(m).unwrap();
1421        }
1422        let t = b.build().unwrap();
1423        let to = t.to_type_object().expect("enum bridge ok");
1424        match to {
1425            TypeObject::Complete(CompleteTypeObject::Enumerated(e)) => {
1426                assert_eq!(e.literal_seq.len(), 3);
1427                assert_eq!(e.literal_seq[0].detail.name, "RED");
1428                assert_eq!(e.literal_seq[2].common.value, 2);
1429            }
1430            other => panic!("expected Enumerated, got {other:?}"),
1431        }
1432    }
1433
1434    #[test]
1435    fn dynamic_bitmask_to_typeobject_complete() {
1436        let mut desc = TypeDescriptor::primitive(TypeKind::Bitmask, "::Flags");
1437        desc.kind = TypeKind::Bitmask;
1438        desc.bound = alloc::vec![8];
1439        let mut b = DynamicTypeBuilderFactory::create_type(desc).unwrap();
1440        for (pos, name) in [(0u32, "A"), (3u32, "D"), (7u32, "H")] {
1441            let m = MemberDescriptor::new(
1442                name,
1443                pos,
1444                TypeDescriptor::primitive(TypeKind::Boolean, "boolean"),
1445            );
1446            b.add_member(m).unwrap();
1447        }
1448        let t = b.build().unwrap();
1449        let to = t.to_type_object().expect("bitmask bridge ok");
1450        match to {
1451            TypeObject::Complete(CompleteTypeObject::Bitmask(bm)) => {
1452                assert_eq!(bm.bit_bound, 8);
1453                assert_eq!(bm.flag_seq.len(), 3);
1454                assert_eq!(bm.flag_seq[1].common.position, 3);
1455            }
1456            other => panic!("expected Bitmask, got {other:?}"),
1457        }
1458    }
1459
1460    #[test]
1461    fn dynamic_bitset_to_typeobject_complete() {
1462        let mut desc = TypeDescriptor::primitive(TypeKind::Bitset, "::Reg");
1463        desc.kind = TypeKind::Bitset;
1464        let mut b = DynamicTypeBuilderFactory::create_type(desc).unwrap();
1465        for (pos, name, width) in [(0u32, "lo", 3u8), (3u32, "hi", 5u8)] {
1466            let mut m = MemberDescriptor::new(
1467                name,
1468                pos,
1469                TypeDescriptor::primitive(TypeKind::UInt8, "uint8"),
1470            );
1471            m.bit_bound = Some(width);
1472            b.add_member(m).unwrap();
1473        }
1474        let t = b.build().unwrap();
1475        let to = t.to_type_object().expect("bitset bridge ok");
1476        match to {
1477            TypeObject::Complete(CompleteTypeObject::Bitset(bs)) => {
1478                assert_eq!(bs.field_seq.len(), 2);
1479                assert_eq!(bs.field_seq[0].common.position, 0);
1480                assert_eq!(bs.field_seq[0].common.bitcount, 3);
1481                assert_eq!(bs.field_seq[0].detail.name, "lo");
1482                assert_eq!(bs.field_seq[1].common.position, 3);
1483                assert_eq!(bs.field_seq[1].common.bitcount, 5);
1484                assert_eq!(
1485                    bs.field_seq[1].common.holder_type,
1486                    PrimitiveKind::UInt8.to_u8()
1487                );
1488            }
1489            other => panic!("expected Bitset, got {other:?}"),
1490        }
1491    }
1492
1493    #[test]
1494    fn dynamic_annotation_to_typeobject_complete() {
1495        let mut desc = TypeDescriptor::primitive(TypeKind::Annotation, "::Range");
1496        desc.kind = TypeKind::Annotation;
1497        let mut b = DynamicTypeBuilderFactory::create_type(desc).unwrap();
1498        let mut m = MemberDescriptor::new(
1499            "max",
1500            1,
1501            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
1502        );
1503        m.default_value = Some("100".into());
1504        b.add_member(m).unwrap();
1505        let t = b.build().unwrap();
1506        let to = t.to_type_object().expect("annotation bridge ok");
1507        match to {
1508            TypeObject::Complete(CompleteTypeObject::Annotation(a)) => {
1509                assert_eq!(a.member_seq.len(), 1);
1510                assert_eq!(a.member_seq[0].name, "max");
1511                assert_eq!(a.member_seq[0].member_id, 1);
1512                assert_eq!(a.member_seq[0].default_value, b"100".to_vec());
1513                assert!(matches!(
1514                    a.member_seq[0].member_type_id,
1515                    TypeIdentifier::Primitive(PrimitiveKind::Int32)
1516                ));
1517            }
1518            other => panic!("expected Annotation, got {other:?}"),
1519        }
1520    }
1521
1522    #[test]
1523    fn dynamic_sequence_to_typeobject_complete() {
1524        let desc = TypeDescriptor::sequence(
1525            "::Seq",
1526            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
1527            10,
1528        );
1529        let t = DynamicTypeBuilderFactory::create_type(desc)
1530            .unwrap()
1531            .build()
1532            .unwrap();
1533        match t.to_type_object().expect("sequence bridge ok") {
1534            TypeObject::Complete(CompleteTypeObject::Sequence(s)) => {
1535                assert_eq!(s.bound, 10);
1536                assert_eq!(s.detail.type_name, "::Seq");
1537                assert!(matches!(
1538                    s.element.common.type_id,
1539                    TypeIdentifier::Primitive(PrimitiveKind::Int32)
1540                ));
1541            }
1542            other => panic!("expected Sequence, got {other:?}"),
1543        }
1544    }
1545
1546    #[test]
1547    fn dynamic_array_to_typeobject_complete() {
1548        let desc = TypeDescriptor::array(
1549            "::Arr",
1550            TypeDescriptor::primitive(TypeKind::Float64, "double"),
1551            alloc::vec![3, 4],
1552        );
1553        let t = DynamicTypeBuilderFactory::create_type(desc)
1554            .unwrap()
1555            .build()
1556            .unwrap();
1557        match t.to_type_object().expect("array bridge ok") {
1558            TypeObject::Complete(CompleteTypeObject::Array(a)) => {
1559                assert_eq!(a.bound_seq, alloc::vec![3, 4]);
1560                assert!(matches!(
1561                    a.element.common.type_id,
1562                    TypeIdentifier::Primitive(PrimitiveKind::Float64)
1563                ));
1564            }
1565            other => panic!("expected Array, got {other:?}"),
1566        }
1567    }
1568
1569    #[test]
1570    fn dynamic_map_to_typeobject_complete() {
1571        let desc = TypeDescriptor::map(
1572            "::Map",
1573            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
1574            TypeDescriptor::primitive(TypeKind::Boolean, "boolean"),
1575            7,
1576        );
1577        let t = DynamicTypeBuilderFactory::create_type(desc)
1578            .unwrap()
1579            .build()
1580            .unwrap();
1581        match t.to_type_object().expect("map bridge ok") {
1582            TypeObject::Complete(CompleteTypeObject::Map(m)) => {
1583                assert_eq!(m.bound, 7);
1584                assert!(matches!(
1585                    m.key.common.type_id,
1586                    TypeIdentifier::Primitive(PrimitiveKind::Int32)
1587                ));
1588                assert!(matches!(
1589                    m.element.common.type_id,
1590                    TypeIdentifier::Primitive(PrimitiveKind::Boolean)
1591                ));
1592            }
1593            other => panic!("expected Map, got {other:?}"),
1594        }
1595    }
1596
1597    #[test]
1598    fn dynamic_union_to_typeobject_complete() {
1599        let disc = TypeDescriptor::primitive(TypeKind::Int32, "int32");
1600        let t = {
1601            let mut b = DynamicTypeBuilderFactory::create_union("::Shape", disc).unwrap();
1602            let mut m1 = MemberDescriptor::new(
1603                "circle",
1604                1,
1605                TypeDescriptor::primitive(TypeKind::Float64, "double"),
1606            );
1607            m1.label = alloc::vec![1];
1608            let mut m2 = MemberDescriptor::new(
1609                "square",
1610                2,
1611                TypeDescriptor::primitive(TypeKind::Float64, "double"),
1612            );
1613            m2.label = alloc::vec![2, 3];
1614            b.add_member(m1).unwrap();
1615            b.add_member(m2).unwrap();
1616            b.build().unwrap()
1617        };
1618        let to = t.to_type_object().expect("union bridge ok");
1619        match to {
1620            TypeObject::Complete(CompleteTypeObject::Union(u)) => {
1621                assert_eq!(u.member_seq.len(), 2);
1622                assert_eq!(u.member_seq[0].detail.name, "circle");
1623                assert_eq!(u.member_seq[1].common.label_seq, alloc::vec![2, 3]);
1624                assert!(matches!(
1625                    u.discriminator.common.type_id,
1626                    TypeIdentifier::Primitive(PrimitiveKind::Int32)
1627                ));
1628            }
1629            other => panic!("expected Union, got {other:?}"),
1630        }
1631    }
1632
1633    #[test]
1634    fn create_type_w_minimal_typeobject_bare_needs_registry() {
1635        // The BARE overload has no registry to resolve nested minimal hashes, so
1636        // it still declines Minimal — callers use the registry-aware `_in` form.
1637        use crate::type_object::minimal::{MinimalStructHeader, MinimalStructType};
1638        let m = MinimalStructType {
1639            struct_flags: StructTypeFlag::default(),
1640            header: MinimalStructHeader {
1641                base_type: TypeIdentifier::None,
1642            },
1643            member_seq: alloc::vec![],
1644        };
1645        let to = TypeObject::Minimal(MinimalTypeObject::Struct(m));
1646        let err = DynamicTypeBuilderFactory::create_type_w_type_object(&to).unwrap_err();
1647        assert!(matches!(err, DynamicError::Unsupported(_)));
1648    }
1649
1650    /// A Minimal TypeObject carries the FULL structure (member ids + types) — only
1651    /// the readable names are hashes. So a Minimal-resolved type encodes/decodes
1652    /// wire data BYTE-IDENTICALLY to its Complete twin; only reflection differs.
1653    #[test]
1654    fn minimal_resolves_wire_identical_to_complete() {
1655        use crate::builder::TypeObjectBuilder;
1656        use crate::dynamic::DynamicData;
1657        use crate::dynamic::codec::{decode_dynamic, encode_dynamic};
1658
1659        let reg = crate::resolve::TypeRegistry::new();
1660        let sb = TypeObjectBuilder::struct_type("::Point")
1661            .member("x", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1662            .member("y", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m);
1663        let complete = TypeObject::Complete(CompleteTypeObject::Struct(sb.build_complete()));
1664        let minimal = TypeObject::Minimal(MinimalTypeObject::Struct(sb.build_minimal()));
1665
1666        let ct = DynamicTypeBuilderFactory::create_type_w_type_object_in(&complete, &reg).unwrap();
1667        let mt = DynamicTypeBuilderFactory::create_type_w_type_object_in(&minimal, &reg).unwrap();
1668
1669        assert_eq!(mt.kind(), TypeKind::Structure);
1670        assert_eq!(mt.member_count(), 2, "minimal carries the full structure");
1671        assert!(
1672            mt.member_by_name("x").is_none(),
1673            "minimal has hash names, not the readable 'x'"
1674        );
1675
1676        // Member IDs are identical between the forms → identical wire encoding.
1677        let cx = ct.member_by_name("x").unwrap().id();
1678        let cy = ct.member_by_name("y").unwrap().id();
1679        let mut dc = DynamicData::new(ct.clone());
1680        dc.set_int32_value(cx, 11).unwrap();
1681        dc.set_int32_value(cy, 22).unwrap();
1682        let mut dm = DynamicData::new(mt.clone());
1683        dm.set_int32_value(cx, 11).unwrap();
1684        dm.set_int32_value(cy, 22).unwrap();
1685
1686        for xcdr2 in [true, false] {
1687            let bc = encode_dynamic(&dc, xcdr2, false).unwrap();
1688            let bm = encode_dynamic(&dm, xcdr2, false).unwrap();
1689            assert_eq!(
1690                bc, bm,
1691                "minimal-resolved encodes byte-identically to complete xcdr2={xcdr2}"
1692            );
1693            let back = decode_dynamic(&mt, &bm, xcdr2, false).unwrap();
1694            assert_eq!(back.get_int32_value(cx).unwrap(), 11);
1695        }
1696    }
1697
1698    /// A Minimal struct member referenced by `EquivalenceHashMinimal` resolves
1699    /// fully through the registry (the old "pending C4.2" residual).
1700    #[test]
1701    fn minimal_nested_member_resolves_via_registry() {
1702        use crate::builder::TypeObjectBuilder;
1703        use crate::resolve::TypeRegistry;
1704        use crate::type_identifier::EquivalenceHash;
1705
1706        let inner = TypeObjectBuilder::struct_type("::Point")
1707            .member("x", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1708            .build_minimal();
1709        let h = EquivalenceHash([0x7; 14]);
1710        let mut reg = TypeRegistry::new();
1711        reg.insert_minimal(h, MinimalTypeObject::Struct(inner));
1712
1713        let outer = TypeObjectBuilder::struct_type("::Line")
1714            .member("p", TypeIdentifier::EquivalenceHashMinimal(h), |m| m)
1715            .build_minimal();
1716        let to = TypeObject::Minimal(MinimalTypeObject::Struct(outer));
1717        let dt = DynamicTypeBuilderFactory::create_type_w_type_object_in(&to, &reg).unwrap();
1718
1719        assert_eq!(dt.member_count(), 1);
1720        let p = dt.member_by_index(0).unwrap();
1721        assert_eq!(
1722            p.dynamic_type().kind(),
1723            TypeKind::Structure,
1724            "nested minimal struct resolved fully"
1725        );
1726        assert_eq!(p.dynamic_type().member_count(), 1);
1727    }
1728
1729    #[test]
1730    fn resolve_nested_struct_member_fully_via_registry() {
1731        use crate::builder::TypeObjectBuilder;
1732        use crate::resolve::TypeRegistry;
1733        use crate::type_identifier::EquivalenceHash;
1734
1735        // inner: Point { x: int32, y: int32 }
1736        let inner = TypeObjectBuilder::struct_type("::Point")
1737            .member("x", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1738            .member("y", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1739            .build_complete();
1740        let h = EquivalenceHash([0x42; 14]);
1741        let mut reg = TypeRegistry::new();
1742        reg.insert_complete(h, CompleteTypeObject::Struct(inner));
1743
1744        // outer: Line { start: Point (by hash), len: int32 }
1745        let outer = TypeObjectBuilder::struct_type("::Line")
1746            .member("start", TypeIdentifier::EquivalenceHashComplete(h), |m| m)
1747            .member(
1748                "len",
1749                TypeIdentifier::Primitive(PrimitiveKind::Int32),
1750                |m| m,
1751            )
1752            .build_complete();
1753        let to = TypeObject::Complete(CompleteTypeObject::Struct(outer));
1754
1755        let dt = DynamicTypeBuilderFactory::create_type_w_type_object_in(&to, &reg).unwrap();
1756        assert_eq!(dt.member_count(), 2);
1757        let start = dt.member_by_name("start").expect("start member");
1758        // The nested Point is FULLY resolved (not a shallow <typeref> placeholder).
1759        assert_eq!(start.dynamic_type().kind(), TypeKind::Structure);
1760        assert_eq!(start.dynamic_type().member_count(), 2);
1761        assert!(start.dynamic_type().member_by_name("x").is_some());
1762        assert!(start.dynamic_type().member_by_name("y").is_some());
1763        // A missing-hash reference resolves to an honest Unsupported, not a panic.
1764        let dangling = TypeObjectBuilder::struct_type("::Bad")
1765            .member(
1766                "ref",
1767                TypeIdentifier::EquivalenceHashComplete(EquivalenceHash([0xFF; 14])),
1768                |m| m,
1769            )
1770            .build_complete();
1771        let bad = TypeObject::Complete(CompleteTypeObject::Struct(dangling));
1772        assert!(matches!(
1773            DynamicTypeBuilderFactory::create_type_w_type_object_in(&bad, &reg),
1774            Err(DynamicError::Unsupported(_))
1775        ));
1776    }
1777
1778    // ------------------------------------------------------------------
1779    // O3: composite-in-collection — the element of a collection is itself a
1780    // composite and must resolve to a fully-populated DynamicType (its members
1781    // carried), not a shallow name-only descriptor.
1782    // ------------------------------------------------------------------
1783
1784    /// Registers `::Point { x: int32, y: int32 }` and returns `(registry, hash)`.
1785    fn point_registry() -> (
1786        crate::resolve::TypeRegistry,
1787        crate::type_identifier::EquivalenceHash,
1788    ) {
1789        use crate::builder::TypeObjectBuilder;
1790        use crate::resolve::TypeRegistry;
1791        use crate::type_identifier::EquivalenceHash;
1792        let point = TypeObjectBuilder::struct_type("::Point")
1793            .member("x", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1794            .member("y", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1795            .build_complete();
1796        let h = EquivalenceHash([0x11; 14]);
1797        let mut reg = TypeRegistry::new();
1798        reg.insert_complete(h, CompleteTypeObject::Struct(point));
1799        (reg, h)
1800    }
1801
1802    /// A struct member `field: <ti>` built through the bridge with `registry`.
1803    fn resolve_member(
1804        ti: TypeIdentifier,
1805        reg: &crate::resolve::TypeRegistry,
1806    ) -> Result<DynamicType, DynamicError> {
1807        use crate::builder::TypeObjectBuilder;
1808        let s = TypeObjectBuilder::struct_type("::Holder")
1809            .member("field", ti, |m| m)
1810            .build_complete();
1811        let to = TypeObject::Complete(CompleteTypeObject::Struct(s));
1812        let dt = DynamicTypeBuilderFactory::create_type_w_type_object_in(&to, reg)?;
1813        Ok(dt
1814            .member_by_name("field")
1815            .expect("field member")
1816            .dynamic_type()
1817            .clone())
1818    }
1819
1820    fn seq_of(elem: TypeIdentifier) -> TypeIdentifier {
1821        TypeIdentifier::PlainSequenceSmall {
1822            header: crate::type_identifier::PlainCollectionHeader::default(),
1823            bound: 0,
1824            element: alloc::boxed::Box::new(elem),
1825        }
1826    }
1827
1828    #[test]
1829    fn resolve_seq_of_struct_fully_via_registry() {
1830        let (reg, h) = point_registry();
1831        let seq = resolve_member(seq_of(TypeIdentifier::EquivalenceHashComplete(h)), &reg).unwrap();
1832        assert_eq!(seq.kind(), TypeKind::Sequence);
1833        let elem = collection::resolved_element(&seq).expect("resolved composite element");
1834        assert_eq!(
1835            elem.kind(),
1836            TypeKind::Structure,
1837            "seq element is a full struct"
1838        );
1839        assert_eq!(elem.member_count(), 2, "Point's x + y survived");
1840        assert!(elem.member_by_name("x").is_some());
1841        assert!(elem.member_by_name("y").is_some());
1842    }
1843
1844    #[test]
1845    fn resolve_array_of_struct_fully_via_registry() {
1846        let (reg, h) = point_registry();
1847        let arr_ti = TypeIdentifier::PlainArraySmall {
1848            header: crate::type_identifier::PlainCollectionHeader::default(),
1849            array_bounds: alloc::vec![3],
1850            element: alloc::boxed::Box::new(TypeIdentifier::EquivalenceHashComplete(h)),
1851        };
1852        let arr = resolve_member(arr_ti, &reg).unwrap();
1853        assert_eq!(arr.kind(), TypeKind::Array);
1854        let elem = collection::resolved_element(&arr).expect("resolved composite element");
1855        assert_eq!(elem.kind(), TypeKind::Structure);
1856        assert_eq!(elem.member_count(), 2);
1857    }
1858
1859    #[test]
1860    fn resolve_seq_of_seq_of_struct_nested() {
1861        let (reg, h) = point_registry();
1862        let nested = seq_of(seq_of(TypeIdentifier::EquivalenceHashComplete(h)));
1863        let outer = resolve_member(nested, &reg).unwrap();
1864        assert_eq!(outer.kind(), TypeKind::Sequence);
1865        let inner = collection::resolved_element(&outer).expect("inner sequence");
1866        assert_eq!(
1867            inner.kind(),
1868            TypeKind::Sequence,
1869            "element is itself a sequence"
1870        );
1871        let leaf = collection::resolved_element(inner).expect("leaf struct");
1872        assert_eq!(leaf.kind(), TypeKind::Structure);
1873        assert_eq!(
1874            leaf.member_count(),
1875            2,
1876            "innermost Point still carries its members"
1877        );
1878    }
1879
1880    #[test]
1881    fn resolve_seq_of_scalar_still_works() {
1882        // Regression: a scalar element must keep round-tripping (no composite).
1883        let reg = crate::resolve::TypeRegistry::new();
1884        let seq = resolve_member(
1885            seq_of(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
1886            &reg,
1887        )
1888        .unwrap();
1889        assert_eq!(seq.kind(), TypeKind::Sequence);
1890        let elem = collection::resolved_element(&seq).expect("scalar element retained");
1891        assert_eq!(elem.kind(), TypeKind::Int32);
1892    }
1893
1894    #[test]
1895    fn resolve_map_of_struct_fully_via_registry() {
1896        // map<int32, Point> — key stays scalar, value resolves to a full Point.
1897        let (reg, h) = point_registry();
1898        let map_ti = TypeIdentifier::PlainMapSmall {
1899            header: crate::type_identifier::PlainCollectionHeader::default(),
1900            bound: 0,
1901            key_flags: crate::type_identifier::CollectionElementFlag(0),
1902            key: alloc::boxed::Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
1903            element: alloc::boxed::Box::new(TypeIdentifier::EquivalenceHashComplete(h)),
1904        };
1905        let map = resolve_member(map_ti, &reg).unwrap();
1906        assert_eq!(map.kind(), TypeKind::Map);
1907        let key = collection::resolved_map_key(&map).expect("resolved key");
1908        let val = collection::resolved_map_value(&map).expect("resolved value");
1909        assert_eq!(key.kind(), TypeKind::Int32);
1910        assert_eq!(
1911            val.kind(),
1912            TypeKind::Structure,
1913            "map value is a full struct"
1914        );
1915        assert_eq!(val.member_count(), 2, "Point's x + y survived");
1916    }
1917
1918    /// End-to-end: a `sequence<Point>` type built via the TypeObject BRIDGE
1919    /// (not hand-assembled) is codec-usable — encode → decode round-trips
1920    /// byte-exact and the nested Point members survive on the wire. This is the
1921    /// O3 guarantee proven against the real reflective codec.
1922    #[test]
1923    fn bridge_built_seq_of_struct_roundtrips_on_the_wire() {
1924        use crate::builder::TypeObjectBuilder;
1925        use crate::dynamic::codec::{decode_dynamic, encode_dynamic};
1926        use crate::dynamic::{DynamicData, DynamicValue};
1927
1928        let (reg, h) = point_registry();
1929        let path = TypeObjectBuilder::struct_type("::Path")
1930            .member(
1931                "pts",
1932                seq_of(TypeIdentifier::EquivalenceHashComplete(h)),
1933                |m| m,
1934            )
1935            .build_complete();
1936        let to = TypeObject::Complete(CompleteTypeObject::Struct(path));
1937        let ty = DynamicTypeBuilderFactory::create_type_w_type_object_in(&to, &reg).unwrap();
1938
1939        let pts_member = ty.member_by_name("pts").expect("pts");
1940        let pts_id = pts_member.id();
1941        let elem = collection::resolved_element(pts_member.dynamic_type()).expect("elem");
1942        let xid = elem.member_by_name("x").unwrap().id();
1943        let yid = elem.member_by_name("y").unwrap().id();
1944
1945        let mut p0 = DynamicData::new(elem.clone());
1946        p0.set_int32_value(xid, 3).unwrap();
1947        p0.set_int32_value(yid, 4).unwrap();
1948        let mut p1 = DynamicData::new(elem.clone());
1949        p1.set_int32_value(xid, 5).unwrap();
1950        p1.set_int32_value(yid, 6).unwrap();
1951
1952        let mut d = DynamicData::new(ty.clone());
1953        d.set_sequence_value(pts_id, alloc::vec![p0, p1]).unwrap();
1954
1955        for xcdr2 in [true, false] {
1956            let bytes = encode_dynamic(&d, xcdr2, false).expect("encode");
1957            let back = decode_dynamic(&ty, &bytes, xcdr2, false).expect("decode");
1958            let DynamicValue::Sequence(v) = back.get_value(pts_id).unwrap() else {
1959                panic!("pts is a sequence")
1960            };
1961            assert_eq!(v.len(), 2);
1962            assert_eq!(v[0].dynamic_type().kind(), TypeKind::Structure);
1963            assert_eq!(v[0].get_int32_value(xid).unwrap(), 3);
1964            assert_eq!(v[1].get_int32_value(yid).unwrap(), 6);
1965            assert_eq!(
1966                encode_dynamic(&back, xcdr2, false).unwrap(),
1967                bytes,
1968                "byte-exact bridge-built seq<struct> xcdr2={xcdr2}"
1969            );
1970        }
1971    }
1972
1973    /// End-to-end: a `map<i32,Point>` built via the bridge round-trips byte-exact
1974    /// through the shared codec, with the composite value carrying its members.
1975    #[test]
1976    fn bridge_built_map_roundtrips_on_the_wire() {
1977        use crate::builder::TypeObjectBuilder;
1978        use crate::dynamic::codec::{decode_dynamic, encode_dynamic};
1979        use crate::dynamic::{DynamicData, DynamicValue};
1980
1981        let (reg, h) = point_registry();
1982        let map_ti = TypeIdentifier::PlainMapSmall {
1983            header: crate::type_identifier::PlainCollectionHeader::default(),
1984            bound: 0,
1985            key_flags: crate::type_identifier::CollectionElementFlag(0),
1986            key: alloc::boxed::Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
1987            element: alloc::boxed::Box::new(TypeIdentifier::EquivalenceHashComplete(h)),
1988        };
1989        let holder = TypeObjectBuilder::struct_type("::Holder")
1990            .member("m", map_ti, |m| m)
1991            .build_complete();
1992        let to = TypeObject::Complete(CompleteTypeObject::Struct(holder));
1993        let ty = DynamicTypeBuilderFactory::create_type_w_type_object_in(&to, &reg).unwrap();
1994
1995        let m_id = ty.member_by_name("m").unwrap().id();
1996        let map_ty = ty.member_by_name("m").unwrap().dynamic_type().clone();
1997        let kt = collection::resolved_map_key(&map_ty).unwrap().clone();
1998        let vt = collection::resolved_map_value(&map_ty).unwrap().clone();
1999        let xid = vt.member_by_name("x").unwrap().id();
2000        let yid = vt.member_by_name("y").unwrap().id();
2001
2002        let mut kd = DynamicData::new(kt.clone());
2003        kd.set_value_raw(0, DynamicValue::Int32(9));
2004        let mut vd = DynamicData::new(vt.clone());
2005        vd.set_int32_value(xid, 3).unwrap();
2006        vd.set_int32_value(yid, 4).unwrap();
2007
2008        let mut d = DynamicData::new(ty.clone());
2009        d.set_map_value(m_id, alloc::vec![(kd, vd)]).unwrap();
2010
2011        for xcdr2 in [true, false] {
2012            let bytes = encode_dynamic(&d, xcdr2, false).unwrap();
2013            let back = decode_dynamic(&ty, &bytes, xcdr2, false).unwrap();
2014            let DynamicValue::Map(got) = back.get_value(m_id).unwrap() else {
2015                panic!("m is a map")
2016            };
2017            assert_eq!(got.len(), 1);
2018            assert_eq!(got[0].0.get_int32_value(0).unwrap(), 9);
2019            assert_eq!(got[0].1.dynamic_type().kind(), TypeKind::Structure);
2020            assert_eq!(got[0].1.get_int32_value(xid).unwrap(), 3);
2021            assert_eq!(
2022                encode_dynamic(&back, xcdr2, false).unwrap(),
2023                bytes,
2024                "byte-exact bridge-built map<i32,struct> xcdr2={xcdr2}"
2025            );
2026        }
2027    }
2028
2029    #[test]
2030    fn alias_to_composite_resolves_transparently() {
2031        // typedef Point PointAlias — the alias target is a composite; typedefs
2032        // are transparent, so resolving the alias yields the full Point struct.
2033        use crate::builder::TypeObjectBuilder;
2034        let (reg, h) = point_registry();
2035        let alias =
2036            TypeObjectBuilder::alias("::PointAlias", TypeIdentifier::EquivalenceHashComplete(h))
2037                .build_complete();
2038        let to = TypeObject::Complete(CompleteTypeObject::Alias(alias));
2039        let dt = DynamicTypeBuilderFactory::create_type_w_type_object_in(&to, &reg).unwrap();
2040        assert_eq!(
2041            dt.kind(),
2042            TypeKind::Structure,
2043            "alias resolved transparently to its target"
2044        );
2045        assert_eq!(dt.member_count(), 2);
2046        assert!(dt.member_by_name("x").is_some());
2047
2048        // A scalar alias (typedef int32 Id) stays a shallow Alias node.
2049        let scalar_alias =
2050            TypeObjectBuilder::alias("::Id", TypeIdentifier::Primitive(PrimitiveKind::Int32))
2051                .build_complete();
2052        let sto = TypeObject::Complete(CompleteTypeObject::Alias(scalar_alias));
2053        let sdt = DynamicTypeBuilderFactory::create_type_w_type_object_in(&sto, &reg).unwrap();
2054        assert_eq!(
2055            sdt.kind(),
2056            TypeKind::Alias,
2057            "scalar typedef stays an alias node"
2058        );
2059    }
2060}