feat(semantic): resolve type-qualified enum members (TEnum.Member)
A bare or type-qualified enum member (`Color.cRed`, or `cRed` where the target type is known) now resolves correctly. Also adds TSymbolTable.InCodegen, set by both backends around code emission, which disables the analysis-time impl-private suppression in Lookup. That guard exists only to stop an implementation-section symbol of unit A leaking into a different unit B DURING ANALYSIS; at codegen a backend legitimately resolves a unit's own impl-section classes to emit their typeinfo/vtable/_FieldCleanup. FDefineOwningUnit can drift to a dependency unit mid-emit, which previously made the suppression wrongly fire and silently drop an impl-section class's typeinfo — leaving a dangling `typeinfo_<Unit>_<Class>` reference the linker bound to a garbage address (an out-of-range-metaclass crash, e.g. via RegisterTest). This is the robust, both-backend root-cause fix for that crash, which this commit's layout change re-exposed; it supersedes the narrower EmitClassSection DefineOwningUnit-pin added earlier.
This commit is contained in:
parent
edf4ffe374
commit
4d0c7c461d
|
|
@ -17449,6 +17449,7 @@ var
|
||||||
VD: TVarDecl;
|
VD: TVarDecl;
|
||||||
Decl: TMethodDecl;
|
Decl: TMethodDecl;
|
||||||
begin
|
begin
|
||||||
|
if FSymTable <> nil then FSymTable.InCodegen := True;
|
||||||
FCurrentUnitName := AProg.Name;
|
FCurrentUnitName := AProg.Name;
|
||||||
FDbgSrcFile := '';
|
FDbgSrcFile := '';
|
||||||
FProgramName := AProg.Name;
|
FProgramName := AProg.Name;
|
||||||
|
|
@ -17724,6 +17725,10 @@ begin
|
||||||
UnitSym := AUnit.SymbolTable
|
UnitSym := AUnit.SymbolTable
|
||||||
else
|
else
|
||||||
UnitSym := FSymTable;
|
UnitSym := FSymTable;
|
||||||
|
{ Disable the analysis-time impl-private suppression in Lookup for the table
|
||||||
|
EmitClassSection consults — at codegen the backend must resolve THIS unit's
|
||||||
|
own implementation-section classes (see TSymbolTable.FInCodegen). }
|
||||||
|
if UnitSym <> nil then UnitSym.InCodegen := True;
|
||||||
{ Re-assert the viewing context (see the init-block note above): the class
|
{ Re-assert the viewing context (see the init-block note above): the class
|
||||||
section emits unit-qualified typeinfo/vtable/_FieldCleanup names via
|
section emits unit-qualified typeinfo/vtable/_FieldCleanup names via
|
||||||
ClassSymName, which must resolve THIS unit's own impl-section classes. }
|
ClassSymName, which must resolve THIS unit's own impl-section classes. }
|
||||||
|
|
|
||||||
|
|
@ -14472,6 +14472,10 @@ begin
|
||||||
begin
|
begin
|
||||||
SavedDOU := FSymTable.DefineOwningUnit;
|
SavedDOU := FSymTable.DefineOwningUnit;
|
||||||
FSymTable.DefineOwningUnit := AUnit.Name;
|
FSymTable.DefineOwningUnit := AUnit.Name;
|
||||||
|
{ Disable the analysis-time impl-private suppression in Lookup: at codegen
|
||||||
|
the backend must resolve THIS unit's own implementation-section classes to
|
||||||
|
emit their typeinfo/vtable/_FieldCleanup (see TSymbolTable.FInCodegen). }
|
||||||
|
FSymTable.InCodegen := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Combined type-decl list (interface first, then implementation) — borrowed
|
{ Combined type-decl list (interface first, then implementation) — borrowed
|
||||||
|
|
|
||||||
|
|
@ -11333,6 +11333,7 @@ var
|
||||||
BaseType: TTypeDesc;
|
BaseType: TTypeDesc;
|
||||||
IntfDesc: TInterfaceTypeDesc;
|
IntfDesc: TInterfaceTypeDesc;
|
||||||
MDecl: TMethodDecl;
|
MDecl: TMethodDecl;
|
||||||
|
EnumOrd: Integer;
|
||||||
begin
|
begin
|
||||||
{ Chained access: A.B.C — base is another expression whose type must be
|
{ Chained access: A.B.C — base is another expression whose type must be
|
||||||
a record or class. Leaf lookup uses Base.ResolvedType; RecordName path
|
a record or class. Leaf lookup uses Base.ResolvedType; RecordName path
|
||||||
|
|
@ -11678,6 +11679,26 @@ begin
|
||||||
|
|
||||||
AAccess.RecordName := RecSym.Name; { normalise to declared casing }
|
AAccess.RecordName := RecSym.Name; { normalise to declared casing }
|
||||||
|
|
||||||
|
{ Type-qualified enum member: TMyEnum.meValue. Resolve the member straight
|
||||||
|
from the named enum's own member list, so the reference is unambiguous even
|
||||||
|
when another enum in scope declares a member of the same name (the bare name
|
||||||
|
resolves to whichever enum claimed the global slot; the qualified form
|
||||||
|
always names this one). }
|
||||||
|
if (RecSym.Kind = skType) and (RecSym.TypeDesc <> nil) and
|
||||||
|
(RecSym.TypeDesc.Kind = tyEnum) then
|
||||||
|
begin
|
||||||
|
EnumOrd := TEnumTypeDesc(RecSym.TypeDesc).OrdinalOf(AAccess.FieldName);
|
||||||
|
if EnumOrd < 0 then
|
||||||
|
SemanticError(
|
||||||
|
Format('Enum ''%s'' has no member ''%s''',
|
||||||
|
[AAccess.RecordName, AAccess.FieldName]),
|
||||||
|
AAccess.Line, AAccess.Col);
|
||||||
|
AAccess.IsConstant := True;
|
||||||
|
AAccess.ConstValue := EnumOrd;
|
||||||
|
AAccess.ResolvedType := RecSym.TypeDesc;
|
||||||
|
Exit(RecSym.TypeDesc);
|
||||||
|
end;
|
||||||
|
|
||||||
{ Constructor call: TypeName.Create }
|
{ Constructor call: TypeName.Create }
|
||||||
if RecSym.Kind = skType then
|
if RecSym.Kind = skType then
|
||||||
begin
|
begin
|
||||||
|
|
|
||||||
|
|
@ -547,6 +547,23 @@ type
|
||||||
retrieve the canonical TSymbol — without this flag we'd loop. }
|
retrieve the canonical TSymbol — without this flag we'd loop. }
|
||||||
FBypassUsesChain: Boolean;
|
FBypassUsesChain: Boolean;
|
||||||
|
|
||||||
|
FInCodegen: Boolean; { True while a backend is emitting code.
|
||||||
|
Disables the Layer-6 impl-private
|
||||||
|
suppression in Lookup: that guard exists
|
||||||
|
only to stop an implementation-section
|
||||||
|
symbol of unit A leaking into a different
|
||||||
|
unit B DURING ANALYSIS. At codegen time a
|
||||||
|
backend legitimately resolves a unit's own
|
||||||
|
impl-section classes (to emit their
|
||||||
|
typeinfo/vtable/_FieldCleanup and the
|
||||||
|
references to them), but FDefineOwningUnit
|
||||||
|
can have drifted to a dependency unit
|
||||||
|
mid-emit, which made the suppression wrongly
|
||||||
|
fire and silently drop the class's typeinfo
|
||||||
|
— a dangling symbol the linker binds to
|
||||||
|
garbage. Set by the QBE and native
|
||||||
|
backends around Generate/GenerateUnit. }
|
||||||
|
|
||||||
FDefineOwningUnit: string; { auto-applied to Sym.OwningUnit on Define
|
FDefineOwningUnit: string; { auto-applied to Sym.OwningUnit on Define
|
||||||
when the symbol has no explicit value;
|
when the symbol has no explicit value;
|
||||||
set by AnalyseUnit/AnalyseUnitForExport
|
set by AnalyseUnit/AnalyseUnitForExport
|
||||||
|
|
@ -604,6 +621,9 @@ type
|
||||||
property BypassUsesChain: Boolean read FBypassUsesChain
|
property BypassUsesChain: Boolean read FBypassUsesChain
|
||||||
write FBypassUsesChain;
|
write FBypassUsesChain;
|
||||||
|
|
||||||
|
{ Set by a backend for the duration of code emission; see FInCodegen. }
|
||||||
|
property InCodegen: Boolean read FInCodegen write FInCodegen;
|
||||||
|
|
||||||
{ Type lookup — case-insensitive, returns nil if not found }
|
{ Type lookup — case-insensitive, returns nil if not found }
|
||||||
function FindType(const AName: string): TTypeDesc;
|
function FindType(const AName: string): TTypeDesc;
|
||||||
|
|
||||||
|
|
@ -2043,9 +2063,16 @@ begin
|
||||||
The check only runs while a unit/program is being analysed
|
The check only runs while a unit/program is being analysed
|
||||||
(FDefineOwningUnit <> '') and the chain walker is not mid-callback; outside
|
(FDefineOwningUnit <> '') and the chain walker is not mid-callback; outside
|
||||||
analysis (some codegen-side lookups clear FDefineOwningUnit) the residue is
|
analysis (some codegen-side lookups clear FDefineOwningUnit) the residue is
|
||||||
returned unchanged so unit-prefix mangling still resolves owned symbols. }
|
returned unchanged so unit-prefix mangling still resolves owned symbols.
|
||||||
|
|
||||||
|
It is also disabled during code emission (FInCodegen): the leak it guards is
|
||||||
|
purely an analysis-time concern, whereas a backend legitimately resolves a
|
||||||
|
unit's own implementation-section classes to emit their typeinfo/vtable/
|
||||||
|
_FieldCleanup. FDefineOwningUnit can have drifted to a dependency unit
|
||||||
|
mid-emit, which would otherwise make this suppression wrongly fire and drop
|
||||||
|
the class's typeinfo, leaving a dangling symbol the linker binds to garbage. }
|
||||||
if (Sym <> nil) and Sym.IsImplPrivate and (FDefineOwningUnit <> '')
|
if (Sym <> nil) and Sym.IsImplPrivate and (FDefineOwningUnit <> '')
|
||||||
and not FBypassUsesChain
|
and not FBypassUsesChain and not FInCodegen
|
||||||
and not SameText(Sym.OwningUnit, FDefineOwningUnit) then
|
and not SameText(Sym.OwningUnit, FDefineOwningUnit) then
|
||||||
begin
|
begin
|
||||||
Result := nil;
|
Result := nil;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ type
|
||||||
{ ------------------------------------------------------------------ }
|
{ ------------------------------------------------------------------ }
|
||||||
procedure TestCodegen_Enum_MemberEmitsIntegerCopy;
|
procedure TestCodegen_Enum_MemberEmitsIntegerCopy;
|
||||||
procedure TestCodegen_Enum_AssignEmitsStore;
|
procedure TestCodegen_Enum_AssignEmitsStore;
|
||||||
|
procedure TestCodegen_ScopedEnum_QualifiedMemberEmitsOrdinal;
|
||||||
|
procedure TestSemantic_ScopedEnum_UnknownMemberRejected;
|
||||||
|
|
||||||
{ ------------------------------------------------------------------ }
|
{ ------------------------------------------------------------------ }
|
||||||
{ enum + case integration }
|
{ enum + case integration }
|
||||||
|
|
@ -576,6 +578,49 @@ begin
|
||||||
Pos('_StringEquals', IR) < 0);
|
Pos('_StringEquals', IR) < 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TCaseEnumTests.TestCodegen_ScopedEnum_QualifiedMemberEmitsOrdinal;
|
||||||
|
const
|
||||||
|
Src =
|
||||||
|
'''
|
||||||
|
program P;
|
||||||
|
type TDir = (dN, dE, dS, dW);
|
||||||
|
var x: Integer;
|
||||||
|
begin
|
||||||
|
x := Ord(TDir.dS)
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var
|
||||||
|
IR: string;
|
||||||
|
begin
|
||||||
|
IR := GenIR(Src);
|
||||||
|
{ TDir.dS is the third member (ordinal 2); the type-qualified reference must
|
||||||
|
resolve to that member and emit copy 2, exactly as the bare dS would. }
|
||||||
|
AssertTrue('TDir.dS emits copy 2', Pos('copy 2', IR) > 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCaseEnumTests.TestSemantic_ScopedEnum_UnknownMemberRejected;
|
||||||
|
const
|
||||||
|
Src =
|
||||||
|
'''
|
||||||
|
program P;
|
||||||
|
type TDir = (dN, dE, dS, dW);
|
||||||
|
var x: Integer;
|
||||||
|
begin
|
||||||
|
x := Ord(TDir.dNope)
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var
|
||||||
|
Raised: Boolean;
|
||||||
|
begin
|
||||||
|
Raised := False;
|
||||||
|
try
|
||||||
|
SemanticOK(Src);
|
||||||
|
except
|
||||||
|
on E: Exception do Raised := True;
|
||||||
|
end;
|
||||||
|
AssertTrue('unknown enum member via TEnum.Member rejected', Raised);
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TCaseEnumTests);
|
RegisterTest(TCaseEnumTests);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ type
|
||||||
procedure TestRun_Enum_ExplicitOrdinals;
|
procedure TestRun_Enum_ExplicitOrdinals;
|
||||||
procedure TestRun_Enum_AutoContinueAfterExplicit;
|
procedure TestRun_Enum_AutoContinueAfterExplicit;
|
||||||
procedure TestRun_Enum_ExplicitInCase;
|
procedure TestRun_Enum_ExplicitInCase;
|
||||||
|
procedure TestRun_Enum_ScopedAccess;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
@ -225,6 +226,40 @@ begin
|
||||||
AssertEquals('NotFound=404 matches case 404', 'not found', Trim(Output));
|
AssertEquals('NotFound=404 matches case 404', 'not found', Trim(Output));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TE2ECaseEnumTests.TestRun_Enum_ScopedAccess;
|
||||||
|
const
|
||||||
|
{ Two enums in one unit share the member name 'Red'. The bare name resolves
|
||||||
|
to whichever enum claimed the global slot (TColorA), so TColorB.Red and
|
||||||
|
TColorB.Blue are reachable ONLY through the type-qualified form. }
|
||||||
|
Src =
|
||||||
|
'''
|
||||||
|
program P;
|
||||||
|
type
|
||||||
|
TColorA = (Red, Green);
|
||||||
|
TColorB = (Red, Blue);
|
||||||
|
begin
|
||||||
|
WriteLn(Ord(TColorA.Red));
|
||||||
|
WriteLn(Ord(TColorA.Green));
|
||||||
|
WriteLn(Ord(TColorB.Red));
|
||||||
|
WriteLn(Ord(TColorB.Blue))
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var Output: string; RCode: Integer; Lines: TStringList;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
AssertTrue('compile+run', CompileAndRun(Src, Output, RCode));
|
||||||
|
Lines := TStringList.Create();
|
||||||
|
try
|
||||||
|
Lines.Text := Trim(Output);
|
||||||
|
AssertEquals('TColorA.Red', '0', Lines.Strings[0]);
|
||||||
|
AssertEquals('TColorA.Green','1', Lines.Strings[1]);
|
||||||
|
AssertEquals('TColorB.Red', '0', Lines.Strings[2]);
|
||||||
|
AssertEquals('TColorB.Blue', '1', Lines.Strings[3]);
|
||||||
|
finally
|
||||||
|
Lines.Free();
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TE2ECaseEnumTests);
|
RegisterTest(TE2ECaseEnumTests);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue