← board

A dynamic-array value assigned to a record variable is accepted

{$mode objfpc}{$H+}
type
  TRec = record v: LongInt; end;
  TRecArr = array of TRec;
var p, q: TRecArr; r: TRec;
begin
  SetLength(p, 1); SetLength(q, 1);
  r := p + q;          { pxx: compiles. r now holds an array HANDLE. }
  WriteLn(r.v);        { reads through it }
end.

p + q is pxx's dynamic-array concatenation ([[feature-p-dynamic-array-concatenation]]), which is correct and deliberate. The defect is the STORE: a record variable accepts an array-valued expression.

Why it is accepted, and why that is not an oversight

AssignSideKind (compiler/ir.inc) returns False — "this side cannot be typed" — for any node with NodeDynDepth > 0, so the kind check stands down and the store goes through unchallenged. Its own header ranks the directions:

a false REJECT of working code is a worse defect than the false accept being fixed here

An array's TypeKind IS its element's, so an array of TRec node reports tyRecord and a check that trusted the kind would have to be wrong in one direction or the other. Abstaining was the right call with the tools available.

What it will take

The assignment check needs to be able to say "this side is an ARRAY of X" rather than abstaining — the same missing concept as [[bug-p-an-enum-or-array-type-cannot-be-named-as-an-operator-operand]], where the operator table could not tell array of Char from Char and needed a REC_ARRAY_OPERAND key to separate them. NodeDynDepth already answers for every node shape that reaches here, so the information exists; what is missing is a place to put it in the (kind) pair the check compares.

Verified pre-existing

stable_linux_amd64/default/pinned accepts the program above. It is NOT a regression of bug-p-an-enum-or-array-type-cannot-be-named-as-an-operator-operand — but that fix did remove an ACCIDENTAL COVER for one narrow shape of it, and the removal is why it was noticed:

With operator + (a, b: TRec) also declared, the pin REFUSED the program with arithmetic operator not supported for dynamic arrays. That refusal was reached by the operator lookup MATCHING the record operator for an array-of-record operand — ResolveNodeRec answers with the ELEMENT's record — so the node was retyped, CheckArithOperandsHaveAMeaning ran, and the dyn-array arm fired. A right answer from a wrong match, on a program whose declaration has nothing to do with it. Keying array operands separately removes the wrong match, and with it the accidental error, leaving the shape consistent with the one that never had a record operator in scope.