feat(semantic): directed lookup for unit-qualified references
A 'Unit.Symbol' reference must resolve against that specific unit's exports — never the flat global table or the uses chain — so a same-named symbol in another used unit can neither shadow it nor be shadowed by it. Previously the unit prefix was discarded at three independent sites (idents, type names, statement targets), collapsing every qualified reference to a bare lookup; a shadowed const/type in an earlier-in-uses unit was then unreachable even when named explicitly. Introduce a single directed-lookup primitive, ResolveQualified(Unit, Name): per-unit cache first (the authoritative map of what each unit exported, where a cross-unit collision loser is retained after being evicted from the flat table), then a lenient flat-table fallback for harnesses that populate the global but not the cache. Route the three sites through it: the parser now preserves the matched unit on TIdentExpr.QualifierUnit (serialised into the .bif), the type resolver consults it for dotted type names, and a unit-qualified class ancestor 'class(Unit.TParent)' resolves through the type machinery so inheritance binds to the named unit's type. The const last-wins path now stashes the shadowed const in the per-unit cache (it claimed to but never did), so 'Unit.Const' reaches the declaring unit's own value regardless of which unit won the bare slot. Parent/implements names parse via ParseTypeName (which already absorbs the unit qualifier and nested generics) instead of ParseGenericName. Tests: qualified-const disambiguation (ua.Foo=100 / ub.Foo=200 read independently of the bare last-wins winner) and qualified inheritance (class(ua.TParent) inherits across units).
This commit is contained in:
parent
6bc8aff47d
commit
c7f264acec
|
|
@ -117,6 +117,11 @@ type
|
||||||
ConstArraySymbol: string; { set by uSemantic — non-empty when this ident resolves
|
ConstArraySymbol: string; { set by uSemantic — non-empty when this ident resolves
|
||||||
to an array const; the mangled QBE data-label codegen
|
to an array const; the mangled QBE data-label codegen
|
||||||
must reference instead of $Name. }
|
must reference instead of $Name. }
|
||||||
|
QualifierUnit: string; { set by the parser — non-empty when written as a
|
||||||
|
unit-qualified reference 'Unit.Symbol'. uSemantic
|
||||||
|
resolves the name against this specific unit's
|
||||||
|
exports (per-unit cache) instead of the uses chain,
|
||||||
|
so a same-named const in another unit can't shadow it. }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TFieldAccessExpr = class(TASTExpr)
|
TFieldAccessExpr = class(TASTExpr)
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,8 @@ type
|
||||||
function PeekLineAt(N: Integer): Integer;
|
function PeekLineAt(N: Integer): Integer;
|
||||||
function PeekColAt(N: Integer): Integer;
|
function PeekColAt(N: Integer): Integer;
|
||||||
function TryCollapseUnitQualifier(var AName: string;
|
function TryCollapseUnitQualifier(var AName: string;
|
||||||
var ALine, ACol: Integer): Boolean;
|
var ALine, ACol: Integer;
|
||||||
|
out AUnit: string): Boolean;
|
||||||
procedure Advance;
|
procedure Advance;
|
||||||
function PeekKind(): TTokenKind;
|
function PeekKind(): TTokenKind;
|
||||||
function PeekKind2(): TTokenKind; { two tokens ahead }
|
function PeekKind2(): TTokenKind; { two tokens ahead }
|
||||||
|
|
@ -324,15 +325,23 @@ end;
|
||||||
a same-prefixed record/field chain (My.Pkg := x) is left intact — the required
|
a same-prefixed record/field chain (My.Pkg := x) is left intact — the required
|
||||||
trailing '.Symbol' is what distinguishes the two. }
|
trailing '.Symbol' is what distinguishes the two. }
|
||||||
function TParser.TryCollapseUnitQualifier(var AName: string;
|
function TParser.TryCollapseUnitQualifier(var AName: string;
|
||||||
var ALine, ACol: Integer): Boolean;
|
var ALine, ACol: Integer; out AUnit: string): Boolean;
|
||||||
var
|
var
|
||||||
Cand: string;
|
Cand, BestUnit: string;
|
||||||
Comps, BestComps, J, DotIdx, IdentIdx: Integer;
|
Comps, BestComps, J, DotIdx, IdentIdx: Integer;
|
||||||
begin
|
begin
|
||||||
Result := False;
|
Result := False;
|
||||||
|
AUnit := '';
|
||||||
|
BestUnit := '';
|
||||||
Cand := AName;
|
Cand := AName;
|
||||||
Comps := 0;
|
Comps := 0;
|
||||||
if IsUsedUnit(Cand) then BestComps := 0 else BestComps := -1;
|
if IsUsedUnit(Cand) then
|
||||||
|
begin
|
||||||
|
BestComps := 0;
|
||||||
|
BestUnit := Cand;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
BestComps := -1;
|
||||||
{ Greedily absorb '.ident' components while the candidate stays a viable unit
|
{ Greedily absorb '.ident' components while the candidate stays a viable unit
|
||||||
prefix; remember the longest position that is an exact used-unit match. }
|
prefix; remember the longest position that is an exact used-unit match. }
|
||||||
J := 1;
|
J := 1;
|
||||||
|
|
@ -346,7 +355,11 @@ begin
|
||||||
Break;
|
Break;
|
||||||
Cand := Cand + '.' + PeekValueAt(IdentIdx);
|
Cand := Cand + '.' + PeekValueAt(IdentIdx);
|
||||||
Comps := J;
|
Comps := J;
|
||||||
if IsUsedUnit(Cand) then BestComps := Comps;
|
if IsUsedUnit(Cand) then
|
||||||
|
begin
|
||||||
|
BestComps := Comps;
|
||||||
|
BestUnit := Cand;
|
||||||
|
end;
|
||||||
Inc(J);
|
Inc(J);
|
||||||
end;
|
end;
|
||||||
if BestComps < 0 then Exit; { AName does not lead to a used unit }
|
if BestComps < 0 then Exit; { AName does not lead to a used unit }
|
||||||
|
|
@ -359,6 +372,7 @@ begin
|
||||||
AName := PeekValueAt(IdentIdx);
|
AName := PeekValueAt(IdentIdx);
|
||||||
ALine := PeekLineAt(IdentIdx);
|
ALine := PeekLineAt(IdentIdx);
|
||||||
ACol := PeekColAt(IdentIdx);
|
ACol := PeekColAt(IdentIdx);
|
||||||
|
AUnit := BestUnit;
|
||||||
for J := 1 to IdentIdx + 1 do Advance(); { consume unit name, dot, symbol }
|
for J := 1 to IdentIdx + 1 do Advance(); { consume unit name, dot, symbol }
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
@ -2121,13 +2135,16 @@ begin
|
||||||
if Check(tkLParen) then
|
if Check(tkLParen) then
|
||||||
begin
|
begin
|
||||||
Advance();
|
Advance();
|
||||||
{ First name may be a plain class name or a generic interface name like IFoo<T> }
|
{ First name may be a plain class name, a unit-qualified ancestor
|
||||||
Result.ParentName := ParseGenericName();
|
(Unit.TParent), or a generic interface name like IFoo<T>. ParseTypeName
|
||||||
|
already absorbs the unit qualifier and (nested) generic arguments, so a
|
||||||
|
qualified ancestor disambiguates a same-named type via the resolver. }
|
||||||
|
Result.ParentName := ParseTypeName();
|
||||||
{ Additional names after a comma are implemented interface names }
|
{ Additional names after a comma are implemented interface names }
|
||||||
while Check(tkComma) do
|
while Check(tkComma) do
|
||||||
begin
|
begin
|
||||||
Advance();
|
Advance();
|
||||||
Result.ImplementsNames.Add(ParseGenericName());
|
Result.ImplementsNames.Add(ParseTypeName());
|
||||||
end;
|
end;
|
||||||
Expect(tkRParen);
|
Expect(tkRParen);
|
||||||
end;
|
end;
|
||||||
|
|
@ -2997,6 +3014,7 @@ end;
|
||||||
function TParser.ParseStmt: TASTStmt;
|
function TParser.ParseStmt: TASTStmt;
|
||||||
var
|
var
|
||||||
Name: string;
|
Name: string;
|
||||||
|
QualUnit: string; { matched unit from a 'Unit.Symbol' target; unused here }
|
||||||
Line, Col: Integer;
|
Line, Col: Integer;
|
||||||
Call: TProcCall;
|
Call: TProcCall;
|
||||||
Assign: TAssignment;
|
Assign: TAssignment;
|
||||||
|
|
@ -3156,7 +3174,7 @@ begin
|
||||||
treats it as an unqualified procedure call or assignment target resolved
|
treats it as an unqualified procedure call or assignment target resolved
|
||||||
through the uses chain. No-op (consumes nothing) for a same-prefixed
|
through the uses chain. No-op (consumes nothing) for a same-prefixed
|
||||||
record/field chain — see TryCollapseUnitQualifier. }
|
record/field chain — see TryCollapseUnitQualifier. }
|
||||||
TryCollapseUnitQualifier(Name, Line, Col);
|
TryCollapseUnitQualifier(Name, Line, Col, QualUnit);
|
||||||
|
|
||||||
if Check(tkLBracket) then
|
if Check(tkLBracket) then
|
||||||
begin
|
begin
|
||||||
|
|
@ -4851,6 +4869,7 @@ var
|
||||||
Inner: TASTExpr;
|
Inner: TASTExpr;
|
||||||
Name: string;
|
Name: string;
|
||||||
SecondName: string;
|
SecondName: string;
|
||||||
|
QualUnit: string;
|
||||||
Line, Col: Integer;
|
Line, Col: Integer;
|
||||||
ZeroNode: TIntLiteral;
|
ZeroNode: TIntLiteral;
|
||||||
NegNode: TBinaryExpr;
|
NegNode: TBinaryExpr;
|
||||||
|
|
@ -4978,8 +4997,10 @@ begin
|
||||||
so everything below resolves Symbol through the uses chain as an
|
so everything below resolves Symbol through the uses chain as an
|
||||||
unqualified reference — a free call when '(' follows, otherwise a
|
unqualified reference — a free call when '(' follows, otherwise a
|
||||||
plain identifier / indexed / chained / generic access. No-op for a
|
plain identifier / indexed / chained / generic access. No-op for a
|
||||||
same-prefixed record/field chain. }
|
same-prefixed record/field chain. QualUnit captures the matched
|
||||||
TryCollapseUnitQualifier(Name, Line, Col);
|
unit so a plain identifier can be resolved against that unit's own
|
||||||
|
exports (see IdNode.QualifierUnit below). }
|
||||||
|
TryCollapseUnitQualifier(Name, Line, Col, QualUnit);
|
||||||
{ Generic constructor: TypeName<Args>.Method or diamond TypeName<>.Method
|
{ Generic constructor: TypeName<Args>.Method or diamond TypeName<>.Method
|
||||||
Heuristic: '<' followed by IDENT followed by '>' or ',' is treated as
|
Heuristic: '<' followed by IDENT followed by '>' or ',' is treated as
|
||||||
generic type args. '<>' (empty) is the diamond operator — type args
|
generic type args. '<>' (empty) is the diamond operator — type args
|
||||||
|
|
@ -5231,6 +5252,7 @@ begin
|
||||||
IdNode.Line := Line;
|
IdNode.Line := Line;
|
||||||
IdNode.Col := Col;
|
IdNode.Col := Col;
|
||||||
IdNode.Name := Name;
|
IdNode.Name := Name;
|
||||||
|
IdNode.QualifierUnit := QualUnit;
|
||||||
Result := IdNode;
|
Result := IdNode;
|
||||||
end;
|
end;
|
||||||
{ Postfix dereference: Expr^ and optional Expr^.Field chaining }
|
{ Postfix dereference: Expr^ and optional Expr^.Field chaining }
|
||||||
|
|
|
||||||
|
|
@ -493,6 +493,17 @@ type
|
||||||
the chain without going through the flat global. }
|
the chain without going through the flat global. }
|
||||||
function FindUnitSymbol(const AUnitName, ASymName: string): TSymbol;
|
function FindUnitSymbol(const AUnitName, ASymName: string): TSymbol;
|
||||||
|
|
||||||
|
{ Directed lookup — "look in unit AUnit for the symbol AName".
|
||||||
|
This is THE single entry point for every unit-qualified reference
|
||||||
|
'Unit.Symbol' (idents, type names, statement targets): a unit
|
||||||
|
prefix means resolve against that specific unit's exports, never
|
||||||
|
the flat table or the uses chain. Returns the symbol AUnit
|
||||||
|
exports under AName (of any kind — const/var/type/routine), or nil
|
||||||
|
when AUnit exports no such name. Consults the per-unit cache
|
||||||
|
first, then the unit's interface as a fallback for symbols not yet
|
||||||
|
harvested into the cache. }
|
||||||
|
function ResolveQualified(const AUnit, AName: string): TSymbol;
|
||||||
|
|
||||||
{ Record one enum member in the reverse index (FEnumMemberIndex).
|
{ Record one enum member in the reverse index (FEnumMemberIndex).
|
||||||
Called once per member as its enum type is analysed. Enum members
|
Called once per member as its enum type is analysed. Enum members
|
||||||
are NOT registered as bare global symbols any more — a bare member
|
are NOT registered as bare global symbols any more — a bare member
|
||||||
|
|
@ -2367,6 +2378,35 @@ begin
|
||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TSemanticAnalyser.ResolveQualified(const AUnit,
|
||||||
|
AName: string): TSymbol;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
if (AUnit = '') or (FTable = nil) then Exit;
|
||||||
|
|
||||||
|
{ 1. Per-unit cache: direct keyed retrieval — the symbol AUnit defined.
|
||||||
|
This is what disambiguates two units exporting the same name (a
|
||||||
|
collision winner is evicted from the flat table but kept here). }
|
||||||
|
Result := FindUnitSymbol(AUnit, AName);
|
||||||
|
if Result <> nil then Exit;
|
||||||
|
|
||||||
|
{ 2. Flat-table fallback: covers paths/harnesses that populate the flat
|
||||||
|
global but not the per-unit cache (the cache is only filled by the
|
||||||
|
driver's RegisterUnitIface loop; the in-process test harness skips
|
||||||
|
it). Accept the symbol unless it demonstrably belongs to a DIFFERENT
|
||||||
|
unit — same leniency as the LookupViaUsesChain fallback: an empty
|
||||||
|
OwningUnit is treated as a match, a non-empty mismatch is rejected. }
|
||||||
|
FTable.BypassUsesChain := True;
|
||||||
|
try
|
||||||
|
Result := FTable.Lookup(AName);
|
||||||
|
finally
|
||||||
|
FTable.BypassUsesChain := False;
|
||||||
|
end;
|
||||||
|
if (Result <> nil) and (Result.OwningUnit <> '')
|
||||||
|
and not SameText(Result.OwningUnit, AUnit) then
|
||||||
|
Result := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
function TSemanticAnalyser.LookupViaUsesChain(const AName: string): TSymbol;
|
function TSemanticAnalyser.LookupViaUsesChain(const AName: string): TSymbol;
|
||||||
var
|
var
|
||||||
I: Integer;
|
I: Integer;
|
||||||
|
|
@ -3337,6 +3377,16 @@ begin
|
||||||
DotPos := I;
|
DotPos := I;
|
||||||
if DotPos >= 0 then
|
if DotPos >= 0 then
|
||||||
begin
|
begin
|
||||||
|
{ Unit-qualified type 'Unit.TypeName' — directed lookup against that
|
||||||
|
unit's own exports first, so two units declaring the same type name
|
||||||
|
are disambiguated. Falls back to bare-tail resolution via the uses
|
||||||
|
chain when that unit's per-unit cache has no such type (covers dotted
|
||||||
|
stdlib qualifiers like System.SysUtils.TFoo whose iface key differs
|
||||||
|
from the dotted prefix, and qualified generic instances). }
|
||||||
|
Sym := ResolveQualified(Copy(AName, 0, DotPos),
|
||||||
|
StrCopyTail(AName, DotPos + 1));
|
||||||
|
if (Sym <> nil) and (Sym.Kind = skType) and (Sym.TypeDesc <> nil) then
|
||||||
|
Exit(Sym.TypeDesc);
|
||||||
Result := FindTypeOrInstantiate(StrCopyTail(AName, DotPos + 1));
|
Result := FindTypeOrInstantiate(StrCopyTail(AName, DotPos + 1));
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
@ -5104,6 +5154,7 @@ var
|
||||||
CD: TConstDecl;
|
CD: TConstDecl;
|
||||||
Sym: TSymbol;
|
Sym: TSymbol;
|
||||||
RefSym: TSymbol;
|
RefSym: TSymbol;
|
||||||
|
Prev: TSymbol;
|
||||||
TD: TTypeDesc;
|
TD: TTypeDesc;
|
||||||
Resolved: string;
|
Resolved: string;
|
||||||
IsSameBlockDup: Boolean;
|
IsSameBlockDup: Boolean;
|
||||||
|
|
@ -5204,13 +5255,20 @@ begin
|
||||||
SemanticError(Format('Duplicate identifier ''%s''', [CD.Name]), CD.Line, CD.Col);
|
SemanticError(Format('Duplicate identifier ''%s''', [CD.Name]), CD.Line, CD.Col);
|
||||||
end;
|
end;
|
||||||
{ Cross-unit collision: last-in-uses wins. Detach the prior unit's
|
{ Cross-unit collision: last-in-uses wins. Detach the prior unit's
|
||||||
const — kept alive via the per-unit cache for qualified access
|
const and stash it in the per-unit cache so a qualified reference
|
||||||
(Unit.Const) — and install this unit's const as the flat winner.
|
(Unit.Const) can still reach the shadowed value; install this unit's
|
||||||
Mirrors RegisterConsts on the prebuilt-import path so source-loaded
|
const as the flat winner. Mirrors RegisterConsts on the prebuilt-
|
||||||
and prebuilt deps behave identically. }
|
import path so source-loaded and prebuilt deps behave identically. }
|
||||||
FTable.ExtractLocal(CD.Name);
|
Prev := FTable.ExtractLocal(CD.Name);
|
||||||
|
if (Prev <> nil) and (Prev.OwningUnit <> '') then
|
||||||
|
RegisterUnitSymbol(Prev.OwningUnit, Prev);
|
||||||
FTable.Define(Sym);
|
FTable.Define(Sym);
|
||||||
end;
|
end;
|
||||||
|
{ Register every interface const in the per-unit cache keyed by its owning
|
||||||
|
unit, so a qualified reference resolves against the declaring unit's own
|
||||||
|
value regardless of which unit won the bare (flat) slot. }
|
||||||
|
if Sym.OwningUnit <> '' then
|
||||||
|
RegisterUnitSymbol(Sym.OwningUnit, Sym);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
@ -5901,10 +5959,14 @@ begin
|
||||||
end;
|
end;
|
||||||
if TClassTypeDef(TD.Def).ParentName <> '' then
|
if TClassTypeDef(TD.Def).ParentName <> '' then
|
||||||
begin
|
begin
|
||||||
ParentSym := FTable.Lookup(TClassTypeDef(TD.Def).ParentName);
|
{ Resolve through the type machinery (not a literal FTable.Lookup) so
|
||||||
|
a unit-qualified ancestor 'Unit.TParent' binds to that unit's type
|
||||||
|
and disambiguates a same-named class in another unit.
|
||||||
|
FindTypeOrInstantiate handles both bare and dotted names. }
|
||||||
|
GenParentDesc := FindTypeOrInstantiate(TClassTypeDef(TD.Def).ParentName);
|
||||||
{ If the first name in class(...) is an interface, not a class,
|
{ If the first name in class(...) is an interface, not a class,
|
||||||
treat it as an implements entry — TObject becomes the implicit parent. }
|
treat it as an implements entry — TObject becomes the implicit parent. }
|
||||||
if (ParentSym <> nil) and (ParentSym.TypeDesc is TInterfaceTypeDesc) then
|
if (GenParentDesc <> nil) and (GenParentDesc is TInterfaceTypeDesc) then
|
||||||
begin
|
begin
|
||||||
TClassTypeDef(TD.Def).ImplementsNames.Insert(
|
TClassTypeDef(TD.Def).ImplementsNames.Insert(
|
||||||
0, TClassTypeDef(TD.Def).ParentName);
|
0, TClassTypeDef(TD.Def).ParentName);
|
||||||
|
|
@ -5912,12 +5974,12 @@ begin
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if (ParentSym = nil) or not (ParentSym.TypeDesc is TRecordTypeDesc) then
|
if (GenParentDesc = nil) or not (GenParentDesc is TRecordTypeDesc) then
|
||||||
SemanticError(
|
SemanticError(
|
||||||
Format('Unknown parent class ''%s'' for ''%s''',
|
Format('Unknown parent class ''%s'' for ''%s''',
|
||||||
[TClassTypeDef(TD.Def).ParentName, TD.Name]),
|
[TClassTypeDef(TD.Def).ParentName, TD.Name]),
|
||||||
TD.Line, TD.Col);
|
TD.Line, TD.Col);
|
||||||
ParentRT := TRecordTypeDesc(ParentSym.TypeDesc);
|
ParentRT := TRecordTypeDesc(GenParentDesc);
|
||||||
RT.Parent := ParentRT;
|
RT.Parent := ParentRT;
|
||||||
RT.CopyVTableFrom(ParentRT);
|
RT.CopyVTableFrom(ParentRT);
|
||||||
for K := 0 to ParentRT.Fields.Count - 1 do
|
for K := 0 to ParentRT.Fields.Count - 1 do
|
||||||
|
|
@ -11538,6 +11600,26 @@ begin
|
||||||
a spurious second ambiguity warning. }
|
a spurious second ambiguity warning. }
|
||||||
if TIdentExpr(AExpr).IsConstant and (AExpr.ResolvedType <> nil) then
|
if TIdentExpr(AExpr).IsConstant and (AExpr.ResolvedType <> nil) then
|
||||||
Exit(AExpr.ResolvedType);
|
Exit(AExpr.ResolvedType);
|
||||||
|
{ Unit-qualified reference 'Unit.Symbol' (parser preserved the unit in
|
||||||
|
QualifierUnit). Resolve via the directed-lookup primitive against
|
||||||
|
that specific unit's exports: a same-named symbol in another used
|
||||||
|
unit cannot shadow it, the uses-chain last-wins rule does not apply,
|
||||||
|
and an explicit qualifier is never shadowed by a class field. An
|
||||||
|
absent member is a hard error at the qualification site. No const/
|
||||||
|
var/type special-casing here — it falls through to the shared symbol
|
||||||
|
normalisation below, dispatched on Sym.Kind like any other symbol. }
|
||||||
|
if TIdentExpr(AExpr).QualifierUnit <> '' then
|
||||||
|
begin
|
||||||
|
Sym := ResolveQualified(TIdentExpr(AExpr).QualifierUnit,
|
||||||
|
TIdentExpr(AExpr).Name);
|
||||||
|
if Sym = nil then
|
||||||
|
SemanticError(Format(
|
||||||
|
'Identifier ''%s'' not declared in unit ''%s''',
|
||||||
|
[TIdentExpr(AExpr).Name, TIdentExpr(AExpr).QualifierUnit]),
|
||||||
|
AExpr.Line, AExpr.Col);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
{ Resolution order (matches AnalyseProcCall / AnalyseFuncCall):
|
{ Resolution order (matches AnalyseProcCall / AnalyseFuncCall):
|
||||||
local vars/params > implicit Self.member > unit-level. Without
|
local vars/params > implicit Self.member > unit-level. Without
|
||||||
this priority, a bare identifier inside a method binds to the
|
this priority, a bare identifier inside a method binds to the
|
||||||
|
|
@ -11626,6 +11708,7 @@ begin
|
||||||
Format('Undeclared identifier ''%s''', [TIdentExpr(AExpr).Name]),
|
Format('Undeclared identifier ''%s''', [TIdentExpr(AExpr).Name]),
|
||||||
AExpr.Line, AExpr.Col);
|
AExpr.Line, AExpr.Col);
|
||||||
end;
|
end;
|
||||||
|
end; { end else: unqualified resolution (qualified set Sym above) }
|
||||||
{ Var-params and value-record/array params are both passed by reference at
|
{ Var-params and value-record/array params are both passed by reference at
|
||||||
the QBE ABI level: the local slot holds a pointer, not the aggregate
|
the QBE ABI level: the local slot holds a pointer, not the aggregate
|
||||||
bytes. Codegen must dereference the slot before reading fields. }
|
bytes. Codegen must dereference the slot before reading fields. }
|
||||||
|
|
|
||||||
|
|
@ -802,7 +802,8 @@ begin
|
||||||
Result := EncodeLpstr('nillit')
|
Result := EncodeLpstr('nillit')
|
||||||
else if AE is TIdentExpr then
|
else if AE is TIdentExpr then
|
||||||
Result := EncodeLpstr('id') +
|
Result := EncodeLpstr('id') +
|
||||||
EncodeLpstr(TIdentExpr(AE).Name)
|
EncodeLpstr(TIdentExpr(AE).Name) +
|
||||||
|
EncodeLpstr(TIdentExpr(AE).QualifierUnit)
|
||||||
else if AE is TBinaryExpr then
|
else if AE is TBinaryExpr then
|
||||||
Result := EncodeLpstr('bin') +
|
Result := EncodeLpstr('bin') +
|
||||||
EncodeLpstr(IntToStr(Ord(TBinaryExpr(AE).Op))) +
|
EncodeLpstr(IntToStr(Ord(TBinaryExpr(AE).Op))) +
|
||||||
|
|
@ -1494,6 +1495,7 @@ begin
|
||||||
begin
|
begin
|
||||||
IE := TIdentExpr.Create();
|
IE := TIdentExpr.Create();
|
||||||
IE.Name := ReadLpstrAt(AText, APos);
|
IE.Name := ReadLpstrAt(AText, APos);
|
||||||
|
IE.QualifierUnit := ReadLpstrAt(AText, APos);
|
||||||
Result := IE;
|
Result := IE;
|
||||||
end
|
end
|
||||||
else if Kind = 'bin' then
|
else if Kind = 'bin' then
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,13 @@ type
|
||||||
the order flips the winner. }
|
the order flips the winner. }
|
||||||
procedure TestRun_CrossUnitConst_LastWins;
|
procedure TestRun_CrossUnitConst_LastWins;
|
||||||
procedure TestRun_CrossUnitConst_LastWins_Reversed;
|
procedure TestRun_CrossUnitConst_LastWins_Reversed;
|
||||||
|
{ Unit-qualified const disambiguation: a 'Unit.Foo' reference resolves
|
||||||
|
against that specific unit's exports, so it is never shadowed by a
|
||||||
|
same-named const in another used unit, regardless of `uses` order. }
|
||||||
|
procedure TestRun_CrossUnitConst_QualifiedDisambig;
|
||||||
|
{ Unit-qualified ancestor in a class declaration: class(Unit.TParent)
|
||||||
|
binds to that unit's type so inheritance works across units. }
|
||||||
|
procedure TestRun_QualifiedInheritance;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
@ -246,6 +253,72 @@ begin
|
||||||
AssertEquals('last-in-uses (ua) wins', '100' + LE, Output);
|
AssertEquals('last-in-uses (ua) wins', '100' + LE, Output);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TE2EUsesChainTests.TestRun_CrossUnitConst_QualifiedDisambig;
|
||||||
|
const
|
||||||
|
DrvSrc = '''
|
||||||
|
program P;
|
||||||
|
uses ua, ub;
|
||||||
|
begin
|
||||||
|
WriteLn(ua.Foo);
|
||||||
|
WriteLn(ub.Foo)
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var Output: string; RCode: Integer;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
{ Both ua and ub export `Foo`; qualified references pick each unit's own
|
||||||
|
value (ua.Foo = 100, ub.Foo = 200) independent of the bare last-wins rule. }
|
||||||
|
AssertTrue('compile+link+run',
|
||||||
|
CompileAndRunWithUnits(UA_Const, UB_Const, DrvSrc, Output, RCode));
|
||||||
|
AssertEquals('exit 0', 0, RCode);
|
||||||
|
AssertEquals('ua.Foo then ub.Foo', '100' + LE + '200' + LE, Output);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TE2EUsesChainTests.TestRun_QualifiedInheritance;
|
||||||
|
const
|
||||||
|
UnitSrc = '''
|
||||||
|
unit ua;
|
||||||
|
interface
|
||||||
|
type
|
||||||
|
TParent = class
|
||||||
|
function Base: Integer;
|
||||||
|
end;
|
||||||
|
implementation
|
||||||
|
function TParent.Base: Integer;
|
||||||
|
begin
|
||||||
|
Result := 41
|
||||||
|
end;
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
DrvSrc = '''
|
||||||
|
program P;
|
||||||
|
uses ua;
|
||||||
|
type
|
||||||
|
TChild = class(ua.TParent)
|
||||||
|
function Plus1: Integer;
|
||||||
|
end;
|
||||||
|
function TChild.Plus1: Integer;
|
||||||
|
begin
|
||||||
|
Result := Base() + 1
|
||||||
|
end;
|
||||||
|
var
|
||||||
|
C: TChild;
|
||||||
|
begin
|
||||||
|
C := TChild.Create();
|
||||||
|
WriteLn(C.Plus1())
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var Output: string; RCode: Integer;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
{ class(ua.TParent) resolves the qualified ancestor to ua's type; the child
|
||||||
|
inherits Base (41) and adds 1 -> 42. }
|
||||||
|
AssertTrue('compile+link+run',
|
||||||
|
CompileAndRunWithUnit('ua', UnitSrc, DrvSrc, Output, RCode));
|
||||||
|
AssertEquals('exit 0', 0, RCode);
|
||||||
|
AssertEquals('inherited Base + 1', '42' + LE, Output);
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TE2EUsesChainTests);
|
RegisterTest(TE2EUsesChainTests);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue