fix(parser): parse nested-proc-first bodies in unit impl routines (Andrew 82e770ac)

A unit implementation-section routine whose body opens with a nested
procedure/function declaration (before any var/const/type section) was
mis-parsed: ParseUnit called ParseMethodDecl without ACanHaveNestedProcs, so a
leading 'procedure'/'function' was treated as the next top-level decl and
parsing failed at the outer 'begin' ("Expected 'end' but got 'begin'").

Pass ACanHaveNestedProcs=True for the two impl-section ParseMethodDecl calls.
The IsForward guard already present in ParseMethodDecl (our commit 33db0f6)
keeps a forward decl from claiming the following sibling as its nested body, so
only the call-site change was needed — Andrew's branch independently added the
same forward-flag mechanism, which we already have.

Note: this is a PARSER fix (the unit body now parses correctly).  Resolving a
CALL to such a nested procedure is a separate, pre-existing semantic-pass gap
that affects program routines too (Inner() reports "Cannot find declaration");
it is out of scope here.  Andrew's tests are likewise parse-level.

Tests: cp.test.units.pas parse-level tests (outer routine is one decl with a
body holding the nested proc).

Cherry-pick policy: the forward-flag half was a duplicate of our work and
skipped; the ACanHaveNestedProcs threading + tests are taken.

Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK; full suite green
on both the QBE-built and native-built runners (3717 tests).
This commit is contained in:
Graeme Geldenhuys 2026-06-21 00:53:17 +01:00
parent 54cfe37e62
commit cf994dcca4
2 changed files with 57 additions and 2 deletions

View file

@ -3954,7 +3954,7 @@ begin
Check(tkVar) or Check(tkThreadVar) or Check(tkConst) or Check(tkType) do
begin
if Check(tkFunction) then
Result.ImplBlock.ProcDecls.Add(ParseMethodDecl(True))
Result.ImplBlock.ProcDecls.Add(ParseMethodDecl(True, True))
else if Check(tkVar) or Check(tkThreadVar) then
ParseVarBlock(Result.ImplBlock)
else if Check(tkConst) then
@ -3962,7 +3962,12 @@ begin
else if Check(tkType) then
ParseTypeSection(Result.ImplBlock)
else
Result.ImplBlock.ProcDecls.Add(ParseMethodDecl(False));
{ ACanHaveNestedProcs=True: an impl routine whose body opens with a
nested procedure/function (before any var/const/type) must parse as
one routine WITH a body, not split into two bodyless decls. The
IsForward guard inside ParseMethodDecl stops a forward decl from
swallowing the following sibling. (Andrew's fix 82e770ac.) }
Result.ImplBlock.ProcDecls.Add(ParseMethodDecl(False, True));
end;
{ Optional initialization / finalization sections }

View file

@ -41,6 +41,11 @@ type
procedure TestParse_Unit_ForwardDeclHasNilBody;
procedure TestParse_Unit_ImplHasFullBodies;
procedure TestParse_Unit_ImplBodyIsNotNil;
{ Nested procedure as the FIRST item in an impl routine's body must not be
mistaken for a second top-level decl (impl-section parser). Andrew's
fix 82e770ac. }
procedure TestParse_Unit_ImplNestedProcFirst_SingleDecl;
procedure TestParse_Unit_ImplNestedProcFirst_HasBody;
procedure TestParse_Unit_IntfTypeDecl;
procedure TestParse_Unit_ForwardDeclParamCount;
procedure TestParse_Unit_MultipleForwardDecls;
@ -205,6 +210,29 @@ const
end.
''';
{ Implementation-section routine whose body leads with a nested procedure
declaration (no preceding var/const/type). The outer routine must parse
as a single decl WITH a body, not split into two bodyless decls. }
SrcUnitNestedProcFirst =
'''
unit Nested;
interface
function Outer(N: Integer): Integer;
implementation
function Outer(N: Integer): Integer;
function Inner(X: Integer): Integer;
begin
Result := X + 1
end;
var
T: Integer;
begin
T := Inner(N);
Result := T
end;
end.
''';
{ ------------------------------------------------------------------ }
{ Lexer tests }
{ ------------------------------------------------------------------ }
@ -299,6 +327,28 @@ begin
finally U.Free(); end;
end;
procedure TUnitTests.TestParse_Unit_ImplNestedProcFirst_SingleDecl;
var U: TUnit;
begin
U := ParseUnit(SrcUnitNestedProcFirst);
try
AssertEquals('impl has 1 decl (outer routine only)',
1, U.ImplBlock.ProcDecls.Count);
finally U.Free(); end;
end;
procedure TUnitTests.TestParse_Unit_ImplNestedProcFirst_HasBody;
var U: TUnit; MD: TMethodDecl;
begin
U := ParseUnit(SrcUnitNestedProcFirst);
try
MD := TMethodDecl(U.ImplBlock.ProcDecls[0]);
AssertNotNull('outer routine has a body', MD.Body);
AssertEquals('outer body holds the nested proc',
1, MD.Body.ProcDecls.Count);
finally U.Free(); end;
end;
procedure TUnitTests.TestParse_Unit_IntfTypeDecl;
var U: TUnit;
begin