Skip to main content

xsd_schema/schema/
resolver.rs

1//! Reference resolution for QName → component ID conversion
2//!
3//! This module resolves QName string references to component keys (IDs).
4//! It is run after all schemas are parsed and assembled, but before
5//! type derivation validation or content model compilation.
6//!
7//! # Resolution Process
8//!
9//! 1. Check built-in types first (for XS namespace types)
10//! 2. Look up in namespace tables for user-defined types
11//! 3. Return error with source location if not found
12//!
13//! # Supported References
14//!
15//! - Type references (QName → TypeKey)
16//! - Element references (QName → ElementKey)
17//! - Attribute references (QName → AttributeKey)
18//! - Model group references (QName → ModelGroupKey)
19//! - Attribute group references (QName → AttributeGroupKey)
20//! - Notation references (QName → NotationKey)
21
22use crate::error::{SchemaError, SchemaResult};
23use crate::ids::*;
24use crate::parser::frames::SimpleTypeVariety;
25use crate::parser::frames::{QNameRef, TypeRefResult};
26use crate::parser::location::SourceRef;
27use crate::schema::composition::ComponentKind;
28use crate::schema::SchemaSet;
29
30/// Enforce XSD §3.17.6.2 `src-resolve` clause 4 (per-document QName visibility)
31/// for a (namespace, local) pair attributed to a lexical document via `source`.
32///
33/// Returns `Ok(())` when no source is attached (synthesized arena entries are
34/// not subject to src-resolve) or when the namespace is reachable from the
35/// lexical document. Otherwise returns a `src-resolve` error.
36pub(crate) fn check_namespace_visible_ns(
37    schema_set: &SchemaSet,
38    namespace: Option<NameId>,
39    local_name: NameId,
40    source: Option<&SourceRef>,
41    kind_label: &str,
42) -> SchemaResult<()> {
43    let Some(source) = source else { return Ok(()) };
44    let Some(doc) = schema_set.documents.get(source.doc_id as usize) else {
45        return Ok(());
46    };
47    if doc.can_see_namespace(namespace, &schema_set.name_table) {
48        return Ok(());
49    }
50    let location = schema_set.source_maps.locate(source);
51    let qname_str = format_resolved_qname(&schema_set.name_table, namespace, local_name);
52    let ns_label = match namespace {
53        Some(ns) => format!("'{}'", schema_set.name_table.resolve_ref(ns)),
54        None => "the absent namespace".to_string(),
55    };
56    Err(SchemaError::structural(
57        "src-resolve",
58        format!(
59            "{} reference '{}' to namespace {} is not <xs:import>-ed by schema document '{}'",
60            kind_label, qname_str, ns_label, doc.base_uri,
61        ),
62        location,
63    ))
64}
65
66/// Convenience wrapper around [`check_namespace_visible_ns`] for callers that
67/// already hold a `QNameRef`.
68pub(crate) fn check_namespace_visible(
69    schema_set: &SchemaSet,
70    qname: &QNameRef,
71    source: Option<&SourceRef>,
72    kind_label: &str,
73) -> SchemaResult<()> {
74    check_namespace_visible_ns(schema_set, qname.namespace, qname.local_name, source, kind_label)
75}
76
77/// Reference resolver for QName → component ID resolution
78///
79/// This struct holds a reference to the schema set and provides
80/// methods to resolve different types of QName references.
81pub struct ReferenceResolver<'a> {
82    schema_set: &'a SchemaSet,
83}
84
85impl<'a> ReferenceResolver<'a> {
86    /// Create a new reference resolver for the given schema set
87    pub fn new(schema_set: &'a SchemaSet) -> Self {
88        Self { schema_set }
89    }
90
91    /// Resolve a type reference (QName → TypeKey)
92    ///
93    /// Checks built-in types first, then user-defined types.
94    /// The namespace should already be resolved during parsing via NamespaceContextSnapshot.
95    pub fn resolve_type_ref(
96        &self,
97        qname: &QNameRef,
98        source: Option<&SourceRef>,
99    ) -> SchemaResult<TypeKey> {
100        // Use the namespace resolved during parsing
101        let namespace = qname.namespace;
102
103        // 1. Check built-in types first (XS namespace)
104        if let Some(type_key) = self
105            .schema_set
106            .get_built_in_type_by_qname(namespace, qname.local_name)
107        {
108            return Ok(type_key);
109        }
110
111        // §3.17.6.2 clause 4 — per-document QName visibility gate.
112        check_namespace_visible(self.schema_set, qname, source, "Type")?;
113
114        // 2. Look up in namespace table
115        if let Some(type_key) = self.schema_set.lookup_type(namespace, qname.local_name) {
116            return Ok(type_key);
117        }
118
119        // 3. Not found - error with provenance note
120        let location = source.and_then(|s| self.schema_set.source_maps.locate(s));
121        Err(SchemaError::structural(
122            "src-resolve",
123            format_type_not_found_message(self.schema_set, qname, "Type"),
124            location,
125        ))
126    }
127
128    /// Lookup-only variant of [`resolve_type_ref`]: returns `Ok(None)` when
129    /// the QName resolves to no component, while still propagating
130    /// namespace-visibility errors as `Err`. Used by callers that want to
131    /// defer a missing-component miss instead of failing compilation.
132    pub fn try_resolve_type_ref(
133        &self,
134        qname: &QNameRef,
135        source: Option<&SourceRef>,
136    ) -> SchemaResult<Option<TypeKey>> {
137        if let Some(type_key) = self
138            .schema_set
139            .get_built_in_type_by_qname(qname.namespace, qname.local_name)
140        {
141            return Ok(Some(type_key));
142        }
143        check_namespace_visible(self.schema_set, qname, source, "Type")?;
144        Ok(self.schema_set.lookup_type(qname.namespace, qname.local_name))
145    }
146
147    /// Lookup-only variant of [`resolve_element_ref`]: returns `Ok(None)` for
148    /// the "not found" case, while still propagating visibility errors.
149    pub fn try_resolve_element_ref(
150        &self,
151        qname: &QNameRef,
152        source: Option<&SourceRef>,
153    ) -> SchemaResult<Option<ElementKey>> {
154        check_namespace_visible(self.schema_set, qname, source, "Element")?;
155        Ok(self
156            .schema_set
157            .lookup_element(qname.namespace, qname.local_name))
158    }
159
160    /// Resolve a TypeRefResult to a TypeKey
161    ///
162    /// Handles both QName references and inline types.
163    /// For inline types, the type must already be allocated in the arena.
164    pub fn resolve_type_ref_result(
165        &self,
166        type_ref: &TypeRefResult,
167        source: Option<&SourceRef>,
168    ) -> SchemaResult<Option<TypeKey>> {
169        match type_ref {
170            TypeRefResult::QName(qname) => Ok(Some(self.resolve_type_ref(qname, source)?)),
171            TypeRefResult::Inline(_) => {
172                // Inline types are handled during assembly and should already
173                // have their keys stored elsewhere. Return None to indicate
174                // the caller should use the inline type key.
175                Ok(None)
176            }
177        }
178    }
179
180    /// Resolve a component reference by looking up in the schema set's namespace
181    /// tables, returning a `src-resolve` error if not found.
182    ///
183    /// When a lookup fails the error message is enriched with provenance
184    /// information (e.g. "originally in base.xsd, redefined by main.xsd")
185    /// when available.
186    fn resolve_ref<K: Copy>(
187        &self,
188        qname: &QNameRef,
189        source: Option<&SourceRef>,
190        kind_label: &str,
191        component_kind: ComponentKind,
192        lookup: impl FnOnce(&SchemaSet, Option<NameId>, NameId) -> Option<K>,
193    ) -> SchemaResult<K> {
194        // §3.17.6.2 clause 4 — per-document QName visibility gate.
195        check_namespace_visible(self.schema_set, qname, source, kind_label)?;
196        if let Some(key) = lookup(self.schema_set, qname.namespace, qname.local_name) {
197            return Ok(key);
198        }
199        let location = source.and_then(|s| self.schema_set.source_maps.locate(s));
200        let name_str = self.format_qname(qname);
201        let note = self.schema_set.format_provenance_note(
202            component_kind,
203            qname.namespace,
204            qname.local_name,
205        );
206        Err(SchemaError::structural(
207            "src-resolve",
208            format!("{} '{}' not found{}", kind_label, name_str, note),
209            location,
210        ))
211    }
212
213    /// Resolve an element reference (QName → ElementKey)
214    pub fn resolve_element_ref(
215        &self,
216        qname: &QNameRef,
217        source: Option<&SourceRef>,
218    ) -> SchemaResult<ElementKey> {
219        self.resolve_ref(
220            qname,
221            source,
222            "Element",
223            ComponentKind::Element,
224            SchemaSet::lookup_element,
225        )
226    }
227
228    /// Resolve an attribute reference (QName → AttributeKey)
229    pub fn resolve_attribute_ref(
230        &self,
231        qname: &QNameRef,
232        source: Option<&SourceRef>,
233    ) -> SchemaResult<AttributeKey> {
234        self.resolve_ref(
235            qname,
236            source,
237            "Attribute",
238            ComponentKind::Attribute,
239            SchemaSet::lookup_attribute,
240        )
241    }
242
243    /// Resolve a model group reference (QName → ModelGroupKey)
244    pub fn resolve_group_ref(
245        &self,
246        qname: &QNameRef,
247        source: Option<&SourceRef>,
248    ) -> SchemaResult<ModelGroupKey> {
249        self.resolve_ref(
250            qname,
251            source,
252            "Group",
253            ComponentKind::ModelGroup,
254            SchemaSet::lookup_model_group,
255        )
256    }
257
258    /// Resolve an attribute group reference (QName → AttributeGroupKey)
259    pub fn resolve_attribute_group_ref(
260        &self,
261        qname: &QNameRef,
262        source: Option<&SourceRef>,
263    ) -> SchemaResult<AttributeGroupKey> {
264        self.resolve_ref(
265            qname,
266            source,
267            "Attribute group",
268            ComponentKind::AttributeGroup,
269            SchemaSet::lookup_attribute_group,
270        )
271    }
272
273    /// Resolve a notation reference (QName → NotationKey)
274    pub fn resolve_notation_ref(
275        &self,
276        qname: &QNameRef,
277        source: Option<&SourceRef>,
278    ) -> SchemaResult<NotationKey> {
279        self.resolve_ref(
280            qname,
281            source,
282            "Notation",
283            ComponentKind::Notation,
284            SchemaSet::lookup_notation,
285        )
286    }
287
288    /// Format a QName for error messages
289    fn format_qname(&self, qname: &QNameRef) -> String {
290        format_resolved_qname(
291            &self.schema_set.name_table,
292            qname.namespace,
293            qname.local_name,
294        )
295    }
296}
297
298/// Format a resolved QName (namespace + local name) for error messages.
299pub(crate) fn format_resolved_qname(
300    name_table: &crate::namespace::NameTable,
301    namespace: Option<crate::ids::NameId>,
302    local_name: crate::ids::NameId,
303) -> String {
304    let local = name_table.resolve(local_name);
305    if let Some(ns_id) = namespace {
306        let ns = name_table.resolve(ns_id);
307        if ns.is_empty() {
308            local
309        } else {
310            format!("{{{}}}{}", ns, local)
311        }
312    } else {
313        local
314    }
315}
316
317/// Collected resolution results for a component
318#[derive(Debug, Default)]
319pub struct ResolvedReferences {
320    /// Resolved type reference for elements/attributes
321    pub resolved_type: Option<TypeKey>,
322    /// Resolved element reference (for element refs)
323    pub resolved_ref: Option<ElementKey>,
324    /// Resolved substitution group heads
325    pub resolved_substitution_groups: Vec<ElementKey>,
326    /// Resolved attribute reference (for attribute refs)
327    pub resolved_attr_ref: Option<AttributeKey>,
328    /// Resolved base type for type definitions
329    pub resolved_base_type: Option<TypeKey>,
330    /// Resolved item type for list types
331    pub resolved_item_type: Option<TypeKey>,
332    /// Resolved member types for union types
333    pub resolved_member_types: Vec<TypeKey>,
334    /// Resolved attribute group references
335    pub resolved_attribute_groups: Vec<AttributeGroupKey>,
336    /// Resolved model group reference
337    pub resolved_group_ref: Option<ModelGroupKey>,
338}
339
340/// Statistics from the resolution pass
341#[derive(Debug, Default)]
342pub struct ResolutionStats {
343    /// Number of type references resolved
344    pub types_resolved: usize,
345    /// Number of element references resolved
346    pub elements_resolved: usize,
347    /// Number of attribute references resolved
348    pub attributes_resolved: usize,
349    /// Number of group references resolved
350    pub groups_resolved: usize,
351    /// Number of attribute group references resolved
352    pub attribute_groups_resolved: usize,
353    /// Number of notation references resolved
354    pub notations_resolved: usize,
355    /// Total errors encountered
356    pub errors: usize,
357}
358
359/// Drain `pending_ic_refs` for one element and try to resolve each entry.
360///
361/// On success the target is appended to the element's `identity_constraints`.
362/// On failure: if `defer_failures` is true, the entry is kept on the element
363/// for a later retry pass; otherwise the error is appended to `errors`.
364fn drain_pending_ic_refs_for(
365    schema_set: &mut SchemaSet,
366    key: ElementKey,
367    defer_failures: bool,
368    errors: &mut Vec<SchemaError>,
369) {
370    let pending = std::mem::take(&mut schema_set.arenas.elements[key].pending_ic_refs);
371    if pending.is_empty() {
372        return;
373    }
374    let target_ns = schema_set.arenas.elements[key].target_namespace;
375    let mut still_pending = Vec::new();
376    for (kind, ref_name, source) in pending {
377        match crate::schema::inline::resolve_ic_ref(
378            kind,
379            &ref_name,
380            source.as_ref(),
381            target_ns,
382            schema_set,
383        ) {
384            Ok(target_key) => {
385                schema_set.arenas.elements[key]
386                    .identity_constraints
387                    .push(target_key);
388            }
389            Err(e) => {
390                if defer_failures {
391                    still_pending.push((kind, ref_name, source));
392                } else {
393                    errors.push(e);
394                }
395            }
396        }
397    }
398    if !still_pending.is_empty() {
399        schema_set.arenas.elements[key].pending_ic_refs = still_pending;
400    }
401}
402
403/// Finalize any remaining pending IC `@ref` references on elements.
404///
405/// `resolve_all_references` keeps unresolved IC refs in `pending_ic_refs`
406/// because the target IC may live on a *local* element, which isn't
407/// allocated until `allocate_content_particle_elements`. After that pass
408/// runs, we call this function to resolve and clear the remaining refs,
409/// reporting an error for any still-unresolved ones.
410pub fn finalize_pending_ic_refs(schema_set: &mut SchemaSet) -> SchemaResult<()> {
411    let element_keys: Vec<ElementKey> = schema_set.arenas.elements.keys().collect();
412    let mut errors: Vec<SchemaError> = Vec::new();
413    for key in element_keys {
414        drain_pending_ic_refs_for(schema_set, key, false, &mut errors);
415    }
416    if let Some(first) = errors.into_iter().next() {
417        return Err(first);
418    }
419    Ok(())
420}
421
422/// Resolve all references in a schema set
423///
424/// This function walks all components and resolves QName references
425/// to component keys. It should be called after all schemas are
426/// parsed and assembled, but before type derivation validation.
427///
428/// # Errors
429///
430/// Returns an error if any reference cannot be resolved.
431/// The error will contain the source location of the unresolved reference.
432pub fn resolve_all_references(schema_set: &mut SchemaSet) -> SchemaResult<ResolutionStats> {
433    let mut stats = ResolutionStats::default();
434    let mut errors: Vec<SchemaError> = Vec::new();
435
436    // Create resolver (borrows schema_set immutably for lookups)
437    // We need to collect keys first, then iterate with mutable access
438
439    // Collect all keys first to avoid borrowing issues
440    let element_keys: Vec<ElementKey> = schema_set.arenas.elements.keys().collect();
441    let attribute_keys: Vec<AttributeKey> = schema_set.arenas.attributes.keys().collect();
442    let simple_type_keys: Vec<SimpleTypeKey> = schema_set.arenas.simple_types.keys().collect();
443    let complex_type_keys: Vec<ComplexTypeKey> = schema_set.arenas.complex_types.keys().collect();
444    let model_group_keys: Vec<ModelGroupKey> = schema_set.arenas.model_groups.keys().collect();
445    let attribute_group_keys: Vec<AttributeGroupKey> =
446        schema_set.arenas.attribute_groups.keys().collect();
447
448    // Resolve element references
449    for key in &element_keys {
450        if let Err(e) = resolve_element_references(schema_set, *key, &mut stats) {
451            errors.push(e);
452            stats.errors += 1;
453        }
454    }
455
456    // Post-pass: inherit type from substitution group head for elements that have a
457    // substitutionGroup but no explicit type (§3.3.2.1 rule 3).  We iterate until
458    // stable so that transitive chains (A → B → C where none have an explicit type)
459    // are fully resolved.
460    if errors.is_empty() {
461        loop {
462            let mut changed = false;
463            for &key in &element_keys {
464                let (needs_type, subst_groups) = {
465                    let elem = schema_set.arenas.elements.get(key).unwrap();
466                    (
467                        elem.resolved_type.is_none()
468                            && elem.resolved_ref.is_none()
469                            && elem.deferred_type_error.is_none()
470                            && !elem.resolved_substitution_groups.is_empty(),
471                        elem.resolved_substitution_groups.clone(),
472                    )
473                };
474                if needs_type {
475                    for &head_key in &subst_groups {
476                        if let Some(head_type) = schema_set
477                            .arenas
478                            .elements
479                            .get(head_key)
480                            .and_then(|h| h.resolved_type)
481                        {
482                            let elem = schema_set.arenas.elements.get_mut(key).unwrap();
483                            assign_element_type(elem, head_type);
484                            changed = true;
485                            break;
486                        }
487                    }
488                }
489            }
490            if !changed {
491                break;
492            }
493        }
494        // Secondary post-pass: when no head has a resolved type but at least
495        // one carries a deferred src-resolve error on its own `type` attribute,
496        // propagate that deferred error down to the member. Without this, the
497        // late `xs:anyType` fallback below would silently substitute anyType
498        // and runtime validation against the member would never fire the
499        // deferred error.
500        for &key in &element_keys {
501            let needs_deferred = {
502                let elem = schema_set.arenas.elements.get(key).unwrap();
503                elem.resolved_type.is_none()
504                    && elem.resolved_ref.is_none()
505                    && elem.deferred_type_error.is_none()
506                    && !elem.resolved_substitution_groups.is_empty()
507            };
508            if !needs_deferred {
509                continue;
510            }
511            let inherited = {
512                let elem = schema_set.arenas.elements.get(key).unwrap();
513                elem.resolved_substitution_groups
514                    .iter()
515                    .find_map(|&head_key| {
516                        schema_set
517                            .arenas
518                            .elements
519                            .get(head_key)
520                            .and_then(|h| h.deferred_type_error.clone())
521                    })
522            };
523            if let Some(deferred) = inherited {
524                if let Some(elem) = schema_set.arenas.elements.get_mut(key) {
525                    elem.deferred_type_error = Some(deferred);
526                }
527            }
528        }
529        // Final anyType fallback for elements that still have no type (e.g., circular
530        // substitution group chains or heads that themselves have no type).
531        // Skip elements that carry a deferred src-resolve error on their explicit
532        // `type` attribute — runtime must report the deferred error rather than
533        // silently substituting `xs:anyType`.
534        let any_type = TypeKey::Complex(schema_set.any_type_key());
535        for &key in &element_keys {
536            if let Some(elem) = schema_set.arenas.elements.get_mut(key) {
537                if elem.resolved_type.is_none()
538                    && elem.resolved_ref.is_none()
539                    && elem.deferred_type_error.is_none()
540                {
541                    assign_element_type(elem, any_type);
542                }
543            }
544        }
545    }
546
547    // Resolve XSD 1.1 identity constraint @ref references on top-level elements.
548    // This runs after element resolution so ICs from all elements are registered.
549    // Failures are KEPT in `pending_ic_refs` so a later pass (after local
550    // elements are allocated) can resolve refs that point to ICs declared on
551    // a local element — otherwise the local IC isn't registered yet here.
552    for &key in &element_keys {
553        drain_pending_ic_refs_for(schema_set, key, true, &mut errors);
554    }
555
556    // Resolve attribute references
557    for key in attribute_keys {
558        if let Err(e) = resolve_attribute_references(schema_set, key, &mut stats) {
559            errors.push(e);
560            stats.errors += 1;
561        }
562    }
563
564    // Resolve simple type references. Run twice so that a restriction-of-X
565    // type whose base X resolves later in the arena order can still inherit
566    // X's variety / resolved_member_types / resolved_item_type.  Without
567    // the second pass, dt = restriction(inline-union) ends up with
568    // variety=Union and an empty resolved_member_types vector when dt is
569    // visited before the inline union (saxon simple016).
570    for key in simple_type_keys.clone() {
571        if let Err(e) = resolve_simple_type_references(schema_set, key, &mut stats) {
572            errors.push(e);
573            stats.errors += 1;
574        }
575    }
576    for key in simple_type_keys {
577        if let Err(e) = resolve_simple_type_references(schema_set, key, &mut stats) {
578            errors.push(e);
579            stats.errors += 1;
580        }
581    }
582
583    // Resolve complex type references
584    for &key in &complex_type_keys {
585        if let Err(e) = resolve_complex_type_references(schema_set, key, &mut stats) {
586            errors.push(e);
587            stats.errors += 1;
588        }
589    }
590
591    // Resolve model group references
592    for key in model_group_keys {
593        if let Err(e) = resolve_model_group_references(schema_set, key, &mut stats) {
594            errors.push(e);
595            stats.errors += 1;
596        }
597    }
598
599    // Resolve attribute group references
600    for key in attribute_group_keys {
601        if let Err(e) = resolve_attribute_group_references(schema_set, key, &mut stats) {
602            errors.push(e);
603            stats.errors += 1;
604        }
605    }
606
607    // Resolve notation references
608    // Note: Notation references can appear in:
609    // 1. The NOTATION facet in simple type restrictions
610    // 2. Element declarations (rare)
611    // Currently the data model doesn't store unresolved notation QNames,
612    // but we iterate notations here for completeness and future support.
613    let notation_keys: Vec<NotationKey> = schema_set.arenas.notations.keys().collect();
614    for key in notation_keys {
615        if let Err(e) = resolve_notation_references(schema_set, key, &mut stats) {
616            errors.push(e);
617            stats.errors += 1;
618        }
619    }
620
621    // Resolve schema-level defaultAttributes and inject into complex types
622    // Step A: Pre-resolve document-level default attribute groups
623    let mut doc_default_attr_groups: Vec<Option<AttributeGroupKey>> =
624        Vec::with_capacity(schema_set.documents.len());
625    for doc in &schema_set.documents {
626        if let Some(ref qname) = doc.default_attributes {
627            if let Err(e) = check_namespace_visible_ns(
628                schema_set,
629                qname.namespace_uri,
630                qname.local_name,
631                doc.source.as_ref(),
632                "Attribute group",
633            ) {
634                errors.push(e);
635                stats.errors += 1;
636                doc_default_attr_groups.push(None);
637                continue;
638            }
639            if let Some(key) =
640                schema_set.lookup_attribute_group(qname.namespace_uri, qname.local_name)
641            {
642                doc_default_attr_groups.push(Some(key));
643                stats.attribute_groups_resolved += 1;
644            } else {
645                // Step B: Error for unresolvable defaultAttributes
646                let location = schema_set.locate(doc.source.as_ref());
647                let name_str = format_resolved_qname(
648                    &schema_set.name_table,
649                    qname.namespace_uri,
650                    qname.local_name,
651                );
652                errors.push(SchemaError::structural(
653                    "src-resolve",
654                    format!("Attribute group '{}' not found", name_str),
655                    location,
656                ));
657                stats.errors += 1;
658                doc_default_attr_groups.push(None);
659            }
660        } else {
661            doc_default_attr_groups.push(None);
662        }
663    }
664
665    // Step C: Inject default attribute group into applicable complex types
666    for &key in &complex_type_keys {
667        let doc_id = {
668            let type_def = match schema_set.arenas.complex_types.get(key) {
669                Some(td) => td,
670                None => continue,
671            };
672            if !type_def.default_attributes_apply {
673                continue;
674            }
675            match type_def.source.as_ref() {
676                // Use defaults_doc() so override children read the
677                // overridden document's defaultAttributes per §4.2.5.
678                Some(src) => src.defaults_doc(),
679                None => continue, // synthesized types have no source
680            }
681        };
682        if let Some(Some(group_key)) = doc_default_attr_groups.get(doc_id as usize) {
683            let group_key = *group_key;
684            let type_def = schema_set.arenas.complex_types.get_mut(key).unwrap();
685            if !type_def.resolved_attribute_groups.contains(&group_key) {
686                type_def.resolved_attribute_groups.push(group_key);
687            }
688        }
689    }
690
691    // §3.17.6.2 clause 4 — per-document QName visibility on complex-type
692    // content particles and model-group particles. References inside
693    // particles are NOT looked up by `resolve_all_references` for top-level
694    // complex types (they're looked up lazily at NFA compile time), so we
695    // must validate visibility here.
696    if let Err(e) = validate_particle_qname_visibility(schema_set) {
697        errors.push(e);
698        stats.errors += 1;
699    }
700
701    // If there were errors, return the first one
702    if let Some(first_error) = errors.into_iter().next() {
703        return Err(first_error);
704    }
705
706    Ok(stats)
707}
708
709/// Walk complex-type content particles, enforcing §3.17.6.2 clause 4 on every
710/// QName reference therein.
711///
712/// Complex-type content particle refs (element/type/group) are looked up
713/// lazily during NFA compilation, bypassing `ReferenceResolver`'s gate; this
714/// pass plugs that gap. Top-level model groups are not re-walked — their
715/// particles already pass through `resolve_model_group_references`.
716fn validate_particle_qname_visibility(schema_set: &SchemaSet) -> SchemaResult<()> {
717    use crate::parser::frames::{ParticleResult, ParticleTerm};
718
719    fn visit(
720        schema_set: &SchemaSet,
721        particles: &[ParticleResult],
722        depth: usize,
723    ) -> SchemaResult<()> {
724        if depth > 64 {
725            return Ok(());
726        }
727        for particle in particles {
728            match &particle.term {
729                ParticleTerm::Element(elem) => {
730                    let src = elem.source.as_ref().or(particle.source.as_ref());
731                    if let Some(ref_qn) = &elem.ref_name {
732                        check_namespace_visible(schema_set, ref_qn, src, "Element")?;
733                    }
734                    if let Some(TypeRefResult::QName(qname)) = &elem.type_ref {
735                        check_namespace_visible(schema_set, qname, src, "Type")?;
736                    }
737                }
738                ParticleTerm::Group(group_def) => {
739                    if let Some(ref_qn) = &group_def.ref_name {
740                        check_namespace_visible(
741                            schema_set,
742                            ref_qn,
743                            particle.source.as_ref(),
744                            "Group",
745                        )?;
746                    }
747                    visit(schema_set, &group_def.particles, depth + 1)?;
748                }
749                ParticleTerm::Any(_) => {}
750            }
751        }
752        Ok(())
753    }
754
755    for (_, ct) in schema_set.arenas.complex_types.iter() {
756        if let crate::parser::frames::ComplexContentResult::Complex(content) = &ct.content {
757            if let Some(particle) = &content.particle {
758                visit(schema_set, std::slice::from_ref(particle), 0)?;
759            }
760        }
761    }
762    Ok(())
763}
764
765/// Set an element's resolved type and propagate to XSD 1.1 type alternatives
766/// that have no explicit type (they use the element's declared type as fallback).
767fn assign_element_type(elem: &mut crate::arenas::ElementDeclData, type_key: TypeKey) {
768    elem.resolved_type = Some(type_key);
769    #[cfg(feature = "xsd11")]
770    for alt in &mut elem.alternatives {
771        if alt.resolved_type.is_none() && alt.type_ref.is_none() {
772            alt.resolved_type = Some(type_key);
773        }
774    }
775}
776
777/// Resolve references in an element declaration
778fn resolve_element_references(
779    schema_set: &mut SchemaSet,
780    key: ElementKey,
781    stats: &mut ResolutionStats,
782) -> SchemaResult<()> {
783    // First pass: extract QName references we need to resolve (only Clone-able data)
784    // Also check if resolved_type is already set (from inline type assembly)
785    let (type_qname, ref_name, substitution_groups, source, already_resolved_type) = {
786        let elem = schema_set
787            .arenas
788            .elements
789            .get(key)
790            .ok_or_else(|| SchemaError::internal("Element not found in arena"))?;
791
792        // Extract QName from TypeRefResult if it's a QName reference
793        let type_qname = match &elem.type_ref {
794            Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
795            _ => None,
796        };
797
798        (
799            type_qname,
800            elem.ref_name.clone(),
801            elem.substitution_group.clone(),
802            elem.source.clone(),
803            elem.resolved_type, // Already resolved from inline type assembly
804        )
805    };
806
807    // Create resolver
808    let resolver = ReferenceResolver::new(schema_set);
809
810    // XSD 1.0 permits an unresolved `type` reference on an unused element
811    // declaration; XSD 1.1 treats the same miss as a fatal compile error.
812    let lazy_src_resolve = schema_set.is_xsd10();
813
814    // Resolve type reference (if not already resolved from inline type).
815    // Under XSD 1.0, a missing target is deferred via `deferred_type_error`
816    // and the schema still compiles. Visibility violations remain fatal.
817    let mut deferred_type_error: Option<crate::arenas::DeferredSrcResolve> = None;
818    let mut resolved_type = if already_resolved_type.is_some() {
819        // Type was already resolved during assembly (inline type)
820        already_resolved_type
821    } else if let Some(ref qname) = type_qname {
822        if lazy_src_resolve {
823            match resolver.try_resolve_type_ref(qname, source.as_ref())? {
824                Some(type_key) => {
825                    stats.types_resolved += 1;
826                    Some(type_key)
827                }
828                None => {
829                    deferred_type_error = Some(build_deferred_type_resolve(
830                        schema_set,
831                        qname,
832                        source.as_ref(),
833                        "Type",
834                    ));
835                    None
836                }
837            }
838        } else {
839            let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
840            stats.types_resolved += 1;
841            Some(type_key)
842        }
843    } else {
844        None
845    };
846    // Only fall back to anyType when there is no substitution group AND no
847    // deferred type error. An explicit but unresolved `type` attribute must
848    // not be silently rewritten as `xs:anyType`; runtime checks the deferred
849    // error instead. Elements with a substitutionGroup but no explicit type
850    // inherit the head's type in the post-pass inside `resolve_all_references`
851    // (§3.3.2.1 rule 3).
852    if resolved_type.is_none()
853        && deferred_type_error.is_none()
854        && ref_name.is_none()
855        && substitution_groups.is_empty()
856    {
857        resolved_type = Some(TypeKey::Complex(schema_set.any_type_key()));
858    }
859
860    // Resolve element reference (for <xs:element ref="...">)
861    let resolved_ref = if let Some(ref qname) = ref_name {
862        let elem_key = resolver.resolve_element_ref(qname, source.as_ref())?;
863        stats.elements_resolved += 1;
864        Some(elem_key)
865    } else {
866        None
867    };
868
869    // Resolve substitution groups. Under XSD 1.0, an unresolved head is
870    // dropped silently — direct validation of the affiliating element is
871    // still permitted, so the missing affiliation must not poison the rest
872    // of the declaration. Under XSD 1.1, a missing head is fatal.
873    let mut resolved_subst_groups = Vec::with_capacity(substitution_groups.len());
874    for qname in &substitution_groups {
875        if lazy_src_resolve {
876            match resolver.try_resolve_element_ref(qname, source.as_ref())? {
877                Some(elem_key) => {
878                    stats.elements_resolved += 1;
879                    resolved_subst_groups.push(elem_key);
880                }
881                None => {
882                    // Drop unresolved head; do not poison the affiliating element.
883                }
884            }
885        } else {
886            let elem_key = resolver.resolve_element_ref(qname, source.as_ref())?;
887            stats.elements_resolved += 1;
888            resolved_subst_groups.push(elem_key);
889        }
890    }
891
892    // Resolve alternative type references (XSD 1.1)
893    #[cfg(feature = "xsd11")]
894    let resolved_alt_types = {
895        let elem = schema_set
896            .arenas
897            .elements
898            .get(key)
899            .ok_or_else(|| SchemaError::internal("Element not found in arena"))?;
900        let mut alt_types: Vec<Option<TypeKey>> = Vec::with_capacity(elem.alternatives.len());
901        for alt in &elem.alternatives {
902            if alt.resolved_type.is_some() {
903                // Already resolved (from inline type assembly)
904                alt_types.push(alt.resolved_type);
905            } else if let Some(TypeRefResult::QName(ref qname)) = alt.type_ref {
906                let resolver = ReferenceResolver::new(schema_set);
907                let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
908                stats.types_resolved += 1;
909                alt_types.push(Some(type_key));
910            } else {
911                // No type specified — use element's declared type as fallback
912                alt_types.push(resolved_type);
913            }
914        }
915        alt_types
916    };
917
918    // Store resolved references back
919    if let Some(elem) = schema_set.arenas.elements.get_mut(key) {
920        elem.resolved_type = resolved_type;
921        elem.resolved_ref = resolved_ref;
922        elem.resolved_substitution_groups = resolved_subst_groups;
923        elem.deferred_type_error = deferred_type_error;
924
925        #[cfg(feature = "xsd11")]
926        for (i, alt_type) in resolved_alt_types.into_iter().enumerate() {
927            if let Some(alt) = elem.alternatives.get_mut(i) {
928                alt.resolved_type = alt_type;
929            }
930        }
931    }
932
933    Ok(())
934}
935
936/// Format a "type not found" `src-resolve` message with provenance, trying
937/// the `SimpleType` arena first and falling back to `ComplexType`. `label`
938/// is the human-facing kind ("Type", "List item type") prepended to the
939/// QName.
940fn format_type_not_found_message(
941    schema_set: &SchemaSet,
942    qname: &QNameRef,
943    label: &str,
944) -> String {
945    let name_str = format_resolved_qname(&schema_set.name_table, qname.namespace, qname.local_name);
946    let simple_note = schema_set.format_provenance_note(
947        ComponentKind::SimpleType,
948        qname.namespace,
949        qname.local_name,
950    );
951    let note = if simple_note.is_empty() {
952        schema_set.format_provenance_note(
953            ComponentKind::ComplexType,
954            qname.namespace,
955            qname.local_name,
956        )
957    } else {
958        simple_note
959    };
960    format!("{} '{}' not found{}", label, name_str, note)
961}
962
963/// Build a deferred `src-resolve` error payload for a missing type reference.
964fn build_deferred_type_resolve(
965    schema_set: &SchemaSet,
966    qname: &QNameRef,
967    source: Option<&SourceRef>,
968    label: &str,
969) -> crate::arenas::DeferredSrcResolve {
970    crate::arenas::DeferredSrcResolve {
971        message: format_type_not_found_message(schema_set, qname, label),
972        source: source.cloned(),
973    }
974}
975
976/// Resolve references in an attribute declaration
977fn resolve_attribute_references(
978    schema_set: &mut SchemaSet,
979    key: AttributeKey,
980    stats: &mut ResolutionStats,
981) -> SchemaResult<()> {
982    // First pass: extract QName references we need to resolve
983    // Also check if resolved_type is already set (from inline type assembly)
984    let (type_qname, ref_name, source, already_resolved_type) = {
985        let attr = schema_set
986            .arenas
987            .attributes
988            .get(key)
989            .ok_or_else(|| SchemaError::internal("Attribute not found in arena"))?;
990
991        // Extract QName from TypeRefResult if it's a QName reference
992        let type_qname = match &attr.type_ref {
993            Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
994            _ => None,
995        };
996
997        (
998            type_qname,
999            attr.ref_name.clone(),
1000            attr.source.clone(),
1001            attr.resolved_type,
1002        )
1003    };
1004
1005    // Create resolver
1006    let resolver = ReferenceResolver::new(schema_set);
1007
1008    // Resolve type reference (if not already resolved from inline type)
1009    let resolved_type = if already_resolved_type.is_some() {
1010        // Type was already resolved during assembly (inline type)
1011        already_resolved_type
1012    } else if let Some(ref qname) = type_qname {
1013        let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
1014        stats.types_resolved += 1;
1015        Some(type_key)
1016    } else {
1017        None
1018    };
1019
1020    // Resolve attribute reference (for <xs:attribute ref="...">)
1021    let resolved_ref = if let Some(ref qname) = ref_name {
1022        let attr_key = resolver.resolve_attribute_ref(qname, source.as_ref())?;
1023        stats.attributes_resolved += 1;
1024        Some(attr_key)
1025    } else {
1026        None
1027    };
1028
1029    // Store resolved references back
1030    if let Some(attr) = schema_set.arenas.attributes.get_mut(key) {
1031        attr.resolved_type = resolved_type;
1032        attr.resolved_ref = resolved_ref;
1033    }
1034
1035    Ok(())
1036}
1037
1038/// Resolve references in a simple type definition
1039fn resolve_simple_type_references(
1040    schema_set: &mut SchemaSet,
1041    key: SimpleTypeKey,
1042    stats: &mut ResolutionStats,
1043) -> SchemaResult<()> {
1044    // First pass: extract QName references we need to resolve
1045    // Also get already resolved types from assembly (for inline types)
1046    let (
1047        base_qname,
1048        item_qname,
1049        member_qnames,
1050        source,
1051        already_resolved_base,
1052        already_resolved_item,
1053        already_resolved_members,
1054        redefine_original,
1055        type_name,
1056        type_ns,
1057    ) = {
1058        let type_def = schema_set
1059            .arenas
1060            .simple_types
1061            .get(key)
1062            .ok_or_else(|| SchemaError::internal("Simple type not found in arena"))?;
1063
1064        // Extract QName from TypeRefResult if it's a QName reference
1065        let base_qname = match &type_def.base_type {
1066            Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
1067            _ => None,
1068        };
1069        let item_qname = match &type_def.item_type {
1070            Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
1071            _ => None,
1072        };
1073        let member_qnames: Vec<_> = type_def
1074            .member_types
1075            .iter()
1076            .filter_map(|tr| match tr {
1077                TypeRefResult::QName(qname) => Some(qname.clone()),
1078                _ => None,
1079            })
1080            .collect();
1081
1082        (
1083            base_qname,
1084            item_qname,
1085            member_qnames,
1086            type_def.source.clone(),
1087            type_def.resolved_base_type,
1088            type_def.resolved_item_type,
1089            type_def.resolved_member_types.clone(),
1090            type_def.redefine_original,
1091            type_def.name,
1092            type_def.target_namespace,
1093        )
1094    };
1095
1096    // Create resolver
1097    let resolver = ReferenceResolver::new(schema_set);
1098
1099    // Resolve base type reference (for restriction) - if not already resolved
1100    // For redefine self-references, redirect to the original type key
1101    let resolved_base = if already_resolved_base.is_some() {
1102        already_resolved_base
1103    } else if let Some(ref qname) = base_qname {
1104        let is_redefine_self_ref = redefine_original.is_some()
1105            && Some(qname.local_name) == type_name
1106            && qname.namespace == type_ns;
1107        if is_redefine_self_ref {
1108            stats.types_resolved += 1;
1109            Some(TypeKey::Simple(redefine_original.unwrap()))
1110        } else {
1111            let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
1112            stats.types_resolved += 1;
1113            Some(type_key)
1114        }
1115    } else {
1116        None
1117    };
1118
1119    // Resolve item type reference (for list) - if not already resolved.
1120    // Under XSD 1.0, a missing target is deferred via
1121    // `deferred_item_type_error` and the simple type still compiles. Under
1122    // XSD 1.1, the miss is fatal. Visibility violations and missing
1123    // `base` / union `memberTypes` references are always fatal.
1124    let lazy_src_resolve = schema_set.is_xsd10();
1125    let mut deferred_item_error: Option<crate::arenas::DeferredSrcResolve> = None;
1126    let resolved_item = if already_resolved_item.is_some() {
1127        already_resolved_item
1128    } else if let Some(ref qname) = item_qname {
1129        if lazy_src_resolve {
1130            match resolver.try_resolve_type_ref(qname, source.as_ref())? {
1131                Some(type_key) => {
1132                    stats.types_resolved += 1;
1133                    Some(type_key)
1134                }
1135                None => {
1136                    deferred_item_error = Some(build_deferred_type_resolve(
1137                        schema_set,
1138                        qname,
1139                        source.as_ref(),
1140                        "List item type",
1141                    ));
1142                    None
1143                }
1144            }
1145        } else {
1146            let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
1147            stats.types_resolved += 1;
1148            Some(type_key)
1149        }
1150    } else {
1151        None
1152    };
1153
1154    // Resolve member type references (for union)
1155    // Per XSD spec, memberTypes attribute members come first, then inline simpleType children
1156    let mut resolved_members = Vec::new();
1157    for qname in &member_qnames {
1158        let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
1159        stats.types_resolved += 1;
1160        resolved_members.push(type_key);
1161    }
1162    resolved_members.extend(already_resolved_members);
1163
1164    // For list and union types without an explicit base, the XSD spec defines the
1165    // {base type definition} to be anySimpleType (§4.1.2 / §3.16.2.2).
1166    // Setting resolved_base_type here makes is_simple_type_derived_from work
1167    // correctly when checking derivation from anySimpleType (e.g. e-props-correct.4).
1168    let resolved_base = if resolved_base.is_none() {
1169        let variety = schema_set
1170            .arenas
1171            .simple_types
1172            .get(key)
1173            .map(|t| t.variety)
1174            .unwrap_or(SimpleTypeVariety::Atomic);
1175        if matches!(variety, SimpleTypeVariety::List | SimpleTypeVariety::Union) {
1176            let any_simple = schema_set.builtin_types().any_simple_type;
1177            Some(TypeKey::Simple(any_simple))
1178        } else {
1179            None
1180        }
1181    } else {
1182        resolved_base
1183    };
1184
1185    // Store resolved references back
1186    if let Some(type_def) = schema_set.arenas.simple_types.get_mut(key) {
1187        type_def.resolved_base_type = resolved_base;
1188        type_def.resolved_item_type = resolved_item;
1189        type_def.resolved_member_types = resolved_members;
1190        type_def.deferred_item_type_error = deferred_item_error;
1191    }
1192
1193    // Inherit variety and structural properties from base type for restriction-derived types.
1194    // Parser sets variety=Atomic for all restrictions, but restrictions of union/list types
1195    // must inherit the base type's variety and member types / item type.
1196    if let Some(TypeKey::Simple(base_sk)) = resolved_base {
1197        let (base_variety, base_members, base_item, base_deferred_item) = {
1198            if let Some(base_def) = schema_set.arenas.simple_types.get(base_sk) {
1199                (
1200                    base_def.variety,
1201                    base_def.resolved_member_types.clone(),
1202                    base_def.resolved_item_type,
1203                    base_def.deferred_item_type_error.clone(),
1204                )
1205            } else {
1206                (SimpleTypeVariety::Atomic, Vec::new(), None, None)
1207            }
1208        };
1209        if let Some(type_def) = schema_set.arenas.simple_types.get_mut(key) {
1210            if type_def.variety == SimpleTypeVariety::Atomic
1211                && base_variety != SimpleTypeVariety::Atomic
1212            {
1213                type_def.variety = base_variety;
1214            }
1215            if base_variety == SimpleTypeVariety::Union && type_def.resolved_member_types.is_empty()
1216            {
1217                type_def.resolved_member_types = base_members;
1218            }
1219            if base_variety == SimpleTypeVariety::List && type_def.resolved_item_type.is_none() {
1220                type_def.resolved_item_type = base_item;
1221                // Propagate the base's deferred itemType miss so a derived
1222                // list type doesn't silently fall through to "untyped" at
1223                // validation time when the base's itemType was lazy-resolved.
1224                if type_def.deferred_item_type_error.is_none() {
1225                    type_def.deferred_item_type_error = base_deferred_item;
1226                }
1227            }
1228        }
1229    }
1230
1231    Ok(())
1232}
1233
1234/// Resolve references in a complex type definition
1235fn resolve_complex_type_references(
1236    schema_set: &mut SchemaSet,
1237    key: ComplexTypeKey,
1238    stats: &mut ResolutionStats,
1239) -> SchemaResult<()> {
1240    use crate::arenas::ResolvedAttributeUse;
1241
1242    // First pass: extract QName references we need to resolve
1243    // Also get already resolved base type from assembly (for inline types)
1244    let (
1245        base_qname,
1246        attribute_groups,
1247        attribute_uses,
1248        source,
1249        already_resolved_base,
1250        redefine_original,
1251        type_name,
1252        type_ns,
1253        already_resolved_attrs,
1254    ) = {
1255        let type_def = schema_set
1256            .arenas
1257            .complex_types
1258            .get(key)
1259            .ok_or_else(|| SchemaError::internal("Complex type not found in arena"))?;
1260
1261        // Extract QName from TypeRefResult if it's a QName reference
1262        let base_qname = match &type_def.base_type {
1263            Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
1264            _ => None,
1265        };
1266
1267        // Extract attribute use info for resolution
1268        let attribute_uses: Vec<_> = type_def
1269            .attributes
1270            .iter()
1271            .map(|attr_use| {
1272                let type_qname = match &attr_use.attribute.type_ref {
1273                    Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
1274                    _ => None,
1275                };
1276                (
1277                    attr_use.attribute.ref_name.clone(),
1278                    type_qname,
1279                    attr_use.attribute.source.clone(),
1280                )
1281            })
1282            .collect();
1283
1284        // Preserve resolved_attributes from inline type assembly (Phase 3)
1285        let already_resolved_attrs = type_def.resolved_attributes.clone();
1286
1287        (
1288            base_qname,
1289            type_def.attribute_groups.clone(),
1290            attribute_uses,
1291            type_def.source.clone(),
1292            type_def.resolved_base_type,
1293            type_def.redefine_original,
1294            type_def.name,
1295            type_def.target_namespace,
1296            already_resolved_attrs,
1297        )
1298    };
1299
1300    // Create resolver
1301    let resolver = ReferenceResolver::new(schema_set);
1302
1303    // Resolve base type reference - if not already resolved
1304    // For redefine self-references, redirect to the original type key
1305    let resolved_base = if already_resolved_base.is_some() {
1306        already_resolved_base
1307    } else if let Some(ref qname) = base_qname {
1308        let is_redefine_self_ref = redefine_original.is_some()
1309            && Some(qname.local_name) == type_name
1310            && qname.namespace == type_ns;
1311        if is_redefine_self_ref {
1312            stats.types_resolved += 1;
1313            Some(TypeKey::Complex(redefine_original.unwrap()))
1314        } else {
1315            let type_key = resolver.resolve_type_ref(qname, source.as_ref())?;
1316            stats.types_resolved += 1;
1317            Some(type_key)
1318        }
1319    } else {
1320        None
1321    };
1322
1323    // Resolve attribute group references
1324    let mut resolved_attr_groups = Vec::with_capacity(attribute_groups.len());
1325    for qname in &attribute_groups {
1326        let group_key = resolver.resolve_attribute_group_ref(qname, source.as_ref())?;
1327        stats.attribute_groups_resolved += 1;
1328        resolved_attr_groups.push(group_key);
1329    }
1330
1331    // Resolve attribute use references
1332    let mut resolved_attrs = Vec::with_capacity(attribute_uses.len());
1333    for (i, (ref_name, type_qname, attr_source)) in attribute_uses.iter().enumerate() {
1334        let resolved_type = if let Some(ref qname) = type_qname {
1335            let type_key = resolver.resolve_type_ref(qname, attr_source.as_ref())?;
1336            stats.types_resolved += 1;
1337            Some(type_key)
1338        } else {
1339            // Preserve type from inline assembly (Phase 3) when no QName ref
1340            already_resolved_attrs.get(i).and_then(|r| r.resolved_type)
1341        };
1342        let resolved_ref = if let Some(ref qname) = ref_name {
1343            let attr_key = resolver.resolve_attribute_ref(qname, attr_source.as_ref())?;
1344            stats.attributes_resolved += 1;
1345            Some(attr_key)
1346        } else {
1347            // Preserve ref from inline assembly
1348            already_resolved_attrs.get(i).and_then(|r| r.resolved_ref)
1349        };
1350        resolved_attrs.push(ResolvedAttributeUse {
1351            resolved_type,
1352            resolved_ref,
1353        });
1354    }
1355
1356    // Store resolved references back
1357    if let Some(type_def) = schema_set.arenas.complex_types.get_mut(key) {
1358        type_def.resolved_base_type = resolved_base;
1359        type_def.resolved_attribute_groups = resolved_attr_groups;
1360        type_def.resolved_attributes = resolved_attrs;
1361    }
1362
1363    Ok(())
1364}
1365
1366/// Resolve references in a model group definition
1367fn resolve_model_group_references(
1368    schema_set: &mut SchemaSet,
1369    key: ModelGroupKey,
1370    stats: &mut ResolutionStats,
1371) -> SchemaResult<()> {
1372    use crate::arenas::ResolvedParticleTerm;
1373    use crate::parser::frames::ParticleTerm;
1374
1375    // Get the model group data to read references
1376    let group = schema_set
1377        .arenas
1378        .model_groups
1379        .get(key)
1380        .ok_or_else(|| SchemaError::internal("Model group not found in arena"))?;
1381
1382    // Clone references we need to resolve
1383    let ref_name = group.ref_name.clone();
1384    let source = group.source.clone();
1385    let particles_clone = group.particles.clone();
1386
1387    // Capture redefine info for self-reference redirection
1388    let redefine_original = group.redefine_original;
1389    let group_name = group.name;
1390    let group_ns = group.target_namespace;
1391
1392    // Read existing resolved_particles BEFORE building new ones,
1393    // so we can preserve inline-resolved types from Phase 3.
1394    let existing_resolved: Vec<_> = group.resolved_particles.clone();
1395    let existing_particle_types = group.resolved_particle_types.clone();
1396
1397    // Extract particle info for resolution
1398    let particle_info: Vec<_> = group
1399        .particles
1400        .iter()
1401        .map(|p| match &p.term {
1402            ParticleTerm::Element(elem) => {
1403                let type_qname = match &elem.type_ref {
1404                    Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
1405                    _ => None,
1406                };
1407                (0, elem.ref_name.clone(), type_qname, p.source.clone())
1408            }
1409            ParticleTerm::Group(grp) => (1, grp.ref_name.clone(), None, p.source.clone()),
1410            ParticleTerm::Any(_) => (2, None, None, p.source.clone()),
1411        })
1412        .collect();
1413
1414    // Create resolver
1415    let resolver = ReferenceResolver::new(schema_set);
1416
1417    // Resolve group reference (for <xs:group ref="...">)
1418    let resolved_ref = if let Some(ref qname) = ref_name {
1419        let group_key = resolver.resolve_group_ref(qname, source.as_ref())?;
1420        stats.groups_resolved += 1;
1421        Some(group_key)
1422    } else {
1423        None
1424    };
1425
1426    // Resolve particle references
1427    let mut resolved_particles = Vec::with_capacity(particle_info.len());
1428    for (i, (kind, elem_or_group_ref, type_qname, particle_source)) in
1429        particle_info.iter().enumerate()
1430    {
1431        match kind {
1432            0 => {
1433                // Element particle — preserve inline-resolved type from Phase 3
1434                let already_resolved_type = existing_resolved.get(i).and_then(|rp| {
1435                    if let ResolvedParticleTerm::Element {
1436                        resolved_type: Some(key),
1437                        ..
1438                    } = rp
1439                    {
1440                        Some(*key)
1441                    } else {
1442                        None
1443                    }
1444                });
1445                let resolved_type = if let Some(key) = already_resolved_type {
1446                    Some(key)
1447                } else if let Some(ref qname) = type_qname {
1448                    let type_key = resolver.resolve_type_ref(qname, particle_source.as_ref())?;
1449                    stats.types_resolved += 1;
1450                    Some(type_key)
1451                } else {
1452                    None
1453                };
1454                let resolved_elem_ref = if let Some(ref qname) = elem_or_group_ref {
1455                    let elem_key = resolver.resolve_element_ref(qname, particle_source.as_ref())?;
1456                    stats.elements_resolved += 1;
1457                    Some(elem_key)
1458                } else {
1459                    None
1460                };
1461                resolved_particles.push(ResolvedParticleTerm::Element {
1462                    resolved_type,
1463                    resolved_ref: resolved_elem_ref,
1464                });
1465            }
1466            1 => {
1467                // Group particle — redirect self-references to the original group
1468                let resolved_group_ref = if let Some(ref qname) = elem_or_group_ref {
1469                    let is_self_ref = redefine_original.is_some()
1470                        && Some(qname.local_name) == group_name
1471                        && qname.namespace == group_ns;
1472                    let grp_key = if is_self_ref {
1473                        redefine_original.unwrap()
1474                    } else {
1475                        resolver.resolve_group_ref(qname, particle_source.as_ref())?
1476                    };
1477                    stats.groups_resolved += 1;
1478                    Some(grp_key)
1479                } else {
1480                    None
1481                };
1482                resolved_particles.push(ResolvedParticleTerm::Group {
1483                    resolved_ref: resolved_group_ref,
1484                });
1485            }
1486            _ => {
1487                // Wildcard
1488                resolved_particles.push(ResolvedParticleTerm::Any);
1489            }
1490        }
1491    }
1492
1493    // Build flat-indexed resolved_particle_types (including nested inline groups)
1494    let mut resolved_particle_types = Vec::new();
1495    let mut flat_idx = 0;
1496    resolve_model_group_particle_types_recursive(
1497        &particles_clone,
1498        &existing_particle_types,
1499        &resolver,
1500        &mut flat_idx,
1501        &mut resolved_particle_types,
1502        stats,
1503    )?;
1504
1505    // Store resolved references back
1506    if let Some(group) = schema_set.arenas.model_groups.get_mut(key) {
1507        group.resolved_ref = resolved_ref;
1508        group.resolved_particles = resolved_particles;
1509        group.resolved_particle_types = resolved_particle_types;
1510    }
1511
1512    Ok(())
1513}
1514
1515/// Recursive helper: resolve types for model group particles in depth-first order
1516fn resolve_model_group_particle_types_recursive(
1517    particles: &[crate::parser::frames::ParticleResult],
1518    existing_types: &[Option<TypeKey>],
1519    resolver: &ReferenceResolver,
1520    flat_idx: &mut usize,
1521    resolved_types: &mut Vec<Option<TypeKey>>,
1522    stats: &mut ResolutionStats,
1523) -> SchemaResult<()> {
1524    use crate::parser::frames::ParticleTerm;
1525
1526    for particle in particles {
1527        match &particle.term {
1528            ParticleTerm::Element(elem) => {
1529                let idx = *flat_idx;
1530                *flat_idx += 1;
1531                // Preserve inline-resolved type from Phase 3
1532                let already_resolved = existing_types.get(idx).copied().flatten();
1533                let resolved_type = if let Some(key) = already_resolved {
1534                    Some(key)
1535                } else {
1536                    // Try QName resolution
1537                    match &elem.type_ref {
1538                        Some(TypeRefResult::QName(qname)) => {
1539                            let type_key =
1540                                resolver.resolve_type_ref(qname, particle.source.as_ref())?;
1541                            stats.types_resolved += 1;
1542                            Some(type_key)
1543                        }
1544                        _ => None,
1545                    }
1546                };
1547                while resolved_types.len() <= idx {
1548                    resolved_types.push(None);
1549                }
1550                resolved_types[idx] = resolved_type;
1551            }
1552            ParticleTerm::Group(group_def) if group_def.ref_name.is_none() => {
1553                resolve_model_group_particle_types_recursive(
1554                    &group_def.particles,
1555                    existing_types,
1556                    resolver,
1557                    flat_idx,
1558                    resolved_types,
1559                    stats,
1560                )?;
1561            }
1562            _ => {} // Skip group refs and wildcards
1563        }
1564    }
1565    Ok(())
1566}
1567
1568/// Resolve references in an attribute group definition
1569fn resolve_attribute_group_references(
1570    schema_set: &mut SchemaSet,
1571    key: AttributeGroupKey,
1572    stats: &mut ResolutionStats,
1573) -> SchemaResult<()> {
1574    use crate::arenas::ResolvedAttributeUse;
1575
1576    // Get the attribute group data to read references
1577    let group = schema_set
1578        .arenas
1579        .attribute_groups
1580        .get(key)
1581        .ok_or_else(|| SchemaError::internal("Attribute group not found in arena"))?;
1582
1583    // Clone references we need to resolve
1584    let ref_name = group.ref_name.clone();
1585    let nested_groups = group.attribute_groups.clone();
1586    let source = group.source.clone();
1587
1588    // Capture redefine info for self-reference redirection
1589    let redefine_original = group.redefine_original;
1590    let group_name = group.name;
1591    let group_ns = group.target_namespace;
1592
1593    // Extract attribute use info for resolution
1594    let attribute_uses: Vec<_> = group
1595        .attributes
1596        .iter()
1597        .map(|attr_use| {
1598            let type_qname = match &attr_use.attribute.type_ref {
1599                Some(TypeRefResult::QName(qname)) => Some(qname.clone()),
1600                _ => None,
1601            };
1602            (
1603                attr_use.attribute.ref_name.clone(),
1604                type_qname,
1605                attr_use.attribute.source.clone(),
1606            )
1607        })
1608        .collect();
1609
1610    // Preserve resolved_attributes from inline type assembly (Phase 3)
1611    let already_resolved_attrs = group.resolved_attributes.clone();
1612
1613    // Create resolver
1614    let resolver = ReferenceResolver::new(schema_set);
1615
1616    // Resolve group reference (for <xs:attributeGroup ref="...">)
1617    let resolved_ref = if let Some(ref qname) = ref_name {
1618        let group_key = resolver.resolve_attribute_group_ref(qname, source.as_ref())?;
1619        stats.attribute_groups_resolved += 1;
1620        Some(group_key)
1621    } else {
1622        None
1623    };
1624
1625    // Resolve nested attribute group references — redirect self-references to the original
1626    let mut resolved_nested = Vec::with_capacity(nested_groups.len());
1627    for qname in &nested_groups {
1628        let is_self_ref = redefine_original.is_some()
1629            && Some(qname.local_name) == group_name
1630            && qname.namespace == group_ns;
1631        let group_key = if is_self_ref {
1632            redefine_original.unwrap()
1633        } else {
1634            resolver.resolve_attribute_group_ref(qname, source.as_ref())?
1635        };
1636        stats.attribute_groups_resolved += 1;
1637        resolved_nested.push(group_key);
1638    }
1639
1640    // Resolve attribute use references
1641    let mut resolved_attrs = Vec::with_capacity(attribute_uses.len());
1642    for (i, (ref_name_opt, type_qname, attr_source)) in attribute_uses.iter().enumerate() {
1643        let resolved_type = if let Some(ref qname) = type_qname {
1644            let type_key = resolver.resolve_type_ref(qname, attr_source.as_ref())?;
1645            stats.types_resolved += 1;
1646            Some(type_key)
1647        } else {
1648            // Preserve type from inline assembly (Phase 3) when no QName ref
1649            already_resolved_attrs.get(i).and_then(|r| r.resolved_type)
1650        };
1651        let resolved_attr_ref = if let Some(ref qname) = ref_name_opt {
1652            let attr_key = resolver.resolve_attribute_ref(qname, attr_source.as_ref())?;
1653            stats.attributes_resolved += 1;
1654            Some(attr_key)
1655        } else {
1656            // Preserve ref from inline assembly
1657            already_resolved_attrs.get(i).and_then(|r| r.resolved_ref)
1658        };
1659        resolved_attrs.push(ResolvedAttributeUse {
1660            resolved_type,
1661            resolved_ref: resolved_attr_ref,
1662        });
1663    }
1664
1665    // Store resolved references back
1666    if let Some(group) = schema_set.arenas.attribute_groups.get_mut(key) {
1667        group.resolved_ref = resolved_ref;
1668        group.resolved_attribute_groups = resolved_nested;
1669        group.resolved_attributes = resolved_attrs;
1670    }
1671
1672    Ok(())
1673}
1674
1675/// Resolve references in a notation declaration
1676///
1677/// Currently notations don't have internal references that need resolution,
1678/// but this function is provided for completeness and to track notation
1679/// processing in statistics. In the future, if notation references are
1680/// added to the data model, resolution logic would go here.
1681fn resolve_notation_references(
1682    schema_set: &mut SchemaSet,
1683    key: NotationKey,
1684    stats: &mut ResolutionStats,
1685) -> SchemaResult<()> {
1686    // Verify the notation exists
1687    let _notation = schema_set
1688        .arenas
1689        .notations
1690        .get(key)
1691        .ok_or_else(|| SchemaError::internal("Notation not found in arena"))?;
1692
1693    // Track notation processing
1694    stats.notations_resolved += 1;
1695
1696    // Currently notations don't have unresolved references in the data model.
1697    // Future: If NOTATION facet references or element notation attributes
1698    // are stored as QNames, resolve them here.
1699
1700    Ok(())
1701}
1702
1703#[cfg(test)]
1704mod tests {
1705    use super::*;
1706    use crate::namespace::table::well_known;
1707
1708    #[test]
1709    fn test_reference_resolver_creation() {
1710        let schema_set = SchemaSet::new();
1711        let _resolver = ReferenceResolver::new(&schema_set);
1712    }
1713
1714    #[test]
1715    fn test_resolve_builtin_type() {
1716        let schema_set = SchemaSet::new();
1717        let resolver = ReferenceResolver::new(&schema_set);
1718
1719        // Create a QNameRef for xs:string
1720        let string_name = schema_set.name_table.get("string").unwrap();
1721        let qname = QNameRef {
1722            prefix: None,
1723            local_name: string_name,
1724            namespace: Some(well_known::XS_NAMESPACE),
1725        };
1726
1727        let result = resolver.resolve_type_ref(&qname, None);
1728        assert!(result.is_ok(), "Should resolve xs:string");
1729
1730        if let Ok(TypeKey::Simple(key)) = result {
1731            // Verify it's the string type
1732            let string_key = schema_set.builtin_types().string;
1733            assert_eq!(key, string_key);
1734        } else {
1735            panic!("Expected Simple type key");
1736        }
1737    }
1738
1739    #[test]
1740    fn test_resolve_builtin_integer() {
1741        let schema_set = SchemaSet::new();
1742        let resolver = ReferenceResolver::new(&schema_set);
1743
1744        // Create a QNameRef for xs:integer
1745        let integer_name = schema_set.name_table.get("integer").unwrap();
1746        let qname = QNameRef {
1747            prefix: None,
1748            local_name: integer_name,
1749            namespace: Some(well_known::XS_NAMESPACE),
1750        };
1751
1752        let result = resolver.resolve_type_ref(&qname, None);
1753        assert!(result.is_ok(), "Should resolve xs:integer");
1754    }
1755
1756    #[test]
1757    fn test_resolve_builtin_any_type() {
1758        let schema_set = SchemaSet::new();
1759        let resolver = ReferenceResolver::new(&schema_set);
1760
1761        let any_type_name = schema_set.name_table.get("anyType").unwrap();
1762        let qname = QNameRef {
1763            prefix: None,
1764            local_name: any_type_name,
1765            namespace: Some(well_known::XS_NAMESPACE),
1766        };
1767
1768        let result = resolver.resolve_type_ref(&qname, None);
1769        assert!(result.is_ok(), "Should resolve xs:anyType");
1770
1771        if let Ok(TypeKey::Complex(key)) = result {
1772            assert_eq!(key, schema_set.builtin_types().any_type);
1773        } else {
1774            panic!("Expected Complex type key");
1775        }
1776    }
1777
1778    #[test]
1779    fn test_resolve_unknown_type_error() {
1780        let schema_set = SchemaSet::new();
1781
1782        // Add the name before creating the resolver to avoid borrow conflicts
1783        let unknown_name = schema_set.name_table.add("nonExistentType");
1784
1785        let resolver = ReferenceResolver::new(&schema_set);
1786        let qname = QNameRef {
1787            prefix: None,
1788            local_name: unknown_name,
1789            namespace: Some(well_known::XS_NAMESPACE),
1790        };
1791
1792        let result = resolver.resolve_type_ref(&qname, None);
1793        assert!(result.is_err(), "Should fail for unknown type");
1794    }
1795
1796    #[test]
1797    fn test_format_qname_with_namespace() {
1798        let schema_set = SchemaSet::new();
1799        let resolver = ReferenceResolver::new(&schema_set);
1800
1801        // "string" should already exist from built-in types initialization
1802        let string_name = schema_set.name_table.get("string").unwrap();
1803        let qname = QNameRef {
1804            prefix: None,
1805            local_name: string_name,
1806            namespace: Some(well_known::XS_NAMESPACE),
1807        };
1808
1809        let formatted = resolver.format_qname(&qname);
1810        assert!(formatted.contains("string"));
1811        assert!(formatted.contains("XMLSchema"));
1812    }
1813
1814    #[test]
1815    fn test_format_qname_without_namespace() {
1816        let schema_set = SchemaSet::new();
1817
1818        // Add the name before creating the resolver to avoid borrow conflicts
1819        let local_name = schema_set.name_table.add("localType");
1820
1821        let resolver = ReferenceResolver::new(&schema_set);
1822        let qname = QNameRef {
1823            prefix: None,
1824            local_name,
1825            namespace: None,
1826        };
1827
1828        let formatted = resolver.format_qname(&qname);
1829        assert_eq!(formatted, "localType");
1830    }
1831
1832    #[test]
1833    fn test_resolution_stats_default() {
1834        let stats = ResolutionStats::default();
1835        assert_eq!(stats.types_resolved, 0);
1836        assert_eq!(stats.elements_resolved, 0);
1837        assert_eq!(stats.errors, 0);
1838    }
1839
1840    #[test]
1841    fn test_resolve_all_references_empty_schema() {
1842        let mut schema_set = SchemaSet::new();
1843
1844        // Should succeed with empty schema (only built-in types)
1845        let result = resolve_all_references(&mut schema_set);
1846        assert!(result.is_ok());
1847
1848        let stats = result.unwrap();
1849        // Should resolve notations (0 user-defined notations)
1850        assert_eq!(stats.errors, 0);
1851    }
1852
1853    #[test]
1854    fn test_resolve_user_defined_element_with_builtin_type() {
1855        use crate::arenas::ElementDeclData;
1856        use crate::parser::frames::QNameRef;
1857        use crate::schema::model::DerivationSet;
1858
1859        let mut schema_set = SchemaSet::new();
1860
1861        // Get names
1862        let elem_name = schema_set.name_table.add("myElement");
1863        let string_name = schema_set.name_table.get("string").unwrap();
1864
1865        // Create an element with type="xs:string"
1866        let type_ref = TypeRefResult::QName(QNameRef {
1867            prefix: None,
1868            local_name: string_name,
1869            namespace: Some(well_known::XS_NAMESPACE),
1870        });
1871
1872        let elem_data = ElementDeclData {
1873            name: Some(elem_name),
1874            target_namespace: None,
1875            ref_name: None,
1876            type_ref: Some(type_ref),
1877            inline_type: None,
1878            substitution_group: Vec::new(),
1879            default_value: None,
1880            fixed_value: None,
1881            nillable: false,
1882            is_abstract: false,
1883            min_occurs: 1,
1884            max_occurs: Some(1),
1885            block: DerivationSet::empty(),
1886            final_derivation: DerivationSet::empty(),
1887            form: None,
1888            id: None,
1889            alternatives: Vec::new(),
1890            identity_constraints: Vec::new(),
1891            pending_ic_refs: vec![],
1892            annotation: None,
1893            source: None,
1894            resolved_type: None,
1895            resolved_ref: None,
1896            resolved_substitution_groups: Vec::new(),
1897            deferred_type_error: None,
1898        };
1899
1900        let elem_key = schema_set.arenas.alloc_element(elem_data);
1901
1902        // Resolve references
1903        let result = resolve_all_references(&mut schema_set);
1904        assert!(result.is_ok(), "Resolution should succeed: {:?}", result);
1905
1906        // Verify the type was resolved
1907        let elem = schema_set.arenas.elements.get(elem_key).unwrap();
1908        assert!(elem.resolved_type.is_some(), "Type should be resolved");
1909
1910        if let Some(TypeKey::Simple(key)) = elem.resolved_type {
1911            assert_eq!(key, schema_set.builtin_types().string);
1912        } else {
1913            panic!("Expected Simple type key for xs:string");
1914        }
1915    }
1916
1917    #[test]
1918    fn test_resolve_attribute_with_builtin_type() {
1919        use crate::arenas::AttributeDeclData;
1920        use crate::parser::frames::QNameRef;
1921
1922        let mut schema_set = SchemaSet::new();
1923
1924        // Get names
1925        let attr_name = schema_set.name_table.add("myAttribute");
1926        let integer_name = schema_set.name_table.get("integer").unwrap();
1927
1928        // Create an attribute with type="xs:integer"
1929        let type_ref = TypeRefResult::QName(QNameRef {
1930            prefix: None,
1931            local_name: integer_name,
1932            namespace: Some(well_known::XS_NAMESPACE),
1933        });
1934
1935        let attr_data = AttributeDeclData {
1936            name: Some(attr_name),
1937            target_namespace: None,
1938            ref_name: None,
1939            type_ref: Some(type_ref),
1940            inline_type: None,
1941            default_value: None,
1942            fixed_value: None,
1943            use_kind: None,
1944            form: None,
1945            inheritable: false,
1946            id: None,
1947            annotation: None,
1948            source: None,
1949            resolved_type: None,
1950            resolved_ref: None,
1951        };
1952
1953        let attr_key = schema_set.arenas.alloc_attribute(attr_data);
1954
1955        // Resolve references
1956        let result = resolve_all_references(&mut schema_set);
1957        assert!(result.is_ok());
1958
1959        // Verify the type was resolved
1960        let attr = schema_set.arenas.attributes.get(attr_key).unwrap();
1961        assert!(attr.resolved_type.is_some(), "Type should be resolved");
1962    }
1963
1964    #[test]
1965    fn test_resolve_element_already_resolved_inline_type() {
1966        use crate::arenas::ElementDeclData;
1967        use crate::schema::model::DerivationSet;
1968
1969        let mut schema_set = SchemaSet::new();
1970
1971        let elem_name = schema_set.name_table.add("myElement");
1972
1973        // Pre-resolved type (as if from inline type assembly)
1974        let string_key = schema_set.builtin_types().string;
1975
1976        let elem_data = ElementDeclData {
1977            name: Some(elem_name),
1978            target_namespace: None,
1979            ref_name: None,
1980            type_ref: None,
1981            inline_type: None,
1982            substitution_group: Vec::new(),
1983            default_value: None,
1984            fixed_value: None,
1985            nillable: false,
1986            is_abstract: false,
1987            min_occurs: 1,
1988            max_occurs: Some(1),
1989            block: DerivationSet::empty(),
1990            final_derivation: DerivationSet::empty(),
1991            form: None,
1992            id: None,
1993            alternatives: Vec::new(),
1994            identity_constraints: Vec::new(),
1995            pending_ic_refs: vec![],
1996            annotation: None,
1997            source: None,
1998            // Already resolved (from inline type assembly)
1999            resolved_type: Some(TypeKey::Simple(string_key)),
2000            resolved_ref: None,
2001            resolved_substitution_groups: Vec::new(),
2002            deferred_type_error: None,
2003        };
2004
2005        let elem_key = schema_set.arenas.alloc_element(elem_data);
2006
2007        // Resolve references - should preserve the pre-resolved type
2008        let result = resolve_all_references(&mut schema_set);
2009        assert!(result.is_ok());
2010
2011        // Verify the pre-resolved type was preserved
2012        let elem = schema_set.arenas.elements.get(elem_key).unwrap();
2013        assert!(elem.resolved_type.is_some());
2014        assert_eq!(elem.resolved_type, Some(TypeKey::Simple(string_key)));
2015    }
2016
2017    #[test]
2018    fn test_resolver_preserves_inline_resolved_type_in_model_group() {
2019        use crate::arenas::{ModelGroupData, ResolvedParticleTerm};
2020        use crate::parser::frames::{Compositor, ElementFrameResult, ParticleResult, ParticleTerm};
2021
2022        let mut schema_set = SchemaSet::new();
2023
2024        let elem_name = schema_set.name_table.add("detail");
2025        let group_name = schema_set.name_table.add("myGroup");
2026
2027        // Pre-resolved type (as if from inline type assembly)
2028        let string_key = schema_set.builtin_types().string;
2029
2030        // Create a model group with one element particle
2031        let group_data = ModelGroupData {
2032            name: Some(group_name),
2033            target_namespace: None,
2034            ref_name: None,
2035            compositor: Some(Compositor::Sequence),
2036            particles: vec![ParticleResult {
2037                term: ParticleTerm::Element(ElementFrameResult {
2038                    name: Some(elem_name),
2039                    ref_name: None,
2040                    target_namespace: None,
2041                    type_ref: None, // No QName type ref
2042                    inline_type: None,
2043                    substitution_group: vec![],
2044                    default_value: None,
2045                    fixed_value: None,
2046                    nillable: false,
2047                    is_abstract: false,
2048                    min_occurs: 1,
2049                    max_occurs: Some(1),
2050                    block: None,
2051                    final_derivation: None,
2052                    form: None,
2053                    id: None,
2054                    alternatives: vec![],
2055                    identity_constraints: vec![],
2056                    identity_constraint_refs: vec![],
2057                    annotation: None,
2058                    source: None,
2059                }),
2060                min_occurs: 1,
2061                max_occurs: Some(1),
2062                source: None,
2063            }],
2064            min_occurs: 1,
2065            max_occurs: Some(1),
2066            id: None,
2067            annotation: None,
2068            source: None,
2069            resolved_ref: None,
2070            // Pre-populate with inline-resolved type
2071            resolved_particles: vec![ResolvedParticleTerm::Element {
2072                resolved_type: Some(TypeKey::Simple(string_key)),
2073                resolved_ref: None,
2074            }],
2075            resolved_particle_types: vec![Some(TypeKey::Simple(string_key))],
2076            resolved_particle_elements: Vec::new(),
2077            redefine_original: None,
2078            redefine_requires_restriction_check: false,
2079        };
2080
2081        let group_key = schema_set.arenas.alloc_model_group(group_data);
2082
2083        // Resolve references - should preserve the pre-resolved type
2084        let result = resolve_all_references(&mut schema_set);
2085        assert!(result.is_ok());
2086
2087        // Verify the pre-resolved type was preserved
2088        let group = schema_set.arenas.model_groups.get(group_key).unwrap();
2089        assert_eq!(group.resolved_particles.len(), 1);
2090        match &group.resolved_particles[0] {
2091            ResolvedParticleTerm::Element {
2092                resolved_type: Some(TypeKey::Simple(key)),
2093                ..
2094            } => {
2095                assert_eq!(*key, string_key, "Inline-resolved type should be preserved");
2096            }
2097            other => panic!("Expected Element with pre-resolved type, got {:?}", other),
2098        }
2099    }
2100
2101    /// Helper: set up a SchemaSet with a default attribute group and a complex type.
2102    /// Returns (schema_set, complex_type_key, attribute_group_key).
2103    fn setup_default_attrs_test(
2104        default_attributes_apply: bool,
2105    ) -> (
2106        SchemaSet,
2107        crate::ids::ComplexTypeKey,
2108        crate::ids::AttributeGroupKey,
2109    ) {
2110        use crate::arenas::{AttributeGroupData, ComplexTypeDefData};
2111        use crate::namespace::QualifiedName;
2112        use crate::parser::frames::ComplexContentResult;
2113        use crate::parser::location::{SourceRef, SourceSpan};
2114        use crate::schema::model::{DerivationSet, SchemaDocument};
2115
2116        let mut schema_set = SchemaSet::new();
2117
2118        let group_name = schema_set.name_table.add("commonAttrs");
2119        let group_data = AttributeGroupData {
2120            name: Some(group_name),
2121            target_namespace: None,
2122            ref_name: None,
2123            attributes: Vec::new(),
2124            attribute_groups: Vec::new(),
2125            attribute_wildcard: None,
2126            id: None,
2127            annotation: None,
2128            source: None,
2129            resolved_ref: None,
2130            resolved_attribute_groups: Vec::new(),
2131            resolved_attributes: Vec::new(),
2132            redefine_original: None,
2133            redefine_requires_restriction_check: false,
2134        };
2135        let group_key = schema_set.arenas.alloc_attribute_group(group_data);
2136        schema_set
2137            .get_or_create_namespace(None)
2138            .register_attribute_group(group_name, group_key);
2139
2140        let doc_id = schema_set.documents.len() as u32;
2141        let mut doc = SchemaDocument::new(doc_id, "test.xsd".to_string());
2142        doc.default_attributes = Some(QualifiedName::local(group_name));
2143        schema_set.documents.push(doc);
2144
2145        let type_name = schema_set.name_table.add("myType");
2146        let ct_data = ComplexTypeDefData {
2147            name: Some(type_name),
2148            target_namespace: None,
2149            base_type: None,
2150            derivation_method: None,
2151            content: ComplexContentResult::Empty,
2152            open_content: None,
2153            attributes: Vec::new(),
2154            attribute_groups: Vec::new(),
2155            attribute_wildcard: None,
2156            mixed: false,
2157            is_abstract: false,
2158            final_derivation: DerivationSet::empty(),
2159            block: DerivationSet::empty(),
2160            default_attributes_apply,
2161            id: None,
2162            #[cfg(feature = "xsd11")]
2163            assertions: Vec::new(),
2164            #[cfg(feature = "xsd11")]
2165            xpath_default_namespace: None,
2166            annotation: None,
2167            source: Some(SourceRef::new(doc_id, SourceSpan::new(0, 0))),
2168            resolved_base_type: None,
2169            resolved_attribute_groups: Vec::new(),
2170            resolved_attributes: Vec::new(),
2171            resolved_content_particle_types: Vec::new(),
2172            resolved_content_particle_elements: Vec::new(),
2173            resolved_simple_content_type: None,
2174            redefine_original: None,
2175        };
2176        let ct_key = schema_set.arenas.alloc_complex_type(ct_data);
2177
2178        (schema_set, ct_key, group_key)
2179    }
2180
2181    #[test]
2182    fn test_resolve_default_attributes_injects_group() {
2183        let (mut schema_set, ct_key, group_key) = setup_default_attrs_test(true);
2184
2185        let result = resolve_all_references(&mut schema_set);
2186        assert!(result.is_ok(), "Resolution should succeed: {:?}", result);
2187
2188        let ct = schema_set.arenas.complex_types.get(ct_key).unwrap();
2189        assert!(
2190            ct.resolved_attribute_groups.contains(&group_key),
2191            "Default attribute group should be injected into resolved_attribute_groups"
2192        );
2193    }
2194
2195    #[test]
2196    fn test_resolve_default_attributes_opt_out() {
2197        let (mut schema_set, ct_key, _group_key) = setup_default_attrs_test(false);
2198
2199        let result = resolve_all_references(&mut schema_set);
2200        assert!(result.is_ok(), "Resolution should succeed: {:?}", result);
2201
2202        let ct = schema_set.arenas.complex_types.get(ct_key).unwrap();
2203        assert!(
2204            ct.resolved_attribute_groups.is_empty(),
2205            "Default attribute group should NOT be injected when defaultAttributesApply=false"
2206        );
2207    }
2208}