feat(semantic+codegen): implicit-Self write through a default array property
Field[I] := V inside a method, where Field is a class field of the current class carrying a writable default array property, reported "Undeclared variable 'Field'": AnalyseStaticSubscriptAssign's implicit-Self branch only handled array-typed fields, so a class field with a default property fell through. Extend that branch: when the Self field is a class with a writable default property, set IsImplicitSelf + ImplicitFieldInfo + PropWriteInfo and lower to the default property's setter (mirroring the variable-receiver path). Both codegen backends gain an implicit-Self receiver in the static-subscript property-write path: load Self, reach the field slot, dereference to the field's object, then call the setter. (The implicit-Self read Field[I] already worked.) e2e test writes and reads a default property through a Self field from inside methods, verified on QBE and native.
This commit is contained in:
parent
1bd52ec39a
commit
dc3b7a1c2e
|
|
@ -12701,12 +12701,24 @@ begin
|
|||
Self.Emit(#9'pushq %rax');
|
||||
Self.EmitExprToEax(SSA.IndexExpr); { index }
|
||||
Self.Emit(#9'pushq %rax');
|
||||
if Self.IsLocal(SSA.ArrayName) then
|
||||
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(SSA.ArrayName)]))
|
||||
if SSA.IsImplicitSelf then
|
||||
begin
|
||||
{ Self.Field[I] := V — load Self, reach the field slot, deref to the
|
||||
field's class object (the setter receiver). }
|
||||
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand('Self')]));
|
||||
if SSA.ImplicitFieldInfo.Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%rdi', [SSA.ImplicitFieldInfo.Offset]));
|
||||
Self.Emit(#9'movq (%rdi), %rdi');
|
||||
end
|
||||
else
|
||||
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [SSA.ArrayName]));
|
||||
if SSA.IsVarParam then
|
||||
Self.Emit(#9'movq (%rdi), %rdi'); { var-param: slot -> instance }
|
||||
begin
|
||||
if Self.IsLocal(SSA.ArrayName) then
|
||||
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(SSA.ArrayName)]))
|
||||
else
|
||||
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [SSA.ArrayName]));
|
||||
if SSA.IsVarParam then
|
||||
Self.Emit(#9'movq (%rdi), %rdi'); { var-param: slot -> instance }
|
||||
end;
|
||||
Self.Emit(#9'popq %rsi'); { index }
|
||||
Self.Emit(#9'popq %rdx'); { value }
|
||||
Self.EmitPropAccessorCallNative(SSA.PropOwnerType,
|
||||
|
|
|
|||
|
|
@ -14703,14 +14703,34 @@ begin
|
|||
if AStmt.PropWriteInfo <> nil then
|
||||
begin
|
||||
DefProp := TPropertyInfo(AStmt.PropWriteInfo);
|
||||
RecvTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[RecvTemp, VarRef(AStmt.ArrayName, AStmt.IsGlobal)]));
|
||||
if AStmt.IsVarParam then
|
||||
if AStmt.IsImplicitSelf then
|
||||
begin
|
||||
{ Self.Field[I] := V — load Self, reach the field slot, deref to the
|
||||
field's class object (the setter receiver). }
|
||||
RecvTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %%_var_Self', [RecvTemp]));
|
||||
if AStmt.ImplicitFieldInfo.Offset > 0 then
|
||||
begin
|
||||
ElemPtr := AllocTemp();
|
||||
EmitLine(Format(' %s =l add %s, %d',
|
||||
[ElemPtr, RecvTemp, AStmt.ImplicitFieldInfo.Offset]));
|
||||
RecvTemp := ElemPtr;
|
||||
end;
|
||||
ElemPtr := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [ElemPtr, RecvTemp]));
|
||||
RecvTemp := ElemPtr;
|
||||
end
|
||||
else
|
||||
begin
|
||||
RecvTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[RecvTemp, VarRef(AStmt.ArrayName, AStmt.IsGlobal)]));
|
||||
if AStmt.IsVarParam then
|
||||
begin
|
||||
ElemPtr := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [ElemPtr, RecvTemp]));
|
||||
RecvTemp := ElemPtr;
|
||||
end;
|
||||
end;
|
||||
IdxW := EmitExpr(AStmt.IndexExpr);
|
||||
IdxQType := QbeTypeOf(DefProp.IndexTypeDesc);
|
||||
|
|
|
|||
|
|
@ -12115,6 +12115,31 @@ begin
|
|||
Format('''%s'' element', [AStmt.ArrayName]), AStmt.Line, AStmt.Col);
|
||||
Exit;
|
||||
end;
|
||||
{ Implicit Self.Field[I] := V where Field is a class with a writable
|
||||
default array property — lower to its setter on the field's object. }
|
||||
if (BaseInfo <> nil) and (BaseInfo.TypeDesc <> nil) and
|
||||
(BaseInfo.TypeDesc.Kind = tyClass) then
|
||||
begin
|
||||
DefProp := TRecordTypeDesc(BaseInfo.TypeDesc).FindDefaultProperty();
|
||||
if (DefProp <> nil) and (DefProp.WriteMethod <> '') then
|
||||
begin
|
||||
AStmt.IsImplicitSelf := True;
|
||||
AStmt.ImplicitFieldInfo := BaseInfo;
|
||||
AStmt.PropWriteInfo := DefProp;
|
||||
AStmt.PropOwnerType := PropAccessorOwner(
|
||||
TRecordTypeDesc(BaseInfo.TypeDesc).Name, DefProp.WriteMethod);
|
||||
AStmt.PropAccessorVSlot := PropAccessorVSlot(
|
||||
TRecordTypeDesc(BaseInfo.TypeDesc).Name, DefProp.WriteMethod);
|
||||
IdxType := AnalyseExpr(AStmt.IndexExpr);
|
||||
if DefProp.IndexTypeDesc <> nil then
|
||||
CheckTypesMatch(DefProp.IndexTypeDesc, IdxType, 'default property index',
|
||||
AStmt.Line, AStmt.Col);
|
||||
ValType := AnalyseExpr(AStmt.ValueExpr);
|
||||
CheckTypesMatch(DefProp.TypeDesc, ValType, 'default property assignment',
|
||||
AStmt.Line, AStmt.Col);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Undeclared variable ''%s''', [AStmt.ArrayName]),
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ type
|
|||
Obj.Field[I] := V and Obj.Prop[I] := V. }
|
||||
procedure TestRun_DefaultProperty_WriteThroughField;
|
||||
procedure TestRun_DefaultProperty_WriteThroughProperty;
|
||||
{ Implicit-Self write/read: Field[I] := V / Field[I] inside a method, where
|
||||
Field is a class with a default array property. }
|
||||
procedure TestRun_DefaultProperty_WriteThroughImplicitSelf;
|
||||
{ Static-array field accessed from inside a method (implicit Self). }
|
||||
procedure TestRun_StaticArrayField_ReadInMethod;
|
||||
procedure TestRun_StaticArrayField_WriteInMethod;
|
||||
|
|
@ -439,6 +442,41 @@ begin
|
|||
AssertRunsOnAll(SrcDefaultWriteProp, '9' + LE, 0);
|
||||
end;
|
||||
|
||||
const
|
||||
{ Implicit Self.Field[I] := V and Field[I] read, inside methods. }
|
||||
SrcDefaultWriteImplicitSelf = '''
|
||||
program P;
|
||||
type
|
||||
TVec = class
|
||||
FData: array[0..4] of Integer;
|
||||
function Get(i: Integer): Integer; begin Result := FData[i] end;
|
||||
procedure Put(i: Integer; v: Integer); begin FData[i] := v end;
|
||||
property Items[i: Integer]: Integer read Get write Put; default;
|
||||
end;
|
||||
TOwner = class
|
||||
FVec: TVec;
|
||||
constructor Create;
|
||||
procedure Fill;
|
||||
function Sum: Integer;
|
||||
end;
|
||||
constructor TOwner.Create; begin inherited Create; FVec := TVec.Create end;
|
||||
procedure TOwner.Fill; begin FVec[0] := 3; FVec[1] := 4; end;
|
||||
function TOwner.Sum: Integer; begin Result := FVec[0] + FVec[1]; end;
|
||||
var o: TOwner;
|
||||
begin
|
||||
o := TOwner.Create;
|
||||
o.Fill();
|
||||
WriteLn(o.Sum());
|
||||
o := nil
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2EPropertyTests.TestRun_DefaultProperty_WriteThroughImplicitSelf;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcDefaultWriteImplicitSelf, '7' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EPropertyTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue