Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
26 changes: 23 additions & 3 deletions src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.</p>
*/
public static void materializeSpecialVarsInResult(RuntimeList result) {
List<RuntimeBase> elems = result.elements;
Expand All @@ -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);
}
}
}
Expand Down
54 changes: 54 additions & 0 deletions src/test/resources/unit/local.t
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
Loading