From 55513eba0b881d4c3fd72dbda32487b50e6d6466 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 14 Jan 2026 09:22:39 +0100 Subject: [PATCH 1/2] Fix list assignment with undef placeholders --- .../org/perlonjava/runtime/RuntimeList.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/perlonjava/runtime/RuntimeList.java b/src/main/java/org/perlonjava/runtime/RuntimeList.java index dd0cfd1d..db35bf11 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeList.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeList.java @@ -1,6 +1,7 @@ package org.perlonjava.runtime; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -386,6 +387,13 @@ public RuntimeList createListReference() { */ public RuntimeArray setFromList(RuntimeList value) { + // Preserve RHS aliases before consuming `value`. + // This is needed for Perl's odd-but-established behavior where an `undef` placeholder on the LHS + // uses the corresponding RHS element as an lvalue in the return list. + RuntimeArray rhsAliases = new RuntimeArray(); + value.setArrayOfAlias(rhsAliases); + List rhsAliasElements = rhsAliases.elements; + // Materialize the RHS once into a flat list. // Avoids O(n^2) from repeated RuntimeArray.shift() which does removeFirst() on ArrayList. RuntimeArray rhs = new RuntimeArray(); @@ -395,6 +403,13 @@ public RuntimeArray setFromList(RuntimeList value) { int rhsSize = rhsElements.size(); int rhsIndex = 0; + IdentityHashMap lhsScalars = new IdentityHashMap<>(); + for (RuntimeBase elem : elements) { + if (elem instanceof RuntimeScalar s && !(s instanceof RuntimeScalarReadOnly)) { + lhsScalars.put(s, Boolean.TRUE); + } + } + // Build result with assigned values (including undef for unassigned scalars) RuntimeArray result = new RuntimeArray(); // Store original size for scalar context @@ -402,7 +417,17 @@ public RuntimeArray setFromList(RuntimeList value) { for (RuntimeBase elem : elements) { if (elem instanceof RuntimeScalarReadOnly runtimeScalarReadOnly && !runtimeScalarReadOnly.getDefinedBoolean()) { - // Discard one RHS value + // `undef` placeholder on LHS: consume one RHS value, but return something for this position. + // When safe, return the RHS element as an lvalue (alias). Otherwise return the pre-assignment value. + RuntimeScalar rhsValue = (rhsIndex < rhsSize) ? rhsElements.get(rhsIndex) : new RuntimeScalar(); + RuntimeScalar rhsAlias = (rhsIndex < rhsAliasElements.size()) ? rhsAliasElements.get(rhsIndex) : null; + + // If the RHS element is also assigned on the LHS, it must not be returned as an alias + // (it would reflect the post-assignment value rather than the pre-assignment value). + boolean useAlias = rhsAlias != null && !lhsScalars.containsKey(rhsAlias); + + result.elements.add(useAlias ? rhsAlias : rhsValue); + if (rhsIndex < rhsSize) { rhsIndex++; } From 58692fb2d3a6940d8b6275bf3d19bf333df888aa Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 14 Jan 2026 10:30:18 +0100 Subject: [PATCH 2/2] Avoid alias materialization for ordinary list assignment --- .../org/perlonjava/runtime/RuntimeList.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/perlonjava/runtime/RuntimeList.java b/src/main/java/org/perlonjava/runtime/RuntimeList.java index db35bf11..1d86ebac 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeList.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeList.java @@ -387,12 +387,31 @@ public RuntimeList createListReference() { */ public RuntimeArray setFromList(RuntimeList value) { + boolean hasUndefPlaceholderLhs = false; + for (RuntimeBase elem : elements) { + if (elem instanceof RuntimeScalarReadOnly runtimeScalarReadOnly + && !runtimeScalarReadOnly.getDefinedBoolean()) { + hasUndefPlaceholderLhs = true; + break; + } + } + // Preserve RHS aliases before consuming `value`. - // This is needed for Perl's odd-but-established behavior where an `undef` placeholder on the LHS - // uses the corresponding RHS element as an lvalue in the return list. - RuntimeArray rhsAliases = new RuntimeArray(); - value.setArrayOfAlias(rhsAliases); - List rhsAliasElements = rhsAliases.elements; + // Only needed when the LHS contains `undef` placeholders. + List rhsAliasElements = null; + IdentityHashMap lhsScalars = null; + if (hasUndefPlaceholderLhs) { + RuntimeArray rhsAliases = new RuntimeArray(); + value.setArrayOfAlias(rhsAliases); + rhsAliasElements = rhsAliases.elements; + + lhsScalars = new IdentityHashMap<>(); + for (RuntimeBase elem : elements) { + if (elem instanceof RuntimeScalar s && !(s instanceof RuntimeScalarReadOnly)) { + lhsScalars.put(s, Boolean.TRUE); + } + } + } // Materialize the RHS once into a flat list. // Avoids O(n^2) from repeated RuntimeArray.shift() which does removeFirst() on ArrayList. @@ -403,13 +422,6 @@ public RuntimeArray setFromList(RuntimeList value) { int rhsSize = rhsElements.size(); int rhsIndex = 0; - IdentityHashMap lhsScalars = new IdentityHashMap<>(); - for (RuntimeBase elem : elements) { - if (elem instanceof RuntimeScalar s && !(s instanceof RuntimeScalarReadOnly)) { - lhsScalars.put(s, Boolean.TRUE); - } - } - // Build result with assigned values (including undef for unassigned scalars) RuntimeArray result = new RuntimeArray(); // Store original size for scalar context @@ -420,11 +432,13 @@ public RuntimeArray setFromList(RuntimeList value) { // `undef` placeholder on LHS: consume one RHS value, but return something for this position. // When safe, return the RHS element as an lvalue (alias). Otherwise return the pre-assignment value. RuntimeScalar rhsValue = (rhsIndex < rhsSize) ? rhsElements.get(rhsIndex) : new RuntimeScalar(); - RuntimeScalar rhsAlias = (rhsIndex < rhsAliasElements.size()) ? rhsAliasElements.get(rhsIndex) : null; + RuntimeScalar rhsAlias = (rhsAliasElements != null && rhsIndex < rhsAliasElements.size()) + ? rhsAliasElements.get(rhsIndex) + : null; // If the RHS element is also assigned on the LHS, it must not be returned as an alias // (it would reflect the post-assignment value rather than the pre-assignment value). - boolean useAlias = rhsAlias != null && !lhsScalars.containsKey(rhsAlias); + boolean useAlias = rhsAlias != null && lhsScalars != null && !lhsScalars.containsKey(rhsAlias); result.elements.add(useAlias ? rhsAlias : rhsValue);