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(
|
raise EParseError.Create(Format(
|
||||||
'Expected type argument after ''<'' at line %d col %d in %s',
|
'Expected type argument after ''<'' at line %d col %d in %s',
|
||||||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||||
Result := Result + FCurrent.Value;
|
{ Each type argument is itself a (possibly-generic) type name, so recurse
|
||||||
Advance();
|
— this is what makes nested generics like TList<TList<Integer>> and
|
||||||
|
TBox<TPair<Integer, string>> parse. }
|
||||||
|
Result := Result + Self.ParseTypeName();
|
||||||
while Check(tkComma) do
|
while Check(tkComma) do
|
||||||
begin
|
begin
|
||||||
Advance();
|
Advance();
|
||||||
|
|
@ -282,8 +284,7 @@ begin
|
||||||
raise EParseError.Create(Format(
|
raise EParseError.Create(Format(
|
||||||
'Expected type argument after '','' at line %d col %d in %s',
|
'Expected type argument after '','' at line %d col %d in %s',
|
||||||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||||
Result := Result + ',' + FCurrent.Value;
|
Result := Result + ',' + Self.ParseTypeName();
|
||||||
Advance();
|
|
||||||
end;
|
end;
|
||||||
Expect(tkGreaterThan);
|
Expect(tkGreaterThan);
|
||||||
Result := Result + '>';
|
Result := Result + '>';
|
||||||
|
|
@ -3970,16 +3971,18 @@ begin
|
||||||
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
[FCurrent.Line, FCurrent.Col, FLexer.Filename]));
|
||||||
end
|
end
|
||||||
else if Check(tkLessThan) and (PeekKind() = tkIdent) and
|
else if Check(tkLessThan) and (PeekKind() = tkIdent) and
|
||||||
(PeekKind2() in [tkGreaterThan, tkComma]) then
|
(PeekKind2() in [tkGreaterThan, tkComma, tkLessThan]) then
|
||||||
begin
|
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 '<' }
|
Advance(); { consume '<' }
|
||||||
Name := Name + '<' + FCurrent.Value;
|
Name := Name + '<' + Self.ParseTypeName();
|
||||||
Advance();
|
|
||||||
while Check(tkComma) do
|
while Check(tkComma) do
|
||||||
begin
|
begin
|
||||||
Advance();
|
Advance();
|
||||||
Name := Name + ',' + FCurrent.Value;
|
Name := Name + ',' + Self.ParseTypeName();
|
||||||
Advance();
|
|
||||||
end;
|
end;
|
||||||
Expect(tkGreaterThan);
|
Expect(tkGreaterThan);
|
||||||
Name := Name + '>';
|
Name := Name + '>';
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,10 @@ type
|
||||||
{ (expr as T).Field := value — a parenthesised cast as an assignment
|
{ (expr as T).Field := value — a parenthesised cast as an assignment
|
||||||
TARGET. Was a parser gap (statements could not start with '('). }
|
TARGET. Was a parser gap (statements could not start with '('). }
|
||||||
procedure TestRun_ParenCastAsAssignmentTarget;
|
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;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
@ -1146,6 +1150,33 @@ begin
|
||||||
AssertRunsOnAll(SrcParenCastTarget, '42' + LE, 0);
|
AssertRunsOnAll(SrcParenCastTarget, '42' + LE, 0);
|
||||||
end;
|
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
|
initialization
|
||||||
RegisterTest(TE2EMiscTests);
|
RegisterTest(TE2EMiscTests);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -711,8 +711,10 @@ SetElement
|
||||||
= Expr [ DOTDOT Expr ]
|
= 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
|
TypeArgList
|
||||||
= IDENT { COMMA IDENT }
|
= GenericName { COMMA GenericName }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue