fix(parser): support nested generic type arguments
TList<TList<Integer>> and TBox<TPair<Integer, string>> failed to parse:
each type argument was read as a single bare identifier, with no recursion
into a nested <...>, so a nested '<' produced "Expected '>' but got '<'"
(type position) or "Expected '.' or '(' after generic type arguments"
(constructor/expression position).
Both type-argument parse sites now recurse through ParseTypeName, so a type
argument may itself be a generic specialisation, to arbitrary depth
(TList<TList<TList<Integer>>> works). The expression-position heuristic also
accepts tkLessThan as the lookahead-2 token (the first arg being itself
generic). The comparison-operator disambiguation (a < b, (a < b) and (b < c))
is unaffected.
Found by the e2e generics hardening sweep. Regression:
TE2EMiscTests.TestRun_NestedGenericTypeArgs (TBox<TBox<Integer>>, both
backends). docs/grammar.ebnf TypeArgList rule updated to recurse via
GenericName.
All three fixpoints + full suite (3224 tests) pass.
This commit is contained in:
parent
236736a3be
commit
e2b715b53b
|
|
@ -273,8 +273,10 @@ begin
|
|||
raise EParseError.Create(Format(
|
||||
'Expected type argument after ''<'' at line %d col %d in %s',
|
||||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||
Result := Result + FCurrent.Value;
|
||||
Advance();
|
||||
{ Each type argument is itself a (possibly-generic) type name, so recurse
|
||||
— this is what makes nested generics like TList<TList<Integer>> and
|
||||
TBox<TPair<Integer, string>> parse. }
|
||||
Result := Result + Self.ParseTypeName();
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance();
|
||||
|
|
@ -282,8 +284,7 @@ begin
|
|||
raise EParseError.Create(Format(
|
||||
'Expected type argument after '','' at line %d col %d in %s',
|
||||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||
Result := Result + ',' + FCurrent.Value;
|
||||
Advance();
|
||||
Result := Result + ',' + Self.ParseTypeName();
|
||||
end;
|
||||
Expect(tkGreaterThan);
|
||||
Result := Result + '>';
|
||||
|
|
@ -3970,16 +3971,18 @@ begin
|
|||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||
end
|
||||
else if Check(tkLessThan) and (PeekKind() = tkIdent) and
|
||||
(PeekKind2() in [tkGreaterThan, tkComma]) then
|
||||
(PeekKind2() in [tkGreaterThan, tkComma, tkLessThan]) then
|
||||
begin
|
||||
{ Generic type args. PeekKind2 = '<' means the first arg is itself
|
||||
generic (TList<TList<Integer>>) — recurse via ParseTypeName so
|
||||
nested generics parse. Plain idents also go through ParseTypeName
|
||||
(which simply returns the ident when no '<' follows). }
|
||||
Advance(); { consume '<' }
|
||||
Name := Name + '<' + FCurrent.Value;
|
||||
Advance();
|
||||
Name := Name + '<' + Self.ParseTypeName();
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance();
|
||||
Name := Name + ',' + FCurrent.Value;
|
||||
Advance();
|
||||
Name := Name + ',' + Self.ParseTypeName();
|
||||
end;
|
||||
Expect(tkGreaterThan);
|
||||
Name := Name + '>';
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ type
|
|||
{ (expr as T).Field := value — a parenthesised cast as an assignment
|
||||
TARGET. Was a parser gap (statements could not start with '('). }
|
||||
procedure TestRun_ParenCastAsAssignmentTarget;
|
||||
|
||||
{ Nested generic type arguments: TList<TList<Integer>>. Was a parser gap
|
||||
(type-arg list did not recurse, in both type and constructor position). }
|
||||
procedure TestRun_NestedGenericTypeArgs;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1146,6 +1150,33 @@ begin
|
|||
AssertRunsOnAll(SrcParenCastTarget, '42' + LE, 0);
|
||||
end;
|
||||
|
||||
const
|
||||
{ Nested generic type args, in both var-type and constructor position. }
|
||||
SrcNestedGeneric = '''
|
||||
program Prg;
|
||||
type TBox<T> = class
|
||||
FV: T;
|
||||
procedure SetV(V: T); begin FV := V end;
|
||||
function GetV: T; begin Result := FV end;
|
||||
end;
|
||||
var outer: TBox<TBox<Integer>>; inner: TBox<Integer>;
|
||||
begin
|
||||
inner := TBox<Integer>.Create();
|
||||
inner.SetV(7);
|
||||
outer := TBox<TBox<Integer>>.Create();
|
||||
outer.SetV(inner);
|
||||
WriteLn(outer.GetV().GetV());
|
||||
outer.Free();
|
||||
inner.Free()
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2EMiscTests.TestRun_NestedGenericTypeArgs;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcNestedGeneric, '7' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EMiscTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -711,8 +711,10 @@ SetElement
|
|||
= Expr [ DOTDOT Expr ]
|
||||
;
|
||||
|
||||
(* Each type argument is itself a (possibly-generic) type name, so type
|
||||
* arguments nest: TList<TList<Integer>>, TBox<TPair<Integer, string>>. *)
|
||||
TypeArgList
|
||||
= IDENT { COMMA IDENT }
|
||||
= GenericName { COMMA GenericName }
|
||||
;
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue