fix(semantic): read a default array property through a property result
Recv.Prop[idx] where Prop is a (non-indexed) property whose type carries a default array property previously dropped the index: the parser folds the trailing [idx] into the field-access PropIndexExpr, and the property-read branches in AnalyseFieldAccess returned the property's type without consuming it (Recv.Prop[idx] resolved to the property's class type, e.g. TStringList). A field with a default property already worked via FindIndexedProperty; a property read did not. Add TryLowerDefaultPropertyIndex: when a resolved read property is non-indexed but has a trailing index and its result type has a default property, rewrite Recv.Prop[idx] to (Recv.Prop).Default[idx] — moving the property read into an inner field-access and re-pointing the node at the default property, then re-analysing (which lands on the existing chained indexed-property path). Wired into the field-backed and method-backed property branches (chained receiver, implicit Self, and variable receiver). Reads only; writing through a property-result default property is a separate gap (the write path reports the property read-only).
This commit is contained in:
parent
bcb2f68222
commit
9024ffc766
|
|
@ -315,6 +315,8 @@ type
|
||||||
function AnalyseExpr(AExpr: TASTExpr): TTypeDesc;
|
function AnalyseExpr(AExpr: TASTExpr): TTypeDesc;
|
||||||
function AnalyseBinaryExpr(ABin: TBinaryExpr): TTypeDesc;
|
function AnalyseBinaryExpr(ABin: TBinaryExpr): TTypeDesc;
|
||||||
function AnalyseFieldAccess(AAccess: TFieldAccessExpr): TTypeDesc;
|
function AnalyseFieldAccess(AAccess: TFieldAccessExpr): TTypeDesc;
|
||||||
|
function TryLowerDefaultPropertyIndex(AAccess: TFieldAccessExpr;
|
||||||
|
APropInfo: TPropertyInfo): TTypeDesc;
|
||||||
function AnalyseIsExpr(AExpr: TIsExpr): TTypeDesc;
|
function AnalyseIsExpr(AExpr: TIsExpr): TTypeDesc;
|
||||||
function AnalyseAsExpr(AExpr: TAsExpr): TTypeDesc;
|
function AnalyseAsExpr(AExpr: TAsExpr): TTypeDesc;
|
||||||
function AnalyseSupportsExpr(AExpr: TSupportsExpr): TTypeDesc;
|
function AnalyseSupportsExpr(AExpr: TSupportsExpr): TTypeDesc;
|
||||||
|
|
@ -10652,6 +10654,45 @@ begin
|
||||||
AExpr.ResolvedType := Result;
|
AExpr.ResolvedType := Result;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ Default array property on a property-read result. The parser folds the
|
||||||
|
trailing '[idx]' of 'Recv.Prop[idx]' into AAccess.PropIndexExpr, so when Prop
|
||||||
|
is a (non-indexed) property whose type has a `default` indexed property, the
|
||||||
|
read above would resolve Recv.Prop and drop the index. Rewrite it as
|
||||||
|
'(Recv.Prop).Default[idx]': move the property read into a fresh inner
|
||||||
|
field-access (the getter, no index) and re-point AAccess at the default
|
||||||
|
property, then re-analyse — which lands on the chained indexed-property path.
|
||||||
|
Returns the resolved element type, or nil when no lowering applies (the
|
||||||
|
caller then handles Prop as before). APropInfo is the already-resolved read
|
||||||
|
property. }
|
||||||
|
function TSemanticAnalyser.TryLowerDefaultPropertyIndex(
|
||||||
|
AAccess: TFieldAccessExpr; APropInfo: TPropertyInfo): TTypeDesc;
|
||||||
|
var
|
||||||
|
Inner: TFieldAccessExpr;
|
||||||
|
DefProp: TPropertyInfo;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
if (APropInfo.IndexParamName <> '') or (AAccess.PropIndexExpr = nil) then
|
||||||
|
Exit;
|
||||||
|
if not (APropInfo.TypeDesc.Kind in [tyRecord, tyClass]) then
|
||||||
|
Exit;
|
||||||
|
DefProp := TRecordTypeDesc(APropInfo.TypeDesc).FindDefaultProperty();
|
||||||
|
if (DefProp = nil) or (DefProp.ReadMethod = '') then
|
||||||
|
Exit;
|
||||||
|
{ Inner = the property getter without the index; carries the receiver. }
|
||||||
|
Inner := TFieldAccessExpr.Create();
|
||||||
|
Inner.Line := AAccess.Line;
|
||||||
|
Inner.Col := AAccess.Col;
|
||||||
|
Inner.Base := AAccess.Base; { transfer ownership (may be nil) }
|
||||||
|
Inner.RecordName := AAccess.RecordName;
|
||||||
|
Inner.FieldName := AAccess.FieldName;
|
||||||
|
{ Re-point AAccess at the default property on that getter result; the
|
||||||
|
PropIndexExpr ('[idx]') is left in place as its index. }
|
||||||
|
AAccess.Base := Inner;
|
||||||
|
AAccess.RecordName := '';
|
||||||
|
AAccess.FieldName := DefProp.Name;
|
||||||
|
Result := AnalyseFieldAccess(AAccess);
|
||||||
|
end;
|
||||||
|
|
||||||
function TSemanticAnalyser.AnalyseFieldAccess(AAccess: TFieldAccessExpr): TTypeDesc;
|
function TSemanticAnalyser.AnalyseFieldAccess(AAccess: TFieldAccessExpr): TTypeDesc;
|
||||||
var
|
var
|
||||||
RecSym: TSymbol;
|
RecSym: TSymbol;
|
||||||
|
|
@ -10694,6 +10735,9 @@ begin
|
||||||
PropInfo := RT.FindProperty(AAccess.FieldName);
|
PropInfo := RT.FindProperty(AAccess.FieldName);
|
||||||
if (PropInfo <> nil) and (PropInfo.ReadField <> '') then
|
if (PropInfo <> nil) and (PropInfo.ReadField <> '') then
|
||||||
begin
|
begin
|
||||||
|
Result := TryLowerDefaultPropertyIndex(AAccess, PropInfo);
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
AAccess.FieldName := PropInfo.ReadField;
|
AAccess.FieldName := PropInfo.ReadField;
|
||||||
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
||||||
Exit(PropInfo.TypeDesc);
|
Exit(PropInfo.TypeDesc);
|
||||||
|
|
@ -10702,6 +10746,9 @@ begin
|
||||||
'[idx]' to AAccess.PropIndexExpr when it parses 'Base.Prop[idx]'). }
|
'[idx]' to AAccess.PropIndexExpr when it parses 'Base.Prop[idx]'). }
|
||||||
if (PropInfo <> nil) and (PropInfo.ReadMethod <> '') then
|
if (PropInfo <> nil) and (PropInfo.ReadMethod <> '') then
|
||||||
begin
|
begin
|
||||||
|
Result := TryLowerDefaultPropertyIndex(AAccess, PropInfo);
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
if PropInfo.IndexParamName <> '' then
|
if PropInfo.IndexParamName <> '' then
|
||||||
begin
|
begin
|
||||||
if AAccess.PropIndexExpr = nil then
|
if AAccess.PropIndexExpr = nil then
|
||||||
|
|
@ -10838,6 +10885,9 @@ begin
|
||||||
begin
|
begin
|
||||||
if PropInfo.ReadField <> '' then
|
if PropInfo.ReadField <> '' then
|
||||||
begin
|
begin
|
||||||
|
Result := TryLowerDefaultPropertyIndex(AAccess, PropInfo);
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
AAccess.FieldName := PropInfo.ReadField;
|
AAccess.FieldName := PropInfo.ReadField;
|
||||||
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
||||||
Result := PropInfo.TypeDesc;
|
Result := PropInfo.TypeDesc;
|
||||||
|
|
@ -10847,6 +10897,9 @@ begin
|
||||||
else if PropInfo.ReadMethod <> '' then
|
else if PropInfo.ReadMethod <> '' then
|
||||||
begin
|
begin
|
||||||
{ Method-backed read (includes indexed properties) }
|
{ Method-backed read (includes indexed properties) }
|
||||||
|
Result := TryLowerDefaultPropertyIndex(AAccess, PropInfo);
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
if PropInfo.IndexParamName <> '' then
|
if PropInfo.IndexParamName <> '' then
|
||||||
begin
|
begin
|
||||||
if AAccess.PropIndexExpr = nil then
|
if AAccess.PropIndexExpr = nil then
|
||||||
|
|
@ -11073,6 +11126,9 @@ begin
|
||||||
if PropInfo.ReadField <> '' then
|
if PropInfo.ReadField <> '' then
|
||||||
begin
|
begin
|
||||||
{ Field-backed read: redirect to the backing field }
|
{ Field-backed read: redirect to the backing field }
|
||||||
|
Result := TryLowerDefaultPropertyIndex(AAccess, PropInfo);
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
AAccess.FieldName := PropInfo.ReadField;
|
AAccess.FieldName := PropInfo.ReadField;
|
||||||
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
||||||
Result := PropInfo.TypeDesc;
|
Result := PropInfo.TypeDesc;
|
||||||
|
|
@ -11082,6 +11138,9 @@ begin
|
||||||
else if PropInfo.ReadMethod <> '' then
|
else if PropInfo.ReadMethod <> '' then
|
||||||
begin
|
begin
|
||||||
{ Method-backed read (includes indexed properties) }
|
{ Method-backed read (includes indexed properties) }
|
||||||
|
Result := TryLowerDefaultPropertyIndex(AAccess, PropInfo);
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
if PropInfo.IndexParamName <> '' then
|
if PropInfo.IndexParamName <> '' then
|
||||||
begin
|
begin
|
||||||
if AAccess.PropIndexExpr = nil then
|
if AAccess.PropIndexExpr = nil then
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ type
|
||||||
procedure TestRun_DefaultProperty_ReadWrite;
|
procedure TestRun_DefaultProperty_ReadWrite;
|
||||||
procedure TestRun_DefaultProperty_StringElement;
|
procedure TestRun_DefaultProperty_StringElement;
|
||||||
procedure TestRun_DefaultProperty_Inherited;
|
procedure TestRun_DefaultProperty_Inherited;
|
||||||
|
{ Default array property read through a property-read result:
|
||||||
|
Obj.Prop[I] where Prop returns a class carrying a default property. }
|
||||||
|
procedure TestRun_DefaultProperty_ViaFieldBackedProperty;
|
||||||
|
procedure TestRun_DefaultProperty_ViaMethodBackedProperty;
|
||||||
{ Static-array field accessed from inside a method (implicit Self). }
|
{ Static-array field accessed from inside a method (implicit Self). }
|
||||||
procedure TestRun_StaticArrayField_ReadInMethod;
|
procedure TestRun_StaticArrayField_ReadInMethod;
|
||||||
procedure TestRun_StaticArrayField_WriteInMethod;
|
procedure TestRun_StaticArrayField_WriteInMethod;
|
||||||
|
|
@ -294,6 +298,79 @@ begin
|
||||||
AssertRunsOnAll(SrcDefaultInherited, '12' + LE, 0);
|
AssertRunsOnAll(SrcDefaultInherited, '12' + LE, 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
const
|
||||||
|
{ Obj.Prop[I] where Prop is a field-backed property returning a class with a
|
||||||
|
default array property — reads through the property result. }
|
||||||
|
SrcDefaultViaFieldProp = '''
|
||||||
|
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;
|
||||||
|
property Vec: TVec read FVec;
|
||||||
|
end;
|
||||||
|
constructor TOwner.Create;
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FVec := TVec.Create;
|
||||||
|
FVec.Put(0, 3); FVec.Put(1, 4);
|
||||||
|
end;
|
||||||
|
var o: TOwner;
|
||||||
|
begin
|
||||||
|
o := TOwner.Create;
|
||||||
|
WriteLn(o.Vec[0] + o.Vec[1]);
|
||||||
|
o := nil
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
|
||||||
|
{ Same, but Prop is a method-backed (getter) property. }
|
||||||
|
SrcDefaultViaMethodProp = '''
|
||||||
|
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;
|
||||||
|
function GetVec: TVec; begin Result := FVec end;
|
||||||
|
property Vec: TVec read GetVec;
|
||||||
|
end;
|
||||||
|
constructor TOwner.Create;
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FVec := TVec.Create;
|
||||||
|
FVec.Put(0, 5); FVec.Put(1, 4);
|
||||||
|
end;
|
||||||
|
var o: TOwner;
|
||||||
|
begin
|
||||||
|
o := TOwner.Create;
|
||||||
|
WriteLn(o.Vec[0] + o.Vec[1]);
|
||||||
|
o := nil
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
|
||||||
|
procedure TE2EPropertyTests.TestRun_DefaultProperty_ViaFieldBackedProperty;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
AssertRunsOnAll(SrcDefaultViaFieldProp, '7' + LE, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TE2EPropertyTests.TestRun_DefaultProperty_ViaMethodBackedProperty;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
AssertRunsOnAll(SrcDefaultViaMethodProp, '9' + LE, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TE2EPropertyTests);
|
RegisterTest(TE2EPropertyTests);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue