feat(types): resolve qualified type names UnitName.TypeName
A type written as 'UnitName.TypeName' (the unit qualifier may itself be dotted, e.g. System.SysUtils.TFormatSettings) was a parse error: after reading the leading identifier ParseTypeName only accepted a generic '<...>' argument list, not a '.' continuation. Parse the full dotted path into the type name, then resolve it by its final component — a type is always a single identifier; the leading components name a unit already loaded by the 'uses' clause. The strip is centralised in TSymbolTable.FindType (via UnitQualifierTail) so every type position benefits uniformly — var/field decls, parameters, and the several routine return-type sites — and FindTypeOrInstantiate strips the qualifier off a generic instance (Unit.TList<Integer>) before instantiation. Plain dotted identifier paths only: the bracket/space/ caret/angle encodings for array, pointer, set and generic types are left untouched. Tested: dotted-name capture in the parser, semantic resolution of a qualified builtin (System.Integer), and end-to-end compile+run of a program using a cross-unit qualified type. (cherry picked from commit 7e9c3d1f61d42769bc3341195220a25190637e15)
This commit is contained in:
parent
cf994dcca4
commit
9d876b46aa
|
|
@ -385,6 +385,17 @@ begin
|
|||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||
Result := FCurrent.Value;
|
||||
Advance();
|
||||
{ Qualified type name: 'UnitName.TypeName'. The unit qualifier may itself
|
||||
be dotted (e.g. System.SysUtils.TFormatSettings). A type is always the
|
||||
final identifier; the leading components name the (possibly dotted) unit.
|
||||
Carry the full dotted form through — the semantic type resolver strips the
|
||||
unit qualifier and resolves the tail via the uses chain. }
|
||||
while Check(tkDot) and (PeekKind() = tkIdent) do
|
||||
begin
|
||||
Advance(); { consume '.' }
|
||||
Result := Result + '.' + FCurrent.Value;
|
||||
Advance();
|
||||
end;
|
||||
if Check(tkLessThan) then
|
||||
begin
|
||||
Advance(); { consume '<' }
|
||||
|
|
|
|||
|
|
@ -2352,6 +2352,7 @@ var
|
|||
EnumDesc: TEnumTypeDesc;
|
||||
SAT: TStaticArrayTypeDesc;
|
||||
DAT: TDynArrayTypeDesc;
|
||||
I, LtPos, DotPos: Integer;
|
||||
begin
|
||||
Result := FTable.FindType(AName);
|
||||
if Result <> nil then Exit;
|
||||
|
|
@ -2539,6 +2540,25 @@ begin
|
|||
[BaseType.Name]), 0, 0);
|
||||
Exit;
|
||||
end;
|
||||
{ Qualified type name 'UnitName.TypeName' — the unit qualifier may itself be
|
||||
dotted (System.SysUtils.TFormatSettings). A type is a single identifier,
|
||||
so strip everything up to the final '.' that precedes any generic '<...>'
|
||||
argument list and resolve the tail via the normal uses chain (the qualifier
|
||||
names the unit, already loaded by the 'uses' clause). A qualified generic
|
||||
instance (Unit.TList<Integer>) reduces to the bare 'TList<Integer>', which
|
||||
the generic branch above instantiates. }
|
||||
LtPos := StrPos('<', AName);
|
||||
if LtPos < 0 then
|
||||
LtPos := Length(AName);
|
||||
DotPos := -1;
|
||||
for I := 0 to LtPos - 1 do
|
||||
if StrAt(AName, I) = Ord('.') then
|
||||
DotPos := I;
|
||||
if DotPos >= 0 then
|
||||
begin
|
||||
Result := FindTypeOrInstantiate(StrCopyTail(AName, DotPos + 1));
|
||||
Exit;
|
||||
end;
|
||||
if StrPos('<', AName) >= 0 then
|
||||
begin
|
||||
Result := InstantiateGeneric(AName);
|
||||
|
|
|
|||
|
|
@ -1932,15 +1932,53 @@ begin
|
|||
Result := Sym;
|
||||
end;
|
||||
|
||||
{ For a plain dotted identifier path 'A.B.C' (every component an identifier,
|
||||
single-dot separators) return the final component 'C'. In a qualified type
|
||||
reference 'UnitName.TypeName' the type is always that final identifier and
|
||||
the leading components name the (possibly dotted) unit. Returns '' when
|
||||
AName is not such a path — e.g. the bracket/space/caret/angle-bracket forms
|
||||
used to encode array, pointer, set and generic-instance types — leaving
|
||||
those untouched for the on-demand resolver. }
|
||||
function UnitQualifierTail(const AName: string): string;
|
||||
var
|
||||
I, LastDot: Integer;
|
||||
B: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
LastDot := -1;
|
||||
for I := 0 to Length(AName) - 1 do
|
||||
begin
|
||||
B := AName[I];
|
||||
if B = Ord('.') then
|
||||
LastDot := I
|
||||
else if not (((B >= Ord('A')) and (B <= Ord('Z'))) or
|
||||
((B >= Ord('a')) and (B <= Ord('z'))) or
|
||||
((B >= Ord('0')) and (B <= Ord('9'))) or
|
||||
(B = Ord('_'))) then
|
||||
Exit; { contains a non-identifier byte — not a qualified name }
|
||||
end;
|
||||
if (LastDot >= 0) and (LastDot < Length(AName) - 1) then
|
||||
Result := Copy(AName, LastDot + 1, MaxInt);
|
||||
end;
|
||||
|
||||
function TSymbolTable.FindType(const AName: string): TTypeDesc;
|
||||
var
|
||||
Sym: TSymbol;
|
||||
Sym: TSymbol;
|
||||
Tail: string;
|
||||
begin
|
||||
Sym := Lookup(AName); { honour uses-chain visibility }
|
||||
if (Sym <> nil) and (Sym.Kind = skType) then
|
||||
Result := Sym.TypeDesc
|
||||
else
|
||||
Result := nil;
|
||||
Exit(Sym.TypeDesc);
|
||||
{ Qualified type name 'UnitName.TypeName' — resolve by the final dotted
|
||||
component through the same uses chain (the qualifier names a used unit). }
|
||||
Tail := UnitQualifierTail(AName);
|
||||
if Tail <> '' then
|
||||
begin
|
||||
Sym := Lookup(Tail);
|
||||
if (Sym <> nil) and (Sym.Kind = skType) then
|
||||
Exit(Sym.TypeDesc);
|
||||
end;
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
function IsUnmangledUnit(const AUnitName: string): Boolean;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ type
|
|||
procedure TestSingleVarDecl;
|
||||
procedure TestMultipleVarDecls;
|
||||
procedure TestMultiNameVarDecl;
|
||||
{ Qualified type name 'UnitName.TypeName' is captured whole. }
|
||||
procedure TestQualifiedTypeName;
|
||||
procedure TestQualifiedTypeName_DottedUnit;
|
||||
|
||||
{ Statements }
|
||||
procedure TestEmptyBeginEnd;
|
||||
|
|
@ -173,6 +176,35 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TParserTests.TestQualifiedTypeName;
|
||||
var
|
||||
Prog: TProgram;
|
||||
begin
|
||||
Prog := ParseSource('program P; var x: System.Integer; begin end.');
|
||||
try
|
||||
AssertEquals('qualified type captured whole', 'System.Integer',
|
||||
TVarDecl(Prog.Block.Decls.Items[0]).TypeName);
|
||||
finally
|
||||
Prog.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TParserTests.TestQualifiedTypeName_DottedUnit;
|
||||
var
|
||||
Prog: TProgram;
|
||||
begin
|
||||
{ The unit qualifier may itself be dotted; the whole dotted path is
|
||||
carried through to the type name. }
|
||||
Prog := ParseSource('program P; var x: System.SysUtils.TFoo; begin end.');
|
||||
try
|
||||
AssertEquals('dotted-unit qualified type captured whole',
|
||||
'System.SysUtils.TFoo',
|
||||
TVarDecl(Prog.Block.Decls.Items[0]).TypeName);
|
||||
finally
|
||||
Prog.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TParserTests.TestMultipleVarDecls;
|
||||
var
|
||||
Prog: TProgram;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ type
|
|||
{ ------------------------------------------------------------------ }
|
||||
procedure TestSemantic_Unit_OK;
|
||||
procedure TestSemantic_Unit_WithType_OK;
|
||||
{ Qualified type name 'System.Integer' resolves to the builtin. }
|
||||
procedure TestSemantic_Unit_QualifiedType_OK;
|
||||
procedure TestSemantic_Unit_ImplBodyUsesIntfType;
|
||||
procedure TestSemantic_Unit_SignatureMismatch_ParamCount_RaisesError;
|
||||
procedure TestSemantic_Unit_MissingImpl_RaisesError;
|
||||
|
|
@ -233,6 +235,23 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
{ Qualified type names resolve by their final dotted component through the
|
||||
uses chain. 'System.Integer' needs no cross-unit load — System is
|
||||
implicit and Integer is builtin — so it self-containedly exercises the
|
||||
qualifier-stripping type resolver. }
|
||||
SrcUnitQualifiedType =
|
||||
'''
|
||||
unit QualT;
|
||||
interface
|
||||
function Twice(N: System.Integer): System.Integer;
|
||||
implementation
|
||||
function Twice(N: System.Integer): System.Integer;
|
||||
begin
|
||||
Result := N * 2
|
||||
end;
|
||||
end.
|
||||
''';
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Lexer tests }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
@ -394,6 +413,11 @@ begin
|
|||
AnalyseUnit(SrcUnitWithType).Free();
|
||||
end;
|
||||
|
||||
procedure TUnitTests.TestSemantic_Unit_QualifiedType_OK;
|
||||
begin
|
||||
AnalyseUnit(SrcUnitQualifiedType).Free();
|
||||
end;
|
||||
|
||||
procedure TUnitTests.TestSemantic_Unit_ImplBodyUsesIntfType;
|
||||
begin
|
||||
{ TBox is declared in interface; impl body can reference it }
|
||||
|
|
|
|||
Loading…
Reference in a new issue