Skip to main content

xsd_schema/schema/
inline.rs

1//! Inline type assembly pass
2//!
3//! This module implements Phase 3 inline type resolution as specified in
4//! XSD_INLINE_TYPE_RESOLUTION_DESIGN.md. It assembles inline type definitions
5//! (TypeRefResult::Inline and inline_type fields) into arena TypeKey values.
6//!
7//! # Overview
8//!
9//! Inline types are anonymous type definitions that appear directly within
10//! element declarations, attribute declarations, or as base/item/member types
11//! in type derivation. These need to be allocated in the type arenas before
12//! the reference resolution phase can complete.
13//!
14//! # Work Queue Approach
15//!
16//! To avoid borrow conflicts and handle nested inline types, we use a two-pass
17//! work queue approach:
18//!
19//! 1. **Scan Pass**: Collect all inline types into `InlineTypeJob` records.
20//!    Each job records the owner component and the inline type AST (cloned).
21//!
22//! 2. **Assembly Pass**: For each job, assemble the inline type into the
23//!    arena and update the owner's resolved_* field with the TypeKey.
24
25use crate::arenas::{
26    ComplexTypeDefData, ElementDeclData, IdentityConstraintData, ResolvedAttributeUse,
27    SimpleTypeDefData,
28};
29use std::collections::{HashMap, HashSet};
30
31use crate::error::{SchemaError, SchemaResult};
32use crate::ids::*;
33use crate::namespace::NameTable;
34use crate::parser::frames::{
35    ComplexContentResult, ElementFrameResult, IdentityKind, IdentityResult, ParticleResult,
36    ParticleTerm, QNameRef, TypeFrameResult, TypeRefResult,
37};
38use crate::parser::location::{SourceMapStorage, SourceRef};
39use crate::schema::model::DerivationSet;
40use crate::schema::SchemaSet;
41
42/// Resolve a parsed `block`/`final` attribute to an effective `DerivationSet`.
43/// `Some(set)` → explicit (including `""` = empty, which overrides the default).
44/// `None` → absent; inherit the source document's default via `pick_default`,
45/// following `schema_defaults_doc` for override-copied components so they see
46/// the override document's defaults rather than the origin schema's.
47fn resolve_derivation_default(
48    parsed: Option<DerivationSet>,
49    source: Option<&SourceRef>,
50    schema_set: &SchemaSet,
51    pick_default: impl Fn(&crate::schema::model::SchemaDocument) -> DerivationSet,
52) -> DerivationSet {
53    parsed.unwrap_or_else(|| {
54        source
55            .and_then(|s| {
56                let doc_id = s.schema_defaults_doc.unwrap_or(s.doc_id);
57                schema_set.documents.get(doc_id as usize)
58            })
59            .map(pick_default)
60            .unwrap_or_default()
61    })
62}
63
64fn resolve_block(
65    block: Option<DerivationSet>,
66    source: Option<&SourceRef>,
67    schema_set: &SchemaSet,
68) -> DerivationSet {
69    resolve_derivation_default(block, source, schema_set, |d| d.block_default)
70}
71
72fn resolve_final(
73    final_derivation: Option<DerivationSet>,
74    source: Option<&SourceRef>,
75    schema_set: &SchemaSet,
76) -> DerivationSet {
77    resolve_derivation_default(final_derivation, source, schema_set, |d| d.final_default)
78}
79
80/// Statistics from the inline type assembly pass
81#[derive(Debug, Default)]
82pub struct InlineAssemblyStats {
83    /// Number of element inline types assembled
84    pub element_inline_types: usize,
85    /// Number of attribute inline types assembled
86    pub attribute_inline_types: usize,
87    /// Number of simple type base/item/member inline types assembled
88    pub simple_type_inline_derivations: usize,
89    /// Number of complex type base type inline types assembled
90    pub complex_type_inline_derivations: usize,
91    /// Number of inline types in model group particles assembled
92    pub model_group_inline_types: usize,
93    /// Number of inline types in attribute groups assembled
94    pub attribute_group_inline_types: usize,
95    /// Number of inline types in complex type attributes assembled
96    pub complex_type_attribute_inline_types: usize,
97    /// Total inline types assembled
98    pub total_inline_types: usize,
99}
100
101/// Role of an inline type within its owner
102#[derive(Debug, Clone, Copy)]
103enum InlineRole {
104    /// Element's type (inline_type field)
105    ElementType,
106    /// Attribute's type (inline_type field)
107    AttributeType,
108    /// Simple type restriction base (base_type: Inline)
109    SimpleTypeBase,
110    /// List type item type (item_type: Inline)
111    ListItemType,
112    /// Union member type (member_types[index]: Inline)
113    UnionMemberType(usize),
114    /// Complex type derivation base (base_type: Inline)
115    ComplexTypeBase,
116    /// Attribute use inline type within complex type (attributes[index])
117    ComplexTypeAttribute(usize),
118    /// Particle inline type in model group (particles[index])
119    ModelGroupParticle(usize),
120    /// Attribute use inline type in attribute group (attributes[index])
121    AttributeGroupAttribute(usize),
122    /// Inline type on element at index in content particle's model group
123    ContentParticleType(usize),
124    /// Inline type on alternative at index in element's alternatives vec (XSD 1.1)
125    #[cfg(feature = "xsd11")]
126    AlternativeType(usize),
127    /// Inline `<xs:simpleType>` inside simpleContent/restriction
128    /// (§3.4.2.2 clause 1.1 — the B simple type definition)
129    SimpleContentInlineType,
130}
131
132/// Owner of an inline type
133#[derive(Debug, Clone, Copy)]
134enum InlineOwner {
135    Element(ElementKey),
136    Attribute(AttributeKey),
137    SimpleType(SimpleTypeKey),
138    ComplexType(ComplexTypeKey),
139    ModelGroup(ModelGroupKey),
140    AttributeGroup(AttributeGroupKey),
141}
142
143/// A job representing an inline type to be assembled
144struct InlineTypeJob {
145    owner: InlineOwner,
146    role: InlineRole,
147    type_frame: TypeFrameResult,
148    target_namespace: Option<NameId>,
149}
150
151/// Assemble all inline types in the schema set
152///
153/// This function walks all components, collects inline type definitions,
154/// assembles them into the arenas, and updates the resolved_* fields.
155///
156/// This should be called after top-level component assembly but before
157/// reference resolution.
158pub fn assemble_inline_types(schema_set: &mut SchemaSet) -> SchemaResult<InlineAssemblyStats> {
159    let mut stats = InlineAssemblyStats::default();
160
161    // Collect all inline type jobs
162    let mut jobs = collect_inline_type_jobs(schema_set);
163
164    // Process jobs iteratively (nested inline types may add more jobs)
165    while !jobs.is_empty() {
166        let current_jobs: Vec<_> = std::mem::take(&mut jobs);
167
168        for job in current_jobs {
169            let type_key = assemble_inline_type(schema_set, &job.type_frame, job.target_namespace)?;
170            update_owner(schema_set, &job, type_key, &mut stats)?;
171
172            // Check if the newly assembled type has nested inline types
173            collect_nested_inline_types(schema_set, type_key, job.target_namespace, &mut jobs);
174        }
175    }
176
177    Ok(stats)
178}
179
180/// Collect all inline type jobs from schema components
181fn collect_inline_type_jobs(schema_set: &SchemaSet) -> Vec<InlineTypeJob> {
182    let mut jobs = Vec::new();
183
184    // Scan elements
185    for (key, elem) in schema_set.arenas.elements.iter() {
186        let target_ns = elem.target_namespace;
187        if let Some(inline_type) = &elem.inline_type {
188            jobs.push(InlineTypeJob {
189                owner: InlineOwner::Element(key),
190                role: InlineRole::ElementType,
191                type_frame: (**inline_type).clone(),
192                target_namespace: target_ns,
193            });
194        }
195    }
196
197    // Scan element alternatives (XSD 1.1)
198    #[cfg(feature = "xsd11")]
199    for (key, elem) in schema_set.arenas.elements.iter() {
200        let target_ns = elem.target_namespace;
201        for (idx, alt) in elem.alternatives.iter().enumerate() {
202            if let Some(inline_type) = &alt.inline_type {
203                jobs.push(InlineTypeJob {
204                    owner: InlineOwner::Element(key),
205                    role: InlineRole::AlternativeType(idx),
206                    type_frame: (**inline_type).clone(),
207                    target_namespace: target_ns,
208                });
209            }
210        }
211    }
212
213    // Scan attributes
214    for (key, attr) in schema_set.arenas.attributes.iter() {
215        let target_ns = attr.target_namespace;
216        if let Some(inline_type) = &attr.inline_type {
217            jobs.push(InlineTypeJob {
218                owner: InlineOwner::Attribute(key),
219                role: InlineRole::AttributeType,
220                type_frame: TypeFrameResult::Simple(Box::new((**inline_type).clone())),
221                target_namespace: target_ns,
222            });
223        }
224    }
225
226    // Scan simple types
227    for (key, simple) in schema_set.arenas.simple_types.iter() {
228        let target_ns = simple.target_namespace;
229
230        // Base type inline
231        if let Some(TypeRefResult::Inline(inline_type)) = &simple.base_type {
232            jobs.push(InlineTypeJob {
233                owner: InlineOwner::SimpleType(key),
234                role: InlineRole::SimpleTypeBase,
235                type_frame: (**inline_type).clone(),
236                target_namespace: target_ns,
237            });
238        }
239
240        // Item type inline (for list types)
241        if let Some(TypeRefResult::Inline(inline_type)) = &simple.item_type {
242            jobs.push(InlineTypeJob {
243                owner: InlineOwner::SimpleType(key),
244                role: InlineRole::ListItemType,
245                type_frame: (**inline_type).clone(),
246                target_namespace: target_ns,
247            });
248        }
249
250        // Member types inline (for union types)
251        for (idx, member) in simple.member_types.iter().enumerate() {
252            if let TypeRefResult::Inline(inline_type) = member {
253                jobs.push(InlineTypeJob {
254                    owner: InlineOwner::SimpleType(key),
255                    role: InlineRole::UnionMemberType(idx),
256                    type_frame: (**inline_type).clone(),
257                    target_namespace: target_ns,
258                });
259            }
260        }
261    }
262
263    // Scan complex types
264    for (key, complex) in schema_set.arenas.complex_types.iter() {
265        let target_ns = complex.target_namespace;
266
267        // Base type inline
268        if let Some(TypeRefResult::Inline(inline_type)) = &complex.base_type {
269            jobs.push(InlineTypeJob {
270                owner: InlineOwner::ComplexType(key),
271                role: InlineRole::ComplexTypeBase,
272                type_frame: (**inline_type).clone(),
273                target_namespace: target_ns,
274            });
275        }
276
277        // Attribute uses with inline types
278        for (idx, attr_use) in complex.attributes.iter().enumerate() {
279            if let Some(inline_type) = &attr_use.attribute.inline_type {
280                jobs.push(InlineTypeJob {
281                    owner: InlineOwner::ComplexType(key),
282                    role: InlineRole::ComplexTypeAttribute(idx),
283                    type_frame: TypeFrameResult::Simple(Box::new((**inline_type).clone())),
284                    target_namespace: target_ns,
285                });
286            }
287        }
288
289        // Content particles (in complex content)
290        collect_content_inline_types(&complex.content, key, target_ns, &mut jobs);
291    }
292
293    // Scan model groups (recursively, including nested inline groups)
294    for (key, group) in schema_set.arenas.model_groups.iter() {
295        let target_ns = group.target_namespace;
296        let mut flat_idx = 0;
297        collect_model_group_inline_types_recursive(
298            &group.particles,
299            key,
300            target_ns,
301            &mut flat_idx,
302            &mut jobs,
303        );
304    }
305
306    // Scan attribute groups
307    for (key, group) in schema_set.arenas.attribute_groups.iter() {
308        let target_ns = group.target_namespace;
309
310        for (idx, attr_use) in group.attributes.iter().enumerate() {
311            if let Some(inline_type) = &attr_use.attribute.inline_type {
312                jobs.push(InlineTypeJob {
313                    owner: InlineOwner::AttributeGroup(key),
314                    role: InlineRole::AttributeGroupAttribute(idx),
315                    type_frame: TypeFrameResult::Simple(Box::new((**inline_type).clone())),
316                    target_namespace: target_ns,
317                });
318            }
319        }
320    }
321
322    jobs
323}
324
325/// Collect inline types from complex content (recursive helper)
326fn collect_content_inline_types(
327    content: &ComplexContentResult,
328    owner_key: ComplexTypeKey,
329    target_ns: Option<NameId>,
330    jobs: &mut Vec<InlineTypeJob>,
331) {
332    match content {
333        ComplexContentResult::Complex(complex_content) => {
334            // Check base type inline
335            if let Some(TypeRefResult::Inline(inline_type)) = &complex_content.base_type {
336                jobs.push(InlineTypeJob {
337                    owner: InlineOwner::ComplexType(owner_key),
338                    role: InlineRole::ComplexTypeBase,
339                    type_frame: (**inline_type).clone(),
340                    target_namespace: target_ns,
341                });
342            }
343
344            // Check attributes in complex content
345            for (idx, attr_use) in complex_content.attributes.iter().enumerate() {
346                if let Some(inline_type) = &attr_use.attribute.inline_type {
347                    jobs.push(InlineTypeJob {
348                        owner: InlineOwner::ComplexType(owner_key),
349                        role: InlineRole::ComplexTypeAttribute(idx),
350                        type_frame: TypeFrameResult::Simple(Box::new((**inline_type).clone())),
351                        target_namespace: target_ns,
352                    });
353                }
354            }
355
356            // Scan content particle for inline types on element children
357            if let Some(particle) = &complex_content.particle {
358                collect_particle_inline_types(particle, owner_key, target_ns, jobs);
359            }
360        }
361        ComplexContentResult::Simple(simple_content) => {
362            // Check base type inline
363            if let Some(TypeRefResult::Inline(inline_type)) = &simple_content.base_type {
364                jobs.push(InlineTypeJob {
365                    owner: InlineOwner::ComplexType(owner_key),
366                    role: InlineRole::ComplexTypeBase,
367                    type_frame: (**inline_type).clone(),
368                    target_namespace: target_ns,
369                });
370            }
371
372            // Check attributes in simple content
373            for (idx, attr_use) in simple_content.attributes.iter().enumerate() {
374                if let Some(inline_type) = &attr_use.attribute.inline_type {
375                    jobs.push(InlineTypeJob {
376                        owner: InlineOwner::ComplexType(owner_key),
377                        role: InlineRole::ComplexTypeAttribute(idx),
378                        type_frame: TypeFrameResult::Simple(Box::new((**inline_type).clone())),
379                        target_namespace: target_ns,
380                    });
381                }
382            }
383
384            // §3.4.2.2 clause 1.1: inline <xs:simpleType> inside
385            // simpleContent/restriction is the content type definition B.
386            if let Some(inline_st) = &simple_content.content_type {
387                jobs.push(InlineTypeJob {
388                    owner: InlineOwner::ComplexType(owner_key),
389                    role: InlineRole::SimpleContentInlineType,
390                    type_frame: TypeFrameResult::Simple(Box::new((**inline_st).clone())),
391                    target_namespace: target_ns,
392                });
393            }
394        }
395        ComplexContentResult::Empty => {}
396    }
397}
398
399/// Collect inline types from a content particle's model group elements (recursive)
400fn collect_particle_inline_types(
401    particle: &ParticleResult,
402    owner_key: ComplexTypeKey,
403    target_ns: Option<NameId>,
404    jobs: &mut Vec<InlineTypeJob>,
405) {
406    if let ParticleTerm::Group(group_def) = &particle.term {
407        let mut flat_idx = 0;
408        collect_group_elements_recursive(
409            &group_def.particles,
410            owner_key,
411            target_ns,
412            &mut flat_idx,
413            jobs,
414        );
415    }
416}
417
418/// Recursive helper: walk particles in depth-first order, assigning flat element indices
419fn collect_group_elements_recursive(
420    particles: &[ParticleResult],
421    owner_key: ComplexTypeKey,
422    target_ns: Option<NameId>,
423    flat_idx: &mut usize,
424    jobs: &mut Vec<InlineTypeJob>,
425) {
426    for particle in particles {
427        match &particle.term {
428            ParticleTerm::Element(elem) => {
429                if let Some(inline_type) = &elem.inline_type {
430                    jobs.push(InlineTypeJob {
431                        owner: InlineOwner::ComplexType(owner_key),
432                        role: InlineRole::ContentParticleType(*flat_idx),
433                        type_frame: (**inline_type).clone(),
434                        target_namespace: target_ns,
435                    });
436                }
437                *flat_idx += 1;
438            }
439            ParticleTerm::Group(group_def) if group_def.ref_name.is_none() => {
440                // Inline group (no ref) - recurse into its particles
441                collect_group_elements_recursive(
442                    &group_def.particles,
443                    owner_key,
444                    target_ns,
445                    flat_idx,
446                    jobs,
447                );
448            }
449            _ => {} // Skip group refs and wildcards
450        }
451    }
452}
453
454/// Recursive helper: walk model group particles in depth-first order, collecting inline types
455/// with flat element indices (mirroring collect_group_elements_recursive for complex types)
456fn collect_model_group_inline_types_recursive(
457    particles: &[ParticleResult],
458    owner_key: ModelGroupKey,
459    target_ns: Option<NameId>,
460    flat_idx: &mut usize,
461    jobs: &mut Vec<InlineTypeJob>,
462) {
463    for particle in particles {
464        match &particle.term {
465            ParticleTerm::Element(elem) => {
466                if let Some(inline_type) = &elem.inline_type {
467                    jobs.push(InlineTypeJob {
468                        owner: InlineOwner::ModelGroup(owner_key),
469                        role: InlineRole::ModelGroupParticle(*flat_idx),
470                        type_frame: (**inline_type).clone(),
471                        target_namespace: target_ns,
472                    });
473                }
474                *flat_idx += 1;
475            }
476            ParticleTerm::Group(group_def) if group_def.ref_name.is_none() => {
477                // Inline group (no ref) - recurse into its particles
478                collect_model_group_inline_types_recursive(
479                    &group_def.particles,
480                    owner_key,
481                    target_ns,
482                    flat_idx,
483                    jobs,
484                );
485            }
486            _ => {} // Skip group refs and wildcards
487        }
488    }
489}
490
491/// Allocation job for a local element in a content particle
492struct ContentParticleElementJob {
493    complex_type_key: ComplexTypeKey,
494    flat_idx: usize,
495    elem: ElementFrameResult,
496    target_namespace: Option<NameId>,
497}
498
499/// Walk content particles recursively and collect local element allocation jobs
500fn collect_content_particle_elements_recursive(
501    particles: &[ParticleResult],
502    complex_type_key: ComplexTypeKey,
503    target_ns: Option<NameId>,
504    flat_idx: &mut usize,
505    jobs: &mut Vec<ContentParticleElementJob>,
506) {
507    for particle in particles {
508        match &particle.term {
509            ParticleTerm::Element(elem) if elem.ref_name.is_none() => {
510                jobs.push(ContentParticleElementJob {
511                    complex_type_key,
512                    flat_idx: *flat_idx,
513                    elem: elem.clone(),
514                    target_namespace: target_ns,
515                });
516                *flat_idx += 1;
517            }
518            ParticleTerm::Element(_) => {
519                // Ref element - skip allocation but still increment counter
520                *flat_idx += 1;
521            }
522            ParticleTerm::Group(group_def) if group_def.ref_name.is_none() => {
523                collect_content_particle_elements_recursive(
524                    &group_def.particles,
525                    complex_type_key,
526                    target_ns,
527                    flat_idx,
528                    jobs,
529                );
530            }
531            _ => {} // Skip group refs and wildcards
532        }
533    }
534}
535
536/// Check that a keyref's refer target is not a keyref and has matching field count.
537fn check_keyref_target(
538    ic: &IdentityResult,
539    target_kind: IdentityKind,
540    target_field_count: usize,
541    refer_name: NameId,
542    name_table: &NameTable,
543    source_maps: &SourceMapStorage,
544) -> SchemaResult<()> {
545    if target_kind == IdentityKind::Keyref {
546        let ic_name = name_table.resolve_ref(ic.name);
547        let refer_name_str = name_table.resolve_ref(refer_name);
548        let location = ic.source.as_ref().and_then(|s| source_maps.locate(s));
549        return Err(SchemaError::structural(
550            "src-identity-constraint",
551            format!(
552                "Keyref '{}': refer target '{}' is a keyref, not a key or unique",
553                ic_name, refer_name_str
554            ),
555            location,
556        ));
557    }
558    if ic.fields.len() != target_field_count {
559        let ic_name = name_table.resolve_ref(ic.name);
560        let refer_name_str = name_table.resolve_ref(refer_name);
561        let location = ic.source.as_ref().and_then(|s| source_maps.locate(s));
562        return Err(SchemaError::structural(
563            "src-identity-constraint",
564            format!(
565                "Keyref '{}': has {} field(s) but refer target '{}' has {} field(s)",
566                ic_name,
567                ic.fields.len(),
568                refer_name_str,
569                target_field_count
570            ),
571            location,
572        ));
573    }
574    Ok(())
575}
576
577/// Validate keyref constraints: refer must resolve to a key/unique with matching
578/// field count (§3.11.4, §3.11.6).
579///
580/// First checks the current element's ICs. If not found locally, performs a
581/// global fallback search across the arena (the spec's `{referenced key}` is
582/// resolved globally in the IC definition symbol space, not restricted to the
583/// same element — the target may be on an ancestor element).
584/// Resolve an XSD 1.1 identity constraint @ref to the referenced IC key.
585///
586/// §3.11.2: the corresponding schema component is the identity-constraint
587/// definition resolved to by the actual value of the ref [attribute].
588/// §3.11.6 clause 5: the referenced IC's category must match the element tag.
589/// Resolve an XSD 1.1 identity constraint @ref to the referenced IC key.
590///
591/// §3.11.2: the corresponding schema component is the identity-constraint
592/// definition resolved to by the actual value of the ref [attribute].
593/// §3.11.6 clause 5: the referenced IC's category must match the element tag.
594pub(crate) fn resolve_ic_ref(
595    kind: IdentityKind,
596    ref_name: &QNameRef,
597    source: Option<&SourceRef>,
598    target_namespace: Option<NameId>,
599    schema_set: &crate::schema::SchemaSet,
600) -> crate::error::SchemaResult<IdentityConstraintKey> {
601    let ref_ns = ref_name.namespace.or(target_namespace);
602    let ref_local = ref_name.local_name;
603
604    // §3.17.6.2 clause 4 — per-document QName visibility gate.
605    // Feed the *resolved* `ref_ns` (after target-namespace fallback) so an
606    // unprefixed IC ref doesn't accidentally bypass the gate as "absent".
607    crate::schema::resolver::check_namespace_visible_ns(
608        schema_set,
609        ref_ns,
610        ref_local,
611        source,
612        "Identity constraint",
613    )?;
614
615    // Look up in namespace tables
616    let target_key = schema_set
617        .namespaces
618        .get(&ref_ns)
619        .and_then(|nt| nt.identity_constraints.get(&ref_local))
620        .copied();
621
622    let target_key = match target_key {
623        Some(k) => k,
624        None => {
625            // Search arena as fallback (IC may not be in namespace table yet)
626            let mut found = None;
627            for (key, ic_data) in &schema_set.arenas.identity_constraints {
628                if ic_data.name != ref_local {
629                    continue;
630                }
631                let ic_ns = ic_data
632                    .source
633                    .as_ref()
634                    .and_then(|s| schema_set.documents.get(s.doc_id as usize))
635                    .and_then(|d| d.target_namespace);
636                if ic_ns == ref_ns {
637                    found = Some(key);
638                    break;
639                }
640            }
641            found.ok_or_else(|| {
642                let ref_display = crate::schema::resolver::format_resolved_qname(
643                    &schema_set.name_table,
644                    ref_ns,
645                    ref_local,
646                );
647                let location = source.and_then(|s| schema_set.source_maps.locate(s));
648                crate::error::SchemaError::structural(
649                    "src-resolve",
650                    format!("Identity constraint ref target '{}' not found", ref_display),
651                    location,
652                )
653            })?
654        }
655    };
656
657    // §3.11.6 clause 5: kind must match
658    let target = &schema_set.arenas.identity_constraints[target_key];
659    if target.kind != kind {
660        let ref_display = crate::schema::resolver::format_resolved_qname(
661            &schema_set.name_table,
662            ref_ns,
663            ref_local,
664        );
665        let location = source.and_then(|s| schema_set.source_maps.locate(s));
666        return Err(crate::error::SchemaError::structural(
667            "src-identity-constraint.5",
668            format!(
669                "Identity constraint ref '{}': referenced constraint is {:?} but expected {:?}",
670                ref_display, target.kind, kind
671            ),
672            location,
673        ));
674    }
675
676    Ok(target_key)
677}
678
679pub(crate) fn validate_keyref_refers(
680    identity_constraints: &[IdentityResult],
681    target_namespace: Option<NameId>,
682    name_table: &NameTable,
683    source_maps: &SourceMapStorage,
684    ic_arena: &slotmap::SlotMap<IdentityConstraintKey, IdentityConstraintData>,
685    documents: &[crate::schema::model::SchemaDocument],
686) -> SchemaResult<()> {
687    for ic in identity_constraints {
688        if ic.kind != IdentityKind::Keyref {
689            continue;
690        }
691        if let Some(refer) = &ic.refer {
692            let refer_name = refer.local_name;
693            let refer_ns = refer.namespace;
694            // Find the referenced constraint on the same element
695            let target = identity_constraints.iter().find(|other| {
696                other.name == refer_name && (refer_ns.is_none() || refer_ns == target_namespace)
697            });
698            match target {
699                None => {
700                    // Not found locally — search globally in the IC arena
701                    // (target may be on an ancestor element)
702                    // Fall back to target_namespace for unqualified refer (matches runtime
703                    // resolve_refer_key which does refer.namespace.or(compiled.target_namespace))
704                    let effective_refer_ns = refer_ns.or(target_namespace);
705                    let global_target = ic_arena.values().find(|ic_data| {
706                        if ic_data.name != refer_name {
707                            return false;
708                        }
709                        // Resolve the IC's namespace via its source document.
710                        // If the document is not yet in `documents` (current
711                        // schema being assembled — `documents.push` happens
712                        // after `assemble_schema` returns), fall back to the
713                        // assembler's `target_namespace`. Distinguish "doc
714                        // missing" from "doc.target_namespace is None" so
715                        // genuinely no-namespace ICs are not coerced.
716                        let ic_ns = match ic_data
717                            .source
718                            .as_ref()
719                            .map(|s| documents.get(s.doc_id as usize))
720                        {
721                            Some(Some(d)) => d.target_namespace,
722                            Some(None) => target_namespace,
723                            None => None,
724                        };
725                        effective_refer_ns == ic_ns
726                    });
727                    match global_target {
728                        Some(target_data) => {
729                            check_keyref_target(
730                                ic,
731                                target_data.kind,
732                                target_data.fields.len(),
733                                refer_name,
734                                name_table,
735                                source_maps,
736                            )?;
737                        }
738                        None => {
739                            let ic_name = name_table.resolve_ref(ic.name);
740                            let refer_name_str = name_table.resolve_ref(refer_name);
741                            let location = ic.source.as_ref().and_then(|s| source_maps.locate(s));
742                            return Err(SchemaError::structural(
743                                "src-identity-constraint",
744                                format!(
745                                    "Keyref '{}': refer target '{}' not found among identity constraints",
746                                    ic_name, refer_name_str
747                                ),
748                                location,
749                            ));
750                        }
751                    }
752                }
753                Some(target_ic) => {
754                    check_keyref_target(
755                        ic,
756                        target_ic.kind,
757                        target_ic.fields.len(),
758                        refer_name,
759                        name_table,
760                        source_maps,
761                    )?;
762                }
763            }
764        }
765    }
766    Ok(())
767}
768
769/// Allocate arena element declarations for local elements in content particles.
770///
771/// This enables the validator to look up nillable, fixed_value, default_value
772/// and other properties for local elements via their ElementKey.
773///
774/// Must be called after inline type assembly and reference resolution.
775pub fn allocate_content_particle_elements(schema_set: &mut SchemaSet) -> SchemaResult<()> {
776    // Collection pass: walk all complex types and collect jobs.
777    //
778    // Skip elements whose slot in `resolved_content_particle_elements` is
779    // already populated, so the function is safe to re-run (e.g., after
780    // a second inline-type assembly pass for alternatives discovered on
781    // local elements). The flat_idx still advances on every particle so
782    // newly added complex types collected in the same pass keep correct
783    // indexing.
784    let mut jobs = Vec::new();
785    for (key, complex) in schema_set.arenas.complex_types.iter() {
786        let target_ns = complex.target_namespace;
787        if let ComplexContentResult::Complex(def) = &complex.content {
788            if let Some(particle) = &def.particle {
789                if let ParticleTerm::Group(group_def) = &particle.term {
790                    let mut flat_idx = 0;
791                    collect_content_particle_elements_recursive(
792                        &group_def.particles,
793                        key,
794                        target_ns,
795                        &mut flat_idx,
796                        &mut jobs,
797                    );
798                }
799            }
800        }
801    }
802    // Drop jobs whose slot is already filled in the owning complex type.
803    jobs.retain(|job| {
804        match schema_set
805            .arenas
806            .complex_types
807            .get(job.complex_type_key)
808            .and_then(|ct| ct.resolved_content_particle_elements.get(job.flat_idx))
809        {
810            Some(Some(_)) => false, // already allocated
811            _ => true,
812        }
813    });
814
815    // Build per-document sets of already-known identity constraint names for uniqueness checking
816    // XSD §4.2.1: IC names must be unique per schema document, not globally
817    let mut ic_names_by_doc: HashMap<DocumentId, HashSet<NameId>> = HashMap::new();
818    for ic in schema_set.arenas.identity_constraints.values() {
819        if let Some(source) = &ic.source {
820            ic_names_by_doc
821                .entry(source.doc_id)
822                .or_default()
823                .insert(ic.name);
824        }
825    }
826
827    // Allocation pass: create element declarations and store keys
828    for job in jobs {
829        // §3.17.6.2 clause 4 — per-document QName visibility gate.
830        // This fast path bypasses `resolve_type_ref` so it must gate the QName
831        // itself; otherwise un-imported local-element type refs slip through.
832        if let Some(TypeRefResult::QName(qname)) = &job.elem.type_ref {
833            crate::schema::resolver::check_namespace_visible(
834                schema_set,
835                qname,
836                job.elem.source.as_ref(),
837                "Type",
838            )?;
839        }
840
841        let resolved_type = schema_set
842            .arenas
843            .complex_types
844            .get(job.complex_type_key)
845            .and_then(|ct| {
846                ct.resolved_content_particle_types
847                    .get(job.flat_idx)
848                    .copied()
849                    .flatten()
850            })
851            .or_else(|| {
852                // Try QName resolution
853                match &job.elem.type_ref {
854                    Some(TypeRefResult::QName(qname)) => schema_set
855                        .lookup_type(qname.namespace, qname.local_name)
856                        .or_else(|| {
857                            schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name)
858                        }),
859                    _ => None,
860                }
861            });
862
863        // §3.3.2 / §3.3.1: when a local element has neither `type=` nor
864        // an inline `<simpleType>` / `<complexType>` (and resolved_type is
865        // therefore not yet set), the {type definition} property defaults to
866        // xs:anyType. Otherwise content-model initialization in
867        // `init_content_model` falls back to (Simple, TextOnly), causing
868        // valid mixed/lax-wildcard children to be rejected (xsd002.v01).
869        let resolved_type = resolved_type.or_else(|| match &job.elem.type_ref {
870            // Only fall back when no type was specified at all. If a QName
871            // type-ref was given but didn't resolve, src-resolve below
872            // surfaces the error.
873            None => Some(TypeKey::Complex(schema_set.any_type_key())),
874            _ => None,
875        });
876
877        // src-resolve §3.3.6: a local element's QName-typed type-ref must
878        // resolve. Falling back silently masks bad schemas (s3_12si02).
879        if resolved_type.is_none() {
880            if let Some(TypeRefResult::QName(qname)) = &job.elem.type_ref {
881                let display = crate::schema::resolver::format_resolved_qname(
882                    &schema_set.name_table,
883                    qname.namespace,
884                    qname.local_name,
885                );
886                let location = schema_set.locate(job.elem.source.as_ref());
887                return Err(SchemaError::structural(
888                    "src-resolve",
889                    format!("Type reference '{}' on local element not found", display),
890                    location,
891                ));
892            }
893        }
894
895        let effective_ns = schema_set.effective_local_element_namespace(
896            job.elem.target_namespace,
897            job.elem.form.as_deref(),
898            job.elem.source.as_ref(),
899            job.target_namespace,
900        );
901        validate_keyref_refers(
902            &job.elem.identity_constraints,
903            job.target_namespace,
904            &schema_set.name_table,
905            &schema_set.source_maps,
906            &schema_set.arenas.identity_constraints,
907            &schema_set.documents,
908        )?;
909        let mut identity_constraint_keys = Vec::with_capacity(job.elem.identity_constraints.len());
910        for ic in job.elem.identity_constraints {
911            // Check per-document uniqueness using the IC's source document
912            if let Some(source) = &ic.source {
913                let doc_names = ic_names_by_doc.entry(source.doc_id).or_default();
914                if !doc_names.insert(ic.name) {
915                    let location = schema_set.source_maps.locate(source);
916                    let name_str = schema_set.name_table.resolve(ic.name);
917                    return Err(SchemaError::structural(
918                        "ic-unique",
919                        format!(
920                            "Duplicate identity constraint name '{}' in schema document",
921                            name_str
922                        ),
923                        location,
924                    ));
925                }
926            }
927            let ic_name = ic.name;
928            let ic_key = schema_set
929                .arenas
930                .alloc_identity_constraint(IdentityConstraintData {
931                    kind: ic.kind,
932                    name: ic.name,
933                    ref_name: ic.ref_name,
934                    refer: ic.refer,
935                    selector: ic.selector,
936                    fields: ic.fields,
937                    id: ic.id,
938                    annotation: ic.annotation,
939                    source: ic.source,
940                });
941            // Register in namespace table for @ref resolution
942            let ns_table = schema_set.get_or_create_namespace(job.target_namespace);
943            ns_table.identity_constraints.insert(ic_name, ic_key);
944            identity_constraint_keys.push(ic_key);
945        }
946        // Resolve XSD 1.1 @ref identity constraint references
947        for ic_ref in &job.elem.identity_constraint_refs {
948            let target_key = resolve_ic_ref(
949                ic_ref.kind,
950                &ic_ref.ref_name,
951                ic_ref.source.as_ref(),
952                job.target_namespace,
953                schema_set,
954            )?;
955            identity_constraint_keys.push(target_key);
956        }
957
958        let block = resolve_block(job.elem.block, job.elem.source.as_ref(), schema_set);
959        let final_derivation = resolve_final(
960            job.elem.final_derivation,
961            job.elem.source.as_ref(),
962            schema_set,
963        );
964        let elem_data = ElementDeclData {
965            name: job.elem.name,
966            target_namespace: effective_ns,
967            ref_name: None,
968            type_ref: job.elem.type_ref.clone(),
969            inline_type: job.elem.inline_type.clone(),
970            substitution_group: Vec::new(),
971            default_value: job.elem.default_value.clone(),
972            fixed_value: job.elem.fixed_value.clone(),
973            nillable: job.elem.nillable,
974            is_abstract: job.elem.is_abstract,
975            min_occurs: job.elem.min_occurs,
976            max_occurs: job.elem.max_occurs,
977            block,
978            final_derivation,
979            form: job.elem.form.clone(),
980            id: job.elem.id.clone(),
981            alternatives: job.elem.alternatives.clone(),
982            identity_constraints: identity_constraint_keys,
983            pending_ic_refs: vec![],
984            annotation: job.elem.annotation.clone(),
985            source: job.elem.source.clone(),
986            resolved_type,
987            resolved_ref: None,
988            resolved_substitution_groups: Vec::new(),
989            deferred_type_error: None,
990        };
991
992        let elem_key = schema_set.arenas.alloc_element(elem_data);
993
994        if let Some(ct) = schema_set
995            .arenas
996            .complex_types
997            .get_mut(job.complex_type_key)
998        {
999            while ct.resolved_content_particle_elements.len() <= job.flat_idx {
1000                ct.resolved_content_particle_elements.push(None);
1001            }
1002            ct.resolved_content_particle_elements[job.flat_idx] = Some(elem_key);
1003        }
1004    }
1005
1006    Ok(())
1007}
1008
1009/// XSD 1.1: Assemble inline `<xs:alternative>` types attached to *local*
1010/// element declarations.
1011///
1012/// The first inline-type assembly pass only walks `arenas.elements`,
1013/// which at that point contains global elements only. Local elements
1014/// declared inside complex-type particles are allocated later by
1015/// [`allocate_content_particle_elements`], and any inline alternative
1016/// types they carry would otherwise stay unresolved.
1017///
1018/// Run this after [`allocate_content_particle_elements`]. It:
1019///
1020/// 1. Walks every element declaration whose alternatives have an
1021///    `inline_type` but no `resolved_type`.
1022/// 2. Calls [`assemble_inline_type`] to add the inline complex/simple
1023///    type to the arena and stores the resulting `TypeKey` on the
1024///    alternative.
1025/// 3. Returns the list of complex-type keys that were newly added so
1026///    the caller can re-run reference resolution and content-particle
1027///    allocation on them.
1028///
1029/// The caller must also call [`resolve_all_references`] and
1030/// [`allocate_content_particle_elements`] again so the new types get
1031/// their `resolved_base_type`, `resolved_content_particle_types`, and
1032/// `resolved_content_particle_elements` populated. Both passes are
1033/// idempotent for already-resolved entries.
1034#[cfg(feature = "xsd11")]
1035pub fn resolve_local_element_alternatives(
1036    schema_set: &mut SchemaSet,
1037) -> SchemaResult<Vec<ComplexTypeKey>> {
1038    use crate::ids::ComplexTypeKey;
1039
1040    // Pass 1: Collect QName type_refs that need resolution (no inline type).
1041    // These are alternatives like `<xs:alternative test=… type="Quadrilateral"/>`
1042    // attached to local element declarations. The first
1043    // `resolve_all_references` pass walked global elements only; local
1044    // element alternatives copied during `allocate_content_particle_elements`
1045    // still carry an unresolved QName here.
1046    let mut qname_pending: Vec<(ElementKey, usize, QNameRef, Option<SourceRef>)> = Vec::new();
1047    for (key, elem) in schema_set.arenas.elements.iter() {
1048        for (idx, alt) in elem.alternatives.iter().enumerate() {
1049            if alt.resolved_type.is_some() {
1050                continue;
1051            }
1052            if let Some(TypeRefResult::QName(qname)) = &alt.type_ref {
1053                qname_pending.push((key, idx, qname.clone(), alt.source.clone()));
1054            }
1055        }
1056    }
1057    for (elem_key, alt_idx, qname, src) in qname_pending {
1058        let resolver = crate::schema::resolver::ReferenceResolver::new(schema_set);
1059        let type_key = resolver.resolve_type_ref(&qname, src.as_ref())?;
1060        if let Some(elem) = schema_set.arenas.elements.get_mut(elem_key) {
1061            if let Some(alt) = elem.alternatives.get_mut(alt_idx) {
1062                alt.resolved_type = Some(type_key);
1063            }
1064        }
1065    }
1066
1067    // Pass 2: Collect (element_key, alt_idx, type_frame) triples first to
1068    // avoid mutating arenas while iterating them. Handles inline types.
1069    let mut pending: Vec<(ElementKey, usize, TypeFrameResult, Option<NameId>)> = Vec::new();
1070    for (key, elem) in schema_set.arenas.elements.iter() {
1071        for (idx, alt) in elem.alternatives.iter().enumerate() {
1072            if alt.resolved_type.is_some() {
1073                continue;
1074            }
1075            let Some(inline_type) = &alt.inline_type else {
1076                continue;
1077            };
1078            pending.push((key, idx, (**inline_type).clone(), elem.target_namespace));
1079        }
1080    }
1081
1082    let mut new_complex_keys: Vec<ComplexTypeKey> = Vec::new();
1083    for (elem_key, alt_idx, type_frame, target_ns) in pending {
1084        let type_key = assemble_inline_type(schema_set, &type_frame, target_ns)?;
1085        if let TypeKey::Complex(ct_key) = type_key {
1086            new_complex_keys.push(ct_key);
1087        }
1088        if let Some(elem) = schema_set.arenas.elements.get_mut(elem_key) {
1089            if let Some(alt) = elem.alternatives.get_mut(alt_idx) {
1090                alt.resolved_type = Some(type_key);
1091            }
1092        }
1093    }
1094
1095    Ok(new_complex_keys)
1096}
1097
1098/// Allocation job for a local element in a named model group particle
1099struct ModelGroupElementJob {
1100    group_key: ModelGroupKey,
1101    particle_idx: usize,
1102    elem: ElementFrameResult,
1103    target_namespace: Option<NameId>,
1104}
1105
1106/// Allocate arena element declarations for local elements in named model group particles.
1107///
1108/// This enables the validator to look up nillable, fixed_value, default_value
1109/// and other properties for local elements inside named groups via their ElementKey.
1110///
1111/// Must be called after inline type assembly and reference resolution.
1112pub fn allocate_model_group_particle_elements(schema_set: &mut SchemaSet) -> SchemaResult<()> {
1113    // Collection pass: walk all model groups recursively and collect jobs
1114    let mut jobs = Vec::new();
1115    for (key, group) in schema_set.arenas.model_groups.iter() {
1116        let target_ns = group.target_namespace;
1117        let mut flat_idx = 0;
1118        collect_model_group_elements_recursive(
1119            &group.particles,
1120            key,
1121            target_ns,
1122            &mut flat_idx,
1123            &mut jobs,
1124        );
1125    }
1126
1127    // Build per-document sets of already-known identity constraint names for uniqueness checking
1128    // XSD §4.2.1: IC names must be unique per schema document, not globally
1129    let mut ic_names_by_doc: HashMap<DocumentId, HashSet<NameId>> = HashMap::new();
1130    for ic in schema_set.arenas.identity_constraints.values() {
1131        if let Some(source) = &ic.source {
1132            ic_names_by_doc
1133                .entry(source.doc_id)
1134                .or_default()
1135                .insert(ic.name);
1136        }
1137    }
1138
1139    // Allocation pass: create element declarations and store keys
1140    for job in jobs {
1141        // §3.17.6.2 clause 4 — per-document QName visibility gate.
1142        if let Some(TypeRefResult::QName(qname)) = &job.elem.type_ref {
1143            crate::schema::resolver::check_namespace_visible(
1144                schema_set,
1145                qname,
1146                job.elem.source.as_ref(),
1147                "Type",
1148            )?;
1149        }
1150
1151        let resolved_type = schema_set
1152            .arenas
1153            .model_groups
1154            .get(job.group_key)
1155            .and_then(|g| {
1156                g.resolved_particle_types
1157                    .get(job.particle_idx)
1158                    .copied()
1159                    .flatten()
1160            })
1161            .or_else(|| {
1162                // Try QName resolution
1163                match &job.elem.type_ref {
1164                    Some(TypeRefResult::QName(qname)) => schema_set
1165                        .lookup_type(qname.namespace, qname.local_name)
1166                        .or_else(|| {
1167                            schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name)
1168                        }),
1169                    _ => None,
1170                }
1171            });
1172
1173        let effective_ns = schema_set.effective_local_element_namespace(
1174            job.elem.target_namespace,
1175            job.elem.form.as_deref(),
1176            job.elem.source.as_ref(),
1177            job.target_namespace,
1178        );
1179        validate_keyref_refers(
1180            &job.elem.identity_constraints,
1181            job.target_namespace,
1182            &schema_set.name_table,
1183            &schema_set.source_maps,
1184            &schema_set.arenas.identity_constraints,
1185            &schema_set.documents,
1186        )?;
1187        let mut identity_constraint_keys = Vec::with_capacity(job.elem.identity_constraints.len());
1188        for ic in job.elem.identity_constraints {
1189            // Check per-document uniqueness using the IC's source document
1190            if let Some(source) = &ic.source {
1191                let doc_names = ic_names_by_doc.entry(source.doc_id).or_default();
1192                if !doc_names.insert(ic.name) {
1193                    let location = schema_set.source_maps.locate(source);
1194                    let name_str = schema_set.name_table.resolve(ic.name);
1195                    return Err(SchemaError::structural(
1196                        "ic-unique",
1197                        format!(
1198                            "Duplicate identity constraint name '{}' in schema document",
1199                            name_str
1200                        ),
1201                        location,
1202                    ));
1203                }
1204            }
1205            let ic_name = ic.name;
1206            let ic_key = schema_set
1207                .arenas
1208                .alloc_identity_constraint(IdentityConstraintData {
1209                    kind: ic.kind,
1210                    name: ic.name,
1211                    ref_name: ic.ref_name,
1212                    refer: ic.refer,
1213                    selector: ic.selector,
1214                    fields: ic.fields,
1215                    id: ic.id,
1216                    annotation: ic.annotation,
1217                    source: ic.source,
1218                });
1219            // Register in namespace table for @ref resolution
1220            let ns_table = schema_set.get_or_create_namespace(job.target_namespace);
1221            ns_table.identity_constraints.insert(ic_name, ic_key);
1222            identity_constraint_keys.push(ic_key);
1223        }
1224        // Resolve XSD 1.1 @ref identity constraint references
1225        for ic_ref in &job.elem.identity_constraint_refs {
1226            let target_key = resolve_ic_ref(
1227                ic_ref.kind,
1228                &ic_ref.ref_name,
1229                ic_ref.source.as_ref(),
1230                job.target_namespace,
1231                schema_set,
1232            )?;
1233            identity_constraint_keys.push(target_key);
1234        }
1235
1236        let block = resolve_block(job.elem.block, job.elem.source.as_ref(), schema_set);
1237        let final_derivation = resolve_final(
1238            job.elem.final_derivation,
1239            job.elem.source.as_ref(),
1240            schema_set,
1241        );
1242        let elem_data = ElementDeclData {
1243            name: job.elem.name,
1244            target_namespace: effective_ns,
1245            ref_name: None,
1246            type_ref: job.elem.type_ref.clone(),
1247            inline_type: job.elem.inline_type.clone(),
1248            substitution_group: Vec::new(),
1249            default_value: job.elem.default_value.clone(),
1250            fixed_value: job.elem.fixed_value.clone(),
1251            nillable: job.elem.nillable,
1252            is_abstract: job.elem.is_abstract,
1253            min_occurs: job.elem.min_occurs,
1254            max_occurs: job.elem.max_occurs,
1255            block,
1256            final_derivation,
1257            form: job.elem.form.clone(),
1258            id: job.elem.id.clone(),
1259            alternatives: job.elem.alternatives.clone(),
1260            identity_constraints: identity_constraint_keys,
1261            pending_ic_refs: vec![],
1262            annotation: job.elem.annotation.clone(),
1263            source: job.elem.source.clone(),
1264            resolved_type,
1265            resolved_ref: None,
1266            resolved_substitution_groups: Vec::new(),
1267            deferred_type_error: None,
1268        };
1269
1270        let elem_key = schema_set.arenas.alloc_element(elem_data);
1271
1272        if let Some(group) = schema_set.arenas.model_groups.get_mut(job.group_key) {
1273            while group.resolved_particle_elements.len() <= job.particle_idx {
1274                group.resolved_particle_elements.push(None);
1275            }
1276            group.resolved_particle_elements[job.particle_idx] = Some(elem_key);
1277        }
1278    }
1279
1280    Ok(())
1281}
1282
1283/// Walk model group particles recursively and collect local element allocation jobs
1284fn collect_model_group_elements_recursive(
1285    particles: &[ParticleResult],
1286    group_key: ModelGroupKey,
1287    target_ns: Option<NameId>,
1288    flat_idx: &mut usize,
1289    jobs: &mut Vec<ModelGroupElementJob>,
1290) {
1291    for particle in particles {
1292        match &particle.term {
1293            ParticleTerm::Element(elem) if elem.ref_name.is_none() => {
1294                jobs.push(ModelGroupElementJob {
1295                    group_key,
1296                    particle_idx: *flat_idx,
1297                    elem: elem.clone(),
1298                    target_namespace: target_ns,
1299                });
1300                *flat_idx += 1;
1301            }
1302            ParticleTerm::Element(_) => {
1303                // Ref element - skip allocation but still increment counter
1304                *flat_idx += 1;
1305            }
1306            ParticleTerm::Group(group_def) if group_def.ref_name.is_none() => {
1307                collect_model_group_elements_recursive(
1308                    &group_def.particles,
1309                    group_key,
1310                    target_ns,
1311                    flat_idx,
1312                    jobs,
1313                );
1314            }
1315            _ => {} // Skip group refs and wildcards
1316        }
1317    }
1318}
1319
1320/// Assemble an inline type into the arena
1321///
1322/// Returns the TypeKey for the newly allocated type.
1323/// Anonymous types are not registered in namespace tables.
1324fn assemble_inline_type(
1325    schema_set: &mut SchemaSet,
1326    type_frame: &TypeFrameResult,
1327    target_namespace: Option<NameId>,
1328) -> SchemaResult<TypeKey> {
1329    match type_frame {
1330        TypeFrameResult::Simple(simple) => {
1331            let final_derivation =
1332                resolve_final(simple.final_derivation, simple.source.as_ref(), schema_set);
1333            let data = SimpleTypeDefData {
1334                name: simple.name, // May be None for anonymous types
1335                target_namespace,
1336                variety: simple.variety,
1337                base_type: simple.base_type.clone(),
1338                item_type: simple.item_type.clone(),
1339                member_types: simple.member_types.clone(),
1340                facets: simple.facets.clone(),
1341                final_derivation,
1342                id: simple.id.clone(),
1343                derivation_id: simple.derivation_id.clone(),
1344                annotation: simple.annotation.clone(),
1345                source: simple.source.clone(),
1346                // Resolved references (to be populated later)
1347                resolved_base_type: None,
1348                resolved_item_type: None,
1349                resolved_member_types: Vec::new(),
1350                redefine_original: None,
1351                deferred_item_type_error: None,
1352            };
1353            let key = schema_set.arenas.alloc_simple_type(data);
1354            Ok(TypeKey::Simple(key))
1355        }
1356        TypeFrameResult::Complex(complex) => {
1357            let open_content = match &complex.content {
1358                ComplexContentResult::Complex(def) => def.open_content.clone(),
1359                _ => None,
1360            };
1361            #[cfg(feature = "xsd11")]
1362            let assertions = match &complex.content {
1363                ComplexContentResult::Simple(sc) => sc.assertions.clone(),
1364                ComplexContentResult::Complex(cc) => cc.assertions.clone(),
1365                ComplexContentResult::Empty => Vec::new(),
1366            };
1367            let block = resolve_block(complex.block, complex.source.as_ref(), schema_set);
1368            let final_derivation = resolve_final(
1369                complex.final_derivation,
1370                complex.source.as_ref(),
1371                schema_set,
1372            );
1373            let data = ComplexTypeDefData {
1374                name: complex.name, // May be None for anonymous types
1375                target_namespace,
1376                base_type: complex.base_type.clone(),
1377                derivation_method: complex.derivation_method,
1378                content: complex.content.clone(),
1379                open_content,
1380                attributes: complex.attributes.clone(),
1381                attribute_groups: complex.attribute_groups.clone(),
1382                attribute_wildcard: complex.attribute_wildcard.clone(),
1383                mixed: complex.mixed,
1384                is_abstract: complex.is_abstract,
1385                final_derivation,
1386                block,
1387                default_attributes_apply: complex.default_attributes_apply,
1388                id: complex.id.clone(),
1389                #[cfg(feature = "xsd11")]
1390                assertions,
1391                #[cfg(feature = "xsd11")]
1392                xpath_default_namespace: complex.xpath_default_namespace.clone(),
1393                annotation: complex.annotation.clone(),
1394                source: complex.source.clone(),
1395                // Resolved references (to be populated later)
1396                resolved_base_type: None,
1397                resolved_attribute_groups: Vec::new(),
1398                resolved_attributes: Vec::new(),
1399                resolved_content_particle_types: Vec::new(),
1400                resolved_content_particle_elements: Vec::new(),
1401                resolved_simple_content_type: None,
1402                redefine_original: None,
1403            };
1404            let key = schema_set.arenas.alloc_complex_type(data);
1405            Ok(TypeKey::Complex(key))
1406        }
1407    }
1408}
1409
1410/// Update the owner component with the resolved type key
1411fn update_owner(
1412    schema_set: &mut SchemaSet,
1413    job: &InlineTypeJob,
1414    type_key: TypeKey,
1415    stats: &mut InlineAssemblyStats,
1416) -> SchemaResult<()> {
1417    match job.owner {
1418        InlineOwner::Element(key) => {
1419            if let Some(elem) = schema_set.arenas.elements.get_mut(key) {
1420                match job.role {
1421                    #[cfg(feature = "xsd11")]
1422                    InlineRole::AlternativeType(idx) => {
1423                        if let Some(alt) = elem.alternatives.get_mut(idx) {
1424                            alt.resolved_type = Some(type_key);
1425                        }
1426                    }
1427                    _ => {
1428                        elem.resolved_type = Some(type_key);
1429                        stats.element_inline_types += 1;
1430                    }
1431                }
1432                stats.total_inline_types += 1;
1433            }
1434        }
1435        InlineOwner::Attribute(key) => {
1436            if let Some(attr) = schema_set.arenas.attributes.get_mut(key) {
1437                attr.resolved_type = Some(type_key);
1438                stats.attribute_inline_types += 1;
1439                stats.total_inline_types += 1;
1440            }
1441        }
1442        InlineOwner::SimpleType(key) => {
1443            if let Some(simple) = schema_set.arenas.simple_types.get_mut(key) {
1444                match job.role {
1445                    InlineRole::SimpleTypeBase => {
1446                        simple.resolved_base_type = Some(type_key);
1447                    }
1448                    InlineRole::ListItemType => {
1449                        simple.resolved_item_type = Some(type_key);
1450                    }
1451                    InlineRole::UnionMemberType(idx) => {
1452                        // For union members, we need to track position
1453                        // Insert at the correct position in resolved_member_types
1454                        while simple.resolved_member_types.len() <= idx {
1455                            // Use a placeholder that will be overwritten
1456                            // This handles sparse indices from inline types
1457                            simple.resolved_member_types.push(type_key);
1458                        }
1459                        simple.resolved_member_types[idx] = type_key;
1460                    }
1461                    _ => {}
1462                }
1463                stats.simple_type_inline_derivations += 1;
1464                stats.total_inline_types += 1;
1465            }
1466        }
1467        InlineOwner::ComplexType(key) => {
1468            if let Some(complex) = schema_set.arenas.complex_types.get_mut(key) {
1469                match job.role {
1470                    InlineRole::ComplexTypeBase => {
1471                        complex.resolved_base_type = Some(type_key);
1472                        stats.complex_type_inline_derivations += 1;
1473                    }
1474                    InlineRole::ComplexTypeAttribute(idx) => {
1475                        // Ensure resolved_attributes vec is large enough
1476                        while complex.resolved_attributes.len() <= idx {
1477                            complex.resolved_attributes.push(ResolvedAttributeUse {
1478                                resolved_type: None,
1479                                resolved_ref: None,
1480                            });
1481                        }
1482                        complex.resolved_attributes[idx].resolved_type = Some(type_key);
1483                        stats.complex_type_attribute_inline_types += 1;
1484                    }
1485                    InlineRole::ContentParticleType(idx) => {
1486                        while complex.resolved_content_particle_types.len() <= idx {
1487                            complex.resolved_content_particle_types.push(None);
1488                        }
1489                        complex.resolved_content_particle_types[idx] = Some(type_key);
1490                        stats.complex_type_inline_derivations += 1;
1491                    }
1492                    InlineRole::SimpleContentInlineType => {
1493                        complex.resolved_simple_content_type = Some(type_key);
1494                        stats.complex_type_inline_derivations += 1;
1495                    }
1496                    _ => {}
1497                }
1498                stats.total_inline_types += 1;
1499            }
1500        }
1501        InlineOwner::ModelGroup(key) => {
1502            if let Some(group) = schema_set.arenas.model_groups.get_mut(key) {
1503                if let InlineRole::ModelGroupParticle(flat_idx) = job.role {
1504                    // Store in flat-indexed resolved_particle_types
1505                    while group.resolved_particle_types.len() <= flat_idx {
1506                        group.resolved_particle_types.push(None);
1507                    }
1508                    group.resolved_particle_types[flat_idx] = Some(type_key);
1509                    stats.model_group_inline_types += 1;
1510                    stats.total_inline_types += 1;
1511                }
1512            }
1513        }
1514        InlineOwner::AttributeGroup(key) => {
1515            if let Some(group) = schema_set.arenas.attribute_groups.get_mut(key) {
1516                if let InlineRole::AttributeGroupAttribute(idx) = job.role {
1517                    // Ensure resolved_attributes vec is large enough
1518                    while group.resolved_attributes.len() <= idx {
1519                        group.resolved_attributes.push(ResolvedAttributeUse {
1520                            resolved_type: None,
1521                            resolved_ref: None,
1522                        });
1523                    }
1524                    group.resolved_attributes[idx].resolved_type = Some(type_key);
1525                    stats.attribute_group_inline_types += 1;
1526                    stats.total_inline_types += 1;
1527                }
1528            }
1529        }
1530    }
1531    Ok(())
1532}
1533
1534/// Collect nested inline types from a newly assembled type
1535fn collect_nested_inline_types(
1536    schema_set: &SchemaSet,
1537    type_key: TypeKey,
1538    target_namespace: Option<NameId>,
1539    jobs: &mut Vec<InlineTypeJob>,
1540) {
1541    match type_key {
1542        TypeKey::Simple(key) => {
1543            if let Some(simple) = schema_set.arenas.simple_types.get(key) {
1544                // Check for nested inline types in base/item/member
1545                if let Some(TypeRefResult::Inline(inline_type)) = &simple.base_type {
1546                    jobs.push(InlineTypeJob {
1547                        owner: InlineOwner::SimpleType(key),
1548                        role: InlineRole::SimpleTypeBase,
1549                        type_frame: (**inline_type).clone(),
1550                        target_namespace,
1551                    });
1552                }
1553                if let Some(TypeRefResult::Inline(inline_type)) = &simple.item_type {
1554                    jobs.push(InlineTypeJob {
1555                        owner: InlineOwner::SimpleType(key),
1556                        role: InlineRole::ListItemType,
1557                        type_frame: (**inline_type).clone(),
1558                        target_namespace,
1559                    });
1560                }
1561                for (idx, member) in simple.member_types.iter().enumerate() {
1562                    if let TypeRefResult::Inline(inline_type) = member {
1563                        jobs.push(InlineTypeJob {
1564                            owner: InlineOwner::SimpleType(key),
1565                            role: InlineRole::UnionMemberType(idx),
1566                            type_frame: (**inline_type).clone(),
1567                            target_namespace,
1568                        });
1569                    }
1570                }
1571            }
1572        }
1573        TypeKey::Complex(key) => {
1574            if let Some(complex) = schema_set.arenas.complex_types.get(key) {
1575                // Check for nested inline types in base type
1576                if let Some(TypeRefResult::Inline(inline_type)) = &complex.base_type {
1577                    jobs.push(InlineTypeJob {
1578                        owner: InlineOwner::ComplexType(key),
1579                        role: InlineRole::ComplexTypeBase,
1580                        type_frame: (**inline_type).clone(),
1581                        target_namespace,
1582                    });
1583                }
1584
1585                // Check for inline types in attributes
1586                for (idx, attr_use) in complex.attributes.iter().enumerate() {
1587                    if let Some(inline_type) = &attr_use.attribute.inline_type {
1588                        jobs.push(InlineTypeJob {
1589                            owner: InlineOwner::ComplexType(key),
1590                            role: InlineRole::ComplexTypeAttribute(idx),
1591                            type_frame: TypeFrameResult::Simple(Box::new((**inline_type).clone())),
1592                            target_namespace,
1593                        });
1594                    }
1595                }
1596
1597                // Check content for nested inline types
1598                collect_content_inline_types(&complex.content, key, target_namespace, jobs);
1599            }
1600        }
1601    }
1602}
1603
1604#[cfg(test)]
1605mod tests {
1606    use super::*;
1607    use crate::arenas::ElementDeclData;
1608    use crate::parser::frames::{SimpleTypeResult, SimpleTypeVariety};
1609    use crate::schema::model::DerivationSet;
1610    use crate::types::facets::FacetSet;
1611
1612    fn create_simple_type_frame(variety: SimpleTypeVariety) -> TypeFrameResult {
1613        TypeFrameResult::Simple(Box::new(SimpleTypeResult {
1614            name: None,
1615            variety,
1616            base_type: None,
1617            item_type: None,
1618            member_types: Vec::new(),
1619            facets: FacetSet::new(),
1620            final_derivation: None,
1621            id: None,
1622            derivation_id: None,
1623            annotation: None,
1624            source: None,
1625        }))
1626    }
1627
1628    #[test]
1629    fn test_assemble_inline_types_empty_schema() {
1630        let mut schema_set = SchemaSet::new();
1631        let result = assemble_inline_types(&mut schema_set);
1632        assert!(result.is_ok());
1633        let stats = result.unwrap();
1634        assert_eq!(stats.total_inline_types, 0);
1635    }
1636
1637    #[test]
1638    fn test_element_with_inline_simple_type() {
1639        let mut schema_set = SchemaSet::new();
1640
1641        let elem_name = schema_set.name_table.add("testElement");
1642        let inline_type = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1643
1644        let elem_data = ElementDeclData {
1645            name: Some(elem_name),
1646            target_namespace: None,
1647            ref_name: None,
1648            type_ref: None,
1649            inline_type: Some(inline_type),
1650            substitution_group: Vec::new(),
1651            default_value: None,
1652            fixed_value: None,
1653            nillable: false,
1654            is_abstract: false,
1655            min_occurs: 1,
1656            max_occurs: Some(1),
1657            block: DerivationSet::empty(),
1658            final_derivation: DerivationSet::empty(),
1659            form: None,
1660            id: None,
1661            alternatives: Vec::new(),
1662            identity_constraints: Vec::new(),
1663            pending_ic_refs: vec![],
1664            annotation: None,
1665            source: None,
1666            resolved_type: None,
1667            resolved_ref: None,
1668            resolved_substitution_groups: Vec::new(),
1669            deferred_type_error: None,
1670        };
1671
1672        let elem_key = schema_set.arenas.alloc_element(elem_data);
1673
1674        // Run inline assembly
1675        let result = assemble_inline_types(&mut schema_set);
1676        assert!(result.is_ok());
1677        let stats = result.unwrap();
1678        assert_eq!(stats.element_inline_types, 1);
1679        assert_eq!(stats.total_inline_types, 1);
1680
1681        // Verify resolved_type is set
1682        let elem = schema_set.arenas.elements.get(elem_key).unwrap();
1683        assert!(elem.resolved_type.is_some());
1684        assert!(matches!(elem.resolved_type, Some(TypeKey::Simple(_))));
1685
1686        // Verify inline_type is preserved
1687        assert!(elem.inline_type.is_some());
1688    }
1689
1690    #[test]
1691    fn test_simple_type_with_inline_base() {
1692        let mut schema_set = SchemaSet::new();
1693
1694        let type_name = schema_set.name_table.add("testType");
1695        let inline_base = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1696
1697        let type_data = SimpleTypeDefData {
1698            name: Some(type_name),
1699            target_namespace: None,
1700            variety: SimpleTypeVariety::Atomic,
1701            base_type: Some(TypeRefResult::Inline(inline_base)),
1702            item_type: None,
1703            member_types: Vec::new(),
1704            facets: FacetSet::new(),
1705            final_derivation: DerivationSet::empty(),
1706            id: None,
1707            derivation_id: None,
1708            annotation: None,
1709            source: None,
1710            resolved_base_type: None,
1711            resolved_item_type: None,
1712            resolved_member_types: Vec::new(),
1713            redefine_original: None,
1714            deferred_item_type_error: None,
1715        };
1716
1717        let type_key = schema_set.arenas.alloc_simple_type(type_data);
1718
1719        // Run inline assembly
1720        let result = assemble_inline_types(&mut schema_set);
1721        assert!(result.is_ok());
1722        let stats = result.unwrap();
1723        assert_eq!(stats.simple_type_inline_derivations, 1);
1724
1725        // Verify resolved_base_type is set
1726        let simple = schema_set.arenas.simple_types.get(type_key).unwrap();
1727        assert!(simple.resolved_base_type.is_some());
1728
1729        // Verify original base_type is preserved
1730        assert!(matches!(simple.base_type, Some(TypeRefResult::Inline(_))));
1731    }
1732
1733    #[test]
1734    fn test_list_type_with_inline_item() {
1735        let mut schema_set = SchemaSet::new();
1736
1737        let type_name = schema_set.name_table.add("listType");
1738        let inline_item = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1739
1740        let type_data = SimpleTypeDefData {
1741            name: Some(type_name),
1742            target_namespace: None,
1743            variety: SimpleTypeVariety::List,
1744            base_type: None,
1745            item_type: Some(TypeRefResult::Inline(inline_item)),
1746            member_types: Vec::new(),
1747            facets: FacetSet::new(),
1748            final_derivation: DerivationSet::empty(),
1749            id: None,
1750            derivation_id: None,
1751            annotation: None,
1752            source: None,
1753            resolved_base_type: None,
1754            resolved_item_type: None,
1755            resolved_member_types: Vec::new(),
1756            redefine_original: None,
1757            deferred_item_type_error: None,
1758        };
1759
1760        let type_key = schema_set.arenas.alloc_simple_type(type_data);
1761
1762        // Run inline assembly
1763        let result = assemble_inline_types(&mut schema_set);
1764        assert!(result.is_ok());
1765
1766        // Verify resolved_item_type is set
1767        let simple = schema_set.arenas.simple_types.get(type_key).unwrap();
1768        assert!(simple.resolved_item_type.is_some());
1769    }
1770
1771    #[test]
1772    fn test_union_type_with_inline_members() {
1773        let mut schema_set = SchemaSet::new();
1774
1775        let type_name = schema_set.name_table.add("unionType");
1776        let inline_member1 = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1777        let inline_member2 = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1778
1779        let type_data = SimpleTypeDefData {
1780            name: Some(type_name),
1781            target_namespace: None,
1782            variety: SimpleTypeVariety::Union,
1783            base_type: None,
1784            item_type: None,
1785            member_types: vec![
1786                TypeRefResult::Inline(inline_member1),
1787                TypeRefResult::Inline(inline_member2),
1788            ],
1789            facets: FacetSet::new(),
1790            final_derivation: DerivationSet::empty(),
1791            id: None,
1792            derivation_id: None,
1793            annotation: None,
1794            source: None,
1795            resolved_base_type: None,
1796            resolved_item_type: None,
1797            resolved_member_types: Vec::new(),
1798            redefine_original: None,
1799            deferred_item_type_error: None,
1800        };
1801
1802        let type_key = schema_set.arenas.alloc_simple_type(type_data);
1803
1804        // Run inline assembly
1805        let result = assemble_inline_types(&mut schema_set);
1806        assert!(result.is_ok());
1807        let stats = result.unwrap();
1808        assert_eq!(stats.simple_type_inline_derivations, 2);
1809
1810        // Verify resolved_member_types has both members
1811        let simple = schema_set.arenas.simple_types.get(type_key).unwrap();
1812        assert_eq!(simple.resolved_member_types.len(), 2);
1813    }
1814
1815    #[test]
1816    fn test_inline_type_in_model_group_resolved() {
1817        use crate::arenas::ModelGroupData;
1818        use crate::parser::frames::{Compositor, ElementFrameResult};
1819
1820        let mut schema_set = SchemaSet::new();
1821
1822        let elem_name = schema_set.name_table.add("detail");
1823        let inline_type = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1824
1825        // Create a named model group with an element that has an inline type
1826        let group_data = ModelGroupData {
1827            name: Some(schema_set.name_table.add("myGroup")),
1828            target_namespace: None,
1829            ref_name: None,
1830            compositor: Some(Compositor::Sequence),
1831            particles: vec![ParticleResult {
1832                term: ParticleTerm::Element(ElementFrameResult {
1833                    name: Some(elem_name),
1834                    ref_name: None,
1835                    target_namespace: None,
1836                    type_ref: None,
1837                    inline_type: Some(inline_type),
1838                    substitution_group: vec![],
1839                    default_value: None,
1840                    fixed_value: None,
1841                    nillable: false,
1842                    is_abstract: false,
1843                    min_occurs: 1,
1844                    max_occurs: Some(1),
1845                    block: Default::default(),
1846                    final_derivation: Default::default(),
1847                    form: None,
1848                    id: None,
1849                    alternatives: vec![],
1850                    identity_constraints: vec![],
1851                    identity_constraint_refs: vec![],
1852                    annotation: None,
1853                    source: None,
1854                }),
1855                min_occurs: 1,
1856                max_occurs: Some(1),
1857                source: None,
1858            }],
1859            min_occurs: 1,
1860            max_occurs: Some(1),
1861            id: None,
1862            annotation: None,
1863            source: None,
1864            resolved_ref: None,
1865            resolved_particles: Vec::new(),
1866            resolved_particle_types: Vec::new(),
1867            resolved_particle_elements: Vec::new(),
1868            redefine_original: None,
1869            redefine_requires_restriction_check: false,
1870        };
1871
1872        let _group_key = schema_set.arenas.alloc_model_group(group_data);
1873
1874        let result = assemble_inline_types(&mut schema_set);
1875        assert!(result.is_ok());
1876        let stats = result.unwrap();
1877        assert_eq!(stats.model_group_inline_types, 1);
1878        assert_eq!(stats.total_inline_types, 1);
1879
1880        // Verify resolved_particle_types has the type set
1881        let group = schema_set.arenas.model_groups.get(_group_key).unwrap();
1882        assert_eq!(group.resolved_particle_types.len(), 1);
1883        assert!(
1884            group.resolved_particle_types[0].is_some(),
1885            "Expected resolved type at flat index 0"
1886        );
1887    }
1888
1889    #[test]
1890    fn test_inline_type_in_content_particle_resolved() {
1891        use crate::parser::frames::{
1892            ComplexContentDefResult, Compositor, ElementFrameResult, ModelGroupDefResult,
1893        };
1894
1895        let mut schema_set = SchemaSet::new();
1896
1897        let elem_name = schema_set.name_table.add("child");
1898        let inline_type = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
1899
1900        // Create a complex type with a content particle containing an element with inline type
1901        let content_particle = ParticleResult {
1902            term: ParticleTerm::Group(ModelGroupDefResult {
1903                name: None,
1904                ref_name: None,
1905                compositor: Some(Compositor::Sequence),
1906                particles: vec![ParticleResult {
1907                    term: ParticleTerm::Element(ElementFrameResult {
1908                        name: Some(elem_name),
1909                        ref_name: None,
1910                        target_namespace: None,
1911                        type_ref: None,
1912                        inline_type: Some(inline_type),
1913                        substitution_group: vec![],
1914                        default_value: None,
1915                        fixed_value: None,
1916                        nillable: false,
1917                        is_abstract: false,
1918                        min_occurs: 1,
1919                        max_occurs: Some(1),
1920                        block: Default::default(),
1921                        final_derivation: Default::default(),
1922                        form: None,
1923                        id: None,
1924                        alternatives: vec![],
1925                        identity_constraints: vec![],
1926                        identity_constraint_refs: vec![],
1927                        annotation: None,
1928                        source: None,
1929                    }),
1930                    min_occurs: 1,
1931                    max_occurs: Some(1),
1932                    source: None,
1933                }],
1934                min_occurs: 1,
1935                max_occurs: Some(1),
1936                id: None,
1937                annotation: None,
1938                source: None,
1939            }),
1940            min_occurs: 1,
1941            max_occurs: Some(1),
1942            source: None,
1943        };
1944
1945        let complex_data = ComplexTypeDefData {
1946            name: Some(schema_set.name_table.add("MyComplexType")),
1947            target_namespace: None,
1948            base_type: None,
1949            derivation_method: None,
1950            content: ComplexContentResult::Complex(ComplexContentDefResult {
1951                particle: Some(content_particle),
1952                derivation: crate::parser::frames::DerivationMethod::Restriction,
1953                mixed: false,
1954                mixed_explicit: false,
1955                base_type: None,
1956                open_content: None,
1957                attributes: Vec::new(),
1958                attribute_groups: Vec::new(),
1959                attribute_wildcard: None,
1960                assertions: Vec::new(),
1961                id: None,
1962                derivation_id: None,
1963                source: None,
1964            }),
1965            open_content: None,
1966            attributes: Vec::new(),
1967            attribute_groups: Vec::new(),
1968            attribute_wildcard: None,
1969            mixed: false,
1970            is_abstract: false,
1971            final_derivation: DerivationSet::empty(),
1972            block: DerivationSet::empty(),
1973            default_attributes_apply: true,
1974            id: None,
1975            #[cfg(feature = "xsd11")]
1976            assertions: Vec::new(),
1977            #[cfg(feature = "xsd11")]
1978            xpath_default_namespace: None,
1979            annotation: None,
1980            source: None,
1981            resolved_base_type: None,
1982            resolved_attribute_groups: Vec::new(),
1983            resolved_attributes: Vec::new(),
1984            resolved_content_particle_types: Vec::new(),
1985            resolved_content_particle_elements: Vec::new(),
1986            resolved_simple_content_type: None,
1987            redefine_original: None,
1988        };
1989
1990        let ct_key = schema_set.arenas.alloc_complex_type(complex_data);
1991
1992        let result = assemble_inline_types(&mut schema_set);
1993        assert!(result.is_ok());
1994        let stats = result.unwrap();
1995        assert!(stats.total_inline_types >= 1);
1996
1997        // Verify resolved_content_particle_types has the type set
1998        let ct = schema_set.arenas.complex_types.get(ct_key).unwrap();
1999        assert_eq!(ct.resolved_content_particle_types.len(), 1);
2000        assert!(ct.resolved_content_particle_types[0].is_some());
2001    }
2002
2003    #[test]
2004    fn test_nested_inline_type_in_model_group() {
2005        use crate::arenas::ModelGroupData;
2006        use crate::parser::frames::{Compositor, ElementFrameResult, ModelGroupDefResult};
2007
2008        let mut schema_set = SchemaSet::new();
2009
2010        let elem_name = schema_set.name_table.add("nested_elem");
2011        let inline_type = Box::new(create_simple_type_frame(SimpleTypeVariety::Atomic));
2012
2013        // Create a named model group:
2014        //   <xs:group name="G">
2015        //     <xs:sequence>
2016        //       <xs:choice>
2017        //         <xs:element name="nested_elem">
2018        //           <xs:simpleType>...</xs:simpleType>
2019        //         </xs:element>
2020        //       </xs:choice>
2021        //     </xs:sequence>
2022        //   </xs:group>
2023        let group_data = ModelGroupData {
2024            name: Some(schema_set.name_table.add("G")),
2025            target_namespace: None,
2026            ref_name: None,
2027            compositor: Some(Compositor::Sequence),
2028            particles: vec![ParticleResult {
2029                term: ParticleTerm::Group(ModelGroupDefResult {
2030                    name: None,
2031                    ref_name: None,
2032                    compositor: Some(Compositor::Choice),
2033                    particles: vec![ParticleResult {
2034                        term: ParticleTerm::Element(ElementFrameResult {
2035                            name: Some(elem_name),
2036                            ref_name: None,
2037                            target_namespace: None,
2038                            type_ref: None,
2039                            inline_type: Some(inline_type),
2040                            substitution_group: vec![],
2041                            default_value: None,
2042                            fixed_value: None,
2043                            nillable: false,
2044                            is_abstract: false,
2045                            min_occurs: 1,
2046                            max_occurs: Some(1),
2047                            block: Default::default(),
2048                            final_derivation: Default::default(),
2049                            form: None,
2050                            id: None,
2051                            alternatives: vec![],
2052                            identity_constraints: vec![],
2053                            identity_constraint_refs: vec![],
2054                            annotation: None,
2055                            source: None,
2056                        }),
2057                        min_occurs: 1,
2058                        max_occurs: Some(1),
2059                        source: None,
2060                    }],
2061                    min_occurs: 1,
2062                    max_occurs: Some(1),
2063                    id: None,
2064                    annotation: None,
2065                    source: None,
2066                }),
2067                min_occurs: 1,
2068                max_occurs: Some(1),
2069                source: None,
2070            }],
2071            min_occurs: 1,
2072            max_occurs: Some(1),
2073            id: None,
2074            annotation: None,
2075            source: None,
2076            resolved_ref: None,
2077            resolved_particles: Vec::new(),
2078            resolved_particle_types: Vec::new(),
2079            resolved_particle_elements: Vec::new(),
2080            redefine_original: None,
2081            redefine_requires_restriction_check: false,
2082        };
2083
2084        let group_key = schema_set.arenas.alloc_model_group(group_data);
2085
2086        // Run inline assembly
2087        let result = assemble_inline_types(&mut schema_set);
2088        assert!(result.is_ok());
2089        let stats = result.unwrap();
2090        assert_eq!(stats.model_group_inline_types, 1);
2091        assert_eq!(stats.total_inline_types, 1);
2092
2093        // Verify resolved_particle_types has the type at flat index 0
2094        let group = schema_set.arenas.model_groups.get(group_key).unwrap();
2095        assert_eq!(group.resolved_particle_types.len(), 1);
2096        assert!(
2097            group.resolved_particle_types[0].is_some(),
2098            "Expected resolved type for nested element at flat index 0"
2099        );
2100        assert!(matches!(
2101            group.resolved_particle_types[0],
2102            Some(TypeKey::Simple(_))
2103        ));
2104    }
2105}