Skip to content

Commit 131ae66

Browse files
authored
Merge pull request jruby#9190 from headius/implicit_var_fixes
Backport implicit local variable fixes from 10.1
2 parents a52d3c5 + f3ae35c commit 131ae66

2 files changed

Lines changed: 148 additions & 20 deletions

File tree

core/src/main/java/org/jruby/parser/RubyParserBase.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ private ListNode makePreNumArgs(int paramCount) {
216216

217217
for (int i = 1; i <= paramCount; i++) {
218218
RubySymbol name = symbolID(new ByteList(("_" + i).getBytes()));
219-
list.add(new ArgumentNode(lexer.getRubySourceline(), name, getCurrentScope().addVariableThisScope(name.idString())));
219+
list.add(new ArgumentNode(lexer.getRubySourceline(), name, getCurrentScope().addImplicitVariableThisScope(name.idString())));
220220
}
221221

222222
return list;
@@ -2465,6 +2465,9 @@ protected Node it_id() {
24652465
}
24662466

24672467
protected void set_it_id(Node node) {
2468+
if (node != null) {
2469+
currentScope.markImplicitVariable(((DVarNode) node).getIndex());
2470+
}
24682471
this.itId = node;
24692472
}
24702473

core/src/main/java/org/jruby/parser/StaticScope.java

Lines changed: 144 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public class StaticScope implements Serializable, Cloneable {
8686
public static final int MAX_SPECIALIZED_SIZE = 50;
8787
private static final long serialVersionUID = 3423852552352498148L;
8888

89+
public static final int IMPLICIT = -2;
90+
8991
// Next immediate scope. Variable and constant scoping rules make use of this variable
9092
// in different ways.
9193
protected StaticScope enclosingScope;
@@ -100,6 +102,8 @@ public class StaticScope implements Serializable, Cloneable {
100102
// as key to Symbol table for actual encoded versions].
101103
private String[] variableNames;
102104

105+
private BitSet implicitVariables;
106+
103107
private int variableNamesLength;
104108

105109
// Arity of this scope if there is one
@@ -260,6 +264,36 @@ public int addVariableThisScope(String name) {
260264

261265
if (slot >= 0) return slot;
262266

267+
// Clear constructor since we are adding a name
268+
return addVariableName(name);
269+
}
270+
271+
/**
272+
* Add an implicit variable ("it", "_1") to this (current) scope unless it is already defined in the
273+
* current scope. The variable will be marked as implicit to omit it from local variable lists and functions.
274+
*
275+
* @param name of new variable
276+
* @return index of variable
277+
*/
278+
public int addImplicitVariableThisScope(String name) {
279+
int slot = exists(name);
280+
281+
if (slot >= 0) return slot;
282+
283+
slot = addVariableName(name);
284+
285+
markImplicitVariable(slot);
286+
287+
return slot;
288+
}
289+
290+
public void markImplicitVariable(int slot) {
291+
if (implicitVariables == null) implicitVariables = new BitSet();
292+
293+
implicitVariables.set(slot);
294+
}
295+
296+
private int addVariableName(String name) {
263297
// Clear constructor since we are adding a name
264298
constructor = null;
265299

@@ -283,13 +317,7 @@ public int addVariable(String name) {
283317
if (slot >= 0) return slot;
284318

285319
// Clear constructor since we are adding a name
286-
constructor = null;
287-
288-
// This is perhaps innefficient timewise? Optimal spacewise
289-
growVariableNames(name);
290-
291-
// Returns slot of variable
292-
return variableNames.length - 1;
320+
return addVariableName(name);
293321
}
294322

295323
public String[] getVariables() {
@@ -396,6 +424,18 @@ public int exists(String name) {
396424
return findVariableName(name);
397425
}
398426

427+
/**
428+
* Does the variable exist and not implicit?
429+
*
430+
* @param name of the variable to find
431+
* @return index of variable; -1 if it does not exist; -2 if is implicit.
432+
*/
433+
public int existsAndNotImplicit(String name) {
434+
int slot = findVariableName(name);
435+
if (slot >= 0 && isImplicitVariable(slot)) return IMPLICIT;
436+
return slot;
437+
}
438+
399439
private int findVariableName(String name) {
400440
for (int i = 0; i < variableNames.length; i++) {
401441
if (name.equals(variableNames[i])) return i;
@@ -414,6 +454,24 @@ public int isDefined(String name) {
414454
return isDefined(name, 0);
415455
}
416456

457+
/**
458+
* Is this name visible to the current scope and not an implicit variable ("it", "_1", etc).
459+
*
460+
* @param name to be looked for
461+
* @return -1 if it is not defined; -2 if it is implicit; or a location where the left-most 16 bits of number of scopes down it is and the
462+
* right-most 16 bits represents its index in that scope.
463+
*/
464+
public int isDefinedNotImplicit(String name) {
465+
return isDefinedNotImplicit(name, 0);
466+
}
467+
468+
/**
469+
* @return whether the given slot contains an implicit variable ("it", "_1", etc).
470+
*/
471+
public boolean isImplicitVariable(int slot) {
472+
return implicitVariables != null && implicitVariables.get(slot);
473+
}
474+
417475
/**
418476
* Make a DASgn or LocalAsgn node based on scope logic
419477
*
@@ -477,6 +535,21 @@ public <T> T collectVariables(IntFunction<T> collectionFactory, BiConsumer<T, St
477535

478536
}
479537

538+
/**
539+
* Populate a deduplicated collection of variable names in scope using the given functions.
540+
*
541+
* This may include variables that are not strictly Ruby local variable names, so the consumer should validate
542+
* names as appropriate.
543+
*
544+
* @param collectionFactory used to construct the collection
545+
* @param collectionPopulator used to pass values into the collection
546+
* @param <T> resulting collection type
547+
* @return populated collection
548+
*/
549+
public <T> T collectAllVariables(ThreadContext context, BiFunction<ThreadContext, Integer, T> collectionFactory, BiConsumer<T, String> collectionPopulator) {
550+
return collectVariables(context, collectionFactory, collectionPopulator, null);
551+
}
552+
480553
/**
481554
* Populate a deduplicated collection of variable names in scope using the given functions.
482555
*
@@ -489,27 +562,64 @@ public <T> T collectVariables(IntFunction<T> collectionFactory, BiConsumer<T, St
489562
* @return populated collection
490563
*/
491564
public <T> T collectVariables(ThreadContext context, BiFunction<ThreadContext, Integer, T> collectionFactory, BiConsumer<T, String> collectionPopulator) {
565+
return collectVariables(context, collectionFactory, collectionPopulator, false);
566+
}
567+
568+
/**
569+
* Populate a deduplicated collection of implicit variable names("it", "_1", etc) in scope using the given functions.
570+
*
571+
* This may include variables that are not strictly Ruby local variable names, so the consumer should validate
572+
* names as appropriate.
573+
*
574+
* @param collectionFactory used to construct the collection
575+
* @param collectionPopulator used to pass values into the collection
576+
* @param <T> resulting collection type
577+
* @return populated collection
578+
*/
579+
public <T> T collectImplicitVariables(ThreadContext context, BiFunction<ThreadContext, Integer, T> collectionFactory, BiConsumer<T, String> collectionPopulator) {
580+
return collectVariables(context, collectionFactory, collectionPopulator, true);
581+
}
582+
583+
public <T> T collectVariables(ThreadContext context, BiFunction<ThreadContext, Integer, T> collectionFactory, BiConsumer<T, String> collectionPopulator, Boolean implicit) {
492584
StaticScope current = this;
493585

494586
T collection = collectionFactory.apply(context, current.variableNamesLength);
495587

496588
HashMap<String, Object> dedup = new HashMap<>();
497589

498590
while (current.isBlockOrEval) {
499-
for (String name : current.variableNames) {
500-
dedup.computeIfAbsent(name, key -> {collectionPopulator.accept(collection, key); return key;});
501-
}
591+
addVariableNamesToCollection(current, collectionPopulator, dedup, collection, implicit);
502592
current = current.enclosingScope;
503593
}
504594

505595
// once more for method scope
506-
for (String name : current.variableNames) {
507-
dedup.computeIfAbsent(name, key -> {collectionPopulator.accept(collection, key); return key;});
508-
}
596+
addVariableNamesToCollection(current, collectionPopulator, dedup, collection, implicit);
509597

510598
return collection;
511599
}
512600

601+
private static <T> void addVariableNamesToCollection(StaticScope current, BiConsumer<T, String> collectionPopulator, HashMap<String, Object> dedup, T collection, Boolean implicit) {
602+
BitSet implicitVariables = current.implicitVariables;
603+
604+
boolean hasImplicits = implicitVariables != null;
605+
boolean filterImplicits = implicit != null;
606+
if (filterImplicits && implicit && !hasImplicits) return;
607+
608+
int variableNamesLength = current.variableNamesLength;
609+
String[] variableNames = current.variableNames;
610+
611+
for (int i = 0; i < variableNamesLength; i++) {
612+
if (hasImplicits && implicitVariables.get(i)) {
613+
if (filterImplicits && !implicit) continue;
614+
} else if (filterImplicits && implicit) continue;
615+
616+
String name = variableNames[i];
617+
dedup.computeIfAbsent(name, key -> {
618+
collectionPopulator.accept(collection, key);
619+
return key;});
620+
}
621+
}
622+
513623
@Deprecated(since = "10.0.0.0")
514624
public RubyArray getLocalVariables(Ruby runtime) {
515625
return getLocalVariables(runtime.getCurrentContext());
@@ -522,13 +632,15 @@ public RubyArray getLocalVariables(Ruby runtime) {
522632
* @return populated RubyArray
523633
*/
524634
public RubyArray getLocalVariables(ThreadContext context) {
525-
return collectVariables(
635+
return collectAllVariables(
526636
context,
527-
(ctxt, length) -> allocArray(ctxt, length),
528-
(array, id) -> {
529-
RubySymbol symbol = Convert.asSymbol(context, id);
530-
if (symbol.validLocalVariableName()) array.append(context, symbol);
531-
});
637+
Create::allocArray,
638+
(array, id) -> appendVariableIfValid(context, array, id));
639+
}
640+
641+
private static void appendVariableIfValid(ThreadContext context, RubyArray<?> array, String id) {
642+
RubySymbol symbol = Convert.asSymbol(context, id);
643+
if (symbol.validLocalVariableName()) array.append(context, symbol);
532644
}
533645

534646
public int isDefined(String name, int depth) {
@@ -542,6 +654,19 @@ public int isDefined(String name, int depth) {
542654
}
543655
}
544656

657+
public int isDefinedNotImplicit(String name, int depth) {
658+
int slot = existsAndNotImplicit(name);
659+
if (slot == IMPLICIT) return slot;
660+
661+
if (isBlockOrEval) {
662+
if (slot >= 0) return (depth << 16) | slot;
663+
664+
return enclosingScope.isDefinedNotImplicit(name, depth + 1);
665+
} else {
666+
return (depth << 16) | slot;
667+
}
668+
}
669+
545670
public AssignableNode addAssign(int line, RubySymbol symbolID, Node value) {
546671
int slot = addVariable(symbolID.idString());
547672
// No bit math to store level since we know level is zero for this case

0 commit comments

Comments
 (0)