← board

A frozen-string concat operand becomes pointer arithmetic

function P: shortstring;
begin P := 'ab'; end;
var v: shortstring; a: ansistring;
begin
  v := 'ab';
  writeln(v + '!');    { FPC ab!   pxx ab!    — the VARIABLE path was fine }
  writeln(P + '!');    { FPC ab!   pxx 4304129              }
  a := P + '!';
  writeln(a);          { FPC ab!   pxx <empty>              }
end.

Root cause

The same one: StrValTk normalises a frozen string's storage kind (tyShortString / tyFixedString) to a tyString value at every symbol read, but a call node carries Ord(Procs[pi].RetType) — the storage kind — verbatim at some 50 sites. So the operand reached the additive arms as tyShortString, missed the concat arm (which tests tyString / tyAnsiString / tyChar) and was claimed by the pointer-arithmetic arm below it. No diagnostic in either direction.

Fix

compiler/pasparser_expr.inc — normalise the operand, immediately before the AN_BINOP node is allocated, for every frozen kind that is not already tyString. Excluded in C mode, where p + 'x' genuinely is pointer arithmetic.

This is the shape the two wraps directly above it already use, and their comment is the argument for putting it here: "Normalising HERE rather than adding a third arm below is the point: every downstream arm then sees two string operands and needs to know nothing about PChar." Storage kind and capacity live on the symbol, not on the value, so nothing downstream loses information.

Outcome — FIXED, 2026-08-27

test/test_frozen_string_concat_operand.pas (wired into test-core) is byte-identical to the FPC 3.2.2 oracle across nine rows: the variable path kept as a guard, a shortstring result on either side of +, a chained '<' + P + '>', frozen + frozen (P + S8), frozen + variable, the assignment form that used to produce the empty string, a method result, and a 200-turn concat loop — the last because the frozen concat codegen carves its result off the stack with no restore, so a mis-typed operand in a loop would show up as an overflow rather than as wrong text.

gate.sh quick GREEN; Pascal conformance 346/0/170/34, C conformance 220/0, fgl 7/7.

Still open from the same family

Two of the three adjacent cases recorded on bug-p-a-shortstring-function-result-prints-as-a-pointer remain, and both are what cutils.pas:1429 still needs:

Log