Skip to content

Commit 5311fdb

Browse files
Esteban Zimanyiestebanzimanyi
authored andcommitted
Give every array of a run the one length that follows it
Arrays read in parallel are declared together and counted once, so the length belongs to all of them. `inputArrays` names only the array the count sits beside, which leaves nine arrays across seven functions with no stated length: `jsonb_make_two_arg(text **keys, text **values, int count)` pairs the two element by element and the entry names `values` alone, `tpointseq_make_coords` reads four coordinate arrays and the entry names `times`, and the h3 and quadbin sequence constructors each read a value array beside their timestamps. A binding meets the unnamed ones as bare pointers it cannot size. The inference walks a RUN of array parameters to the first parameter that is not one, and where that parameter is a by-value integer every array of the run takes its length from it. Where a family counts each array separately the run is one long and this says what it always said: `edwithin_tgeoarr_tgeoarr(arr1, count1, arr2, count2, …)` keeps `arr1` on `count1`, which is the case the suite states beside the run itself. The catalog carries 167 input arrays over 158, the nine gained belonging to those seven functions, and no entry loses the length it had.
1 parent e6ddac7 commit 5311fdb

3 files changed

Lines changed: 80 additions & 25 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
# carries, or a change to them is not exercised until after it merges.
9797
# Consumers use the action; this repository owns the rules.
9898
- name: Refuse a skip, and a suite that shrank
99-
run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 315
99+
run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 317
100100

101101
# The rules earn their place by refusing a log that carries what they
102102
# name. Both fixtures are written here rather than tracked, and the

parser/shapeinfer.py

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,40 +110,57 @@ def _input_arrays(func: dict) -> list:
110110
length from.
111111
112112
An input array is a parameter that is an array of pointers (``TYPE **``) or
113-
of by-value scalars (``uint8_t *``, ``int64_t *``), immediately followed by
114-
a by-value integer. That the length is by VALUE is what tells an argument
115-
apart from a written-back out-array, whose length is by POINTER — the same
116-
distinction this module already reads in the other direction.
113+
of by-value scalars (``uint8_t *``, ``int64_t *``), followed by a by-value
114+
integer. That the length is by VALUE is what tells an argument apart from
115+
a written-back out-array, whose length is by POINTER — the same distinction
116+
this module already reads in the other direction.
117117
118118
Without it a binding matches the LENGTH PARAMETER'S NAME, and the names
119119
disagree: ``count``, ``size``, ``ngeoms``, ``keys_len``, ``path_len``,
120120
``pixels_size``, ``wkb_size``, ``count1``. Every one of them is a length,
121121
and a binding that knows only some of them silently drops the rest.
122+
123+
A RUN of arrays shares the one length that follows it. Arrays read in
124+
parallel are declared together and counted once — ``jsonb_make_two_arg(text
125+
**keys, text **values, int count)`` pairs the two element by element, and
126+
``tpointseq_make_coords`` reads four — so the length belongs to every array
127+
of the run, not only to the one the count happens to sit beside. Where a
128+
family counts each array separately the run is one long and this says what
129+
it always said: ``edwithin_tgeoarr_tgeoarr(arr1, count1, arr2, count2, …)``
130+
keeps ``arr1`` on ``count1``.
122131
"""
123132
params = func.get("params", [])
124-
out = []
125-
for i, prm in enumerate(params[:-1]):
133+
134+
def is_array(prm) -> bool:
126135
ctype = _bare(prm.get("cType"))
127-
if _bare(params[i + 1].get("cType")) not in _LENGTH_TYPES:
128-
continue
129136
if ctype.endswith("**"):
130-
if ctype in ("char **", "void **"):
131-
continue
132-
elif not (ctype.endswith("*")
133-
and ctype[:-1].strip() in _ELEMENT_SCALARS):
137+
return ctype not in ("char **", "void **")
138+
return ctype.endswith("*") and ctype[:-1].strip() in _ELEMENT_SCALARS
139+
140+
out = []
141+
start = 0
142+
while start < len(params):
143+
if not is_array(params[start]):
144+
start += 1
134145
continue
135-
out.append({
136-
"param": prm["name"],
137-
"lengthFrom": {"kind": "param", "name": params[i + 1]["name"]},
138-
# The element reads as the return's does — the type with one
139-
# pointer level off and no `const`, which belongs to the argument
140-
# rather than to the element type a binding marshals.
141-
"element": {
142-
"c": _strip_one_ptr(_bare(prm.get("cType"))),
143-
"canonical": _strip_one_ptr(
144-
_bare(prm.get("canonical") or prm.get("cType"))),
145-
},
146-
})
146+
end = start
147+
while end < len(params) and is_array(params[end]):
148+
end += 1
149+
if end < len(params) and _bare(params[end].get("cType")) in _LENGTH_TYPES:
150+
for prm in params[start:end]:
151+
out.append({
152+
"param": prm["name"],
153+
"lengthFrom": {"kind": "param", "name": params[end]["name"]},
154+
# The element reads as the return's does — the type with one
155+
# pointer level off and no `const`, which belongs to the
156+
# argument rather than to the element type a binding marshals.
157+
"element": {
158+
"c": _strip_one_ptr(_bare(prm.get("cType"))),
159+
"canonical": _strip_one_ptr(
160+
_bare(prm.get("canonical") or prm.get("cType"))),
161+
},
162+
})
163+
start = end
147164
return out
148165

149166

tests/test_shapeinfer.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,44 @@ def test_a_value_beside_a_number_is_not_an_array(self):
162162
for f in idl["functions"]:
163163
self.assertNotIn("inputArrays", f.get("shape", {}))
164164

165+
def test_a_run_of_arrays_shares_the_length_that_follows_it(self):
166+
# `jsonb_make_two_arg(text **keys, text **values, int count)` pairs the
167+
# two arrays element by element and counts them once, so `count` is the
168+
# length of BOTH; reading only the array it sits beside drops the other.
169+
idl = {"functions": [_fn(
170+
"jsonb_make_two_arg", "Jsonb *",
171+
[("keys", "text **"), ("values", "text **"), ("count", "int")]),
172+
_fn("tpointseq_make_coords", "TSequence *",
173+
[("xcoords", "const double *"), ("ycoords", "const double *"),
174+
("zcoords", "const double *"), ("times", "const TimestampTz *"),
175+
("count", "int"), ("srid", "int32_t")])]}
176+
idl, stats = infer_shapes(idl)
177+
self.assertEqual(
178+
[(a["param"], a["lengthFrom"]["name"])
179+
for a in idl["functions"][0]["shape"]["inputArrays"]],
180+
[("keys", "count"), ("values", "count")])
181+
self.assertEqual(
182+
[(a["param"], a["lengthFrom"]["name"])
183+
for a in idl["functions"][1]["shape"]["inputArrays"]],
184+
[("xcoords", "count"), ("ycoords", "count"),
185+
("zcoords", "count"), ("times", "count")])
186+
self.assertEqual(stats["inputArrays"], 6)
187+
188+
def test_an_array_counted_on_its_own_keeps_its_own_length(self):
189+
# The counter-case the run rule must not swallow: a family that counts
190+
# each array separately declares each one beside ITS length, so every
191+
# run is one long and each array keeps the count it is declared with.
192+
idl = {"functions": [_fn(
193+
"edwithin_tgeoarr_tgeoarr", "int *",
194+
[("arr1", "const Temporal **"), ("count1", "int"),
195+
("arr2", "const Temporal **"), ("count2", "int"),
196+
("dist", "double"), ("count", "int *")])]}
197+
idl, _ = infer_shapes(idl)
198+
self.assertEqual(
199+
[(a["param"], a["lengthFrom"]["name"])
200+
for a in idl["functions"][0]["shape"]["inputArrays"]],
201+
[("arr1", "count1"), ("arr2", "count2")])
202+
165203
def test_an_out_array_is_not_read_as_an_input_one(self):
166204
# `temporal_time_split(temp, ..., TimestampTz **bins, int *count)` writes
167205
# `bins` back, and its length being BY POINTER is what says so.

0 commit comments

Comments
 (0)