A Char-array ROW of a 2-D array is not a string
Residual of [[bug-p-a-char-array-through-a-field-or-a-deref-is-not-a-string]], named at close rather than found separately. Nothing measured wants this — it is here so the exculpation has an owner, not because a program hit it.
The one shape still refused
var a: array[0..2] of array[0..15] of Char;
begin
a[0] := 'hi'; { error: incompatible types: cannot assign ShortString to Char }
end.
Six of seven lvalue shapes work as of the parent fix (plain variable, record field, nested field, a field of a record array element, pointer deref, and the read direction of each). This is the seventh.
Why it was left out rather than forgotten
ASTCharArrayCap answers "the element count if this node denotes a STATIC Char
array". For the three shapes it now handles, the extent is a single recorded
number — Syms[].ArrLen, RecFieldArrLen, DerefPtrArrayInfo's flatCount.
For an AN_INDEX row it is not: the capacity of a[0] is the INNER dimension's
span, and the node's own tag carries neither. Getting it right means resolving
the base's dimension table and asking for span d=1, which is a different
derivation from the other three and would want its own measurement — including
the array[0..2, 0..15] of Char spelling, which may or may not land in the
same node shape.
The parent fix deliberately returns -1 for every multi-dimensional case in all three arms rather than guessing an extent, because a WRONG capacity here is a buffer write past the row, and the current behaviour is a loud refusal.
Do not confuse this with
array[0..15] of Char reached through an index — ra[1].a := 'q', where the
INDEX is on the record and the field is the Char array — which works and is
asserted by test/test_char_array_field_is_a_string.pas (elem-field).
Resolved
The ticket's own ranking was the thing that was wrong
prio: 25, and the body said "nothing measured wants this -- it is here so the
exculpation has an owner, not because a program hit it." Both true of the shape
that was reported. But the reported shape is the STORE, and the store is the arm
that FAILS LOUDLY. The load direction of the same predicate was never probed:
var a: array[0..2, 0..3] of Char; s: ShortString;
...
s := a[1]; { 'x' -- one character. fpc gives 'xyz!'. exit 0. }
writeln(a[2]); { the same, silently }
An AN_INDEX node's own ASTTk is the ELEMENT's kind, so a partial row read as
one Char everywhere, with no diagnostic. That is a wrong VALUE in real
compiling code, which the goal doc ranks as a bug outright, and it was sitting
under a prio that said nobody wants this. The refusal is what got reported
because a refusal is what you can see -- the same asymmetry the parent ticket
found between five refused lvalue shapes and one silently-unchecked one.
The fix
ASTCharArrayCap grew a fourth arm. The extent is not a single recorded number
the way Syms[].ArrLen, RecFieldArrLen and DerefPtrArrayInfo's flatCount
are -- which is the reason the parent fix left it out -- but it is not derived
here either: NDRowSourceInfo already answers how many elements does this
partial index name, and of what, and it walks IDENT, FIELD and DEREF bases. The
arm asks it. No fifth copy of the layout.
Addressing needed nothing, and that was measured rather than assumed:
BuildPartialNDRowIndex already scales the leading subscript to the row's
first-element flat index, so @a[1] was the row base at HEAD. What was missing
was only the ANSWER to the capacity question -- which is why both directions
broke at once, and only one of them loudly.
Exactly one dimension must remain
a[0] on a 3-D array names a 2-D sub-array and fpc refuses a string there.
NDRowSourceInfo would return the PRODUCT of the trailing dimensions, which is
a plausible number and the wrong one: a capacity that spans rows is a write past
the row -- the risk the parent fix refused to guess at. ASTNDRowSubs[node] = NDInfoNDims - 1 separates them, and
test_char_array_3d_row_not_a_string_fail.pas is the only test that fails if it
is dropped, because every positive row is 2-D, where one subscript already IS
all-but-one.
The DEREF base is excluded on purpose
Measured: q^[1] over ^array[0..2, 0..5] of Char STORES correctly and LOADS
three characters of six, while q^[0] loads all six. At HEAD both loaded ONE.
Admitting it would trade a wrong answer for a MORE PLAUSIBLE wrong answer.
Filed with the measurement as
[[bug-p-a-char-array-row-through-a-pointer-deref-loads-short]].