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
Fixes #8952. Nested 'it' behavior broken
'it' should not be seen by an inner 'it' UNLESS the outer 'it'
is not a special 'it' but merely an ordinary variable.
  • Loading branch information
enebo committed Jan 28, 2026
commit 16da75a11d59e7aebace8f793f8e356d8b70d126
35 changes: 29 additions & 6 deletions core/src/main/java/org/jruby/parser/RubyParserBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -397,15 +397,28 @@ public Node declareIdentifier(ByteList byteName) {
node = new LocalVarNode(lexer.tokline, slot, name);
} else if (dyna_in_block() && id.equals("it")) {
if (!hasArguments()) {
int existing = currentScope.isDefined(id);
slot = existing == -1 ?
currentScope.addVariable(id) : existing;
int existing = currentScope.isDefinedNotImplicit(id);
boolean newIt = false;

if (existing == -1) {
slot = currentScope.addVariable(id);
newIt = true;
} else if (existing == -2) { // We found an it either in this scope or above it
if (it_id() != null) { // It is the current scope
slot = currentScope.isDefined(id); // Get location of it
} else {
slot = currentScope.addVariable(id); // Was above this scope so make one for it.
newIt = true;
}
} else {
slot = existing;
}
node = new DVarNode(lexer.tokline, slot, name);
if (existing == -1) set_it_id(node);
if (newIt) set_it_id(node);
} else {
slot = currentScope.isDefined(id);
slot = currentScope.isDefinedNotImplicit(id);
// A special it cannot exist without being marked as a special it.
if (it_id() == null && slot == -1) compile_error("`it` is not allowed when an ordinary parameter is defined");
if (it_id() != null) compile_error("`it` is not allowed when an ordinary parameter is defined");

node = currentScope.declare(lexer.tokline, name);
}
Expand All @@ -431,6 +444,16 @@ public AssignableNode assignableLabelOrIdentifier(ByteList byteName, Node value)

if (warnOnUnusedVariables) addOrMarkVariable(name, currentScope.isDefined(name.idString()));

String id = name.idString();
// This differs from MRI annd it is a special branch because I do not want to infect staticscope with 'it'
// logic since it likely will be done differently in Prism (This will play out more once 10.1 updates
// to latest Prism). If it is the same then we can reconsider whether this should be in staticscope or not.
if (id.equals("it") && currentScope.exists(id) == -1) { // case: foo { it.bar { <<it = something>> } it }
// we are assigning and there is no 'it' here so make it as a normal local
int slot = currentScope.addVariableName(id);
return new DAsgnNode(lexer.getRubySourceline(), name, slot, value);
}

return currentScope.assign(lexer.getRubySourceline(), name, makeNullNil(value));
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/parser/StaticScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public void markImplicitVariable(int slot) {
implicitVariables.set(slot);
}

private int addVariableName(String name) {
public int addVariableName(String name) {
// Clear constructor since we are adding a name
constructor = null;

Expand Down