Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1715 Drop array bound checking #1766

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 71 additions & 26 deletions specifications/xpath-functions-40/src/function-catalog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28331,8 +28331,6 @@ return document {
<fos:proto name="get" return-type="item()*">
<fos:arg name="array" type="array(*)" usage="inspection"/>
<fos:arg name="position" type="xs:integer"/>
<fos:arg name="fallback" type="(fn(xs:integer) as item()*)?"
default="fn($i) { fn:error(fn:QName('', 'FOAY0001')) }"/>
</fos:proto>
</fos:signatures>
<fos:properties>
Expand All @@ -28345,50 +28343,97 @@ return document {
(counting from 1).</p>
</fos:summary>
<fos:rules>
<p diff="chg" at="2022-12-16">Informally, the function returns the member at a specified position in the array.
<p>Informally, the function returns the member at a specified position in the array.
If <code>$position</code> is less than one or greater than <code>array:size($array)</code>,
then the <code>$fallback</code> function is called, supplying the value of <code>$position</code>
as the argument value; and the result of this call is returned.</p>
<p diff="chg" at="2022-12-16">The default <code>$fallback</code> function raises a dynamic error. The call on <function>fn:error</function>
shown as the default is for illustrative purposes only; apart from the error code (<code>err:FOAY0001</code>)
the details of the error (such as the error message) are <termref def="implementation-dependent">implementation-dependent</termref>.</p>

then the function returns an empty sequence.</p>
</fos:rules>
<fos:equivalent style="xpath-expression" covers-error-cases="false">
if ($position = (1 to array:size($array)))
then array:members($array)[$position] => map:get('value')
else $fallback($position)
<fos:equivalent style="xpath-expression" covers-error-cases="true">
array:members($array)[$position] ? value
</fos:equivalent>
<fos:errors>
<p><phrase diff="add" at="2022-12-16">In the absence of a <code>$fallback</code> function</phrase>,
a dynamic error occurs <errorref class="AY" code="0001"
/> if <code>$position</code> is not in the range <code>1 to
array:size($array)</code> inclusive.</p>
</fos:errors>
<fos:notes>
<p>Array bound checking is dropped in version 4.0 of the specification. If bound checking
is required, the new function <function>array:get-if-present</function> is available. This
delivers the same result as <function>array:get</function> if the index is within
bounds, but raises a dynamic error (<errorref class="AY" code="0001"/>) if not.</p>
</fos:notes>
<fos:examples>
<fos:example>
<fos:test>
<fos:expression>[ "a", "b", "c" ] => array:get(2)</fos:expression>
<fos:result>"b"</fos:result>
</fos:test>
<fos:test>
<fos:expression>[ "a", "b", "c" ] => array:get(0)</fos:expression>
<fos:result>()</fos:result>
</fos:test>
<fos:test>
<fos:expression>[ "a", "b", "c" ] => array:get(4)</fos:expression>
<fos:result>()</fos:result>
</fos:test>
<fos:test>
<fos:expression>[ "a", [ "b", "c" ] ] => array:get(2)</fos:expression>
<fos:result>[ "b", "c" ]</fos:result>
</fos:test>
</fos:example>
</fos:examples>
<fos:changes>
<fos:change issue="1715">
<p>No error is raised when the array index is out of bounds.
See also <function>array:get-if-present</function>.</p>
</fos:change>
</fos:changes>
</fos:function>

<fos:function name="get-if-present" prefix="array">
<fos:signatures>
<fos:proto name="get" return-type="item()*">
<fos:arg name="array" type="array(*)" usage="inspection"/>
<fos:arg name="position" type="xs:integer"/>
</fos:proto>
</fos:signatures>
<fos:properties>
<fos:property>deterministic</fos:property>
<fos:property>context-independent</fos:property>
<fos:property>focus-independent</fos:property>
</fos:properties>
<fos:summary>
<p>Returns the value at the specified position in the supplied array
(counting from 1), raising an error if the position is out of bounds.</p>
</fos:summary>
<fos:rules>
<p>Informally, the function returns the member at a specified position in the array.
If <code>$position</code> is less than one or greater than <code>array:size($array)</code>,
then the function raises an error.</p>
</fos:rules>
<fos:equivalent style="xpath-expression" covers-error-cases="false">
if ($position = (1 to array:size($array)))
then array:get($position)
else error(xs:QName("err:FOAY0001"))
</fos:equivalent>
<fos:notes>
<p>The <function>array:get</function> function no longer performs bound checking
in this version of the specification. If bound checking
is required, this new function can be used in its place.</p>
</fos:notes>
<fos:examples>
<fos:example>
<fos:test>
<fos:expression>[ "a", "b", "c" ] => array:get-if-present(2)</fos:expression>
<fos:result>"b"</fos:result>
</fos:test>
<fos:test>
<fos:expression>[ "a" ] => array:get(1, void#1)</fos:expression>
<fos:result>"a"</fos:result>
<fos:expression>[ "a", "b", "c" ] => array:get-if-present(0)</fos:expression>
<fos:error-result error-code="FOAY0001"/>
</fos:test>
<fos:test>
<fos:expression>[] => array:get(1, void#1)</fos:expression>
<fos:result>()</fos:result>
<fos:expression>[ "a", "b", "c" ] => array:get-if-present(4)</fos:expression>
<fos:error-result error-code="FOAY0001"/>
</fos:test>
</fos:example>
</fos:examples>
<fos:changes>
<fos:change>
<p>A third argument is added, allowing user control of how index-out-of-bounds
conditions should be handled.</p>
<fos:change issue="1715">
<p>New in 4.0</p>
</fos:change>
</fos:changes>
</fos:function>
Expand Down
5 changes: 4 additions & 1 deletion specifications/xpath-functions-40/src/xpath-functions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9416,7 +9416,7 @@ return <table>
<p>There is one exception to this rule: for convenience, the notation <code>[]</code> is used to represent
an empty array, in preference to a call on <code>dm:empty-array()</code>.</p>

<p>The formal equivalents are not intended to provide a realistic way of implementating the
<p>The formal equivalents are not intended to provide a realistic way of implementing the
functions (in particular, any real implementation might be expected to implement <function>array:get</function>
much more efficiently). They do, however, provide a framework that allows
the correctness of a practical implementation to be verified.</p>
Expand Down Expand Up @@ -9491,6 +9491,9 @@ return <table>
<div3 id="func-array-get">
<head><?function array:get?></head>
</div3>
<div3 id="func-array-get-if-present">
<head><?function array:get-if-present?></head>
</div3>
<div3 id="func-array-head">
<head><?function array:head?></head>
</div3>
Expand Down
40 changes: 23 additions & 17 deletions specifications/xquery-40/src/expressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19537,6 +19537,12 @@ processing with JSON processing.</p>
</div4>
<div4 id="id-array-lookup">
<head>Arrays as Functions</head>

<changes>
<change issue="1715">
No error is raised when an array index is out of bounds.
</change>
</changes>

<p>Arrays are <termref def="dt-function-item">function items</termref>,
and a <termref def="dt-dynamic-function-call"/> can be used to look up
Expand Down Expand Up @@ -19582,8 +19588,8 @@ processing with JSON processing.</p>
</item>
<item>
<p>
<code>array { "licorice", "ginger" }(20)</code> raises a dynamic error <xerrorref
spec="FO40" class="AY" code="0001"/>.</p>
<code>array { "licorice", "ginger" }(20)</code> returns <code>()</code>
because the array index is out of bounds.</p>
</item>

</ulist>
Expand Down Expand Up @@ -19624,8 +19630,14 @@ processing with JSON processing.</p>
they can be qualified with a type to select only the results that
match that type.-->
</change>

<change issue="1800 1845" PR="1817 1853" date="2025-03-04">An inline function may be annotated
as a <code>%method</code>, giving it access to its containing map.</change>

<change issue="1715">
No error is raised when an array index is out of bounds.
</change>

</changes>

<p>&language; provides two lookup operators <code>?</code> and <code>??</code>
Expand All @@ -19643,6 +19655,7 @@ processing with JSON processing.</p>
<div4 id="id-postfix-lookup">
<head>Postfix Lookup Expressions</head>


<scrap>
<prodrecap ref="LookupExpr"/>
</scrap>
Expand Down Expand Up @@ -19722,8 +19735,7 @@ processing with JSON processing.</p>

<p>Similarly, given <code>$M</code> as a map
<code>{ "X": ("a", "b"), "Y": ("c", "d"), "Z": ("e", "f") }</code>,
some example lookup expressions are as follows. Note that because maps are unordered,
the results are not necessarily in the order shown.</p>
some example lookup expressions are as follows.</p>

<table width="100%">
<caption>Example Lookup Expressions on a Map</caption>
Expand Down Expand Up @@ -19826,13 +19838,10 @@ processing with JSON processing.</p>
the result is the same as <code>$V?pairs::(1 to array:size($V))</code>:</p>

<note>
<p>Note that array items are returned in order.</p>
<p>Array items are returned in order.</p>
</note>
</item>
<!--<item>
<p>If <code>KS</code> is a <nt def="TypeQualifier">TypeQualifier</nt> <code>~T</code>,
the result is the same as <code>$V?pairs::*[?value instance of T]</code>.</p>
</item>-->

</olist>
</item>
<item><p>If <var>$V</var> is a <termref def="dt-singleton"/>
Expand Down Expand Up @@ -19867,10 +19876,7 @@ processing with JSON processing.</p>
of the map.</p>
</note>
</item>
<!--<item>
<p>If <code>KS</code> is a <nt def="TypeQualifier">TypeQualifier</nt> <code>~T</code>,
the result is the same as <code>$V?pairs::*[?value instance of T]</code>.</p>
</item>-->

</olist>
</item>
<item><p>Otherwise (that is, if <code>$V</code> is neither a map nor an array)
Expand Down Expand Up @@ -19950,8 +19956,8 @@ return if ($value instance of %method function(*))
</item>
<item>
<p>
<code>[ "a", "b" ]?3</code> raises a dynamic error <xerrorref spec="FO40"
class="AY" code="0001"/>
<code>[ "a", "b" ]?3</code> evaluates to the empty sequence, because the array
index is out of bounds.
</p>
</item>
</ulist>
Expand Down Expand Up @@ -20192,10 +20198,10 @@ declare function recursive-content($item as item()) as record(key, value)* {
</ulist>
</note>
<note>
<p>The effect of evaluating all shallow lookups on maps rather than arrays is that no error arises
<p>No error arises
if an array subscript is out of bounds. In the above example, <code>$value??3</code> would
return an empty sequence, it would not raise an error.</p>
<p>This also affects the way an <code>xs:untypedAtomic</code> key value is handled.
<p>Note the way in which an <code>xs:untypedAtomic</code> key value is handled.
Given the shallow lookup
expression <code>$A?$x</code>, if <code>$A</code> is an array and <code>$x</code>
(after atomization) is <code>xs:untypedAtomic</code> then the value of <code>$x</code>
Expand Down