Skip to content

feat: infer child and attribute steps from schema content models - #1890

Draft
CaiJimmy wants to merge 4 commits into
jimmy/schema-static-typesfrom
jimmy/schema-path-steps
Draft

CaiJimmy wants to merge 4 commits into
jimmy/schema-static-typesfrom
jimmy/schema-path-steps

Conversation

@CaiJimmy

@CaiJimmy CaiJimmy commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Child and attribute steps from a schema-typed element or document now infer the nodes that the schema declares, instead of element()* or attribute()*.

Based on #1889, which gives validate results and typed node tests their schema types.

Examples

From static-typing/schema-path-steps/, where $order := validate { <p:order>...</p:order> } (types from PathSteps.xsd):

Expression Inferred type Why
$order/p:item element(p:item, p:Item)* members of the substitution group, such as p:special, can take its place
$order/p:item/p:price element(p:price, xs:decimal)* so sum(...) is xs:decimal
$order/p:item/@id attribute(id, xs:integer)* a required attribute
$item/@note attribute(note, xs:string)? an optional attribute
$order/p:paid element(p:paid, xs:boolean)? a choice between p:paid and p:due
$order/p:customer/p:extra element(p:extra, xs:integer)? only the derived p:Extended declares it, which an instance may select with xsi:type
$order/p:item/* (element(p:price, xs:decimal) | element(p:tag, xs:string))* different declarations stay a union
$order/*/p:name element(p:name, xs:string)* steps from a union still use the schema
$order/p:meta/* element()* an xs:any wildcard may match any element from another namespace
$library/l:library/l:book element(l:book, <anonymous>)* a document matching document-node(E) has one child, which matches E

A step that the schema rules out, such as $order/p:missing or $order/p:meta/p:x (the wildcard is ##other), is statically empty and raises XPST0005, like other empty steps.

How it works

XmlSchemaCatalog.getStepType resolves the context's schema type, including anonymous ones, and walks the content model of the type and of every type derived from it:

  • Element declarations count once each, members of their substitution group included and abstract declarations excluded.
  • A sequence adds the counts of its particles, a choice takes the range over its branches, and minOccurs/maxOccurs multiply. The resulting bounds become the step's cardinality.
  • An attribute is exactly one if required, otherwise optional.
  • A nillable context element adds the empty sequence for children, since a nilled element has none.
  • Element declarations become typed node types with their typed value, so atomizing the result uses the schema too (sum($order/p:item/p:price)).

If the context is a union, every member must be schema-typed, and the selections of the members are combined.

Where it falls back

The step keeps the previous inference when the schema does not describe every node it can select:

  • a wildcard in the content model that allows the name,
  • an xs:anyType context,
  • @*, since instances also carry undeclared xsi: attributes,
  • partial wildcards (p:*, *:name) and typed tests (element(n, T), schema-element(E)),
  • other axes, which schema-descendant-steps covers for descendants.

Anonymous schema types now display as <anonymous>, for example element(p:meta, <anonymous>), instead of their internal names.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

QT3 Test Results - Parser: jsoniq

Regression baseline: run 37854736992

🎉 No regressions found for this build.

✅ Improvement summary: 12 test(s) previously failing, errored, or skipped now pass.

Suite Test file Test case
fn fn/avg.xml:K-SeqAVGFunc-31
fn fn/avg.xml:K-SeqAVGFunc-32
fn fn/avg.xml:K-SeqAVGFunc-34
fn fn/avg.xml:fn-avg-mix-args-029
fn fn/avg.xml:fn-avg-mix-args-030
fn fn/avg.xml:fn-avg-mix-args-031
fn fn/sum.xml:K-SeqSUMFunc-23
fn fn/sum.xml:K-SeqSUMFunc-24
fn fn/sum.xml:fn-sum-10
fn fn/sum.xml:fn-sum-9
prod prod/SwitchExpr.xml:switch-901
prod prod/SwitchExpr.xml:switch-902
Summary of passed tests for jsoniq
Test Suite Passing Failing Errors Skipped Missing features Total
app 2056 27 69 5 5 2157
array 208 0 1 0 0 209
fn 10321 258 169 387 315 11135
map 220 0 0 0 0 220
math 149 0 0 0 0 149
misc 765 34 20 52 18 871
op 4351 3 12 0 0 4366
prod 11507 326 166 208 7 12207
ser 332 11 0 0 0 343
xs 155 0 0 9 0 164
Total 30064 659 437 661 345 31821

Full analysis report: see analysis-jsoniq in artifacts.

Download detailed test results

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

QT3 Test Results - Parser: xquery

Regression baseline: run 37854736992

🎉 No regressions found for this build.

✅ Improvement summary: 12 test(s) previously failing, errored, or skipped now pass.

Suite Test file Test case
fn fn/avg.xml:K-SeqAVGFunc-31
fn fn/avg.xml:K-SeqAVGFunc-32
fn fn/avg.xml:K-SeqAVGFunc-34
fn fn/avg.xml:fn-avg-mix-args-029
fn fn/avg.xml:fn-avg-mix-args-030
fn fn/avg.xml:fn-avg-mix-args-031
fn fn/sum.xml:K-SeqSUMFunc-23
fn fn/sum.xml:K-SeqSUMFunc-24
fn fn/sum.xml:fn-sum-10
fn fn/sum.xml:fn-sum-9
prod prod/SwitchExpr.xml:switch-901
prod prod/SwitchExpr.xml:switch-902
Summary of passed tests for xquery
Test Suite Passing Failing Errors Skipped Missing features Total
app 2063 26 63 5 5 2157
array 209 0 0 0 0 209
fn 10343 237 168 387 315 11135
map 220 0 0 0 0 220
math 149 0 0 0 0 149
misc 769 30 20 52 18 871
op 4351 3 12 0 0 4366
prod 11590 277 132 208 7 12207
ser 343 0 0 0 0 343
xs 155 0 0 9 0 164
Total 30192 573 395 661 345 31821

Full analysis report: see analysis-xquery in artifacts.

Download detailed test results

A step that selects nodes of several declarations widened them to element()
or element(name), which dropped their schema types. Keep them as an inferred
union instead, as comma expressions and conditionals do, and let steps from a
union use the schema when every member is schema-typed.
Fill a reverse map when nameOf names an anonymous type, instead of scanning
every named anonymous type on each lookup.
The exclusion depends on how Xerces stores ##other, as a not-list with the
target namespace and no namespace.
@CaiJimmy
CaiJimmy force-pushed the jimmy/schema-static-types branch from 8f11125 to 8022463 Compare October 12, 2026 15:20
@CaiJimmy
CaiJimmy force-pushed the jimmy/schema-path-steps branch from 53a09b9 to e48d6a6 Compare October 12, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant