Why can a numeric literal never be of type xs:positiveInteger in XQuery?

236 Views Asked by At

I noticed subtle differences how XQuery implementations handle (sub-)types. Especially, handling of literal numbers as input to functions that have the accepted input type declared. I naively thought any number literal that is castable to that specific numeric type would be accepted.

declare function local:any ($n as xs:anyAtomic) { $n };
declare function local:decimal ($n as xs:decimal) { $n };
declare function local:integer ($n as xs:integer) { $n };
declare function local:pos-int ($n as xs:positiveInteger) { $n };

local:any(1), (: works :)
local:decimal(1), (: works :)
local:integer(1), (: works :)
local:pos-int(1)  (: throws in all tested implementations :)

exist-db allows xs:long,xs:int, ... Saxon does not.

I could not find any reason for that behaviour in the Xquery Spec 2.5.5 SequenceType Matching nor Xpath functions spec 1.6.3 Atomic Type Hierarchy

Could someone here shed some light on why Saxon 9.3.1 HE, BaseX 9.3.1 [Standalone] and eXist 5.3.0-SNAPSHOT behave like this?

Did I just miss the part in the spec where it is defined that a literal 1 is cast to xs:integer? xs:decimal as the topmost type would have made more sense, but if one subtype is allowed why not go all the way?

here is a live demo

3

There are 3 best solutions below

1
On BEST ANSWER

I think the spec in this area is very unfortunate, but it is clear: a value is an xs:positiveInteger only if it is labelled as such, not simply because it is (a) an integer and (b) positive. There were long discussions about this in the XQuery Working Group, involving some eminent experts on programming language type systems (like Phil Wadler), and that's the decision that was made. I didn't like it myself.

Where does the spec say this? The definitions in the XDM spec are a good start:

https://www.w3.org/TR/xpath-datamodel-31/#xs-types

[Definition: An atomic value is a value in the value space of an atomic type and is labeled with the name of that atomic type.]

[Definition: An atomic type is a primitive simple type or a type derived by restriction from another atomic type.] (Types derived by list or union are not atomic.)

[Definition: The primitive simple types are the types defined in 2.1.1 Types adopted from XML Schema.]

Then §3.1.1 in the XQuery spec talks about numeric literals:

The value of a numeric literal containing no "." and no e or E character is an atomic value of type xs:integer.

§3.18.1 gives the rules for the "instance of" operator:

The boolean operator instance of returns true if the value of its first operand matches the SequenceType in its second operand, according to the rules for SequenceType matching;

and §2.5.5.2 gives the relevant rule for SequenceType matching:

An ItemType consisting simply of an EQName is interpreted as an AtomicOrUnionType. The expected type AtomicOrUnionType matches an atomic value whose actual type is AT if derives-from( AT, AtomicOrUnionType ) is true.

Taken together, the effect is that the expression 3 instance of xs:positiveInteger returns false (because xs:integer is not derived from xs:positiveinteger).

Finally, when the expected type of a function argument is xs:positiveInteger, and the function call supplies the value 3, then the function conversion rules in §3.1.5.2 come into play. These allow various conversions from the supplied value to the required type, but "down-casting" from xs:integer to xs:positiveInteger is not one of them. So it's an error:

If, after the above conversions, the resulting value does not match the expected type according to the rules for SequenceType Matching, a type error is raised [err:XPTY0004].

As I say, I don't like the rules and have tried on numerous occasions to get them changed. But they are clear, and any product that doesn't follow them is non-conformant.

0
On

https://www.w3.org/TR/xpath-31/#promotion specifies which type promotions are allowed:

Numeric type promotion:

A value of type xs:float (or any type derived by restriction from xs:float) can be promoted to the type xs:double. The result is the xs:double value that is the same as the original value.

A value of type xs:decimal (or any type derived by restriction from xs:decimal) can be promoted to either of the types xs:float or xs:double. The result of this promotion is created by casting the original value to the required type. This kind of promotion may cause loss of precision.

For other types you have to use a constructor explicitly e.g. local:int(xs:int(1)).

3
On

You can pass a numeric literal 1 to the local:pos-int() function in MarkLogic:

declare function local:any($n as xs:anyAtomicType ) { $n };
declare function local:decimal($n as xs:decimal) { $n };
declare function local:integer($n as xs:integer) { $n };
declare function local:pos-int($n as xs:positiveInteger) { $n };

local:any(1), (: works :)
local:decimal(1), (: works :)
local:integer(1), (: works :)
local:pos-int(1)  (: works fine in MarkLogic :)

And you can use xdmp:type() to report that the value returned is of type positiveInteger

xquery version "1.0-ml";
declare function local:pos-int($n as xs:positiveInteger) { $n };
xdmp:type(local:pos-int(1))