From 668a34844207220090d8772990f4fef060508ae4 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 27 Apr 2026 20:48:02 +0200 Subject: [PATCH] fix(parser): don't greedily consume `new` as indirect-object class When parsing `myfunc new Foo (4,5)`, the parser was treating `new` as the indirect-object class for `myfunc`, producing `new->myfunc(Foo(4,5))` and then failing with "Undefined subroutine &main::Foo". Real Perl parses this as `myfunc(Foo->new(4,5))`. The disambiguation rule: if the candidate "class" identifier is itself an unknown package and is followed by another bareword identifier, prefer the inner indirect-object interpretation (the candidate is actually a method name). This fixes XML::Generator's t/DOM.t which uses `croak new XML::DOM::DOMException (WRONG_DOCUMENT_ERR, ...)` inside XML::DOM.pm. Before: $ ./jperl -e 'sub f{}; package Foo; sub new{}; package main; f new Foo (1)' Undefined subroutine &main::Foo called After: (parses correctly as f(Foo->new(1))) 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 | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 3e7a1a250..6e9c6d5ba 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 = "1c27ead97"; + public static final String gitCommitId = "a671eccbf"; /** * 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 20:14:07"; + public static final String buildTimestamp = "Apr 27 2026 20:46:44"; // 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 648d72e54..505450a11 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -258,7 +258,14 @@ && isValidIndirectMethod(subName, 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)) + // If packageName is not a known class and the next token is itself a + // bareword identifier, then `packageName` is more likely a method name + // (e.g. `new`) rather than a class — and the inner `packageName IDENT` + // 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)) { parser.tokenIndex = currentIndex2; } else { // Not a known subroutine, check if it's valid indirect object syntax