@a = (1,2); map { y/1/./ for $_ } @a;
# Expected: @a = (".", 2)
# Got: @a = (1, 2) ❌The transliteration operator y/// inside for $_ doesn't modify the original array elements.
Using --disassemble on y/1/./ for $_ reveals:
L1: makeLocal("main::_") // Localizes $_
POP
L2: getGlobalVariable("main::_") // Get localized $_ for iteration
iterator()
L3: aliasGlobalVariable("main::_", iteratorValue) // ❌ PROBLEM HERE
... y/// operator ...
GOTO L3
- Step 1:
local $_creates a newGlobalRuntimeScalarand replaces the global$_ - Step 2: Get the localized
$_to create an iterator - Step 3:
aliasGlobalVariable("main::_", iteratorValue)REPLACES the localized$_with the iterator value - Result: The localization is destroyed, and modifications don't propagate back
GlobalVariable.aliasGlobalVariable() does:
public static void aliasGlobalVariable(String key, RuntimeScalar var) {
globalVariables.put(key, var); // Replaces the entry!
}This replaces the global variable entry, destroying any localization in effect.
$x = 1; y/1/./ for $x; # Works - $x becomes "."
@a = (1,2); map { y/1/./; } @a; # Works - implicit $_
@a = (1,2); map { $_ =~ y/1/./; } @a; # Works - explicit binding$_ = 1; y/1/./ for $_; # Fails - $_ stays "1"
@a = (1,2); map { y/1/./ for $_ } @a; # Fails - array unchangedThe issue involves the interaction of three mechanisms:
- Localization (
local $_) - creates a new RuntimeScalar - Aliasing (
forloops) - should create references, not copies - Global variables - stored in a shared map
When for $_ tries to alias a localized variable, it destroys the localization by replacing the map entry.
public static void aliasGlobalVariable(String key, RuntimeScalar var) {
RuntimeScalar existing = globalVariables.get(key);
if (existing instanceof GlobalRuntimeScalar) {
existing.set(var); // Copy value instead of replacing
} else {
globalVariables.put(key, var);
}
}Problem: set() copies the VALUE, not the REFERENCE. Modifications to var don't propagate back to existing.
The for loop should detect when the loop variable is localized and use a different mechanism:
- Option A: Make
GlobalRuntimeScalaract as a proxy that forwards operations to the aliased value - Option B: Store the aliasing information separately from the global variables map
- Option C: Use a different bytecode sequence for localized variables in
forloops
All options require significant refactoring of the localization and aliasing mechanisms.
- op/tr.t: Test 210 fails (and possibly others)
- Workaround: Use explicit binding:
$_ =~ y///instead ofy/// for $_ - Severity: Medium - affects specific pattern of
for $_with localized$_
EmitForeach.javaline 128: EmitsaliasGlobalVariablecallGlobalVariable.javaline 102:aliasGlobalVariableimplementationGlobalRuntimeScalar.javaline 33:dynamicSaveState()for localizationDynamicVariableManager.javaline 38:pushLocalVariable()triggers localization
OPEN - Requires architectural changes to localization/aliasing interaction.