fix: inherit a non-generic class from a generic-class instance
A non-generic class could not inherit from a generic-class instance:
TBox<T> = class ... end;
TIntBox = class(TBox<Integer>) ... end; // rejected, then bad IR
Two bugs:
1. Semantic: AnalyseTypeDecls assumed any generic name in the first
heritage slot was an interface — it cast FindTypeOrInstantiate's
result to TInterfaceTypeDesc and moved it to the implements list, so a
generic CLASS parent was rejected as "Unknown interface 'TBox<Integer>'
in implements list". It now classifies the instantiated descriptor: a
tyInterface result is an implements entry, anything else stays as the
parent class for normal class-parent resolution.
2. QBE codegen: the inheriting class's typeinfo referenced its parent as
$typeinfo_TBox<Integer> via ClassSymName (which does not mangle <>),
but the generic instance's typeinfo is defined under the QBEMangle'd
symbol $typeinfo_TBox_Integer — producing invalid QBE IR ("invalid
character <"). The parent reference now uses QBEMangle for a generic
instance, matching its definition. The native backend already mangled
correctly and only needed the semantic fix.
Verified inherited method + field access and a virtual method declared on
the generic base and overridden in the derived class, on both backends.
Adds TE2EGenericsTests.TestRun_InheritFromGenericInstance_{MethodAndField,
VirtualOverride} and a note in docs/language-rationale.adoc.
This commit is contained in:
parent
d91be47103
commit
659071f136
|
|
@ -7233,7 +7233,16 @@ begin
|
|||
CD := TClassTypeDef(TD.Def);
|
||||
|
||||
if RT.Parent <> nil then
|
||||
ParentStr := '$typeinfo_' + ClassSymName(RT.Parent.Name)
|
||||
begin
|
||||
{ A generic-instance parent (TBox<Integer>) is defined under the
|
||||
QBEMangle'd symbol (typeinfo_TBox_Integer) and carries no unit prefix,
|
||||
so reference it the same way. Non-generic parents keep ClassSymName
|
||||
(which adds the unit prefix). }
|
||||
if StrPos('<', RT.Parent.Name) >= 0 then
|
||||
ParentStr := '$typeinfo_' + QBEMangle(RT.Parent.Name)
|
||||
else
|
||||
ParentStr := '$typeinfo_' + ClassSymName(RT.Parent.Name);
|
||||
end
|
||||
else
|
||||
ParentStr := '0';
|
||||
if RT.ImplementsCount() > 0 then
|
||||
|
|
|
|||
|
|
@ -3576,6 +3576,7 @@ var
|
|||
RT: TRecordTypeDesc;
|
||||
ParentRT: TRecordTypeDesc;
|
||||
ParentSym: TSymbol;
|
||||
GenParentDesc: TTypeDesc;
|
||||
FldType: TTypeDesc;
|
||||
FldName: string;
|
||||
Sym: TSymbol;
|
||||
|
|
@ -3944,18 +3945,25 @@ begin
|
|||
if TClassTypeDef(TD.Def).ParentName <> '' then
|
||||
begin
|
||||
ParentSym := nil;
|
||||
{ If name looks generic, try instantiating as interface first }
|
||||
{ A generic name as the first heritage entry (class(TBox<Integer>) or
|
||||
class(IFoo<T>)) must be instantiated, then classified: a generic
|
||||
CLASS instance is the parent class, a generic INTERFACE instance is
|
||||
an implements entry. Earlier this assumed interface unconditionally,
|
||||
so inheriting from a generic class was rejected as an unknown
|
||||
interface. }
|
||||
if StrPos('<', TClassTypeDef(TD.Def).ParentName) >= 0 then
|
||||
begin
|
||||
IntfDesc := TInterfaceTypeDesc(
|
||||
FindTypeOrInstantiate(TClassTypeDef(TD.Def).ParentName));
|
||||
if IntfDesc <> nil then
|
||||
GenParentDesc := FindTypeOrInstantiate(TClassTypeDef(TD.Def).ParentName);
|
||||
if (GenParentDesc <> nil) and
|
||||
(GenParentDesc is TInterfaceTypeDesc) then
|
||||
begin
|
||||
{ Treat it as an interface to implement — move to implements list }
|
||||
{ Generic interface — move to the implements list. }
|
||||
TClassTypeDef(TD.Def).ImplementsNames.Insert(
|
||||
0, TClassTypeDef(TD.Def).ParentName);
|
||||
TClassTypeDef(TD.Def).ParentName := '';
|
||||
end;
|
||||
{ Otherwise (generic class instance, or unresolved) leave it as the
|
||||
ParentName for the class-parent resolution below. }
|
||||
end;
|
||||
if TClassTypeDef(TD.Def).ParentName <> '' then
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ type
|
|||
rejected as shadowing a visible type. }
|
||||
procedure TestRun_GenericClass_LocalNamedLikeTypeParam;
|
||||
procedure TestRun_GenericRecord_LocalNamedLikeTypeParam;
|
||||
{ Non-generic class inheriting from a generic-class instance
|
||||
(class(TBox<Integer>)) — parent classification + symbol mangling. }
|
||||
procedure TestRun_InheritFromGenericInstance_MethodAndField;
|
||||
procedure TestRun_InheritFromGenericInstance_VirtualOverride;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -240,6 +244,56 @@ begin
|
|||
AssertRunsOnAll(SrcRecordLocalLikeParam, '55' + LE, 0);
|
||||
end;
|
||||
|
||||
const
|
||||
SrcInheritGenericMethodField = '''
|
||||
program P;
|
||||
type
|
||||
TBox<T> = class
|
||||
FVal: T;
|
||||
procedure SetIt(v: T); begin FVal := v; end;
|
||||
function GetIt: T; begin Result := FVal; end;
|
||||
end;
|
||||
TIntBox = class(TBox<Integer>) end;
|
||||
var b: TIntBox;
|
||||
begin
|
||||
b := TIntBox.Create;
|
||||
b.SetIt(7);
|
||||
WriteLn(b.GetIt());
|
||||
WriteLn(b.FVal);
|
||||
b := nil
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcInheritGenericVirtual = '''
|
||||
program P;
|
||||
type
|
||||
TBase<T> = class
|
||||
function Describe: string; virtual; begin Result := 'base' end;
|
||||
function Wrap: string; begin Result := '[' + Self.Describe() + ']' end;
|
||||
end;
|
||||
TIntD = class(TBase<Integer>)
|
||||
function Describe: string; override; begin Result := 'derived' end;
|
||||
end;
|
||||
var d: TBase<Integer>;
|
||||
begin
|
||||
d := TIntD.Create;
|
||||
WriteLn(d.Wrap());
|
||||
d := nil
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2EGenericsTests.TestRun_InheritFromGenericInstance_MethodAndField;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcInheritGenericMethodField, '7' + LE + '7' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EGenericsTests.TestRun_InheritFromGenericInstance_VirtualOverride;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcInheritGenericVirtual, '[derived]' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EGenericsTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -2461,6 +2461,15 @@ The codegen emits itab and impllist data for generic class instances in
|
|||
Both class and interface names in itab symbols are passed through `QBEMangle`
|
||||
to produce valid QBE identifiers (e.g. `$itab_TDictionary_Integer_Integer_IMap_Integer_Integer`).
|
||||
|
||||
A non-generic class may also *inherit* from a generic-class instance — e.g.
|
||||
`TIntBox = class(TBox<Integer>)`. The first heritage entry is instantiated and
|
||||
then classified: a `tyInterface` result becomes an implements entry (above),
|
||||
any other (a class instance) is the parent class. Because the generic instance
|
||||
is emitted under its `QBEMangle`'d symbol (`typeinfo_TBox_Integer`), the
|
||||
inheriting class's typeinfo must reference the parent through `QBEMangle` too,
|
||||
not the unmangled name — otherwise the emitted QBE identifier contains the
|
||||
illegal `<`/`>` characters.
|
||||
|
||||
=== Alternatives Rejected
|
||||
|
||||
* *Structural duck-typing without interfaces* — Blaise already uses
|
||||
|
|
|
|||
Loading…
Reference in a new issue