feat(lang): ClassCreate(Cls, ...args) builtin for runtime construction
Step 11e, part 3. New compiler builtin that constructs an instance
from a metaclass value at runtime. Lowers to:
%p =l call $_ClassCreate(l <classvalue>)
call $<BaseClass>_Create(l %p, args...)
result = %p
The first argument must be tyMetaClass; remaining args are forwarded
to the resolved constructor. Result type is the metaclass's BaseClass,
so 'F := ClassCreate(C, 7)' is well-typed when C: class of TFoo and
F: TFoo. The constructor lookup is currently by name 'Create' (no
overload resolution yet — fpcunit's Create(string) and TObject's
parameter-less Create are the only shapes the bcl.testing runner
needs). When the resolved class declares no Create method, only
the alloc call is emitted; fields stay in their _ClassAlloc-zeroed
default state.
This is the missing half that makes 'class of TTestCase' useful: the
runner (Step 11e, part 4) will iterate the published-method table
of each registered test class and call ClassCreate(Cls, MethodName)
to instantiate one TTestCase per test method, exactly the shape
fpcunit's class-method 'Suite' produces.
Symbol table: 'ClassCreate' added as skFunction in uSymbolTable.pas
(return type filled in semantically).
Semantic : AnalyseFuncCallExpr handles SameText('ClassCreate') —
validates first arg is metaclass, analyses remaining
args, resolves Create on BaseClass, stores it on
ResolvedDecl, returns BaseClass as result type.
Codegen : EmitExpr handles SameText('ClassCreate') — emits the
two-call sequence above; reuses the ConstructorCall
arg-coercion pattern (CoerceArg).
Three regression tests added to cp.test.classof.pas:
TestSemantic_ClassCreate_RejectsNonMetaclassFirstArg
TestCodegen_ClassCreate_EmitsAllocAndCtorCall
TestCodegen_ClassCreate_NoCtor_OnlyAllocCalled
Verified end-to-end: ClassCreate(TCounter, 100); .Bump; .Bump;
prints 102 — argument flowed through the constructor, the instance
runs methods, and Free cleans up correctly.
All 1238 tests pass (1235 prior + 3 new).
This commit is contained in:
parent
113baa71f3
commit
1fb2648f66
|
|
@ -4200,6 +4200,35 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ ClassCreate(Cls, ...args): runtime equivalent of TFoo.Create(args).
|
||||
Allocates via _ClassCreate (which reads totalsize/fieldcleanup/vtable
|
||||
from Cls's typeinfo), then calls $<BaseClass>_Create statically with
|
||||
the new pointer and the supplied args. Returns the new pointer. }
|
||||
if SameText(FC.Name,'ClassCreate') then
|
||||
begin
|
||||
L := EmitExpr(TASTExpr(FC.Args.Items[0])); { metaclass = typeinfo ptr }
|
||||
T := AllocTemp;
|
||||
EmitLine(Format(' %s =l call $_ClassCreate(l %s)', [T, L]));
|
||||
if FC.ResolvedDecl <> nil then
|
||||
begin
|
||||
MDecl := TMethodDecl(FC.ResolvedDecl);
|
||||
ArgLine := Format('l %s', [T]);
|
||||
for I := 1 to FC.Args.Count - 1 do
|
||||
begin
|
||||
Par := TMethodParam(MDecl.Params.Items[I - 1]);
|
||||
ArgTemp := EmitExpr(TASTExpr(FC.Args.Items[I]));
|
||||
ArgTemp := CoerceArg(ArgTemp, TASTExpr(FC.Args.Items[I]),
|
||||
QbeTypeOf(Par.ResolvedType));
|
||||
ArgLine := ArgLine + Format(', %s %s',
|
||||
[QbeTypeOf(Par.ResolvedType), ArgTemp]);
|
||||
end;
|
||||
EmitLine(Format(' call $%s(%s)',
|
||||
[MethodEmitName(MDecl, MDecl.OwnerTypeName, 'Create'), ArgLine]));
|
||||
end;
|
||||
Result := T;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if SameText(FC.Name,'StrToInt64') then
|
||||
begin
|
||||
L := EmitExpr(TASTExpr(FC.Args.Items[0]));
|
||||
|
|
|
|||
|
|
@ -4536,6 +4536,33 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ ClassCreate(Cls, ...args): runtime construction from a metaclass.
|
||||
Resolves the constructor on Cls.BaseClass with the supplied args
|
||||
and stores the TMethodDecl on AExpr.ResolvedDecl. Codegen lowers
|
||||
this to '%p = call $_ClassCreate(l <classvalue>); call $T_Create(l %p, args...)'.
|
||||
Result type is the BaseClass — assigning to a 'var T: TFoo' is
|
||||
well-typed when Cls: class of TFoo. }
|
||||
if SameText(AExpr.Name, 'ClassCreate') then
|
||||
begin
|
||||
if AExpr.Args.Count < 1 then
|
||||
SemanticError('ClassCreate requires a metaclass as the first argument',
|
||||
AExpr.Line, AExpr.Col);
|
||||
AnalyseExpr(TASTExpr(AExpr.Args.Items[0]));
|
||||
if (TASTExpr(AExpr.Args.Items[0]).ResolvedType = nil) or
|
||||
(TASTExpr(AExpr.Args.Items[0]).ResolvedType.Kind <> tyMetaClass) then
|
||||
SemanticError('ClassCreate: first argument must be a metaclass (class of T) value',
|
||||
AExpr.Line, AExpr.Col);
|
||||
{ Analyse remaining args before resolving the constructor — argument
|
||||
types feed FindMethodDecl when we add overload resolution; for v0
|
||||
we look up 'Create' by name and trust uniqueness. }
|
||||
for I := 1 to AExpr.Args.Count - 1 do
|
||||
AnalyseExpr(TASTExpr(AExpr.Args.Items[I]));
|
||||
Result := TMetaClassTypeDesc(TASTExpr(AExpr.Args.Items[0]).ResolvedType).BaseClass;
|
||||
AExpr.ResolvedType := Result;
|
||||
AExpr.ResolvedDecl := FindMethodDecl(Result.Name, 'Create');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if SameText(AExpr.Name, 'Format') then
|
||||
begin
|
||||
if AExpr.Args.Count < 1 then
|
||||
|
|
|
|||
|
|
@ -1169,6 +1169,11 @@ begin
|
|||
Sym := TSymbol.Create('Abs', skFunction, FTypeInteger); Define(Sym);
|
||||
{ MethodAddress(Obj, Name) — published-method lookup via typeinfo chain }
|
||||
Sym := TSymbol.Create('MethodAddress', skFunction, FTypePointer); Define(Sym);
|
||||
{ ClassCreate(Cls, ...args) — runtime construction from a metaclass value.
|
||||
Calls _ClassCreate to allocate + install vtable, then invokes the
|
||||
constructor on Cls.BaseClass with the supplied args. Result type is
|
||||
the BaseClass; resolved by AnalyseFuncCallExpr in uSemantic. }
|
||||
Sym := TSymbol.Create('ClassCreate', skFunction, FTypePointer); Define(Sym);
|
||||
{ Inc/Dec — in-place increment/decrement (var param, 1 or 2 args) }
|
||||
Sym := TSymbol.Create('Inc', skProcedure, nil); Define(Sym);
|
||||
Sym := TSymbol.Create('Dec', skProcedure, nil); Define(Sym);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,13 @@ type
|
|||
procedure TestCodegen_ClassIdent_EmitsTypeinfo;
|
||||
procedure TestCodegen_MetaClassVar_StorelTypeinfo;
|
||||
procedure TestCodegen_MetaClassEquality_UsesCEQL;
|
||||
|
||||
{ ClassCreate builtin (Step 11e): runtime construction via a
|
||||
metaclass value. Lowers to '_ClassCreate(Cls)' followed by a
|
||||
static call to the resolved constructor. }
|
||||
procedure TestSemantic_ClassCreate_RejectsNonMetaclassFirstArg;
|
||||
procedure TestCodegen_ClassCreate_EmitsAllocAndCtorCall;
|
||||
procedure TestCodegen_ClassCreate_NoCtor_OnlyAllocCalled;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -381,6 +388,64 @@ begin
|
|||
Pos('ceql', IR) > 0);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ ClassCreate builtin }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TClassOfTests.TestSemantic_ClassCreate_RejectsNonMetaclassFirstArg;
|
||||
begin
|
||||
AnalyseExpectError(
|
||||
'program P;' + LineEnding +
|
||||
'type TFoo = class(TObject) end;' + LineEnding +
|
||||
'var F: TFoo;' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' F := ClassCreate(F)' + LineEnding +
|
||||
'end.'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TClassOfTests.TestCodegen_ClassCreate_EmitsAllocAndCtorCall;
|
||||
const
|
||||
Src =
|
||||
'program P;' + LineEnding +
|
||||
'type' + LineEnding +
|
||||
' TFoo = class(TObject)' + LineEnding +
|
||||
' Value: Integer;' + LineEnding +
|
||||
' constructor Create(N: Integer);' + LineEnding +
|
||||
' end;' + LineEnding +
|
||||
'constructor TFoo.Create(N: Integer);' + LineEnding +
|
||||
'begin Self.Value := N end;' + LineEnding +
|
||||
'var C: class of TFoo; F: TFoo;' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' C := TFoo;' + LineEnding +
|
||||
' F := ClassCreate(C, 7)' + LineEnding +
|
||||
'end.';
|
||||
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);
|
||||
end;
|
||||
|
||||
procedure TClassOfTests.TestCodegen_ClassCreate_NoCtor_OnlyAllocCalled;
|
||||
const
|
||||
Src =
|
||||
'program P;' + LineEnding +
|
||||
'type TFoo = class(TObject) end;' + LineEnding +
|
||||
'var C: class of TFoo; F: TFoo;' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' C := TFoo;' + LineEnding +
|
||||
' F := ClassCreate(C)' + LineEnding +
|
||||
'end.';
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(Src);
|
||||
AssertTrue('emits call to $_ClassCreate', Pos('call $_ClassCreate(', IR) > 0);
|
||||
AssertEquals('no constructor call emitted when class declares none',
|
||||
0, Pos('TFoo_Create', IR));
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TClassOfTests);
|
||||
end.
|
||||
|
|
|
|||
Loading…
Reference in a new issue