From 67a7923596e554d357ae989d7b7ec14086622f9e Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 24 Apr 2026 15:11:48 +0200 Subject: [PATCH] fix(parser): join multi-element subscripts with $; in chained hash deref Previously, chained hash access like $h{a}{-word => 'ou'} (implicit arrow deref) evaluated the multi-element subscript in scalar context, keeping only the last element ('ou'). The initial (non-deref) level already joined keys with $; (SUBSEP) to form 'a$;b'-style keys, so the two paths disagreed: $h{-word => 'ou'} -> FETCH("-word\x1cou") OK $h{a}{-word => 'ou'} -> FETCH("ou") BUG Fix: in handleArrowHashDeref, when the HashLiteralNode subscript has more than one element, emit it as a list and join with $; just like the top-level case does. Found while investigating `jcpan -t Regexp::Common`. Regexp::Common's FETCH-chaining tied hash relies on this semantic (e.g. $RE{list}{conj}{-word => 'ou'}). With the fix: - Failed test files: 22/73 -> 11/73 - Completed tests: 116146 -> 140752 (previously aborted tests such as t/test_list.t, t/URI/http.t, t/number/decimal.t, t/zip/us.t now run to completion) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../java/org/perlonjava/backend/jvm/Dereference.java | 12 +++++++++++- src/main/java/org/perlonjava/core/Configuration.java | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/jvm/Dereference.java b/src/main/java/org/perlonjava/backend/jvm/Dereference.java index a57f6ee82..f6e53455a 100644 --- a/src/main/java/org/perlonjava/backend/jvm/Dereference.java +++ b/src/main/java/org/perlonjava/backend/jvm/Dereference.java @@ -1284,7 +1284,17 @@ public static void handleArrowHashDeref(EmitterVisitor emitterVisitor, BinaryOpe } if (CompilerOptions.DEBUG_ENABLED) emitterVisitor.ctx.logDebug("visit -> (HashLiteralNode) autoquote " + node.right); - nodeRight.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + if (nodeRight.elements.size() > 1) { + // Multiple elements: join them with $; (SUBSEP), like $h{a,b,c} + emitterVisitor.ctx.mv.visitLdcInsn("main::;"); + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/perlonjava/runtime/runtimetypes/GlobalVariable", + "getGlobalVariable", "(Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + nodeRight.accept(emitterVisitor.with(RuntimeContextType.LIST)); + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/perlonjava/runtime/operators/StringOperators", + "join", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + } else { + nodeRight.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + } int keySlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); boolean pooledKey = keySlot >= 0; diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 7cf7384ed..b33c59cf3 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 = "6f96f1c74"; + public static final String gitCommitId = "21a48f590"; /** * 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 24 2026 13:47:17"; + public static final String buildTimestamp = "Apr 24 2026 15:10:59"; // Prevent instantiation private Configuration() {