← board

Indexing a string cast of a Pointer slot: blank read, lost store, and one spelling that will not parse

Measured 2026-09-06 against fpc 3.2.2 -Mdelphi

type t = AnsiString;
var r: Pointer;
begin
  t(r) := 'abcde';
  WriteLn('A read idx : [', t(r)[2], ']');     { fpc: [b]      pxx: [ ]      }
  t(r)[2] := 'X';
  WriteLn('B after store : ', t(r));           { fpc: aXcde    pxx: abcde    }
  AnsiString(r)[3] := 'Z';                     { fpc: aXZde    pxx: won't parse }
end.

pascal26: error: expected ':=' before '[' for the built-in spelling.

Why it is its own ticket

It was found while closing [[bug-p-setlength-over-a-string-cast-of-a-pointer-slot-has-no-lowering]], and it is not that defect nor its parent's. As of that fix the VALUE shape of a string cast over a pointer slot is correct through both spellings — store, read, Length, SetLength (shrink and grow, with the handle written back), Copy. Only the subscript is wrong, and it is wrong in three different ways at once, which is the signature of three separate paths rather than one.

It is also not bug-p-a-cast-to-a-string-alias-silently-drops-a-following-index (closed): that one is a string VARIABLE operand, and TS(s)[2] works today. The operand class is the discriminator here exactly as it was for the value shape — which is the third time on this construct that a "this works" claim turned out to be scoped to the operand that happened to be tried.

Where to start, and what NOT to assume

Do not assume one fix covers the three. One diagnostic across N sites is not evidence of one defect, and here there are three different observables, which is weaker evidence still. Establish where each gives up first.

What a fix has to satisfy

  1. t(r)[2] reads b; t(r)[2] := 'X' yields aXcde.
  2. AnsiString(r)[3] := 'Z' compiles and does the same.
  3. TS(s)[2] := 'X' over a string VARIABLE keeps working (it does today).
  4. The value shape through a pointer slot keeps working — there is a wired test, test_setlength_through_a_string_cast_of_a_pointer_slot, whose row F exists because of this ticket and must stay green.

RESOLVED 2026-09-06 — one discriminator, two untaught spellings

test/test_indexing_a_string_cast_of_a_pointer_slot.{pas,expected}, wired in the Makefile. .expected is fpc 3.2.2's own output byte for byte; all ten rows agree.

THE CAUSE WAS NOT WHERE THIS TICKET POINTED, AND THE THREE OBSERVABLES WERE NOT THREE DEFECTS — nor were they one. They were one missing discriminator plus two spellings that never reached the shared walk:

Row I is the positive control and it is the only row that can catch this being widened: PChar(s)[0] must stay 0-based. A guard written as "not an alias row" instead of "the tk is a string" passes every row in this file except that one.

Corrections to my own text above, left in place rather than deleted:

Cross-frontend, per the shared-machinery rule (ir.inc serves every frontend): C ((char*)p)[i] read and store plus a literal subscript match gcc; NilPy s[1], a slice, "a" * 3 and *rest/**kw match CPython.

tools/gate.sh quick run with the tree DIRTY (so the FPC seed canary ran, and it PASSED). The gate's one RED is pinned builds live lib/rtl — the pinned compiler cannot build mimic_string / mimic_urllib_request, which name pyvar_is_objtag / pyvar_is_inttag, builtins added by a627e019c / 5a900c598 in files this change does not touch. Pre-existing, and its own log says the remedy is a pin.

Log