From 7d3ce702582e2ace52f4aebf387dc3e4ad0dcf4b Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 27 Apr 2026 22:09:20 +0200 Subject: [PATCH] fix(parser): allow `new Foo IDENT => ...` indirect-object form Commit 668a34844 added a check that rejected indirect-object parsing whenever the candidate "class" was unknown and followed by another bareword IDENTIFIER. That correctly disambiguated `myfunc new Foo (4,5)` (where `myfunc` is a known sub) but also wrongly rejected the legitimate top-level form `new HTTP::Request GET => $url`, breaking Log::Log4perl::Config (which uses exactly this idiom). The discriminator is the outer `subExists`: in the buggy case the outer `myfunc` IS a defined sub, while in `new HTTP::Request GET => ...` the outer `new` is not declared and is itself the indirect method. Restrict the new rejection clause to `subExists` so the second case parses as `HTTP::Request->new(GET => $url)` again. Repro before: $ jcpan -t URI::Simple ... Undefined subroutine &Log::Log4perl::Logger::cleanup ... $ jperl -MLog::Log4perl -e 1 syntax error at .../Log/Log4perl/Config.pm line 647, near "::Request GET " After: both run cleanly; `myfunc new Foo (4,5)` still parses as `myfunc(Foo->new(4,5))`. Generated with [Devin](https://app.devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/main/java/org/perlonjava/core/Configuration.java | 4 ++-- .../org/perlonjava/frontend/parser/SubroutineParser.java | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index b0b73ce32..67ab62999 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "3e7654d5a"; + public static final String gitCommitId = "69964ee2f"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 27 2026 21:36:05"; + public static final String buildTimestamp = "Apr 27 2026 22:11:34"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index 505450a11..3cfd9b442 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -265,7 +265,12 @@ && isValidIndirectMethod(subName, parser) // pair is the real indirect-object call. So reject the outer form. // Example: `myfunc new Foo (4,5)` should parse as `myfunc(Foo->new(4,5))`, // NOT as `new->myfunc(Foo(4,5))`. - || (isPackage == null && !isKnownSub && token.type == LexerTokenType.IDENTIFIER)) { + // Only reject when the outer `subName` is itself a known sub — otherwise we'd + // wrongly reject the legitimate top-level case + // `new HTTP::Request GET => $url` where `subName=new` is not declared and + // `packageName=HTTP::Request` followed by IDENTIFIER `GET` is just the + // method's first list-arg. + || (subExists && isPackage == null && !isKnownSub && token.type == LexerTokenType.IDENTIFIER)) { parser.tokenIndex = currentIndex2; } else { // Not a known subroutine, check if it's valid indirect object syntax