fix(bif): serialise the overload directive across the unit-interface boundary
Imported methods always had IsOverload=False: the .bif method layouts
round-tripped IsVirtual/IsOverride/IsStatic but not IsOverload, and the
importers never set it. ResolveMethodOverload's hiding walk stops at the
first non-overload candidate, so an overload set split across an imported
class and an imported ancestor would be truncated to the more-derived
level (latent today because the known overload sets live on a single
class).
Add IsOverload to both method-encoding paths and bump BLAISE-IFACE 5 -> 6:
* TRoutineSig (class methods): field added in uUnitInterface, set in
BuildRoutineSig, encoded/decoded in EncodeMethodSig/ReadMethodSig,
propagated in SynthesiseMethodDecl.
* TMethodDecl (interface and generic-template methods, the
EncodeMethodDecl/ReadMethodDecl path): the same flag was dropped there
too, so an overloaded interface method lost its directive across the
.bif.
Round-trip tests for both paths: TestRoundTrip_Class_WithOverloadedMethods
and TestRoundTrip_Interface_WithOverloadedMethods. bif-coverage status
gains TRoutineSig.IsOverload.
This commit is contained in:
parent
fdf99b26c0
commit
a934d73566
|
|
@ -234,6 +234,10 @@ begin
|
|||
VTableSlot alone cannot distinguish a static method from a final
|
||||
non-virtual instance method (both are -1). }
|
||||
Result.IsStatic := ASrc.IsStatic;
|
||||
{ Carry the `overload` directive so a cross-unit overload set whose members
|
||||
are split across an imported class and its ancestor is not truncated by
|
||||
ResolveMethodOverload's hiding walk (which stops at a non-overload). }
|
||||
Result.IsOverload := ASrc.IsOverload;
|
||||
{ Carry visibility so cross-unit member access enforces private/protected. }
|
||||
Result.Visibility := ASrc.Visibility;
|
||||
Result.ReturnType := ResolveTypeRef(ASrc.ReturnTypeName, AIface, ADeps);
|
||||
|
|
|
|||
|
|
@ -981,6 +981,10 @@ begin
|
|||
{ Carry static-ness so the semantic pass's TypeName.StaticMethod() resolution
|
||||
(which checks MDecl.IsStatic) succeeds for a method imported from a .bif. }
|
||||
Result.IsStatic := ASig.IsStatic;
|
||||
{ Carry the `overload` directive so ResolveMethodOverload's hiding walk treats
|
||||
an imported overload as overloadable — otherwise an overload set split
|
||||
across an imported class and its ancestor is truncated to the derived level. }
|
||||
Result.IsOverload := ASig.IsOverload;
|
||||
{ Carry visibility so cross-unit private/protected method access is enforced. }
|
||||
Result.Visibility := ASig.Visibility;
|
||||
for J := 0 to ASig.Params.Count - 1 do
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ type
|
|||
static and final non-virtual instance
|
||||
methods, so this is a distinct flag rather
|
||||
than being inferred from the slot. }
|
||||
IsOverload: Boolean; { declared with the `overload` directive.
|
||||
ResolveMethodOverload's hiding walk stops at
|
||||
the first non-overload candidate, so this must
|
||||
survive the .bif or an overload set split
|
||||
across an imported class and its ancestor is
|
||||
truncated to the more-derived level. }
|
||||
Visibility: TMemberVisibility; { member access scope; default mvPublic }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,17 @@ uses
|
|||
|
||||
const
|
||||
IFACE_MAGIC = 'BLAISE-IFACE';
|
||||
IFACE_VERSION = 5; { v5 (this cycle): member Visibility (private/protected/
|
||||
IFACE_VERSION = 6; { v6 (this cycle): the `overload` directive now round-trips.
|
||||
TRoutineSig.IsOverload added to EncodeMethodSig/
|
||||
ReadMethodSig (one extra byte after IsStatic) and
|
||||
TMethodDecl.IsOverload added to EncodeMethodDecl/
|
||||
ReadMethodDecl (after IsOverride; this is the
|
||||
interface- and generic-template-method path). Without
|
||||
it a cross-unit overload set split across an imported
|
||||
class and its ancestor is truncated by the importer's
|
||||
hiding walk. Both method layouts grew, so v5 readers
|
||||
must reject these .bif and recompile.
|
||||
v5: member Visibility (private/protected/
|
||||
strict) now serialised — field, method, and property
|
||||
payloads each carry one extra byte for the visibility
|
||||
ordinal, so v4 readers must reject these .bif and
|
||||
|
|
@ -342,6 +352,9 @@ begin
|
|||
{ static-method flag — distinct from VTableSlot (both static and final
|
||||
non-virtual instance methods carry slot -1). }
|
||||
EncodeBool (AR.IsStatic) +
|
||||
{ `overload` directive — must round-trip so a split overload set is not
|
||||
truncated by the importer's hiding walk. }
|
||||
EncodeBool (AR.IsOverload) +
|
||||
{ member visibility ordinal }
|
||||
EncodeLpstr(IntToStr(Ord(AR.Visibility))) +
|
||||
EncodeCount(AR.Params.Count);
|
||||
|
|
@ -437,6 +450,9 @@ begin
|
|||
resolved cross-unit refs }
|
||||
EncodeBool (AM.IsVirtual) +
|
||||
EncodeBool (AM.IsOverride) +
|
||||
EncodeBool (AM.IsOverload) + { `overload` directive — generic-template
|
||||
and interface methods share this path
|
||||
and may be overloaded }
|
||||
EncodeCount(AM.Params.Count);
|
||||
for J := 0 to AM.Params.Count - 1 do
|
||||
begin
|
||||
|
|
@ -1913,9 +1929,10 @@ begin
|
|||
Result.IsOverride := DecodeBool(AText, APos);
|
||||
Result.ResolvedQbeName := ReadLpstrAt(AText, APos);
|
||||
Result.VTableSlot := StrToInt(ReadLpstrAt(AText, APos));
|
||||
{ Order must mirror EncodeMethodSig: IsStatic follows VTableSlot, then the
|
||||
visibility ordinal. }
|
||||
{ Order must mirror EncodeMethodSig: IsStatic follows VTableSlot, then
|
||||
IsOverload, then the visibility ordinal. }
|
||||
Result.IsStatic := DecodeBool(AText, APos);
|
||||
Result.IsOverload := DecodeBool(AText, APos);
|
||||
Result.Visibility := TMemberVisibility(StrToInt(ReadLpstrAt(AText, APos)));
|
||||
Pc := DecodeCount(AText, APos);
|
||||
for J := 1 to Pc do
|
||||
|
|
@ -1989,6 +2006,7 @@ begin
|
|||
if not HasReturn then Result.ReturnTypeName := '';
|
||||
Result.IsVirtual := DecodeBool(AText, APos);
|
||||
Result.IsOverride := DecodeBool(AText, APos);
|
||||
Result.IsOverload := DecodeBool(AText, APos);
|
||||
Pc := DecodeCount(AText, APos);
|
||||
for J := 1 to Pc do
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -245,6 +245,8 @@ type
|
|||
procedure TestRoundTrip_ViaFile;
|
||||
procedure TestRoundTrip_Record;
|
||||
procedure TestRoundTrip_Class_WithVirtualMethod;
|
||||
procedure TestRoundTrip_Class_WithOverloadedMethods;
|
||||
procedure TestRoundTrip_Interface_WithOverloadedMethods;
|
||||
procedure TestRoundTrip_Interface;
|
||||
procedure TestRoundTrip_Interface_WithProperty;
|
||||
procedure TestRoundTrip_ProceduralType;
|
||||
|
|
@ -2691,12 +2693,12 @@ begin
|
|||
Iface := TUnitInterface.Create('U');
|
||||
try
|
||||
Buf := WriteUnitInterface(Iface);
|
||||
{ Blaise Pos is 0-based; match-at-start returns 0. Version is 5 since
|
||||
member Visibility (private/protected/strict) was added to the field,
|
||||
method, and property encoded layouts (on top of v4's TRoutineSig.IsStatic
|
||||
and v3's static-member facts). }
|
||||
{ Blaise Pos is 0-based; match-at-start returns 0. Version is 6 since the
|
||||
`overload` directive (TRoutineSig.IsOverload + TMethodDecl.IsOverload) was
|
||||
added to the method encoded layouts (on top of v5's member Visibility,
|
||||
v4's TRoutineSig.IsStatic, and v3's static-member facts). }
|
||||
AssertTrue('starts with magic',
|
||||
Pos('BLAISE-IFACE 5', Buf) = 0);
|
||||
Pos('BLAISE-IFACE 6', Buf) = 0);
|
||||
finally
|
||||
Iface.Free();
|
||||
end;
|
||||
|
|
@ -2964,6 +2966,98 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TIfaceIOTests.TestRoundTrip_Class_WithOverloadedMethods;
|
||||
{ Regression (bugs.txt: imported methods lose IsOverload). A class with an
|
||||
overloaded method set must round-trip the `overload` directive through the
|
||||
.bif: ResolveMethodOverload's hiding walk stops at the first NON-overload
|
||||
candidate, so a method imported with IsOverload=False would wrongly truncate
|
||||
an overload set that is split across an imported class and its ancestor. }
|
||||
const
|
||||
SRC =
|
||||
'unit U;' + #10 +
|
||||
'interface' + #10 +
|
||||
'type TFoo = class' + #10 +
|
||||
' procedure Add(A: Integer); overload;' + #10 +
|
||||
' procedure Add(A: string); overload;' + #10 +
|
||||
'end;' + #10 +
|
||||
'implementation' + #10 +
|
||||
'procedure TFoo.Add(A: Integer); begin end;' + #10 +
|
||||
'procedure TFoo.Add(A: string); begin end;' + #10 +
|
||||
'end.' + #10;
|
||||
var
|
||||
Iface, Round: TUnitInterface;
|
||||
Buf: string;
|
||||
E: TTypeEntry;
|
||||
M: TRoutineSig;
|
||||
I: Integer;
|
||||
begin
|
||||
Iface := ParseAnalyseAndExport(SRC);
|
||||
try
|
||||
Buf := WriteUnitInterface(Iface);
|
||||
Round := ReadUnitInterface(Buf);
|
||||
try
|
||||
E := Round.FindType('TFoo');
|
||||
AssertTrue('TFoo present', E <> nil);
|
||||
AssertEquals('2 methods', 2, E.Methods.Count);
|
||||
{ Every Add overload must carry IsOverload across the .bif boundary. }
|
||||
for I := 0 to E.Methods.Count - 1 do
|
||||
begin
|
||||
M := TRoutineSig(E.Methods.Items[I]);
|
||||
AssertEquals('overload method name', 'Add', M.Name);
|
||||
AssertTrue('IsOverload preserved for ' + M.ResolvedQbeName, M.IsOverload);
|
||||
end;
|
||||
finally
|
||||
Round.Free();
|
||||
end;
|
||||
finally
|
||||
Iface.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIfaceIOTests.TestRoundTrip_Interface_WithOverloadedMethods;
|
||||
{ Interface methods serialise via EncodeMethodDecl/ReadMethodDecl (the AST
|
||||
TMethodDecl path, distinct from the class TRoutineSig path). That path also
|
||||
dropped IsOverload, so an overloaded interface method imported from a .bif
|
||||
would lose its `overload` directive. }
|
||||
const
|
||||
SRC =
|
||||
'unit U;' + #10 +
|
||||
'interface' + #10 +
|
||||
'type IShape = interface' + #10 +
|
||||
' procedure Draw(X: Integer); overload;' + #10 +
|
||||
' procedure Draw(X: string); overload;' + #10 +
|
||||
'end;' + #10 +
|
||||
'implementation end.' + #10;
|
||||
var
|
||||
Iface, Round: TUnitInterface;
|
||||
Buf: string;
|
||||
E: TTypeEntry;
|
||||
M: TMethodDecl;
|
||||
I: Integer;
|
||||
begin
|
||||
Iface := ParseAnalyseAndExport(SRC);
|
||||
try
|
||||
Buf := WriteUnitInterface(Iface);
|
||||
Round := ReadUnitInterface(Buf);
|
||||
try
|
||||
E := Round.FindType('IShape');
|
||||
AssertTrue('IShape present', E <> nil);
|
||||
AssertTrue('is interface', E.Def is TInterfaceTypeDef);
|
||||
AssertEquals('2 methods', 2, TInterfaceTypeDef(E.Def).Methods.Count);
|
||||
for I := 0 to TInterfaceTypeDef(E.Def).Methods.Count - 1 do
|
||||
begin
|
||||
M := TMethodDecl(TInterfaceTypeDef(E.Def).Methods.Items[I]);
|
||||
AssertEquals('overload method name', 'Draw', M.Name);
|
||||
AssertTrue('IsOverload preserved', M.IsOverload);
|
||||
end;
|
||||
finally
|
||||
Round.Free();
|
||||
end;
|
||||
finally
|
||||
Iface.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIfaceIOTests.TestRoundTrip_Interface;
|
||||
const
|
||||
SRC =
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ TRoutineSig.ResolvedQbeName serialise
|
|||
TRoutineSig.IsVirtual serialise
|
||||
TRoutineSig.IsOverride serialise
|
||||
TRoutineSig.IsStatic serialise
|
||||
TRoutineSig.IsOverload serialise
|
||||
TRoutineSig.Visibility serialise
|
||||
|
||||
# TConstEntry (uUnitInterface.pas:102)
|
||||
|
|
|
|||
Loading…
Reference in a new issue