feat(codegen): implicit-virtual constructor dispatch via metaclass
Constructors are now auto-slotted into the vtable by the semantic pass. When a constructor is called through a metaclass-typed variable (C.Create(args)), the compiler emits _ClassCreate for allocation followed by a vtable-indirect call to the most-derived constructor body. Direct calls (TFoo.Create) remain fully static. Both backends (QBE and native x86-64) emit correct dispatch for: - MetaclassVar.Create(args) syntax - ClassCreate(MetaclassVar, args) builtin - Zero-arg and multi-arg constructor signatures This is a layout-changing commit (adds constructor vtable slots). Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK, 3370 tests pass.
This commit is contained in:
parent
1753b5d2a8
commit
215082afc3
|
|
@ -5385,9 +5385,17 @@ begin
|
|||
for SetI := FC.Args.Count - 1 downto 1 do
|
||||
Self.Emit(Format(#9'popq %s', [SysVArg64(SetI)]));
|
||||
Self.Emit(#9'movq %rbx, %rdi');
|
||||
Self.Emit(Format(#9'callq %s',
|
||||
[MethodEmitNameNative(TMethodDecl(FC.ResolvedDecl),
|
||||
TMethodDecl(FC.ResolvedDecl).OwnerTypeName, 'Create')]));
|
||||
if TMethodDecl(FC.ResolvedDecl).VTableSlot >= 0 then
|
||||
begin
|
||||
Self.Emit(#9'movq (%rdi), %rax');
|
||||
Self.Emit(Format(#9'movq %d(%%rax), %%rax',
|
||||
[(TMethodDecl(FC.ResolvedDecl).VTableSlot + 1) * 8]));
|
||||
Self.Emit(#9'callq *%rax');
|
||||
end
|
||||
else
|
||||
Self.Emit(Format(#9'callq %s',
|
||||
[MethodEmitNameNative(TMethodDecl(FC.ResolvedDecl),
|
||||
TMethodDecl(FC.ResolvedDecl).OwnerTypeName, 'Create')]));
|
||||
Self.Emit(#9'movq %rbx, %rax');
|
||||
Self.Emit(#9'popq %rbx');
|
||||
end
|
||||
|
|
@ -5396,9 +5404,17 @@ begin
|
|||
Self.Emit(#9'pushq %rbx');
|
||||
Self.Emit(#9'movq %rax, %rbx');
|
||||
Self.Emit(#9'movq %rax, %rdi');
|
||||
Self.Emit(Format(#9'callq %s',
|
||||
[MethodEmitNameNative(TMethodDecl(FC.ResolvedDecl),
|
||||
TMethodDecl(FC.ResolvedDecl).OwnerTypeName, 'Create')]));
|
||||
if TMethodDecl(FC.ResolvedDecl).VTableSlot >= 0 then
|
||||
begin
|
||||
Self.Emit(#9'movq (%rdi), %rax');
|
||||
Self.Emit(Format(#9'movq %d(%%rax), %%rax',
|
||||
[(TMethodDecl(FC.ResolvedDecl).VTableSlot + 1) * 8]));
|
||||
Self.Emit(#9'callq *%rax');
|
||||
end
|
||||
else
|
||||
Self.Emit(Format(#9'callq %s',
|
||||
[MethodEmitNameNative(TMethodDecl(FC.ResolvedDecl),
|
||||
TMethodDecl(FC.ResolvedDecl).OwnerTypeName, 'Create')]));
|
||||
Self.Emit(#9'movq %rbx, %rax');
|
||||
Self.Emit(#9'popq %rbx');
|
||||
end;
|
||||
|
|
@ -7329,13 +7345,21 @@ begin
|
|||
if ACall.IsConstructorCall then
|
||||
begin
|
||||
RT := TRecordTypeDesc(ACall.ResolvedClassType);
|
||||
Self.Emit(Format(#9'movq $%d, %%rdi', [RT.TotalSize()]));
|
||||
Self.Emit(Format(#9'leaq _FieldCleanup_%s(%%rip), %%rsi', [Self.ClassSymName(RT.Name)]));
|
||||
Self.Emit(#9'callq _ClassAlloc');
|
||||
if RT.HasVTable() then
|
||||
if ACall.IsMetaclassDispatch then
|
||||
begin
|
||||
Self.Emit(Format(#9'leaq vtable_%s(%%rip), %%rcx', [Self.ClassSymName(RT.Name)]));
|
||||
Self.Emit(#9'movq %rcx, (%rax)');
|
||||
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(ACall.ObjectName)]));
|
||||
Self.Emit(#9'callq _ClassCreate');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.Emit(Format(#9'movq $%d, %%rdi', [RT.TotalSize()]));
|
||||
Self.Emit(Format(#9'leaq _FieldCleanup_%s(%%rip), %%rsi', [Self.ClassSymName(RT.Name)]));
|
||||
Self.Emit(#9'callq _ClassAlloc');
|
||||
if RT.HasVTable() then
|
||||
begin
|
||||
Self.Emit(Format(#9'leaq vtable_%s(%%rip), %%rcx', [Self.ClassSymName(RT.Name)]));
|
||||
Self.Emit(#9'movq %rcx, (%rax)');
|
||||
end;
|
||||
end;
|
||||
if FDebugMode then
|
||||
begin
|
||||
|
|
@ -7370,7 +7394,14 @@ begin
|
|||
for I := UserSlots - 1 downto 0 do
|
||||
Self.Emit(#9'popq ' + SysVArg64(I + 1));
|
||||
Self.Emit(#9'movq %rbx, %rdi');
|
||||
Self.Emit(#9'callq ' + Sym);
|
||||
if ACall.IsMetaclassDispatch and (MD.VTableSlot >= 0) then
|
||||
begin
|
||||
Self.Emit(#9'movq (%rdi), %rax');
|
||||
Self.Emit(Format(#9'movq %d(%%rax), %%rax', [(MD.VTableSlot + 1) * 8]));
|
||||
Self.Emit(#9'callq *%rax');
|
||||
end
|
||||
else
|
||||
Self.Emit(#9'callq ' + Sym);
|
||||
Self.EndCallArgs();
|
||||
Self.Emit(#9'movq %rbx, %rax');
|
||||
Self.Emit(#9'popq %rbx');
|
||||
|
|
@ -7406,7 +7437,14 @@ begin
|
|||
for I := 0 to 5 do
|
||||
Self.Emit(Format(#9'movq %d(%%rsp), %s', [I * 8, SysVArg64(I)]));
|
||||
Self.Emit(Format(#9'addq $%d, %%rsp', [6 * 8]));
|
||||
Self.Emit(#9'callq ' + Sym);
|
||||
if ACall.IsMetaclassDispatch and (MD.VTableSlot >= 0) then
|
||||
begin
|
||||
Self.Emit(#9'movq (%rdi), %rax');
|
||||
Self.Emit(Format(#9'movq %d(%%rax), %%rax', [(MD.VTableSlot + 1) * 8]));
|
||||
Self.Emit(#9'callq *%rax');
|
||||
end
|
||||
else
|
||||
Self.Emit(#9'callq ' + Sym);
|
||||
Self.EmitHoistEpilogue(ACall.Args, HD, HK, HTotal, CleanUp, True);
|
||||
HD.Free();
|
||||
HK.Free();
|
||||
|
|
|
|||
|
|
@ -9698,8 +9698,20 @@ begin
|
|||
ArgLine := ArgLine + Format(', %s %s',
|
||||
[QbeParamTypeOf(Par.ResolvedType), ArgTemp]);
|
||||
end;
|
||||
EmitLine(Format(' call $%s(%s)',
|
||||
[MethodEmitName(MDecl, MDecl.OwnerTypeName, 'Create'), ArgLine]));
|
||||
if MDecl.VTableSlot >= 0 then
|
||||
begin
|
||||
VTblTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [VTblTemp, T]));
|
||||
FPtrTemp := AllocTemp();
|
||||
SlotOff := (MDecl.VTableSlot + 1) * 8;
|
||||
ArgTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
||||
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
||||
EmitLine(Format(' call %s(%s)', [FPtrTemp, ArgLine]));
|
||||
end
|
||||
else
|
||||
EmitLine(Format(' call $%s(%s)',
|
||||
[MethodEmitName(MDecl, MDecl.OwnerTypeName, 'Create'), ArgLine]));
|
||||
end;
|
||||
Exit(T);
|
||||
end;
|
||||
|
|
@ -10484,15 +10496,24 @@ begin
|
|||
if MCallExpr.IsConstructorCall then
|
||||
begin
|
||||
SelfTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l call $_ClassAlloc(l %d, l $_FieldCleanup_%s)',
|
||||
[SelfTemp, RT.TotalSize(), ClassSymName(QBEMangle(RT.Name))]));
|
||||
if RT.HasVTable() then
|
||||
EmitLine(Format(' storel $vtable_%s, %s',
|
||||
[ClassSymName(QBEMangle(RT.Name)), SelfTemp]));
|
||||
{ No _ClassAddRef here — the assignment site (EmitAssignment) is
|
||||
responsible for the retain on the receiving slot. Adding it here
|
||||
as well produces a double-AddRef that prevents the object from
|
||||
ever reaching refcount zero. }
|
||||
if MCallExpr.IsMetaclassDispatch then
|
||||
begin
|
||||
{ Metaclass-var dispatch: C.Create(args). Load the metaclass value
|
||||
(a typeinfo pointer) and call _ClassCreate which reads size/cleanup/
|
||||
vtable from the typeinfo at runtime. }
|
||||
L := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[L, VarRef(MCallExpr.ObjectName, MCallExpr.IsGlobal)]));
|
||||
EmitLine(Format(' %s =l call $_ClassCreate(l %s)', [SelfTemp, L]));
|
||||
end
|
||||
else
|
||||
begin
|
||||
EmitLine(Format(' %s =l call $_ClassAlloc(l %d, l $_FieldCleanup_%s)',
|
||||
[SelfTemp, RT.TotalSize(), ClassSymName(QBEMangle(RT.Name))]));
|
||||
if RT.HasVTable() then
|
||||
EmitLine(Format(' storel $vtable_%s, %s',
|
||||
[ClassSymName(QBEMangle(RT.Name)), SelfTemp]));
|
||||
end;
|
||||
if FDebugMode then
|
||||
begin
|
||||
L := AllocTemp();
|
||||
|
|
@ -10529,8 +10550,6 @@ begin
|
|||
else if (Par.ResolvedType <> nil) and (Par.ResolvedType.Kind = tyInterface) then
|
||||
begin
|
||||
ArgTemps.Add('');
|
||||
{ Class expression passed to an interface param: emit obj and
|
||||
look up the static itab using the known target interface name. }
|
||||
if TASTExpr(MCallExpr.Args.Items[I]).ResolvedType.Kind = tyClass then
|
||||
begin
|
||||
ArgTemp := EmitExpr(TASTExpr(MCallExpr.Args.Items[I]));
|
||||
|
|
@ -10553,11 +10572,27 @@ begin
|
|||
[QbeParamTypeOf(Par.ResolvedType), ArgTemp]);
|
||||
end;
|
||||
end;
|
||||
if MDecl.OwnerTypeName <> '' then
|
||||
FuncName := '$' + MethodEmitName(MDecl, MDecl.OwnerTypeName, MCallExpr.Name)
|
||||
if MCallExpr.IsMetaclassDispatch and (MDecl.VTableSlot >= 0) then
|
||||
begin
|
||||
{ Indirect ctor call via vtable: load vptr from instance[0],
|
||||
then load ctor address from vtable[ctorSlot]. }
|
||||
VTblTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [VTblTemp, SelfTemp]));
|
||||
FPtrTemp := AllocTemp();
|
||||
SlotOff := (MDecl.VTableSlot + 1) * 8;
|
||||
ArgTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
||||
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
||||
EmitLine(Format(' call %s(%s)', [FPtrTemp, ArgLine]));
|
||||
end
|
||||
else
|
||||
FuncName := '$' + MethodEmitName(MDecl, RT.Name, MCallExpr.Name);
|
||||
EmitLine(Format(' call %s(%s)', [FuncName, ArgLine]));
|
||||
begin
|
||||
if MDecl.OwnerTypeName <> '' then
|
||||
FuncName := '$' + MethodEmitName(MDecl, MDecl.OwnerTypeName, MCallExpr.Name)
|
||||
else
|
||||
FuncName := '$' + MethodEmitName(MDecl, RT.Name, MCallExpr.Name);
|
||||
EmitLine(Format(' call %s(%s)', [FuncName, ArgLine]));
|
||||
end;
|
||||
FlushPendingReleases(PMark);
|
||||
EmitOwnedArgReleases(MCallExpr.Args, ArgTemps, MDecl.Params);
|
||||
ReleaseConstStringArgs(MCallExpr.Args, ArgTemps, MDecl.Params);
|
||||
|
|
|
|||
|
|
@ -536,6 +536,8 @@ type
|
|||
IsGlobal: Boolean; { set by uSemantic — ObjectName is a program-level global }
|
||||
IsVarParam: Boolean; { set by uSemantic — ObjectName is a var/out parameter }
|
||||
IsBuiltinToString: Boolean; { set by uSemantic — built-in TObject.ToString virtual dispatch }
|
||||
IsConstructorCall: Boolean; { set by uSemantic — metaclass-var Create in stmt position }
|
||||
IsMetaclassDispatch: Boolean; { set by uSemantic — receiver is a metaclass var }
|
||||
IsProcFieldCall: Boolean; { set by uSemantic — Name is a procedural-typed field of
|
||||
the receiver, invoked directly (F.Handler;). Codegen
|
||||
loads the (Code, Data) pair from the field slot and
|
||||
|
|
@ -833,6 +835,7 @@ type
|
|||
[Unretained] ResolvedClassType: TTypeDesc; { not owned; set by uSemantic }
|
||||
[Unretained] ResolvedMethod: TObject; { TMethodDecl — not owned }
|
||||
IsConstructorCall: Boolean; { set by uSemantic — TypeName.Create(args) }
|
||||
IsMetaclassDispatch: Boolean; { set by uSemantic — receiver is a metaclass var }
|
||||
IsGlobal: Boolean; { set by uSemantic — ObjectName is a program-level global }
|
||||
IsVarParam: Boolean; { set by uSemantic — ObjectName is a var/out parameter }
|
||||
IsBuiltinToString: Boolean; { set by uSemantic — built-in TObject.ToString virtual dispatch }
|
||||
|
|
|
|||
|
|
@ -2465,7 +2465,17 @@ begin
|
|||
else if NewMDecl.IsOverride then
|
||||
RT.OverrideVTableSlot(
|
||||
RT.FindVTableSlot(NewMDecl.Name),
|
||||
'$' + CurrentUnitPrefix() + ATypeName + '_' + NewMDecl.Name);
|
||||
'$' + CurrentUnitPrefix() + ATypeName + '_' + NewMDecl.Name)
|
||||
else if (SameText(NewMDecl.Name, 'Create') or
|
||||
(StrPos('Create', NewMDecl.Name) = 0)) and
|
||||
(not NewMDecl.IsVirtual) and (not NewMDecl.IsOverride) then
|
||||
begin
|
||||
if RT.FindVTableSlot(NewMDecl.Name) >= 0 then
|
||||
RT.OverrideVTableSlot(RT.FindVTableSlot(NewMDecl.Name),
|
||||
'$' + CurrentUnitPrefix() + ATypeName + '_' + NewMDecl.Name)
|
||||
else
|
||||
RT.AddVTableSlot(NewMDecl.Name, '$' + CurrentUnitPrefix() + ATypeName + '_' + NewMDecl.Name);
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Resolve fields }
|
||||
|
|
@ -2513,7 +2523,8 @@ begin
|
|||
RT.DestroyResolvedQbeName := NewMDecl.ResolvedQbeName;
|
||||
end;
|
||||
|
||||
if NewMDecl.IsVirtual or NewMDecl.IsOverride then
|
||||
if NewMDecl.IsVirtual or NewMDecl.IsOverride or
|
||||
(SameText(NewMDecl.Name, 'Create') or (StrPos('Create', NewMDecl.Name) = 0)) then
|
||||
NewMDecl.VTableSlot := RT.FindVTableSlot(NewMDecl.Name);
|
||||
|
||||
for K := 0 to NewMDecl.Params.Count - 1 do
|
||||
|
|
@ -4317,6 +4328,16 @@ begin
|
|||
{ Override clears the abstract flag on the inherited slot }
|
||||
if Slot >= 0 then
|
||||
RT.VTableEntryAt(Slot).IsAbstract := False;
|
||||
end
|
||||
else if (SameText(MDecl.Name, 'Create') or
|
||||
(StrPos('Create', MDecl.Name) = 0)) and
|
||||
(not MDecl.IsVirtual) and (not MDecl.IsOverride) then
|
||||
begin
|
||||
Slot := RT.FindVTableSlot(MangledKey);
|
||||
if Slot >= 0 then
|
||||
RT.OverrideVTableSlot(Slot, '$' + CurrentUnitPrefix() + TD.Name + '_' + MangledKey)
|
||||
else
|
||||
RT.AddVTableSlot(MangledKey, '$' + CurrentUnitPrefix() + TD.Name + '_' + MangledKey);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
|
@ -4425,8 +4446,10 @@ begin
|
|||
RT.DestroyResolvedQbeName := MDecl.ResolvedQbeName;
|
||||
end;
|
||||
|
||||
{ Retrieve the vtable slot assigned in the pre-pass above. }
|
||||
if MDecl.IsVirtual or MDecl.IsOverride then
|
||||
{ Retrieve the vtable slot assigned in the pre-pass above.
|
||||
Constructors get implicit vtable slots (metaclass dispatch). }
|
||||
if MDecl.IsVirtual or MDecl.IsOverride or
|
||||
(SameText(MDecl.Name, 'Create') or (StrPos('Create', MDecl.Name) = 0)) then
|
||||
begin
|
||||
MDecl.VTableSlot := RT.FindVTableSlot(MangledKey);
|
||||
if MDecl.IsOverride and (MDecl.VTableSlot < 0) then
|
||||
|
|
@ -6451,6 +6474,26 @@ begin
|
|||
SemanticError(
|
||||
Format('''%s'' is not a variable', [ACall.ObjectName]),
|
||||
ACall.Line, ACall.Col);
|
||||
{ Metaclass-var constructor dispatch in statement position. }
|
||||
if (ObjSym.TypeDesc.Kind = tyMetaClass) and
|
||||
(SameText(ACall.Name, 'Create') or (StrPos('Create', ACall.Name) = 0)) then
|
||||
begin
|
||||
RT := TRecordTypeDesc(TMetaClassTypeDesc(ObjSym.TypeDesc).BaseClass);
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
AnalyseExpr(TASTExpr(ACall.Args.Items[I]));
|
||||
MDecl := ResolveMethodOverload(RT.Name, ACall.Name, ACall.Args,
|
||||
ACall.Line, ACall.Col);
|
||||
if MDecl = nil then
|
||||
MDecl := FindMethodDecl(RT.Name, ACall.Name);
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := MDecl;
|
||||
ACall.IsConstructorCall := True;
|
||||
ACall.IsMetaclassDispatch := True;
|
||||
ACall.IsGlobal := ObjSym.IsGlobal;
|
||||
ACall.IsVarParam := (ObjSym.Kind = skVarParameter);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if not (ObjSym.TypeDesc.Kind in [tyClass, tyInterface, tyRecord]) then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a class, interface, or record variable', [ACall.ObjectName]),
|
||||
|
|
@ -9062,6 +9105,30 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ Metaclass-var constructor dispatch: C.Create(args) where C is a
|
||||
metaclass variable. Resolve against the BaseClass and flag for
|
||||
indirect ctor dispatch via vtable at codegen time. }
|
||||
if (ObjSym.TypeDesc.Kind = tyMetaClass) and
|
||||
(SameText(AExpr.Name, 'Create') or (StrPos('Create', AExpr.Name) = 0)) then
|
||||
begin
|
||||
RT := TRecordTypeDesc(TMetaClassTypeDesc(ObjSym.TypeDesc).BaseClass);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
AnalyseExpr(TASTExpr(AExpr.Args.Items[I]));
|
||||
MDecl := ResolveMethodOverload(RT.Name, AExpr.Name,
|
||||
AExpr.Args, AExpr.Line, AExpr.Col);
|
||||
if MDecl = nil then
|
||||
MDecl := FindMethodDecl(RT.Name, AExpr.Name);
|
||||
if MDecl <> nil then
|
||||
AppendDefaultArgs(AExpr.Args, MDecl, AExpr.Name, AExpr.Line, AExpr.Col);
|
||||
AExpr.ResolvedMethod := MDecl;
|
||||
AExpr.ResolvedClassType := RT;
|
||||
AExpr.IsConstructorCall := True;
|
||||
AExpr.IsMetaclassDispatch := True;
|
||||
AExpr.IsGlobal := ObjSym.IsGlobal;
|
||||
AExpr.IsVarParam := (ObjSym.Kind = skVarParameter);
|
||||
Exit(RT);
|
||||
end;
|
||||
|
||||
if not (ObjSym.TypeDesc.Kind in [tyClass, tyInterface, tyRecord]) then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a class, interface, or record variable', [AExpr.ObjectName]),
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ type
|
|||
procedure TestSemantic_ClassCreate_RejectsNonMetaclassFirstArg;
|
||||
procedure TestCodegen_ClassCreate_EmitsAllocAndCtorCall;
|
||||
procedure TestCodegen_ClassCreate_NoCtor_OnlyAllocCalled;
|
||||
|
||||
{ Metaclass-var dispatch: C.Create() emits _ClassCreate + indirect
|
||||
ctor call via vtable, not a static call to the base Create. }
|
||||
procedure TestSemantic_MetaclassVar_Create_Accepted;
|
||||
procedure TestCodegen_MetaclassVar_Create_IndirectCtor;
|
||||
procedure TestCodegen_MetaclassVar_Create_NoCtor_NoIndirectCall;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -454,8 +460,8 @@ var IR: string;
|
|||
begin
|
||||
IR := GenIR(Src);
|
||||
AssertTrue('emits call to $_ClassCreate', Pos('call $_ClassCreate(', IR) > 0);
|
||||
AssertTrue('emits static call to TFoo_Create after alloc',
|
||||
Pos('call $TFoo_Create(', IR) > 0);
|
||||
AssertTrue('emits indirect ctor call via vtable (no static TFoo_Create call)',
|
||||
Pos('call $TFoo_Create(', IR) < 0);
|
||||
end;
|
||||
|
||||
procedure TClassOfTests.TestCodegen_ClassCreate_NoCtor_OnlyAllocCalled;
|
||||
|
|
@ -478,6 +484,70 @@ begin
|
|||
Pos('TFoo_Create', IR) < 0);
|
||||
end;
|
||||
|
||||
{ ---------- Metaclass-var C.Create() dispatch tests ---------- }
|
||||
|
||||
procedure TClassOfTests.TestSemantic_MetaclassVar_Create_Accepted;
|
||||
const Src = '''
|
||||
program P;
|
||||
type
|
||||
TFoo = class(TObject)
|
||||
constructor Create;
|
||||
end;
|
||||
constructor TFoo.Create; begin end;
|
||||
var C: class of TFoo; F: TFoo;
|
||||
begin
|
||||
C := TFoo;
|
||||
F := C.Create()
|
||||
end.
|
||||
''';
|
||||
begin
|
||||
AnalyseSrc(Src);
|
||||
end;
|
||||
|
||||
procedure TClassOfTests.TestCodegen_MetaclassVar_Create_IndirectCtor;
|
||||
const Src = '''
|
||||
program P;
|
||||
type
|
||||
TFoo = class(TObject)
|
||||
constructor Create;
|
||||
end;
|
||||
constructor TFoo.Create; begin end;
|
||||
var C: class of TFoo; F: TFoo;
|
||||
begin
|
||||
C := TFoo;
|
||||
F := C.Create()
|
||||
end.
|
||||
''';
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(Src);
|
||||
AssertTrue('emits _ClassCreate for runtime alloc',
|
||||
Pos('call $_ClassCreate(', IR) > 0);
|
||||
AssertTrue('emits indirect ctor call via vtable (loadl from vtable slot)',
|
||||
Pos('loadl', IR) > 0);
|
||||
AssertTrue('does NOT emit a static TFoo_Create call',
|
||||
Pos('call $TFoo_Create(', IR) < 0);
|
||||
end;
|
||||
|
||||
procedure TClassOfTests.TestCodegen_MetaclassVar_Create_NoCtor_NoIndirectCall;
|
||||
const Src = '''
|
||||
program P;
|
||||
type TFoo = class(TObject) end;
|
||||
var C: class of TFoo; F: TFoo;
|
||||
begin
|
||||
C := TFoo;
|
||||
F := C.Create()
|
||||
end.
|
||||
''';
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(Src);
|
||||
AssertTrue('emits _ClassCreate for runtime alloc',
|
||||
Pos('call $_ClassCreate(', IR) > 0);
|
||||
AssertTrue('no ctor call when class declares no Create',
|
||||
Pos('TFoo_Create', IR) < 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TClassOfTests);
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -117,6 +117,17 @@ type
|
|||
{ var-param interface: method dispatch and reassignment through the
|
||||
var param must reach the caller's variable. }
|
||||
procedure TestRun_VarParamInterface_DispatchAndReassign;
|
||||
|
||||
{ Metaclass-var constructor dispatch: cls.Create() runs the most-derived
|
||||
ctor body via vtable (implicitly virtual), not the base ctor statically. }
|
||||
procedure TestRun_MetaclassCreate_DispatchesToDerived;
|
||||
{ Metaclass-var ctor with args: cls.Create(N) passes args correctly. }
|
||||
procedure TestRun_MetaclassCreate_WithArgs;
|
||||
{ Direct TFoo.Create stays static (no vtable dispatch). }
|
||||
procedure TestRun_DirectCreate_StaysStatic;
|
||||
{ ClassCreate builtin still works (backwards compat) and now dispatches
|
||||
the ctor body via vtable too. }
|
||||
procedure TestRun_ClassCreate_StillWorks;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1743,6 +1754,131 @@ begin
|
|||
'hello 1' + LE + 'hello 2' + LE + '2' + LE + 'hello 101' + LE + '101' + LE, 0);
|
||||
end;
|
||||
|
||||
{ ---------- Metaclass-var constructor dispatch tests ---------- }
|
||||
|
||||
const SrcMetaclassCreateDerived = '''
|
||||
program Prg;
|
||||
type
|
||||
TAnimal = class(TObject)
|
||||
constructor Create;
|
||||
end;
|
||||
TDog = class(TAnimal)
|
||||
constructor Create;
|
||||
end;
|
||||
TAnimalClass = class of TAnimal;
|
||||
constructor TAnimal.Create;
|
||||
begin WriteLn('animal') end;
|
||||
constructor TDog.Create;
|
||||
begin WriteLn('dog') end;
|
||||
procedure MakeAndGreet(C: TAnimalClass);
|
||||
var A: TAnimal;
|
||||
begin
|
||||
A := C.Create();
|
||||
A.Free()
|
||||
end;
|
||||
begin
|
||||
MakeAndGreet(TAnimal);
|
||||
MakeAndGreet(TDog)
|
||||
end.
|
||||
''';
|
||||
|
||||
const SrcMetaclassCreateArgs = '''
|
||||
program Prg;
|
||||
type
|
||||
TBase = class(TObject)
|
||||
FVal: Integer;
|
||||
constructor Create(N: Integer);
|
||||
end;
|
||||
TChild = class(TBase)
|
||||
constructor Create(N: Integer);
|
||||
end;
|
||||
TBaseClass = class of TBase;
|
||||
constructor TBase.Create(N: Integer);
|
||||
begin FVal := N end;
|
||||
constructor TChild.Create(N: Integer);
|
||||
begin FVal := N * 10 end;
|
||||
procedure Run(C: TBaseClass; N: Integer);
|
||||
var B: TBase;
|
||||
begin
|
||||
B := C.Create(N);
|
||||
WriteLn(B.FVal);
|
||||
B.Free()
|
||||
end;
|
||||
begin
|
||||
Run(TBase, 3);
|
||||
Run(TChild, 3)
|
||||
end.
|
||||
''';
|
||||
|
||||
const SrcDirectCreateStatic = '''
|
||||
program Prg;
|
||||
type
|
||||
TBase = class(TObject)
|
||||
constructor Create;
|
||||
end;
|
||||
TChild = class(TBase)
|
||||
constructor Create;
|
||||
end;
|
||||
constructor TBase.Create;
|
||||
begin WriteLn('base') end;
|
||||
constructor TChild.Create;
|
||||
begin WriteLn('child') end;
|
||||
begin
|
||||
TBase.Create().Free();
|
||||
TChild.Create().Free()
|
||||
end.
|
||||
''';
|
||||
|
||||
const SrcClassCreateStillWorks = '''
|
||||
program Prg;
|
||||
type
|
||||
TFoo = class(TObject)
|
||||
constructor Create;
|
||||
end;
|
||||
TBar = class(TFoo)
|
||||
constructor Create;
|
||||
end;
|
||||
TFooClass = class of TFoo;
|
||||
constructor TFoo.Create;
|
||||
begin WriteLn('foo') end;
|
||||
constructor TBar.Create;
|
||||
begin WriteLn('bar') end;
|
||||
var C: TFooClass;
|
||||
begin
|
||||
C := TBar;
|
||||
ClassCreate(C).Free();
|
||||
ClassCreate(TFoo).Free()
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2EClasses2Tests.TestRun_MetaclassCreate_DispatchesToDerived;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcMetaclassCreateDerived,
|
||||
'animal' + LE + 'dog' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EClasses2Tests.TestRun_MetaclassCreate_WithArgs;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcMetaclassCreateArgs,
|
||||
'3' + LE + '30' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EClasses2Tests.TestRun_DirectCreate_StaysStatic;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcDirectCreateStatic,
|
||||
'base' + LE + 'child' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EClasses2Tests.TestRun_ClassCreate_StillWorks;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcClassCreateStillWorks,
|
||||
'bar' + LE + 'foo' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EClasses2Tests);
|
||||
|
||||
|
|
|
|||
|
|
@ -1004,8 +1004,8 @@ ArrayLiteral
|
|||
* ClassCreate ( Cls, ...args ) : Cls.BaseClass
|
||||
* — runtime construction from a metaclass
|
||||
* value; lowers to _ClassCreate(Cls)
|
||||
* followed by a static call to the
|
||||
* resolved constructor on Cls.BaseClass.
|
||||
* followed by a vtable-indirect call to
|
||||
* the constructor on Cls.BaseClass.
|
||||
* Low ( Ordinal | Array | String ) : T — for ordinal type/expression
|
||||
* returns lower bound of T;
|
||||
* 0 for open/dynamic arrays and
|
||||
|
|
@ -1061,6 +1061,9 @@ ArrayLiteral
|
|||
*
|
||||
* IDENT DOT IDENT in Factor:
|
||||
* - Left IDENT resolves to a type symbol → constructor call (TypeName.Create)
|
||||
* - Left IDENT resolves to a metaclass variable and right IDENT is a
|
||||
* constructor name → metaclass constructor dispatch (_ClassCreate +
|
||||
* vtable-indirect call to the most-derived Create body)
|
||||
* - Left IDENT resolves to a class variable → class field read
|
||||
* - Left IDENT resolves to a record variable → record field read
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2198,7 +2198,8 @@ if A.ClassType = EError then … // identity match
|
|||
|
||||
The compiler exposes a built-in function `ClassCreate(Cls, ...args)` that
|
||||
allocates an instance of the class denoted by `Cls` (a value of type `class
|
||||
of T`) and invokes the resolved constructor on that class's `BaseClass`.
|
||||
of T`) and dispatches the constructor body via vtable, so the most-derived
|
||||
override runs.
|
||||
|
||||
[source,pascal]
|
||||
----
|
||||
|
|
@ -2207,7 +2208,7 @@ var
|
|||
Inst: TTestCase;
|
||||
begin
|
||||
Cls := TFooTests; // metaclass = typeinfo pointer
|
||||
Inst := ClassCreate(Cls, 'TestSomething'); // calls TTestCase.Create(string)
|
||||
Inst := ClassCreate(Cls, 'TestSomething'); // vtable dispatch → TFooTests.Create
|
||||
end;
|
||||
----
|
||||
|
||||
|
|
@ -2216,6 +2217,9 @@ remaining arguments are passed to the constructor on `Cls.BaseClass`.
|
|||
The result type is `Cls.BaseClass`, so the call expression assigns
|
||||
cleanly to a variable of the base class type.
|
||||
|
||||
`ClassCreate` is the lower-level primitive; for most code the
|
||||
`MetaclassVar.Create(args)` syntax (see next section) is preferred.
|
||||
|
||||
=== Rationale
|
||||
|
||||
The xUnit pattern needs to instantiate one test fixture per published
|
||||
|
|
@ -2226,10 +2230,11 @@ the concrete TotalSize, vtable label, and field-cleanup label into the
|
|||
emitted IR, all of which must come from the runtime class value.
|
||||
|
||||
The split design — a generic RTL helper `_ClassCreate(TInfo)` that
|
||||
allocates and installs the vtable, plus a static call to the
|
||||
constructor — avoids stuffing every constructor signature through the
|
||||
RTL. The helper reads the three additional typeinfo slots (totalsize,
|
||||
fieldcleanup, vtable) added in Step 11e for exactly this purpose.
|
||||
allocates and installs the vtable, plus a vtable-indirect call to the
|
||||
constructor body — dispatches to the most-derived constructor without
|
||||
the caller knowing the concrete class. The helper reads the three
|
||||
additional typeinfo slots (totalsize, fieldcleanup, vtable) added in
|
||||
Step 11e for exactly this purpose.
|
||||
|
||||
The constructor lookup is by name (`Create`). Overload resolution on
|
||||
the constructor signature is not yet implemented; in practice the only
|
||||
|
|
@ -2245,17 +2250,88 @@ ClassCreate surface.
|
|||
`RegisterTest` call site, polluting the cp.test.* migration
|
||||
mechanically required by Step 11f. The runtime construction path
|
||||
exists either way; this just moves where the boilerplate lives.
|
||||
* *Make `metaclassVar.Create(args)` a special method-call shape in the
|
||||
parser.* Requires the metaclass to know its own constructor symbol
|
||||
table, which doesn't fit the current `class of T` lowering (a value
|
||||
of `class of T` is just a typeinfo pointer; the constructor symbols
|
||||
belong to T, not to the metaclass type). A dedicated builtin is
|
||||
honest about what's actually a runtime dispatch.
|
||||
* *Add a new vtable slot for the constructor and dispatch through
|
||||
it.* Constructors in Blaise are not virtual; introducing a virtual
|
||||
constructor would break the static `TFoo.Create` codegen invariant.
|
||||
Keeping ClassCreate as a separate path leaves the existing
|
||||
constructor lowering untouched.
|
||||
|
||||
---
|
||||
|
||||
== Implicit-Virtual Constructor Dispatch via Metaclass
|
||||
|
||||
=== Decision
|
||||
|
||||
When a constructor is called through a metaclass-typed variable
|
||||
(`C.Create(args)` where `C: class of TBase`), the compiler emits
|
||||
runtime vtable dispatch so the most-derived constructor body runs.
|
||||
Direct calls (`TFoo.Create(args)` where `TFoo` is a concrete type
|
||||
name) remain fully static.
|
||||
|
||||
Constructors are auto-slotted into the vtable by the semantic pass.
|
||||
A constructor name that matches an ancestor's constructor overrides
|
||||
the ancestor's vtable slot; new constructor names get a fresh slot.
|
||||
No `virtual` or `override` keywords are required on constructors —
|
||||
the dispatch is implicit and transparent.
|
||||
|
||||
[source,pascal]
|
||||
----
|
||||
type
|
||||
TAnimal = class(TObject)
|
||||
constructor Create;
|
||||
end;
|
||||
TDog = class(TAnimal)
|
||||
constructor Create; // implicitly overrides TAnimal.Create's vtable slot
|
||||
end;
|
||||
var
|
||||
C: class of TAnimal;
|
||||
begin
|
||||
C := TDog;
|
||||
C.Create(); // dispatches to TDog.Create via vtable
|
||||
TAnimal.Create(); // static call to TAnimal.Create (no dispatch)
|
||||
end;
|
||||
----
|
||||
|
||||
The lowering is:
|
||||
|
||||
1. `_ClassCreate(C)` — reads totalsize, fieldcleanup, and vtable from the
|
||||
typeinfo pointer `C`; allocates and zeroes the instance; installs the
|
||||
vtable; calls `_ClassAddRef`.
|
||||
2. Load the vtable pointer from the new instance (`[instance+0]`), index
|
||||
into the constructor's slot, and call indirectly.
|
||||
|
||||
Both the `C.Create(args)` syntax and `ClassCreate(C, args)` use this path.
|
||||
|
||||
=== Rationale
|
||||
|
||||
The xUnit test runner instantiates fixtures through `class of TTestCase`
|
||||
and must run the derived class's constructor, not the base. Without
|
||||
virtual dispatch, `ClassCreate` called the base constructor statically —
|
||||
a derived class with initialisation in its own `Create` would silently
|
||||
skip it.
|
||||
|
||||
Making constructors implicitly virtual only through metaclass-typed
|
||||
receivers preserves the zero-overhead guarantee for direct
|
||||
`TFoo.Create` calls (the overwhelming majority) while giving metaclass
|
||||
dispatch the polymorphic behaviour users expect. Requiring explicit
|
||||
`virtual`/`override` on constructors was considered and rejected: it
|
||||
would force boilerplate on every class in the hierarchy, and the mental
|
||||
model of "metaclass dispatch = polymorphic" is intuitive without it.
|
||||
|
||||
The vtable slot is assigned by constructor name (same name = same slot,
|
||||
new name = new slot), matching the existing virtual-method slot
|
||||
assignment algorithm.
|
||||
|
||||
=== Alternatives rejected
|
||||
|
||||
* *Require `virtual`/`override` on constructors.* Imposes annotation
|
||||
burden on every class for a behaviour that only manifests through
|
||||
metaclass dispatch. Direct `TFoo.Create` does not benefit from the
|
||||
annotation. The implicit model is simpler and matches Delphi's
|
||||
`virtual constructor` behaviour without the keyword noise.
|
||||
* *Dispatch constructors through a separate RTTI table instead of the
|
||||
vtable.* Adds a parallel lookup mechanism that must be kept in sync.
|
||||
Constructors are regular methods with a `Self` parameter; the vtable
|
||||
is the natural dispatch vector.
|
||||
* *Keep constructors static even through metaclass dispatch.* This was
|
||||
the original `ClassCreate` implementation. It fails the test-runner
|
||||
use case: the derived fixture's constructor never runs, so the test
|
||||
fixture is uninitialised.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -69,29 +69,33 @@ TFieldAccessExpr.PropIndexExpr serialise
|
|||
TFieldAccessExpr.IsCharAccess safe
|
||||
TFieldAccessExpr.IsArrayAccess safe
|
||||
|
||||
# TIsExpr (uAST.pas:150)
|
||||
# TIsExpr (uAST.pas:151)
|
||||
TIsExpr.Obj serialise
|
||||
TIsExpr.TypeName serialise
|
||||
|
||||
# TAsExpr (uAST.pas:158)
|
||||
# TAsExpr (uAST.pas:159)
|
||||
TAsExpr.Obj serialise
|
||||
TAsExpr.TypeName serialise
|
||||
|
||||
# TSupportsExpr (uAST.pas:167)
|
||||
# TSupportsExpr (uAST.pas:168)
|
||||
TSupportsExpr.Obj serialise
|
||||
TSupportsExpr.IntfTypeName serialise
|
||||
TSupportsExpr.OutVarName serialise
|
||||
TSupportsExpr.OutVarIsGlobal safe
|
||||
|
||||
# TBinaryExpr (uAST.pas:183)
|
||||
# TInheritedCallExpr (uAST.pas:182)
|
||||
TInheritedCallExpr.Name serialise
|
||||
TInheritedCallExpr.Args serialise
|
||||
|
||||
# TBinaryExpr (uAST.pas:199)
|
||||
TBinaryExpr.Op serialise
|
||||
TBinaryExpr.Left serialise
|
||||
TBinaryExpr.Right serialise
|
||||
|
||||
# TNotExpr (uAST.pas:191)
|
||||
# TNotExpr (uAST.pas:207)
|
||||
TNotExpr.Expr serialise
|
||||
|
||||
# TAssignment (uAST.pas:203)
|
||||
# TAssignment (uAST.pas:219)
|
||||
TAssignment.Name serialise
|
||||
TAssignment.Expr serialise
|
||||
TAssignment.IsVarParam safe
|
||||
|
|
@ -100,23 +104,23 @@ TAssignment.IsThreadVar safe
|
|||
TAssignment.IsWeakLhs safe
|
||||
TAssignment.ImplicitSelfField safe
|
||||
|
||||
# TIfStmt (uAST.pas:219)
|
||||
# TIfStmt (uAST.pas:235)
|
||||
TIfStmt.Condition serialise
|
||||
TIfStmt.ThenStmt serialise
|
||||
TIfStmt.ElseStmt serialise
|
||||
|
||||
# TCompoundStmt (uAST.pas:227)
|
||||
# TCompoundStmt (uAST.pas:243)
|
||||
TCompoundStmt.Stmts serialise
|
||||
|
||||
# TWhileStmt (uAST.pas:234)
|
||||
# TWhileStmt (uAST.pas:250)
|
||||
TWhileStmt.Condition serialise
|
||||
TWhileStmt.Body serialise
|
||||
|
||||
# TRepeatStmt (uAST.pas:241)
|
||||
# TRepeatStmt (uAST.pas:257)
|
||||
TRepeatStmt.Body serialise
|
||||
TRepeatStmt.Condition serialise
|
||||
|
||||
# TForStmt (uAST.pas:248)
|
||||
# TForStmt (uAST.pas:264)
|
||||
TForStmt.VarName serialise
|
||||
TForStmt.IsGlobal safe
|
||||
TForStmt.StartExpr serialise
|
||||
|
|
@ -124,7 +128,7 @@ TForStmt.EndExpr serialise
|
|||
TForStmt.IsDownTo serialise
|
||||
TForStmt.Body serialise
|
||||
|
||||
# TForInStmt (uAST.pas:259)
|
||||
# TForInStmt (uAST.pas:275)
|
||||
TForInStmt.VarName serialise
|
||||
TForInStmt.VarIsGlobal safe
|
||||
TForInStmt.CollExpr serialise
|
||||
|
|
@ -144,34 +148,34 @@ TForInStmt.SetBitCount safe
|
|||
TForInStmt.SetMaskVarName safe
|
||||
TForInStmt.SetIsJumbo safe
|
||||
|
||||
# TTryFinallyStmt (uAST.pas:296)
|
||||
# TTryFinallyStmt (uAST.pas:312)
|
||||
TTryFinallyStmt.TryBody serialise
|
||||
TTryFinallyStmt.FinallyBody serialise
|
||||
|
||||
# TTryExceptStmt (uAST.pas:313)
|
||||
# TTryExceptStmt (uAST.pas:329)
|
||||
TTryExceptStmt.TryBody serialise
|
||||
TTryExceptStmt.Handlers serialise
|
||||
TTryExceptStmt.ElseBody serialise
|
||||
TTryExceptStmt.ExceptBody serialise
|
||||
|
||||
# TRaiseStmt (uAST.pas:326)
|
||||
# TRaiseStmt (uAST.pas:342)
|
||||
TRaiseStmt.Expr serialise
|
||||
|
||||
# TExitStmt (uAST.pas:337)
|
||||
# TExitStmt (uAST.pas:353)
|
||||
TExitStmt.Value safe
|
||||
TExitStmt.ResultAssign safe
|
||||
|
||||
# TBreakStmt (uAST.pas:345)
|
||||
# TBreakStmt (uAST.pas:361)
|
||||
|
||||
# TContinueStmt (uAST.pas:348)
|
||||
# TContinueStmt (uAST.pas:364)
|
||||
|
||||
# TCaseStmt (uAST.pas:359)
|
||||
# TCaseStmt (uAST.pas:375)
|
||||
TCaseStmt.Selector serialise
|
||||
TCaseStmt.Branches serialise
|
||||
TCaseStmt.ElseStmt serialise
|
||||
TCaseStmt.IsStringCase safe
|
||||
|
||||
# TFieldAssignment (uAST.pas:370)
|
||||
# TFieldAssignment (uAST.pas:386)
|
||||
TFieldAssignment.RecordName serialise
|
||||
TFieldAssignment.FieldName serialise
|
||||
TFieldAssignment.Expr serialise
|
||||
|
|
@ -186,7 +190,7 @@ TFieldAssignment.PropOwnerType safe
|
|||
TFieldAssignment.PropAccessorVSlot safe
|
||||
TFieldAssignment.IsElemWrite safe
|
||||
|
||||
# TStaticSubscriptAssign (uAST.pas:397)
|
||||
# TStaticSubscriptAssign (uAST.pas:414)
|
||||
TStaticSubscriptAssign.ArrayName serialise
|
||||
TStaticSubscriptAssign.IndexExpr serialise
|
||||
TStaticSubscriptAssign.ValueExpr serialise
|
||||
|
|
@ -197,18 +201,18 @@ TStaticSubscriptAssign.IsImplicitSelf safe
|
|||
TStaticSubscriptAssign.PropOwnerType safe
|
||||
TStaticSubscriptAssign.PropAccessorVSlot safe
|
||||
|
||||
# TPointerWriteStmt (uAST.pas:420)
|
||||
# TPointerWriteStmt (uAST.pas:444)
|
||||
TPointerWriteStmt.PtrExpr serialise
|
||||
TPointerWriteStmt.ValExpr serialise
|
||||
|
||||
# TProcCall (uAST.pas:428)
|
||||
# TProcCall (uAST.pas:452)
|
||||
TProcCall.Name serialise
|
||||
TProcCall.Args serialise
|
||||
TProcCall.IsImplicitSelfMethod safe
|
||||
TProcCall.IsIndirectCall safe
|
||||
TProcCall.IndirectCallIsGlobal safe
|
||||
|
||||
# TFuncCallExpr (uAST.pas:441)
|
||||
# TFuncCallExpr (uAST.pas:465)
|
||||
TFuncCallExpr.Name serialise
|
||||
TFuncCallExpr.Args serialise
|
||||
TFuncCallExpr.IsImplicitSelfMethod safe
|
||||
|
|
@ -218,18 +222,18 @@ TFuncCallExpr.IsBuiltinHasClassAttr safe
|
|||
TFuncCallExpr.HasClassAttrClass safe
|
||||
TFuncCallExpr.HasClassAttrAttr safe
|
||||
|
||||
# TIndirectFuncCallExpr (uAST.pas:467)
|
||||
# TIndirectFuncCallExpr (uAST.pas:491)
|
||||
TIndirectFuncCallExpr.CalleeExpr serialise
|
||||
TIndirectFuncCallExpr.Args serialise
|
||||
|
||||
# TDerefExpr (uAST.pas:477)
|
||||
# TDerefExpr (uAST.pas:501)
|
||||
TDerefExpr.Expr serialise
|
||||
|
||||
# TAddrOfExpr (uAST.pas:484)
|
||||
# TAddrOfExpr (uAST.pas:508)
|
||||
TAddrOfExpr.Expr serialise
|
||||
TAddrOfExpr.ResolvedFreeRoutine safe
|
||||
|
||||
# TMethodCallStmt (uAST.pas:496)
|
||||
# TMethodCallStmt (uAST.pas:520)
|
||||
TMethodCallStmt.ObjectName serialise
|
||||
TMethodCallStmt.Name serialise
|
||||
TMethodCallStmt.Args serialise
|
||||
|
|
@ -238,22 +242,21 @@ TMethodCallStmt.IsImplicitSelf safe
|
|||
TMethodCallStmt.IsGlobal safe
|
||||
TMethodCallStmt.IsVarParam safe
|
||||
TMethodCallStmt.IsBuiltinToString safe
|
||||
TMethodCallStmt.IsConstructorCall safe
|
||||
TMethodCallStmt.IsMetaclassDispatch safe
|
||||
TMethodCallStmt.IsProcFieldCall safe
|
||||
|
||||
# TInheritedCallStmt (uAST.pas:528)
|
||||
# TInheritedCallStmt (uAST.pas:554)
|
||||
TInheritedCallStmt.Name serialise
|
||||
TInheritedCallStmt.Args serialise
|
||||
|
||||
# TInheritedCallExpr (uAST.pas:183)
|
||||
TInheritedCallExpr.Name serialise
|
||||
TInheritedCallExpr.Args serialise
|
||||
|
||||
# TMethodCallExpr (uAST.pas:803)
|
||||
# TMethodCallExpr (uAST.pas:829)
|
||||
TMethodCallExpr.ObjectName serialise
|
||||
TMethodCallExpr.Name serialise
|
||||
TMethodCallExpr.Args serialise
|
||||
TMethodCallExpr.ObjExpr serialise
|
||||
TMethodCallExpr.IsConstructorCall safe
|
||||
TMethodCallExpr.IsMetaclassDispatch safe
|
||||
TMethodCallExpr.IsGlobal safe
|
||||
TMethodCallExpr.IsVarParam safe
|
||||
TMethodCallExpr.IsBuiltinToString safe
|
||||
|
|
|
|||
Loading…
Reference in a new issue