@@ -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