From cf994dcca4618f91559b862667153b3c876f2b25 Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Sun, 21 Jun 2026 00:53:17 +0100 Subject: [PATCH] fix(parser): parse nested-proc-first bodies in unit impl routines (Andrew 82e770ac) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- compiler/src/main/pascal/uParser.pas | 9 +++- compiler/src/test/pascal/cp.test.units.pas | 50 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/pascal/uParser.pas b/compiler/src/main/pascal/uParser.pas index 491b325..a01db4c 100644 --- a/compiler/src/main/pascal/uParser.pas +++ b/compiler/src/main/pascal/uParser.pas @@ -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 } diff --git a/compiler/src/test/pascal/cp.test.units.pas b/compiler/src/test/pascal/cp.test.units.pas index 95c3ec9..ed5e07c 100644 --- a/compiler/src/test/pascal/cp.test.units.pas +++ b/compiler/src/test/pascal/cp.test.units.pas @@ -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