← board

A chained class var of class type loses its record identity

Repro

type
  TInner = class
    class var IV: LongInt;
  end;
  TC = class
    class var Inner: TInner;
  end;
var c: TC;
begin
  TC.Inner := TInner.Create;  TInner.IV := 22;  c := TC.Create;
  WriteLn(TC.Inner.IV);   { pxx 22        fpc 22 }
  WriteLn(c.Inner.IV);    { pxx 4265192   fpc 22 }
end.

Cause

ParseLValueAST and ParseClassRecordSelectors each resolve a class var reached through an instance, build an AN_IDENT over the shared backing symbol, and then set recName := REC_NONE / recId := REC_NONE before Continueing the selector loop. The class var's own record identity is gone, so whatever selector comes next has nothing to be looked up in.

The same clearing sits on the typed-class-const symbol arm beside each of them — four sites, and they are two pairs of a double case.

Why it hid

The shape only breaks when BOTH links are per-class. An instance FIELD off the same chain re-derives the record from the node further down the loop, so c.Inner.SomeField was right. The class-var arm is the one that asks FindClassVar(recName - REC_UCLASS_BASE, ...) directly, so REC_NONE takes it past every arm to the no-namespace fallback and it reads at offset 0 — returning the same constant for every member tried, which is the tell that no member was being read at all.

A second trap sat on top of it: class var opens a SECTION. In

TInner = class
  class var IV: LongInt;
  F: LongInt;
end;

F is a class var too, under both compilers (a.F := 1; b.F := 2 leaves both reading 2). Two probes written to contrast a class var with an instance field were contrasting a class var with a class var, and the boundary only appeared once a var section was re-opened.

Fix

Carry the identity instead of clearing it, at all four sites:

if (tk = tyClass) or (tk = tyRecord) then recName := ResolveNodeRec(node)
else recName := REC_NONE;

ResolveNodeRec on an AN_IDENT is SymRecOf(ASTIVal), which is the same question the field path already asks.

Gate

test/test_a_class_var_of_class_type_carries_its_record_identity_through_a_chain.pas — thirteen rows, test-core, byte-identical to fpc 3.2.2: the class-var and class-const links, an instance field and a method off the same chain, the lvalue side, a class var of RECORD type, a two-link chain, a parenthesised receiver (the second arm), a typed record class const (the symbol arm), and the two QUALIFIED spellings as controls, which were correct throughout.

Log