← board

A record's static class function is called with a dummy Self it no longer has

The repro (frankZ's)

program y1; {$mode objfpc}{$H+} {$modeswitch advancedrecords}
type TP = record K: Integer; V: AnsiString;
  class function Make(const a: Integer; const b: AnsiString): TP; static; end;
class function TP.Make(const a: Integer; const b: AnsiString): TP;
begin Result.K := a; Result.V := b; end;
var p: TP;
begin p := TP.Make(3,'three'); WriteLn('y1 ', p.K, '/', p.V); end.

fpc and the pin print y1 3/three. HEAD segfaulted.

The mechanism

b0d53c73a made the static directive mean no Self parameter. The call site at pasparser_expr.inc — the one an assignment's right-hand side takes — hand-rolls its own argument loop and kept prepending a by-value dummy Self, which before that commit was correct and lined the chain up with the parameter list. Afterwards the chain was one argument longer than the signature, and the lowering pairs by position.

Nothing about that is visible until an argument is managed, because the only thing that reads the pairing is the managed-argument temp. IR for TP.Make('three') against Make(const b: AnsiString):

pinned   const_int 0 tk=17 -> arg                 the dummy, a pointer
         const_str -> store_sym tk=23 -> arg      the STRING gets the temp
HEAD     const_int 0 tk=23 -> store_sym -> arg    the DUMMY gets the temp
         const_str -> arg                         the string gets none

So the callee read a length word behind a bare literal.

Measured, eight signatures, values not exit codes

parameters fpc HEAD
(Integer) 3/0 3/0
(AnsiString) 5/0 1073741824/0
(Integer; AnsiString) 3/5 SEGFAULT
(AnsiString; Integer) 5/3 1073741824/3
(AnsiString; AnsiString) 5/2 5/1073741824
(Integer; Integer) 3/4 3/4
(Integer; Integer; AnsiString) 7/5 SEGFAULT
(Integer; AnsiString; Integer) 7/5 SEGFAULT

Three of the eight exit 0 and print the wrong number. Both frankZ and I first published a rule that was wrong because our probes read rc or printed a literal ok: mine said "the second argument", frankZ's said "any managed argument after the first". The truth is any managed argument at all, and it only became visible once every row printed its value. frankZ caught my version; the corrected matrix caught frankZ's.

Why nothing caught it

What was tried and did not work

Retyping the dummy — StaticDummySelfTk instead of ParamOwnKind(mpi, 0) at all three sites that hand-build it. Measured: changes the AST node's tk and changes nothing else, because the lowering pairs arguments with the parameter list by position and never reads that node's kind. Recorded because it is the obvious fix and it is wrong: the chain has to be the right LENGTH, not the right types. Reverted rather than landed beside the real fix.

Not fixed here

TP.Make('three').Bump — a selector chained onto a record static's result — is refused with expected ')' before '.'. The pin refuses it too, so it is pre-existing and out of scope.

Two notes carried from frankZ rather than written twice