fix(schema): reject xsi:type=anyType on an anySimpleType-declared element

cvc-elt.4.3.2.1 requires an xsi:type to name a type DERIVED from the element's
declared type. anyType is the ur-supertype of every type, so it is never derived
from anySimpleType (its own subtype); an anyType override on an anySimpleType
element is therefore invalid. The general not-derived check stays silent on
ur-type declared types to avoid false positives, so this adds the narrow,
provably-invalid anyType-for-anySimpleType pairing only, leaving anyType valid on
an anyType-declared element (which resolves to a complex ur-type and is excluded).

XSTS invalid-instances-accepted 30 -> 29 (stZ056), no other bucket moved
(valid-schemas-rejected and valid-instances-rejected held at 0 across all 14383
groups). 1450 unit tests pass.
This commit is contained in:
Mihaela Mihaljevic 2026-06-20 01:48:43 +02:00
parent 01650a454e
commit 6927eb26f1
5 changed files with 74 additions and 8 deletions

View File

@ -28,6 +28,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- `xsi:type` naming `anyType` is now rejected on an element whose declared type is
`anySimpleType` (cvc-elt.4.3.2.1). `anyType` is the ur-supertype of every type,
so it is never validly *derived* from `anySimpleType` (its own subtype). The
general not-derived check stays silent on ur-type declared types to avoid false
positives; this adds only the provably-invalid `anyType`-for-`anySimpleType`
pairing, leaving `anyType` valid on an `anyType`-declared element. XSTS
invalid-instances-accepted 30 -> 29 (`stZ056`), no other bucket moved.
- `dateTime`, `date`, `gYearMonth`, and `gYear` now reject the year `0000`. XSD
1.0 has no year zero (the year before `0001` is `-0001`), so `0000-01-01T00:00:00`
and the other `0000` forms are not valid values. `DateTimeParser.parseYearOnly`

View File

@ -16,6 +16,7 @@ extension PureXML.Schema.ComplexValidator {
) -> PureXML.Validation.ValidationError? {
if let blocked = blockedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return blocked }
if let notDerived = notDerivedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return notDerived }
if let anyTypeSub = anyTypeForAnySimpleTypeError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return anyTypeSub }
if let listUnion = listUnionSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return listUnion }
if let abstractType = abstractXsiTypeError(child: child, at: path, namespaceBindings: namespaceBindings) { return abstractType }
if case let .typeReference(name) = declared { return abstractTypeError(named: name, child: child, at: path) }
@ -66,6 +67,42 @@ extension PureXML.Schema.ComplexValidator {
)
}
/// cvc-elt.4.3.2.1 for an `anyType` `xsi:type`: `anyType` is the ur-supertype of
/// every type, so it is never DERIVED from `anySimpleType` (its own subtype). An
/// `anyType` override on an `anySimpleType`-declared element is therefore invalid.
/// The general not-derived check stays silent on ur-type declared types to avoid
/// false positives; this rejects only the provably-invalid anyType-for-anySimpleType
/// pairing. `anyType` still validly substitutes for an `anyType`-declared element,
/// which `declaredIsAnySimpleType` excludes (that resolves to a complex ur-type).
func anyTypeForAnySimpleTypeError(
declared: PureXML.Schema.ElementType,
child: PureXML.Model.Element,
at path: [PureXML.Validation.PathKey],
namespaceBindings: [String: String],
) -> PureXML.Validation.ValidationError? {
guard let label = Self.xsiTypeName(child),
let reference = Self.xsiTypeReference(child, namespaceBindings: namespaceBindings),
case let .complex(substitute)? = Self.resolveNamedType(reference, in: types),
Self.isUrType(substitute),
declaredIsAnySimpleType(declared)
else { return nil }
return PureXML.Validation.ValidationError(
reason: "xsi:type '\(label)' is not validly derived from the anySimpleType declared type of '\(child.name.localName)'",
at: path,
)
}
/// Whether `declared` resolves to the simple ur-type `anySimpleType` itself, not
/// `anyType` (a complex ur-type), a list/union, or any more specific type.
private func declaredIsAnySimpleType(_ declared: PureXML.Schema.ElementType) -> Bool {
let resolved: PureXML.Schema.ElementType? = switch declared {
case .simple, .complex: declared
case let .typeReference(reference): resolvedDeclaredType(reference)
}
if case let .simple(type)? = resolved { return type.isAnySimpleType }
return false
}
/// Whether a list or union substitute may validly stand in for `declared`: only
/// the ur-types (`anySimpleType`/`anyType`) admit one. A declared type that is
/// itself a list or union, or an unresolvable reference, is also treated as

View File

@ -113,4 +113,25 @@ struct SchemaListUnionXsiTypeTests {
// Drr restricts Dr: validly derived, accepted.
#expect(try schema.validate(doc("Drr")).isEmpty)
}
@Test("xsi:type=anyType cannot substitute for an anySimpleType-declared element")
func test_anyTypeForAnySimpleType() throws {
let xsi = "http://www.w3.org/2001/XMLSchema-instance"
let schema = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="item" type="xs:anySimpleType"/>
<xs:element name="root">
<xs:complexType><xs:sequence><xs:element ref="item" maxOccurs="unbounded"/></xs:sequence></xs:complexType>
</xs:element>
</xs:schema>
""")
func root(_ type: String, _ value: String) -> String {
#"<root xmlns:xsi="\#(xsi)" xmlns:xs="http://www.w3.org/2001/XMLSchema"><item xsi:type="\#(type)">\#(value)</item></root>"#
}
// anyType is anySimpleType's supertype, not derived from it: rejected, for that reason.
#expect(try schema.validate(root("xs:anyType", "x")).contains { $0.reason.contains("anySimpleType declared type") })
// anySimpleType (reflexive) and a derived atomic type are valid substitutes.
#expect(try schema.validate(root("xs:anySimpleType", "x")).isEmpty)
#expect(try schema.validate(root("xs:int", "123")).isEmpty)
}
}

View File

@ -150,13 +150,13 @@ struct XSTSSuiteTests {
private let knownSchemaValidRejected = 0
private let knownSchemaInvalidAccepted = 43
private let knownInstanceValidRejected = 0
/// Instance bucket (133 -> 30), per-step deltas in CHANGELOG.md: xsi:type must derive from the
/// declared type (complex, then atomic and list/union, resolved through the element-ref chain);
/// block/blockDefault (incl. "substitution" and an untyped head) bar substitutions after the
/// derivation backbone was re-keyed by namespace; ur-type `xsd:anyType` wildcards are `lax`;
/// identity fields compare in the decimal value space and take a declared default/fixed when
/// absent/empty; an attribute wildcard is the INTERSECTION of its sources; year 0000 is invalid.
private let knownInstanceInvalidAccepted = 30
/// Instance bucket (133 -> 29), per-step deltas in CHANGELOG.md: xsi:type must derive from the
/// declared type (complex/atomic/list/union via the element-ref chain), and anyType cannot stand
/// in for an anySimpleType element (its supertype); block/blockDefault bar substitutions on the
/// namespace-keyed backbone; ur-type anyType wildcards are `lax`; identity fields compare in
/// decimal value space and take a declared default/fixed when absent/empty; an attribute wildcard
/// is the INTERSECTION of its sources; dateTime/date/gYear* reject year 0000.
private let knownInstanceInvalidAccepted = 29
@Test("Every XSTS case behaves: compile, reject, validate, invalidate")
func test_suite() throws {

View File

@ -101,7 +101,7 @@ classification is wrong.
| Known particle under-rejection fallout | `particlesZ001/particlesZ001.i` | `ParticleRestriction` |
| Regex instance edges | `reT17/reT17.i`, `reT38/reT38.i`, `RegexTest_422/RegexTest_422.i`, `RegexTest_430/RegexTest_430.i`, `reZ006i/reZ006i.i` | `Sources/Regex/*`, `SimpleType` |
| Schema composition instance fallout | `schA2/schA2.i`, `schA5/schA5.i`, `schA7/schA7.i`, `schU3/schU3.i`, `schU4/schU4.i`, `schU5/schU5.i` | schema composition and instance validator |
| Simple-type instance edge | `stZ056/stZ056.i` | `SimpleType` |
| Simple-type instance edge | ~~`stZ056/stZ056.i`~~ closed: `xsi:type=anyType` cannot substitute for an `anySimpleType` element (its supertype) | `ComplexValidatorDerivation` |
| Wildcard instance matching | `wildZ013a/wildZ013a.i`, `wildZ013d/wildZ013d.i` | `ContentMatcher`, wildcard declaration lookup |
## Tasks (ordered)