From 7fb7df7a8f2d3a82336123a8a24c816aa0a853a7 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 20 Mar 2026 19:34:10 +0100 Subject: [PATCH] Fix indirect object syntax with variable class: new $type $arg Support indirect object syntax where the class is stored in a variable: new $type $arg -> $type->new($arg) This is valid Perl syntax used by Set::Infinite and other CPAN modules. The fix only applies when the subroutine doesn't exist as a function, preventing incorrect parsing of existing function calls. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../org/perlonjava/core/Configuration.java | 4 +- .../frontend/parser/SubroutineParser.java | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index ea23377f6..f3b6c2158 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,14 +33,14 @@ 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 = "67da75215"; + public static final String gitCommitId = "cf17bfaaa"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitDate = "2026-03-19"; + public static final String gitCommitDate = "2026-03-20"; // 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 6b496a466..17ff76c2b 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -260,6 +260,44 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { } } + // Handle indirect object syntax with variable class: new $type $arg -> $type->new($arg) + // This is similar to the IDENTIFIER case above, but for variable class names + // Only applies when the subroutine doesn't exist (otherwise it's a function call) + if (!subExists && peek(parser).text.equals("$") && isValidIndirectMethod(subName) && !prototypeHasGlob) { + int currentIndex2 = parser.tokenIndex; + // Parse the variable that holds the class name + Node classVar = ParsePrimary.parsePrimary(parser); + if (classVar != null) { + LexerToken nextTok = peek(parser); + // Check this isn't actually a binary operator like $type + 1 + if (!(nextTok.text.equals("->") || nextTok.text.equals("=>") || INFIX_OP.contains(nextTok.text))) { + // Parse arguments for the method call + ListNode arguments; + if (nextTok.text.equals(",") || nextTok.text.equals(";") || + nextTok.text.equals(")") || nextTok.text.equals("}") || + nextTok.type == LexerTokenType.EOF) { + // No arguments after class variable + arguments = new ListNode(currentIndex); + } else { + // Parse remaining arguments + arguments = consumeArgsWithPrototype(parser, "@"); + } + // Create method call: $classVar->method(args) + return new BinaryOperatorNode( + "->", + classVar, + new BinaryOperatorNode("(", + new OperatorNode("&", + new IdentifierNode(subName, currentIndex2), + currentIndex), + arguments, currentIndex2), + currentIndex2); + } + // Not indirect object syntax - backtrack + parser.tokenIndex = currentIndex2; + } + } + // Create an identifier node for the subroutine name IdentifierNode nameNode = new IdentifierNode(subName, parser.tokenIndex);