diff --git a/src/main/java/org/perlonjava/runtime/RuntimeList.java b/src/main/java/org/perlonjava/runtime/RuntimeList.java index dd0cfd1d..1d86ebac 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,32 @@ 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`. + // 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. RuntimeArray rhs = new RuntimeArray(); @@ -402,7 +429,19 @@ 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 = (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 != null && !lhsScalars.containsKey(rhsAlias); + + result.elements.add(useAlias ? rhsAlias : rhsValue); + if (rhsIndex < rhsSize) { rhsIndex++; }