diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 20461e7f7..4256e1c56 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 = "ea174deff"; + public static final String gitCommitId = "36a15d5a2"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java index 8549cf816..21a7efdb7 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java @@ -2186,9 +2186,14 @@ public static String getCurrentPackage() { } /** - * Replace lazy {@link ScalarSpecialVariable} references ($1, $&, etc.) in a return list - * with concrete {@link RuntimeScalar} copies. Must be called BEFORE {@link RegexState#restore()} - * so that the values reflect the subroutine's regex state, not the caller's. + * Replace lazy {@link ScalarSpecialVariable} references ($1, $&, etc.) and + * {@link RuntimeArray} references in a return list with concrete copies. + * Must be called BEFORE {@link RegexState#restore()} and local variable restoration + * so that the values reflect the subroutine's state, not the caller's. + * + *

This is critical for returning localized arrays (e.g., {@code local @ARGV}). + * Without this, the array reference in the return list would point to the restored + * (original) values after the local scope exits.

*/ public static void materializeSpecialVarsInResult(RuntimeList result) { List elems = result.elements; @@ -2200,6 +2205,21 @@ public static void materializeSpecialVarsInResult(RuntimeList result) { concrete.type = resolved.type; concrete.value = resolved.value; elems.set(i, concrete); + } else if (elem instanceof RuntimeArray arr) { + // Copy array elements to ensure independence from local restoration. + // Replace the RuntimeArray reference with a new RuntimeArray containing copies. + RuntimeArray copy = new RuntimeArray(); + for (RuntimeScalar arrElem : arr.elements) { + copy.elements.add(arrElem == null ? null : new RuntimeScalar(arrElem)); + } + elems.set(i, copy); + } else if (elem instanceof RuntimeHash hash) { + // Copy hash elements for the same reason as arrays + RuntimeHash copy = new RuntimeHash(); + for (var entry : hash.elements.entrySet()) { + copy.elements.put(entry.getKey(), new RuntimeScalar(entry.getValue())); + } + elems.set(i, copy); } } } diff --git a/src/test/resources/unit/local.t b/src/test/resources/unit/local.t index b6e6557a3..cd8b7684b 100644 --- a/src/test/resources/unit/local.t +++ b/src/test/resources/unit/local.t @@ -223,6 +223,60 @@ ok(scalar(@global_array) < 10, 'local array element restored, array size ' . sca is(NestedTest::outer(), 0, 'nested sub with our - after local'); } +# Test for returning local array from subroutine +# Regression test: the returned array should contain the localized values, +# not the restored original values +{ + our @test_array = ("original1", "original2"); + + sub return_local_array { + local @test_array = ("local1", "local2", "local3"); + return @test_array; + } + + my @result = return_local_array(); + is(scalar(@result), 3, 'returned local array has correct size'); + is($result[0], "local1", 'returned local array element 0 is localized value'); + is($result[1], "local2", 'returned local array element 1 is localized value'); + is($result[2], "local3", 'returned local array element 2 is localized value'); + is(scalar(@test_array), 2, 'original array restored after return'); + is($test_array[0], "original1", 'original array element 0 restored'); +} + +# Test for returning modified local array from subroutine +{ + our @modify_array = ("a", "b", "c"); + + sub modify_and_return_local { + local @modify_array = @modify_array; + @modify_array = ("x"); # Modify the localized copy + return ("result", @modify_array); + } + + my @result = modify_and_return_local(); + is($result[0], "result", 'first return value is scalar'); + is($result[1], "x", 'returned local array contains modified value'); + is(scalar(@result), 2, 'return list has correct size'); + is(join(",", @modify_array), "a,b,c", 'original array restored after return'); +} + +# Test for returning local hash from subroutine +{ + our %test_hash = (orig_key => "orig_value"); + + sub return_local_hash { + local %test_hash = (local_key => "local_value"); + return %test_hash; + } + + my %result = return_local_hash(); + ok(exists $result{local_key}, 'returned local hash has local key'); + is($result{local_key}, "local_value", 'returned local hash has local value'); + ok(!exists $result{orig_key}, 'returned local hash does not have original key'); + ok(exists $test_hash{orig_key}, 'original hash restored after return'); + is($test_hash{orig_key}, "orig_value", 'original hash value restored'); +} + done_testing(); __END__