← board

A Char array through a FIELD or a DEREF is not a string

Found by attempting the target: three lib-test lib_synapse reds were wired to [[umbrella-one-full-tier-run-with-no-red-tier]] as unreproducible because external/synapse is absent on plexus. Fetched it; all three reproduce, and all three are one construct.

The construct

external/synapse/ssfpc.inc, inside WSAStartup's with WSData do:

szDescription  := 'Synsock - Synapse Platform Independent Socket Layer';
szSystemStatus := 'Running on Unix/Linux by FreePascal';

Both fields are array[0..N] of Char in TWSAData. Two lines, two errors, and the error is the same one in all three jobs:

pascal26:0: error: incompatible types: cannot assign ShortString to Char

Root cause — one oracle, one node kind

ASTCharArrayCap (pasparser_lval.inc) is what every consumer of "a static array of Char IS a string" asks: the AN_ASSIGN arm in ir.inc for the store, WrapCharArrayToStringExpr for the read, the argument coercion, the comparison. Its header said "the element count if node denotes a STATIC Char array". Its body said:

if ASTKind[node] <> AN_IDENT then Exit;

A field, an element or a deref answered -1, the conversion never fired, and the assignment fell through to AssignSideKind's AN_FIELD arm — which reads ASTTk, and ASTTk on an array field is the ELEMENT kind. So the destination typed as Char, the source as ShortString, and the check refused it. The check was right; it was answering the question it was given.

Measured before the fix, seven lvalue shapes:

shape before after
a := 'hi' (plain var) ok ok
r.a := 'hi' (field) refused ok
r.i.a := 'hi' (nested field) refused ok
ra[1].a := 'hi' (field of an array element) refused ok
p^ := 'hi' (deref) refused ok
r.a := s (field, from a variable) refused ok
a[0] := 'hi' (row of a 2-D array) refused refused — [[bug-p-a-char-array-row-of-a-2d-array-is-not-a-string]]

Five of six, and the exact mirror of the defect AssignSideKind's own header records, where five of six lvalue shapes were UNCHECKED and a string handle was stored over a record. Same function, same six shapes, opposite sign. A predicate that answers about an IDENT while its name says NODE is wrong in whichever direction its caller happens to face — and both faces have now been paid for.

The fix

ASTCharArrayCap learns AN_FIELD and AN_DEREF, through the accessors that already know each shape (RecFieldIsArray/ArrLen/DynDepth/ArrNDims/ Type, and IsDerefPtrArray/DerefPtrArrayInfo) rather than by re-deriving the layout. IsNodeArray is the sibling predicate and asks exactly these three node kinds for exactly this reason — its own header names "writing to one was rejected as cannot assign ShortString to Char" as one of four faces of one wrong answer. This is a fifth face of the same one.

Multi-dimensional returns -1 in every arm rather than guessing an extent: a wrong capacity here is a write past the row, and a loud refusal is the better failure.

Verification

What this does NOT close

The three regression-lib-test-lib-synapse-* jobs build with $(PXX_STABLE), and the pinned compiler still has the bug. They stay RED until the owner takes the next pin, and no amount of work in this tree changes that. Stated on each of the three tickets.

Log