From 042f49e8087afc92ee4d76c45e483c2b4b27e731 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 27 Oct 2026 13:47:58 +0100 Subject: [PATCH 1/2] test: cover forward qualified indirect constructors Add regression coverage for a fully qualified class used by an indirect constructor before that package is declared. This matches the constructor pattern used by Apache-Admin-Config. Generated with [Codex](https://openai.com/codex) Co-Authored-By: Codex <158243242+openai-codex[bot]@users.noreply.github.com> --- .../unit/indirect_object_constructor.t | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/resources/unit/indirect_object_constructor.t b/src/test/resources/unit/indirect_object_constructor.t index c90ae5bf37..bc23c3cc2a 100644 --- a/src/test/resources/unit/indirect_object_constructor.t +++ b/src/test/resources/unit/indirect_object_constructor.t @@ -42,6 +42,32 @@ is( 'require Foo::Bar marks the package for following indirect constructor syntax' ); +{ + package IndirectObject::ForwardFactory; + sub new { + my $class = shift; + my $tree = new IndirectObject::Forward::Tree(@_); + return bless { tree => $tree }, $class; + } +} + +{ + package IndirectObject::Forward::Tree; + sub new { bless { args => [@_[1 .. $#_]] }, shift } +} + +my $forward_factory = IndirectObject::ForwardFactory->new('from-forward-class'); +is( + ref($forward_factory->{tree}), + 'IndirectObject::Forward::Tree', + 'qualified indirect constructor works for a package declared later' +); +is_deeply( + $forward_factory->{tree}->{args}, + ['from-forward-class'], + 'qualified indirect constructor forwards arguments to a later package' +); + # A fully-qualified class used only in platform-specific code need not be # loaded for Perl to parse an indirect constructor followed by a method call. # Argv::Win32Utils uses this exact form on non-Windows platforms. From 663933ec28dec109340a9212c0fa874572004fb8 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 27 Oct 2026 14:01:17 +0100 Subject: [PATCH 2/2] fix: parse forward-qualified indirect constructors in new methods When a constructor method uses an indirect constructor for a fully qualified class that is declared later, keep the expression as indirect-object syntax instead of resolving it as a direct call to the current new method. Cover the behavior and record the CPAN compatibility fix. Fixes #1899. Generated with [Codex](https://openai.com/codex) Co-Authored-By: Codex <158243242+openai-codex[bot]@users.noreply.github.com> --- docs/about/changelog.md | 2 ++ .../org/perlonjava/frontend/parser/SubroutineParser.java | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 19cdddb7d4..15bf39ba71 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -6,6 +6,8 @@ priorities and future plans. ## Work in progress +- Parse forward-declared qualified classes as indirect constructors inside `new` methods (#1899). + - Match Perl `Encode` aliases for ANSI X3.4-1968, HZ-GB-2312, and ISO-8859-8-I, and include the BOM when encoding generic UTF-32 (#1897). - Separate organizational and project issue queues, record temporary agent claims and dependencies, and keep agent checkpoints concise (#1895). diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index e03f738f28..6709a55340 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -386,7 +386,12 @@ && isValidIndirectMethod(sourceSubName, parser) if ((isPackage != null && !isPackage) || (isPackage == null && !isKnownSub && token.text.equals("(") && !packageName.contains("::") && subExists) || (subExists && packageName.contains("::") && token.text.equals("(") - && !(isPackage != null && isPackage)) + && !(isPackage != null && isPackage) + // Inside a named `new` method, a qualified class may be + // declared later in the file. Keep it as indirect + // constructor syntax instead of parsing it as a direct + // call to the current `new` subroutine. + && !sourceSubName.equals("new")) // If the method name is a known subroutine and the invocant // candidate is an unknown bareword followed by a list boundary, // Perl keeps the direct sub call: `foo Bar, baz` is