feat(oop): support 'inherited Method()' in expression position
`inherited` previously parsed only as a statement, so calling an inherited
FUNCTION and using its result failed to parse:
Result := inherited Value() + 100; -> "Parse error: Expected expression"
This blocked the normal OOP pattern of an overriding function extending its
parent's result.
Adds the expression form alongside the existing statement form:
* uAST: new TInheritedCallExpr (sibling of TInheritedCallStmt) + CloneExpr case.
* uParser: tkInherited handled in ParseFactor (primary expression).
* uSemantic: AnalyseInheritedCallExpr resolves to the parent method (must be a
non-void function) and sets ResolvedType to the return type.
* Codegen: static (non-virtual) call to the parent slot, result returned as a
value. QBE EmitInheritedCallExpr; native EmitInheritedCallSeq (shared by the
statement and expression forms) leaves the result in %rax/%xmm0.
* uUnitInterfaceIO: 'inhc' encode/decode so inherited-expr in an inline method
body round-trips through the .bif unit-interface cache; bif-coverage.status
marks the two fields serialise (bif-coverage OK).
* docs/grammar.ebnf + docs/language-rationale.adoc updated (same commit).
Found by the e2e test-hardening sweep of the inheritance feature cluster.
Regression: TE2EMiscTests.TestRun_InheritedFunctionCall_InExpression covers a
value-returning inherited call and an inherited call with an argument, run on
BOTH backends via AssertRunsOnAll.
All three fixpoints (FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK) and
the full suite (3222 tests) pass.
This commit is contained in:
parent
5f340f0956
commit
74f71023da
|
|
@ -491,6 +491,10 @@ type
|
|||
dispatch to the parent method (no vtable); Self is the current method's
|
||||
Self; a value-returning parent stores its result into the Result slot. }
|
||||
procedure EmitInheritedCall(ACall: TInheritedCallStmt);
|
||||
{ Shared static-dispatch sequence (Self + args + direct callq); result in
|
||||
%rax/%xmm0 on return. Used by both inherited forms (stmt + expr). }
|
||||
procedure EmitInheritedCallSeq(MD: TMethodDecl; AArgs: TObjectList;
|
||||
const AName: string);
|
||||
{ Evaluate one method-call argument and push it onto the stack. Scalar and
|
||||
var/out params push one value; interface params push two (itab first, then
|
||||
obj — reversed so the pop loop restores them in the correct register order). }
|
||||
|
|
@ -6983,6 +6987,17 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ `inherited Method(args)` in expression position — static dispatch to the
|
||||
parent slot; result lands in %rax / %xmm0 (the EmitExprToEax contract). }
|
||||
if AExpr is TInheritedCallExpr then
|
||||
begin
|
||||
Self.EmitInheritedCallSeq(
|
||||
TMethodDecl(TInheritedCallExpr(AExpr).ResolvedMethod),
|
||||
TInheritedCallExpr(AExpr).Args,
|
||||
TInheritedCallExpr(AExpr).Name);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ X is TFoo — class type test via _IsInstance / _ImplementsInterface.
|
||||
Result: 0/1 in %eax. }
|
||||
if AExpr is TIsExpr then
|
||||
|
|
@ -7779,35 +7794,41 @@ begin
|
|||
Self.EndCallArgs();
|
||||
end;
|
||||
|
||||
procedure TX86_64Backend.EmitInheritedCall(ACall: TInheritedCallStmt);
|
||||
{ Shared static-dispatch call sequence for both inherited forms. Marshals
|
||||
Self + args and emits the direct callq to the parent method; on return the
|
||||
result (if any) is in %rax / %xmm0 per the SysV ABI. AMD/AArgs/AName come
|
||||
from either TInheritedCallStmt or TInheritedCallExpr. }
|
||||
procedure TX86_64Backend.EmitInheritedCallSeq(MD: TMethodDecl;
|
||||
AArgs: TObjectList; const AName: string);
|
||||
var
|
||||
I: Integer;
|
||||
MD: TMethodDecl;
|
||||
Sym: string;
|
||||
Par: TMethodParam;
|
||||
begin
|
||||
{ `inherited` on TObject (no parent body) is a no-op. }
|
||||
MD := TMethodDecl(ACall.ResolvedMethod);
|
||||
if MD = nil then Exit;
|
||||
Sym := MethodEmitNameNative(MD, MD.OwnerTypeName, ACall.Name);
|
||||
|
||||
{ Evaluate args left-to-right and push them. }
|
||||
Self.BeginCallArgs(MD.Params, ACall.Args);
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
Sym := MethodEmitNameNative(MD, MD.OwnerTypeName, AName);
|
||||
Self.BeginCallArgs(MD.Params, AArgs);
|
||||
for I := 0 to AArgs.Count - 1 do
|
||||
begin
|
||||
Par := TMethodParam(MD.Params.Items[I]);
|
||||
Self.PushCallArg(Par, TASTExpr(ACall.Args.Items[I]), I);
|
||||
Self.PushCallArg(Par, TASTExpr(AArgs.Items[I]), I);
|
||||
end;
|
||||
|
||||
{ Self is the current method's Self slot. }
|
||||
Self.Emit(Format(#9'movq %s, %%r10', [Self.VarOperand('Self')]));
|
||||
{ Pop args into %rsi/%rdx/... (shift by 1 for %rdi = Self).
|
||||
Use slot count so interface args (2 slots each) get two pops. }
|
||||
for I := CountArgSlots(MD.Params) - 1 downto 0 do
|
||||
Self.Emit(#9'popq ' + SysVArgRegs64[I + 1]);
|
||||
Self.Emit(#9'movq %r10, %rdi');
|
||||
Self.Emit(#9'callq ' + Sym);
|
||||
Self.EndCallArgs();
|
||||
end;
|
||||
|
||||
procedure TX86_64Backend.EmitInheritedCall(ACall: TInheritedCallStmt);
|
||||
var
|
||||
MD: TMethodDecl;
|
||||
begin
|
||||
{ `inherited` on TObject (no parent body) is a no-op. }
|
||||
MD := TMethodDecl(ACall.ResolvedMethod);
|
||||
if MD = nil then Exit;
|
||||
Self.EmitInheritedCallSeq(MD, ACall.Args, ACall.Name);
|
||||
|
||||
{ A value-returning parent stores its result into the current Result slot,
|
||||
so `Result := ...` is unnecessary for `inherited F;` to set Result. }
|
||||
|
|
|
|||
|
|
@ -303,6 +303,7 @@ type
|
|||
procedure ReleaseConstStringArgs(AArgs: TObjectList;
|
||||
AArgTemps: TStringList; AParams: TObjectList);
|
||||
procedure EmitInheritedCall(ACall: TInheritedCallStmt);
|
||||
function EmitInheritedCallExpr(ACall: TInheritedCallExpr): string;
|
||||
procedure EmitCaseStmt(AStmt: TCaseStmt);
|
||||
procedure EmitProcCall(ACall: TProcCall);
|
||||
{ Helper for string-mutator built-ins (Delete, SetLength).
|
||||
|
|
@ -6713,6 +6714,62 @@ begin
|
|||
FlushPendingReleases(PMark);
|
||||
end;
|
||||
|
||||
function TCodeGenQBE.EmitInheritedCallExpr(ACall: TInheritedCallExpr): string;
|
||||
var
|
||||
MDecl: TMethodDecl;
|
||||
SelfTemp: string;
|
||||
ArgLine: string;
|
||||
ArgTemp: string;
|
||||
ArgTemp2: string;
|
||||
Par: TMethodParam;
|
||||
QType: string;
|
||||
I: Integer;
|
||||
begin
|
||||
{ Expression form of `inherited Method(args)` — semantic guarantees a
|
||||
non-void function. Same static-dispatch arg marshalling as
|
||||
EmitInheritedCall, but the call result is RETURNED (not stored to
|
||||
%_var_Result). }
|
||||
MDecl := TMethodDecl(ACall.ResolvedMethod);
|
||||
|
||||
SelfTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %%_var_Self', [SelfTemp]));
|
||||
|
||||
ArgLine := Format('l %s', [SelfTemp]);
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
begin
|
||||
Par := TMethodParam(MDecl.Params.Items[I]);
|
||||
if Par.IsVarParam then
|
||||
begin
|
||||
ArgLine := ArgLine + Format(', l %s',
|
||||
[EmitLValueAddr(TASTExpr(ACall.Args.Items[I]))]);
|
||||
Continue;
|
||||
end;
|
||||
if (Par.ResolvedType <> nil) and (Par.ResolvedType.Kind = tyInterface) then
|
||||
begin
|
||||
if TASTExpr(ACall.Args.Items[I]).ResolvedType.Kind = tyClass then
|
||||
begin
|
||||
ArgTemp := EmitExpr(TASTExpr(ACall.Args.Items[I]));
|
||||
ArgTemp2 := '$itab_' +
|
||||
ClassSymName(QBEMangle(TASTExpr(ACall.Args.Items[I]).ResolvedType.Name))
|
||||
+ '_' + QBEMangle(Par.ResolvedType.Name);
|
||||
ArgLine := ArgLine + Format(', l %s, l %s', [ArgTemp, ArgTemp2]);
|
||||
end
|
||||
else
|
||||
ArgLine := ArgLine + InterfaceArgFragment(TASTExpr(ACall.Args.Items[I]));
|
||||
Continue;
|
||||
end;
|
||||
ArgTemp := EmitExpr(TASTExpr(ACall.Args.Items[I]));
|
||||
QType := QbeTypeOf(Par.ResolvedType);
|
||||
ArgTemp := CoerceArg(ArgTemp, TASTExpr(ACall.Args.Items[I]), QType);
|
||||
ArgLine := ArgLine + Format(', %s %s', [QbeParamTypeOf(Par.ResolvedType), ArgTemp]);
|
||||
end;
|
||||
|
||||
QType := QbeTypeOf(MDecl.ResolvedReturnType);
|
||||
Result := AllocTemp();
|
||||
EmitLine(Format(' %s =%s call $%s(%s)',
|
||||
[Result, QType, MethodEmitName(MDecl, MDecl.OwnerTypeName, ACall.Name), ArgLine]));
|
||||
end;
|
||||
|
||||
procedure TCodeGenQBE.EmitParamAllocs(AMethod: TMethodDecl;
|
||||
AClassType: TRecordTypeDesc);
|
||||
var
|
||||
|
|
@ -12222,6 +12279,8 @@ begin
|
|||
Result := EmitAddrOfExpr(TAddrOfExpr(AExpr))
|
||||
else if AExpr is TIsExpr then
|
||||
Result := EmitIsExpr(TIsExpr(AExpr))
|
||||
else if AExpr is TInheritedCallExpr then
|
||||
Result := EmitInheritedCallExpr(TInheritedCallExpr(AExpr))
|
||||
else if AExpr is TAsExpr then
|
||||
Result := EmitAsExpr(TAsExpr(AExpr))
|
||||
else if AExpr is TSupportsExpr then
|
||||
|
|
|
|||
|
|
@ -174,6 +174,21 @@ type
|
|||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ `inherited Method(args)` used in EXPRESSION position (the value of the
|
||||
parent method's result is needed). The statement form is
|
||||
TInheritedCallStmt; this is its expression sibling. Resolved and lowered
|
||||
the same way (static dispatch to the parent slot), but yields a value. }
|
||||
TInheritedCallExpr = class(TASTExpr)
|
||||
public
|
||||
Name: string; { parent method name to call }
|
||||
Args: TObjectList; { owned TASTExpr items }
|
||||
{ Set by uSemantic: }
|
||||
[Unretained] ResolvedParentType: TObject; { TRecordTypeDesc — not owned }
|
||||
[Unretained] ResolvedMethod: TObject; { TMethodDecl — not owned }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ boSlash is the `/` operator: real division, always yields a float.
|
||||
boDiv is the `div` operator: integer division between integer operands. }
|
||||
TBinaryOp = (boAdd, boSub, boMul, boSlash, boDiv, boMod,
|
||||
|
|
@ -1325,6 +1340,20 @@ begin
|
|||
inherited Destroy();
|
||||
end;
|
||||
|
||||
{ TInheritedCallExpr }
|
||||
|
||||
constructor TInheritedCallExpr.Create;
|
||||
begin
|
||||
inherited Create();
|
||||
Args := TObjectList.Create(True);
|
||||
end;
|
||||
|
||||
destructor TInheritedCallExpr.Destroy;
|
||||
begin
|
||||
{ Owned class fields released by ARC field cleanup. }
|
||||
inherited Destroy();
|
||||
end;
|
||||
|
||||
{ TMethodParam }
|
||||
|
||||
destructor TMethodParam.Destroy;
|
||||
|
|
@ -1741,6 +1770,8 @@ var
|
|||
IE: TIdentExpr;
|
||||
FA: TFieldAccessExpr;
|
||||
IsE: TIsExpr;
|
||||
InhE: TInheritedCallExpr;
|
||||
CloneI: Integer;
|
||||
AsE: TAsExpr;
|
||||
SuE: TSupportsExpr;
|
||||
BE: TBinaryExpr;
|
||||
|
|
@ -1822,6 +1853,14 @@ begin
|
|||
IsE.TypeName := TIsExpr(AExpr).TypeName;
|
||||
Result := IsE;
|
||||
end
|
||||
else if AExpr is TInheritedCallExpr then
|
||||
begin
|
||||
InhE := TInheritedCallExpr.Create();
|
||||
InhE.Name := TInheritedCallExpr(AExpr).Name;
|
||||
for CloneI := 0 to TInheritedCallExpr(AExpr).Args.Count - 1 do
|
||||
InhE.Args.Add(CloneExpr(TASTExpr(TInheritedCallExpr(AExpr).Args.Items[CloneI])));
|
||||
Result := InhE;
|
||||
end
|
||||
else if AExpr is TAsExpr then
|
||||
begin
|
||||
AsE := TAsExpr.Create();
|
||||
|
|
|
|||
|
|
@ -3803,8 +3803,39 @@ var
|
|||
IndCallNode: TIndirectFuncCallExpr;
|
||||
ParseIntV: Int64;
|
||||
ParseIntFlag: Boolean;
|
||||
InhNode: TInheritedCallExpr;
|
||||
begin
|
||||
case FCurrent.Kind of
|
||||
tkInherited:
|
||||
begin
|
||||
{ `inherited Method(args)` in expression position — the parent
|
||||
method's result value is needed. Mirrors ParseInheritedStmt. }
|
||||
InhNode := TInheritedCallExpr.Create();
|
||||
InhNode.Line := FCurrent.Line;
|
||||
InhNode.Col := FCurrent.Col;
|
||||
Advance(); { consume 'inherited' }
|
||||
if not Check(tkIdent) then
|
||||
raise EParseError.Create(Format(
|
||||
'Expected method name after ''inherited'' at line %d col %d in %s',
|
||||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||
InhNode.Name := FCurrent.Value;
|
||||
Advance();
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
Advance();
|
||||
if not Check(tkRParen) then
|
||||
begin
|
||||
InhNode.Args.Add(Self.ParseExpr());
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance();
|
||||
InhNode.Args.Add(Self.ParseExpr());
|
||||
end;
|
||||
end;
|
||||
Expect(tkRParen);
|
||||
end;
|
||||
Result := InhNode;
|
||||
end;
|
||||
tkAt:
|
||||
begin
|
||||
AddrNode := TAddrOfExpr.Create();
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ type
|
|||
function ResolveIntfMethodReturn(AIntf: TInterfaceTypeDesc;
|
||||
const AMethName: string): TTypeDesc;
|
||||
procedure AnalyseInheritedCall(ACall: TInheritedCallStmt);
|
||||
function AnalyseInheritedCallExpr(ACall: TInheritedCallExpr): TTypeDesc;
|
||||
procedure AnalyseCaseStmt(AStmt: TCaseStmt);
|
||||
function AnalyseMethodCallExpr(AExpr: TMethodCallExpr): TTypeDesc;
|
||||
function AnalyseFuncCallExpr(AExpr: TFuncCallExpr): TTypeDesc;
|
||||
|
|
@ -6320,6 +6321,63 @@ begin
|
|||
ACall.ResolvedMethod := MDecl;
|
||||
end;
|
||||
|
||||
function TSemanticAnalyser.AnalyseInheritedCallExpr(
|
||||
ACall: TInheritedCallExpr): TTypeDesc;
|
||||
var
|
||||
ParentType: TRecordTypeDesc;
|
||||
MDecl: TMethodDecl;
|
||||
ArgType: TTypeDesc;
|
||||
Par: TMethodParam;
|
||||
I: Integer;
|
||||
begin
|
||||
{ Mirrors AnalyseInheritedCall (the statement form) but the call is used as
|
||||
a VALUE, so the parent method must be a function (non-void return) and the
|
||||
result type becomes this expression's ResolvedType. }
|
||||
if FCurrentClass = nil then
|
||||
SemanticError('''inherited'' used outside a method body',
|
||||
ACall.Line, ACall.Col);
|
||||
if FCurrentClass.Parent = nil then
|
||||
SemanticError(
|
||||
Format('Class ''%s'' has no parent; ''inherited'' is not valid',
|
||||
[FCurrentClass.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
|
||||
ParentType := FCurrentClass.Parent;
|
||||
MDecl := FindMethodDecl(ParentType.Name, ACall.Name);
|
||||
if MDecl = nil then
|
||||
SemanticError(
|
||||
Format('Parent class ''%s'' has no method ''%s''',
|
||||
[ParentType.Name, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
|
||||
if (MDecl.ResolvedReturnType = nil) or
|
||||
(MDecl.ResolvedReturnType.Kind = tyVoid) then
|
||||
SemanticError(
|
||||
Format('inherited ''%s'' is a procedure and has no value',
|
||||
[ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
|
||||
if ACall.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[ParentType.Name, ACall.Name, MDecl.Params.Count, ACall.Args.Count]),
|
||||
ACall.Line, ACall.Col);
|
||||
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
begin
|
||||
ArgType := AnalyseExpr(TASTExpr(ACall.Args.Items[I]));
|
||||
Par := TMethodParam(MDecl.Params.Items[I]);
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of inherited ''%s''', [I + 1, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
end;
|
||||
|
||||
ACall.ResolvedParentType := ParentType;
|
||||
ACall.ResolvedMethod := MDecl;
|
||||
ACall.ResolvedType := MDecl.ResolvedReturnType;
|
||||
Result := MDecl.ResolvedReturnType;
|
||||
end;
|
||||
|
||||
{ Effective float type of a float-builtin argument: float types pass
|
||||
through unchanged; integer-family arguments implicitly widen to Double
|
||||
(FPC/Delphi semantics, consistent with Blaise's int→float assignment
|
||||
|
|
@ -8836,6 +8894,8 @@ begin
|
|||
Result := AnalyseBinaryExpr(TBinaryExpr(AExpr))
|
||||
else if AExpr is TIsExpr then
|
||||
Result := AnalyseIsExpr(TIsExpr(AExpr))
|
||||
else if AExpr is TInheritedCallExpr then
|
||||
Result := AnalyseInheritedCallExpr(TInheritedCallExpr(AExpr))
|
||||
else if AExpr is TAsExpr then
|
||||
Result := AnalyseAsExpr(TAsExpr(AExpr))
|
||||
else if AExpr is TSupportsExpr then
|
||||
|
|
|
|||
|
|
@ -731,6 +731,10 @@ begin
|
|||
EncodeExpr(TSupportsExpr(AE).Obj) +
|
||||
EncodeLpstr(TSupportsExpr(AE).IntfTypeName) +
|
||||
EncodeLpstr(TSupportsExpr(AE).OutVarName)
|
||||
else if AE is TInheritedCallExpr then
|
||||
Result := EncodeLpstr('inhc') +
|
||||
EncodeLpstr(TInheritedCallExpr(AE).Name) +
|
||||
EncodeExprList(TInheritedCallExpr(AE).Args)
|
||||
else
|
||||
raise EIfaceFormatError.Create(
|
||||
'EncodeExpr: unhandled expression node ' + AE.ClassName);
|
||||
|
|
@ -1289,6 +1293,7 @@ var
|
|||
IsE: TIsExpr;
|
||||
AsE: TAsExpr;
|
||||
SuE: TSupportsExpr;
|
||||
InhE: TInheritedCallExpr;
|
||||
begin
|
||||
Kind := ReadLpstrAt(AText, APos);
|
||||
if Kind = 'nil' then begin Result := nil; Exit; end;
|
||||
|
|
@ -1419,6 +1424,13 @@ begin
|
|||
SuE.OutVarName := ReadLpstrAt(AText, APos);
|
||||
Result := SuE;
|
||||
end
|
||||
else if Kind = 'inhc' then
|
||||
begin
|
||||
InhE := TInheritedCallExpr.Create();
|
||||
InhE.Name := ReadLpstrAt(AText, APos);
|
||||
ReadExprList(AText, APos, InhE.Args);
|
||||
Result := InhE;
|
||||
end
|
||||
else
|
||||
raise EIfaceFormatError.Create(
|
||||
'ReadExpr: unknown expression kind ''' + Kind + '''');
|
||||
|
|
|
|||
|
|
@ -99,6 +99,11 @@ type
|
|||
`loadl $Obj; add .., off`, producing a garbage address that
|
||||
segfaulted when dereferenced (e.g. passed to memcpy). }
|
||||
procedure TestRun_AddrOfClassFieldDynArrayElem_LoadsInstance;
|
||||
|
||||
{ inherited Method() in EXPRESSION position — calling an inherited
|
||||
function and using its result (Result := inherited F() + ...). Was a
|
||||
parser gap (inherited only worked as a statement). }
|
||||
procedure TestRun_InheritedFunctionCall_InExpression;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1080,6 +1085,40 @@ begin
|
|||
AssertRunsOnAll(SrcAddrClassFieldDynArr, 'Hi!!' + LE, 0);
|
||||
end;
|
||||
|
||||
const
|
||||
{ inherited as an expression: the overriding function adds to the parent's
|
||||
result. Exercises a value-returning inherited call plus inherited with an
|
||||
argument (inherited Scale(F)). }
|
||||
SrcInheritedExprCall = '''
|
||||
program Prg;
|
||||
type
|
||||
TBase = class
|
||||
function V: Integer; virtual;
|
||||
begin Result := 5 end;
|
||||
function Scale(F: Integer): Integer; virtual;
|
||||
begin Result := F * 10 end;
|
||||
end;
|
||||
TDerived = class(TBase)
|
||||
function V: Integer; override;
|
||||
begin Result := inherited V() + 100 end;
|
||||
function Scale(F: Integer): Integer; override;
|
||||
begin Result := inherited Scale(F) + 1 end;
|
||||
end;
|
||||
var D: TDerived;
|
||||
begin
|
||||
D := TDerived.Create();
|
||||
WriteLn(D.V());
|
||||
WriteLn(D.Scale(3));
|
||||
D.Free()
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2EMiscTests.TestRun_InheritedFunctionCall_InExpression;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcInheritedExprCall, '105' + LE + '31' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EMiscTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -916,6 +916,7 @@ Factor
|
|||
| ArrayLiteral
|
||||
| AddrOf
|
||||
| NOT Factor (* Boolean or bitwise NOT *)
|
||||
| INHERITED IDENT [ LPAREN [ ArgList ] RPAREN ] (* inherited function call in expr position *)
|
||||
| "Supports" LPAREN Expr COMMA IDENT [ COMMA IDENT ] RPAREN (* compiler intrinsic *)
|
||||
| IDENT DOT IDENT [ LBRACKET Expr RBRACKET ] (* indexed property read *)
|
||||
LPAREN [ ArgList ] RPAREN (* constructor/method call *)
|
||||
|
|
|
|||
|
|
@ -1970,6 +1970,37 @@ method must repeat any defaults it wants to expose. This matches
|
|||
Delphi semantics and avoids surprising callers of an override that the
|
||||
defaults silently changed underneath them.
|
||||
|
||||
=== `inherited` in expression position
|
||||
|
||||
`inherited Method(args)` is accepted both as a statement and as an
|
||||
*expression*. The statement form (`inherited Setup();`) calls the
|
||||
parent method for its side effects; the expression form lets an
|
||||
overriding *function* build on its parent's result:
|
||||
|
||||
[source,pascal]
|
||||
----
|
||||
function TDerived.Value: Integer; override;
|
||||
begin
|
||||
Result := inherited Value() + 100; // call parent, then extend
|
||||
end;
|
||||
----
|
||||
|
||||
Both forms lower to the same thing — a *static* (non-virtual) call to
|
||||
the parent's method slot, bypassing vtable dispatch (that is the whole
|
||||
point of `inherited`: reach the specific parent implementation, not
|
||||
re-dispatch to `Self`). The only difference is what happens to the
|
||||
result: the statement form stores it into the current method's `Result`
|
||||
slot (so a bare `inherited F;` still sets `Result`), while the
|
||||
expression form yields the value to the surrounding expression.
|
||||
|
||||
The expression form was added because the statement-only form forced an
|
||||
awkward two-step (`inherited F; X := Result + 1;`) and could not be
|
||||
used inside a larger expression at all. AST: `TInheritedCallExpr`
|
||||
(the sibling of `TInheritedCallStmt`); both resolve and lower through
|
||||
the same parent-method lookup and the same static-dispatch call
|
||||
sequence (`EmitInheritedCallSeq` on native, `EmitInheritedCallExpr` on
|
||||
QBE).
|
||||
|
||||
=== Overload resolution and tie-break
|
||||
|
||||
The arity filter in `ResolveStandaloneOverload` and
|
||||
|
|
|
|||
|
|
@ -240,6 +240,10 @@ TMethodCallStmt.IsProcFieldCall safe
|
|||
TInheritedCallStmt.Name serialise
|
||||
TInheritedCallStmt.Args serialise
|
||||
|
||||
# TInheritedCallExpr (uAST.pas:183)
|
||||
TInheritedCallExpr.Name serialise
|
||||
TInheritedCallExpr.Args serialise
|
||||
|
||||
# TMethodCallExpr (uAST.pas:803)
|
||||
TMethodCallExpr.ObjectName serialise
|
||||
TMethodCallExpr.Name serialise
|
||||
|
|
|
|||
Loading…
Reference in a new issue