Browse Source

Add string concatenation node and update ir tree visitor (#61099)

Two changes:
- The first is the addition of a specialized string concatenation node as part of the ir tree. This allows us 
to roll up all of the binary nodes containing string concatenations into one node as a set of arguments 
which will happen in the next PR. This will help in removing optimization complexity from the semantic 
phases.
- This updates the ir tree visitor to have a base class with visitChildren for each node making new ir 
phases easier to write as only the nodes that are relevant need to be visited. This change is mostly 
mechanical with the addition of a visitChildren to each ir node.
Jack Conradson 5 years ago
parent
commit
5f222a3a6f
74 changed files with 946 additions and 415 deletions
  1. 1 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ArgumentsNode.java
  2. 9 32
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java
  3. 15 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryNode.java
  4. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java
  5. 8 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java
  6. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java
  7. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java
  8. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java
  9. 17 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java
  10. 8 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java
  11. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java
  12. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java
  13. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java
  14. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java
  15. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java
  16. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DefInterfaceReferenceNode.java
  17. 11 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java
  18. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DupNode.java
  19. 8 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java
  20. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java
  21. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FlipArrayIndexNode.java
  22. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FlipCollectionIndexNode.java
  23. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FlipDefIndexNode.java
  24. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java
  25. 8 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java
  26. 9 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java
  27. 21 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java
  28. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java
  29. 7 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java
  30. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java
  31. 8 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java
  32. 9 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java
  33. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InvokeCallDefNode.java
  34. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InvokeCallMemberNode.java
  35. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InvokeCallNode.java
  36. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java
  37. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadBraceDefNode.java
  38. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadBraceNode.java
  39. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotArrayLengthNode.java
  40. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotDefNode.java
  41. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotNode.java
  42. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotShortcutNode.java
  43. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadFieldMemberNode.java
  44. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadListShortcutNode.java
  45. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadMapShortcutNode.java
  46. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadVariableNode.java
  47. 13 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java
  48. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java
  49. 9 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java
  50. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java
  51. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java
  52. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java
  53. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java
  54. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java
  55. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreBraceDefNode.java
  56. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreBraceNode.java
  57. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreDotDefNode.java
  58. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreDotNode.java
  59. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreDotShortcutNode.java
  60. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreFieldMemberNode.java
  61. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreListShortcutNode.java
  62. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreMapShortcutNode.java
  63. 14 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreVariableNode.java
  64. 81 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StringConcatenationNode.java
  65. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java
  66. 11 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java
  67. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java
  68. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedInterfaceReferenceNode.java
  69. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java
  70. 13 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileLoopNode.java
  71. 2 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java
  72. 73 39
      modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java
  73. 149 131
      modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeBaseVisitor.java
  74. 72 66
      modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeVisitor.java

+ 1 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ArgumentsNode.java

@@ -36,6 +36,6 @@ public abstract class ArgumentsNode extends ExpressionNode {
         return argumentNodes;
     }
 
-    /* ---- end tree structure */
+    /* ---- end tree structure ---- */
 
 }

+ 9 - 32
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java

@@ -38,7 +38,6 @@ public class BinaryMathNode extends BinaryNode {
     private Operation operation;
     private Class<?> binaryType;
     private Class<?> shiftType;
-    private boolean cat;
     private int flags;
 
     public void setOperation(Operation operation) {
@@ -73,14 +72,6 @@ public class BinaryMathNode extends BinaryNode {
         return PainlessLookupUtility.typeToCanonicalTypeName(shiftType);
     }
 
-    public void setCat(boolean cat) {
-        this.cat = cat;
-    }
-
-    public boolean getCat() {
-        return cat;
-    }
-
     public void setFlags(int flags) {
         this.flags = flags;
     }
@@ -92,8 +83,14 @@ public class BinaryMathNode extends BinaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitBinaryMath(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitBinaryMath(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getLeftNode().visit(irTreeVisitor, scope);
+        getRightNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */
@@ -102,27 +99,7 @@ public class BinaryMathNode extends BinaryNode {
     protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
-        if (getBinaryType() == String.class && operation == Operation.ADD) {
-            if (cat == false) {
-                methodWriter.writeNewStrings();
-            }
-
-            getLeftNode().write(classWriter, methodWriter, writeScope);
-
-            if (getLeftNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getLeftNode()).getCat() == false) {
-                methodWriter.writeAppendStrings(getLeftNode().getExpressionType());
-            }
-
-            getRightNode().write(classWriter, methodWriter, writeScope);
-
-            if (getRightNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getRightNode()).getCat() == false) {
-                methodWriter.writeAppendStrings(getRightNode().getExpressionType());
-            }
-
-            if (cat == false) {
-                methodWriter.writeToStrings();
-            }
-        } else if (operation == Operation.FIND || operation == Operation.MATCH) {
+        if (operation == Operation.FIND || operation == Operation.MATCH) {
             getRightNode().write(classWriter, methodWriter, writeScope);
             getLeftNode().write(classWriter, methodWriter, writeScope);
             methodWriter.invokeVirtual(org.objectweb.asm.Type.getType(Pattern.class), WriterConstants.PATTERN_MATCHER);

+ 15 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryNode.java

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.phase.IRTreeVisitor;
 import org.elasticsearch.painless.symbol.WriteScope;
 
 public class BinaryNode extends ExpressionNode {
@@ -46,7 +47,20 @@ public class BinaryNode extends ExpressionNode {
         return rightNode;
     }
 
-    /* ---- end tree structure ---- */
+    /* ---- end tree structure, begin visitor ---- */
+
+    @Override
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitBinary(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        leftNode.visit(irTreeVisitor, scope);
+        rightNode.visit(irTreeVisitor, scope);
+    }
+
+    /* ---- end visitor ---- */
 
     @Override
     protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java

@@ -65,8 +65,15 @@ public class BlockNode extends StatementNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitBlock(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitBlock(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (StatementNode statementNode : statementNodes) {
+            statementNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 8 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java

@@ -44,8 +44,14 @@ public class BooleanNode extends BinaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitBoolean(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitBoolean(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getLeftNode().visit(irTreeVisitor, scope);
+        getRightNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java

@@ -29,8 +29,13 @@ public class BreakNode extends StatementNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitBreak(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitBreak(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java

@@ -42,8 +42,13 @@ public class CastNode extends UnaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitCast(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitCast(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java

@@ -62,8 +62,13 @@ public class CatchNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitCatch(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitCatch(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        blockNode.visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 17 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java

@@ -55,7 +55,7 @@ public class ClassNode extends IRNode {
     public List<FieldNode> getFieldsNodes() {
         return fieldNodes;
     }
-    
+
     public void addFunctionNode(FunctionNode functionNode) {
         functionNodes.add(functionNode);
     }
@@ -92,8 +92,17 @@ public class ClassNode extends IRNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitClass(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitClass(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        clinitBlockNode.visit(irTreeVisitor, scope);
+
+        for (FunctionNode functionNode : functionNodes) {
+            functionNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */
@@ -164,4 +173,9 @@ public class ClassNode extends IRNode {
         classVisitor.visitEnd();
         return classWriter.getClassBytes();
     }
+
+    @Override
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        throw new UnsupportedOperationException("use write() instead");
+    }
 }

+ 8 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java

@@ -63,8 +63,14 @@ public class ComparisonNode extends BinaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitComparison(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitComparison(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getLeftNode().visit(irTreeVisitor, scope);
+        getRightNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java

@@ -43,8 +43,15 @@ public class ConditionalNode extends BinaryNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitConditional(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitConditional(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        conditionNode.visit(irTreeVisitor, scope);
+        getLeftNode().visit(irTreeVisitor, scope);
+        getRightNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java

@@ -41,8 +41,13 @@ public class ConstantNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitConstant(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitConstant(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java

@@ -29,8 +29,13 @@ public class ContinueNode extends StatementNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitContinue(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitContinue(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java

@@ -44,8 +44,15 @@ public class DeclarationBlockNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitDeclarationBlock(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitDeclarationBlock(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (DeclarationNode declarationNode : declarationNodes) {
+            declarationNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java

@@ -69,8 +69,15 @@ public class DeclarationNode extends StatementNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitDeclaration(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitDeclaration(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        if (expressionNode != null) {
+            expressionNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DefInterfaceReferenceNode.java

@@ -42,8 +42,13 @@ public class DefInterfaceReferenceNode extends ReferenceNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitDefInterfaceReference(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitDefInterfaceReference(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 11 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java

@@ -32,8 +32,17 @@ public class DoWhileLoopNode extends LoopNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitDoWhileLoop(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitDoWhileLoop(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getBlockNode().visit(irTreeVisitor, scope);
+
+        if (getConditionNode() != null) {
+            getConditionNode().visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DupNode.java

@@ -50,8 +50,13 @@ public class DupNode extends UnaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitDup(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitDup(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 8 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java

@@ -30,8 +30,14 @@ public class ElvisNode extends BinaryNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitElvis(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitElvis(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getLeftNode().visit(irTreeVisitor, scope);
+        getRightNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java

@@ -65,8 +65,13 @@ public class FieldNode extends IRNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitField(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitField(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FlipArrayIndexNode.java

@@ -31,8 +31,13 @@ public class FlipArrayIndexNode extends UnaryNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitFlipArrayIndex(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitFlipArrayIndex(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FlipCollectionIndexNode.java

@@ -32,8 +32,13 @@ public class FlipCollectionIndexNode extends UnaryNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitFlipCollectionIndex(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitFlipCollectionIndex(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FlipDefIndexNode.java

@@ -32,8 +32,13 @@ public class FlipDefIndexNode extends UnaryNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitFlipDefIndex(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitFlipDefIndex(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java

@@ -41,8 +41,13 @@ public class ForEachLoopNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitForEachLoop(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitForEachLoop(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        conditionNode.visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 8 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java

@@ -125,8 +125,14 @@ public class ForEachSubArrayNode extends LoopNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitForEachSubArrayLoop(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitForEachSubArrayLoop(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getConditionNode().visit(irTreeVisitor, scope);
+        getBlockNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 9 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java

@@ -65,7 +65,7 @@ public class ForEachSubIterableNode extends LoopNode {
     public String getVariableName() {
         return variableName;
     }
-    
+
     public void setCast(PainlessCast cast) {
         this.cast = cast;
     }
@@ -101,8 +101,14 @@ public class ForEachSubIterableNode extends LoopNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitForEachSubIterableLoop(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitForEachSubIterableLoop(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getConditionNode().visit(irTreeVisitor, scope);
+        getBlockNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 21 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java

@@ -53,8 +53,27 @@ public class ForLoopNode extends LoopNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitForLoop(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitForLoop(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        if (initializerNode != null) {
+            initializerNode.visit(irTreeVisitor, scope);
+        }
+
+        if (getConditionNode() != null) {
+            getConditionNode().visit(irTreeVisitor, scope);
+        }
+
+        if (afterthoughtNode != null) {
+            afterthoughtNode.visit(irTreeVisitor, scope);
+        }
+
+        if (getBlockNode() != null) {
+            getBlockNode().visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java

@@ -123,8 +123,13 @@ public class FunctionNode extends IRNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitFunction(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitFunction(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getBlockNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java

@@ -44,13 +44,14 @@ public abstract class IRNode {
     /**
      * Callback to visit an ir tree node.
      */
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        throw new UnsupportedOperationException("cannot visit ir node type [" + getClass().getCanonicalName() + "]");
-    }
+    public abstract <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope);
+
+    /**
+     * Visits all child ir tree nodes for this ir tree node.
+     */
+    public abstract <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope);
 
     /* ---- end visitor ---- */
 
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope);
 }

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java

@@ -43,8 +43,15 @@ public class IfElseNode extends ConditionNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitIfElse(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitIfElse(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getConditionNode().visit(irTreeVisitor, scope);
+        getBlockNode().visit(irTreeVisitor, scope);
+        getElseBlockNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 8 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java

@@ -31,8 +31,14 @@ public class IfNode extends ConditionNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitIf(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitIf(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getConditionNode().visit(irTreeVisitor, scope);
+        getBlockNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 9 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java

@@ -27,9 +27,9 @@ import org.elasticsearch.painless.phase.IRTreeVisitor;
 import org.elasticsearch.painless.symbol.WriteScope;
 
 public class InstanceofNode extends UnaryNode {
-    
+
     /* ---- begin node data ---- */
-    
+
     private Class<?> instanceType;
 
     public void setInstanceType(Class<?> instanceType) {
@@ -47,8 +47,13 @@ public class InstanceofNode extends UnaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitInstanceof(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitInstanceof(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InvokeCallDefNode.java

@@ -48,8 +48,15 @@ public class InvokeCallDefNode extends ArgumentsNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitInvokeCallDef(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitInvokeCallDef(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode argumentNode : getArgumentNodes()) {
+            argumentNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InvokeCallMemberNode.java

@@ -95,8 +95,15 @@ public class InvokeCallMemberNode extends ArgumentsNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitInvokeCallMember(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitInvokeCallMember(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode argumentNode : getArgumentNodes()) {
+            argumentNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InvokeCallNode.java

@@ -51,8 +51,15 @@ public class InvokeCallNode extends ArgumentsNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitInvokeCall(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitInvokeCall(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode argumentNode : getArgumentNodes()) {
+            argumentNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java

@@ -54,8 +54,15 @@ public class ListInitializationNode extends ArgumentsNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitListInitialization(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitListInitialization(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode argumentNode : getArgumentNodes()) {
+            argumentNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadBraceDefNode.java

@@ -47,8 +47,13 @@ public class LoadBraceDefNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadBraceDef(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadBraceDef(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadBraceNode.java

@@ -29,8 +29,13 @@ public class LoadBraceNode extends ExpressionNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadBrace(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadBrace(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotArrayLengthNode.java

@@ -29,8 +29,13 @@ public class LoadDotArrayLengthNode extends ExpressionNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadDotArrayLengthNode(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadDotArrayLengthNode(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotDefNode.java

@@ -44,8 +44,13 @@ public class LoadDotDefNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadDotDef(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadDotDef(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotNode.java

@@ -43,8 +43,13 @@ public class LoadDotNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadDot(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadDot(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadDotShortcutNode.java

@@ -42,8 +42,13 @@ public class LoadDotShortcutNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadDotShortcut(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadDotShortcut(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadFieldMemberNode.java

@@ -56,8 +56,13 @@ public class LoadFieldMemberNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadFieldMember(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadFieldMember(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadListShortcutNode.java

@@ -42,8 +42,13 @@ public class LoadListShortcutNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadListShortcut(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadListShortcut(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadMapShortcutNode.java

@@ -42,8 +42,13 @@ public class LoadMapShortcutNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadMapShortcut(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadMapShortcut(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoadVariableNode.java

@@ -43,8 +43,13 @@ public class LoadVariableNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitLoadVariable(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitLoadVariable(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 13 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java

@@ -87,8 +87,19 @@ public class MapInitializationNode extends ExpressionNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitMapInitialization(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitMapInitialization(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode keyNode : keyNodes) {
+            keyNode.visit(irTreeVisitor, scope);
+        }
+
+        for (ExpressionNode valueNode : valueNodes) {
+            valueNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java

@@ -41,8 +41,15 @@ public class NewArrayNode extends ArgumentsNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitNewArray(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitNewArray(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode argumentNode : getArgumentNodes()) {
+            argumentNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 9 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java

@@ -53,8 +53,15 @@ public class NewObjectNode extends ArgumentsNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitNewObject(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitNewObject(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        for (ExpressionNode argumentNode : getArgumentNodes()) {
+            argumentNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java

@@ -30,8 +30,13 @@ public class NullNode extends ExpressionNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitNull(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitNull(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java

@@ -30,8 +30,13 @@ public class NullSafeSubNode extends UnaryNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitNullSafeSub(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitNullSafeSub(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java

@@ -41,8 +41,13 @@ public class ReturnNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitReturn(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitReturn(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        expressionNode.visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java

@@ -41,8 +41,13 @@ public class StatementExpressionNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStatementExpression(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStatementExpression(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        expressionNode.visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java

@@ -29,8 +29,13 @@ public class StaticNode extends ExpressionNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStatic(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStatic(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreBraceDefNode.java

@@ -49,8 +49,13 @@ public class StoreBraceDefNode extends StoreAccessNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreBraceDef(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreBraceDef(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreBraceNode.java

@@ -29,8 +29,13 @@ public class StoreBraceNode extends StoreAccessNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreBrace(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreBrace(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreDotDefNode.java

@@ -44,8 +44,13 @@ public class StoreDotDefNode extends StoreAccessNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreDotDef(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreDotDef(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreDotNode.java

@@ -43,8 +43,13 @@ public class StoreDotNode extends StoreAccessNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreDot(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreDot(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreDotShortcutNode.java

@@ -42,8 +42,13 @@ public class StoreDotShortcutNode extends StoreAccessNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreDotShortcut(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreDotShortcut(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreFieldMemberNode.java

@@ -57,8 +57,13 @@ public class StoreFieldMemberNode extends StoreNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreFieldMember(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreFieldMember(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreListShortcutNode.java

@@ -42,8 +42,13 @@ public class StoreListShortcutNode extends StoreAccessNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreListShortcut(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreListShortcut(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreMapShortcutNode.java

@@ -42,8 +42,13 @@ public class StoreMapShortcutNode extends StoreAccessNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitStoreMapShortcut(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreMapShortcut(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getAccessNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 14 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StoreVariableNode.java

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.phase.IRTreeVisitor;
 import org.elasticsearch.painless.symbol.WriteScope;
 import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Opcodes;
@@ -39,7 +40,19 @@ public class StoreVariableNode extends StoreNode {
         return name;
     }
 
-    /* ---- end node data ---- */
+    /* ---- end node data, begin visitor ---- */
+
+    @Override
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStoreVariable(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
+    }
+
+    /* ---- end visitor ---- */
 
     @Override
     protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {

+ 81 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StringConcatenationNode.java

@@ -0,0 +1,81 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.painless.ir;
+
+import org.elasticsearch.painless.ClassWriter;
+import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.phase.IRTreeVisitor;
+import org.elasticsearch.painless.symbol.WriteScope;
+
+public class StringConcatenationNode extends ArgumentsNode {
+
+    /* ---- begin node data ---- */
+
+    private boolean cat;
+
+    public void setCat(boolean cat) {
+        this.cat = cat;
+    }
+
+    public boolean getCat() {
+        return cat;
+    }
+
+    /* ---- end node data, begin visitor ---- */
+
+    @Override
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitStringConcatenation(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+
+    }
+
+    /* ---- end visitor ---- */
+
+    @Override
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        methodWriter.writeDebugInfo(location);
+
+        if (cat == false) {
+            methodWriter.writeNewStrings();
+        }
+
+        ExpressionNode leftNode = getArgumentNodes().get(0);
+        leftNode.write(classWriter, methodWriter, writeScope);
+
+        if (leftNode instanceof StringConcatenationNode == false || ((StringConcatenationNode)leftNode).getCat() == false) {
+            methodWriter.writeAppendStrings(leftNode.getExpressionType());
+        }
+
+        ExpressionNode rightNode = getArgumentNodes().get(1);
+        rightNode.write(classWriter, methodWriter, writeScope);
+
+        if (rightNode instanceof StringConcatenationNode == false || ((StringConcatenationNode)rightNode).getCat() == false) {
+            methodWriter.writeAppendStrings(rightNode.getExpressionType());
+        }
+
+        if (cat == false) {
+            methodWriter.writeToStrings();
+        }
+    }
+}

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java

@@ -41,8 +41,13 @@ public class ThrowNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitThrow(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitThrow(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 11 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java

@@ -54,8 +54,17 @@ public class TryNode extends StatementNode {
     /* ---- end tree structure, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitTry(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitTry(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        blockNode.visit(irTreeVisitor, scope);
+
+        for (CatchNode catchNode : catchNodes) {
+            catchNode.visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java

@@ -45,8 +45,13 @@ public class TypedCaptureReferenceNode extends ReferenceNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitTypeCaptureReference(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitTypeCaptureReference(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedInterfaceReferenceNode.java

@@ -43,8 +43,13 @@ public class TypedInterfaceReferenceNode extends ReferenceNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitTypedInterfaceReference(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitTypedInterfaceReference(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        // do nothing; terminal node
     }
 
     /* ---- end visitor ---- */

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java

@@ -79,8 +79,13 @@ public class UnaryMathNode extends UnaryNode {
     /* ---- end node data, begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitUnaryMath(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitUnaryMath(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        getChildNode().visit(irTreeVisitor, scope);
     }
 
     /* ---- end visitor ---- */

+ 13 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileLoopNode.java

@@ -32,8 +32,19 @@ public class WhileLoopNode extends LoopNode {
     /* ---- begin visitor ---- */
 
     @Override
-    public <Input, Output> Output visit(IRTreeVisitor<Input, Output> irTreeVisitor, Input input) {
-        return irTreeVisitor.visitWhileLoop(this, input);
+    public <Scope> void visit(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        irTreeVisitor.visitWhileLoop(this, scope);
+    }
+
+    @Override
+    public <Scope> void visitChildren(IRTreeVisitor<Scope> irTreeVisitor, Scope scope) {
+        if (getConditionNode() != null) {
+            getConditionNode().visit(irTreeVisitor, scope);
+        }
+
+        if (getBlockNode() != null) {
+            getBlockNode().visit(irTreeVisitor, scope);
+        }
     }
 
     /* ---- end visitor ---- */

+ 2 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java

@@ -66,14 +66,10 @@ public abstract class ANode {
     /**
      * Callback to visit a user tree node.
      */
-    public <Scope> void visit(UserTreeVisitor<Scope> userTreeVisitor, Scope scope) {
-        throw new UnsupportedOperationException("cannot visit user node type [" + getClass().getCanonicalName() + "]");
-    }
+    public abstract <Scope> void visit(UserTreeVisitor<Scope> userTreeVisitor, Scope scope);
 
     /**
      * Visits all child user tree nodes for this user tree node.
      */
-    public <Scope> void visitChildren(UserTreeVisitor<Scope> userTreeVisitor, Scope scope) {
-        throw new UnsupportedOperationException("cannot visit children of user node type [" + getClass().getCanonicalName() + "]");
-    }
+    public abstract <Scope> void visitChildren(UserTreeVisitor<Scope> userTreeVisitor, Scope scope);
 }

+ 73 - 39
modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java

@@ -22,6 +22,7 @@ package org.elasticsearch.painless.phase;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.ir.BinaryMathNode;
 import org.elasticsearch.painless.ir.BinaryNode;
 import org.elasticsearch.painless.ir.BlockNode;
@@ -90,6 +91,7 @@ import org.elasticsearch.painless.ir.StoreListShortcutNode;
 import org.elasticsearch.painless.ir.StoreMapShortcutNode;
 import org.elasticsearch.painless.ir.StoreNode;
 import org.elasticsearch.painless.ir.StoreVariableNode;
+import org.elasticsearch.painless.ir.StringConcatenationNode;
 import org.elasticsearch.painless.ir.ThrowNode;
 import org.elasticsearch.painless.ir.TryNode;
 import org.elasticsearch.painless.ir.TypedCaptureReferenceNode;
@@ -485,7 +487,7 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
                 }
             // this is a compound assignment
             } else {
-                // this store has a prefix node that we much dup for a load
+                // this store has a prefix node that we must dup for a load
                 if (irAccessNode != null) {
                     DupNode dupNode = new DupNode();
                     dupNode.setLocation(location);
@@ -497,12 +499,7 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
                     ((StoreAccessNode)irStoreNode).setAccessNode(dupNode);
                 }
 
-                // this adds a stub node to do the operation
-                BinaryMathNode binaryMathNode = new BinaryMathNode();
-                binaryMathNode.setLocation(location);
-                binaryMathNode.setLeftNode(irLoadNode);
-
-                irStoreNode.setChildNode(binaryMathNode);
+                irStoreNode.setChildNode(irLoadNode);
             }
 
             irExpressionNode = irStoreNode;
@@ -845,41 +842,62 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
 
         // handles a compound assignment using the stub generated from buildLoadStore
         if (compoundType != null) {
+            boolean concatenate = userAssignmentNode.getOperation() == Operation.ADD && compoundType == String.class;
             scriptScope.setCondition(userAssignmentNode.getLeftNode(), Compound.class);
             irStoreNode = (StoreNode)visit(userAssignmentNode.getLeftNode(), scriptScope);
-            BinaryMathNode irBinaryMathNode = (BinaryMathNode)irStoreNode.getChildNode();
+            ExpressionNode irLoadNode = irStoreNode.getChildNode();
+            ExpressionNode irCompoundNode;
+
+            // handles when the operation is a string concatenation
+            if (concatenate) {
+                StringConcatenationNode stringConcatenationNode = new StringConcatenationNode();
+                stringConcatenationNode.setLocation(irStoreNode.getLocation());
+                stringConcatenationNode.setExpressionType(String.class);
+                stringConcatenationNode.setCat(false);
+                irCompoundNode = stringConcatenationNode;
+            // handles when the operation is mathematical
+            } else {
+                BinaryMathNode irBinaryMathNode = new BinaryMathNode();
+                irBinaryMathNode.setLocation(irStoreNode.getLocation());
+                irBinaryMathNode.setLeftNode(irLoadNode);
+                irBinaryMathNode.setExpressionType(compoundType);
+                irBinaryMathNode.setBinaryType(compoundType);
+                irBinaryMathNode.setOperation(userAssignmentNode.getOperation());
+                // add a compound assignment flag to the binary math node
+                irBinaryMathNode.setFlags(DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
+                irCompoundNode = irBinaryMathNode;
+            }
 
             PainlessCast downcast = scriptScope.hasDecoration(userAssignmentNode, DowncastPainlessCast.class) ?
                     scriptScope.getDecoration(userAssignmentNode, DowncastPainlessCast.class).getDowncastPainlessCast() : null;
 
             // no need to downcast so the binary math node is the value for the store node
             if (downcast == null) {
-                irBinaryMathNode.setExpressionType(irStoreNode.getStoreType());
+                irCompoundNode.setExpressionType(irStoreNode.getStoreType());
+                irStoreNode.setChildNode(irCompoundNode);
             // add a cast node to do a downcast as the value for the store node
             } else {
                 CastNode irCastNode = new CastNode();
-                irCastNode.setLocation(irBinaryMathNode.getLocation());
+                irCastNode.setLocation(irCompoundNode.getLocation());
                 irCastNode.setExpressionType(downcast.targetType);
                 irCastNode.setCast(downcast);
-                irCastNode.setChildNode(irBinaryMathNode);
+                irCastNode.setChildNode(irCompoundNode);
                 irStoreNode.setChildNode(irCastNode);
             }
 
             // the value is also read from this assignment
             if (read) {
                 int accessDepth = scriptScope.getDecoration(userAssignmentNode.getLeftNode(), AccessDepth.class).getAccessDepth();
-
                 DupNode irDupNode = new DupNode();
 
                 // the value is read from prior to assignment (post-increment)
                 if (userAssignmentNode.postIfRead()) {
-                    ExpressionNode irLoadNode = irBinaryMathNode.getLeftNode();
                     irDupNode.setLocation(irLoadNode.getLocation());
                     irDupNode.setExpressionType(irLoadNode.getExpressionType());
                     irDupNode.setSize(MethodWriter.getType(irLoadNode.getExpressionType()).getSize());
                     irDupNode.setDepth(accessDepth);
                     irDupNode.setChildNode(irLoadNode);
-                    irBinaryMathNode.setLeftNode(irDupNode);
+                    irLoadNode = irDupNode;
                 // the value is read from after the assignment (pre-increment/compound)
                 } else {
                     irDupNode.setLocation(irStoreNode.getLocation());
@@ -896,21 +914,23 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
 
             // upcast the stored value if necessary
             if (upcast != null) {
-                ExpressionNode irLoadNode = irBinaryMathNode.getLeftNode();
                 CastNode irCastNode = new CastNode();
                 irCastNode.setLocation(irLoadNode.getLocation());
                 irCastNode.setExpressionType(upcast.targetType);
                 irCastNode.setCast(upcast);
                 irCastNode.setChildNode(irLoadNode);
-                irBinaryMathNode.setLeftNode(irCastNode);
+                irLoadNode = irCastNode;
             }
 
-            // add a compound assignment flag to the binary math node
-            irBinaryMathNode.setExpressionType(compoundType);
-            irBinaryMathNode.setBinaryType(compoundType);
-            irBinaryMathNode.setOperation(userAssignmentNode.getOperation());
-            irBinaryMathNode.setFlags(DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
-            irBinaryMathNode.setRightNode(irValueNode);
+            if (concatenate) {
+                StringConcatenationNode irStringConcatenationNode = (StringConcatenationNode)irCompoundNode;
+                irStringConcatenationNode.addArgumentNode(irLoadNode);
+                irStringConcatenationNode.addArgumentNode(irValueNode);
+            } else {
+                BinaryMathNode irBinaryMathNode = (BinaryMathNode)irCompoundNode;
+                irBinaryMathNode.setLeftNode(irLoadNode);
+                irBinaryMathNode.setRightNode(irValueNode);
+            }
         // handles a standard assignment
         } else {
             irStoreNode = (StoreNode)visit(userAssignmentNode.getLeftNode(), scriptScope);
@@ -960,25 +980,39 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
 
     @Override
     public void visitBinary(EBinary userBinaryNode, ScriptScope scriptScope) {
-        Class<?> shiftType = scriptScope.hasDecoration(userBinaryNode, ShiftType.class) ?
-                scriptScope.getDecoration(userBinaryNode, ShiftType.class).getShiftType() : null;
-
-        BinaryMathNode irBinaryMathNode = new BinaryMathNode();
-        irBinaryMathNode.setLocation(userBinaryNode.getLocation());
-        irBinaryMathNode.setExpressionType(scriptScope.getDecoration(userBinaryNode, ValueType.class).getValueType());
-        irBinaryMathNode.setBinaryType(scriptScope.getDecoration(userBinaryNode, BinaryType.class).getBinaryType());
-        irBinaryMathNode.setShiftType(shiftType);
-        irBinaryMathNode.setOperation(userBinaryNode.getOperation());
-        irBinaryMathNode.setCat(scriptScope.getCondition(userBinaryNode, Concatenate.class));
-
-        if (scriptScope.getCondition(userBinaryNode, Explicit.class)) {
-            irBinaryMathNode.setFlags(DefBootstrap.OPERATOR_EXPLICIT_CAST);
-        }
+        ExpressionNode irExpressionNode;
 
-        irBinaryMathNode.setLeftNode(injectCast(userBinaryNode.getLeftNode(), scriptScope));
-        irBinaryMathNode.setRightNode(injectCast(userBinaryNode.getRightNode(), scriptScope));
+        Operation operation = userBinaryNode.getOperation();
+        Class<?> valueType = scriptScope.getDecoration(userBinaryNode, ValueType.class).getValueType();
+
+        if (operation == Operation.ADD && valueType == String.class) {
+            StringConcatenationNode stringConcatenationNode = new StringConcatenationNode();
+            stringConcatenationNode.setCat(scriptScope.getCondition(userBinaryNode, Concatenate.class));
+            stringConcatenationNode.addArgumentNode((ExpressionNode)visit(userBinaryNode.getLeftNode(), scriptScope));
+            stringConcatenationNode.addArgumentNode((ExpressionNode)visit(userBinaryNode.getRightNode(), scriptScope));
+            irExpressionNode = stringConcatenationNode;
+        } else {
+            Class<?> shiftType = scriptScope.hasDecoration(userBinaryNode, ShiftType.class) ?
+                    scriptScope.getDecoration(userBinaryNode, ShiftType.class).getShiftType() : null;
+
+            BinaryMathNode irBinaryMathNode = new BinaryMathNode();
+
+            irBinaryMathNode.setBinaryType(scriptScope.getDecoration(userBinaryNode, BinaryType.class).getBinaryType());
+            irBinaryMathNode.setShiftType(shiftType);
+            irBinaryMathNode.setOperation(operation);
+
+            if (scriptScope.getCondition(userBinaryNode, Explicit.class)) {
+                irBinaryMathNode.setFlags(DefBootstrap.OPERATOR_EXPLICIT_CAST);
+            }
+
+            irBinaryMathNode.setLeftNode(injectCast(userBinaryNode.getLeftNode(), scriptScope));
+            irBinaryMathNode.setRightNode(injectCast(userBinaryNode.getRightNode(), scriptScope));
+            irExpressionNode = irBinaryMathNode;
+        }
 
-        scriptScope.putDecoration(userBinaryNode, new IRNodeDecoration(irBinaryMathNode));
+        irExpressionNode.setLocation(userBinaryNode.getLocation());
+        irExpressionNode.setExpressionType(valueType);
+        scriptScope.putDecoration(userBinaryNode, new IRNodeDecoration(irExpressionNode));
     }
 
     @Override

+ 149 - 131
modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeBaseVisitor.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.phase;
 
 import org.elasticsearch.painless.ir.BinaryMathNode;
+import org.elasticsearch.painless.ir.BinaryNode;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.BooleanNode;
 import org.elasticsearch.painless.ir.BreakNode;
@@ -78,6 +79,8 @@ import org.elasticsearch.painless.ir.StoreDotShortcutNode;
 import org.elasticsearch.painless.ir.StoreFieldMemberNode;
 import org.elasticsearch.painless.ir.StoreListShortcutNode;
 import org.elasticsearch.painless.ir.StoreMapShortcutNode;
+import org.elasticsearch.painless.ir.StoreVariableNode;
+import org.elasticsearch.painless.ir.StringConcatenationNode;
 import org.elasticsearch.painless.ir.ThrowNode;
 import org.elasticsearch.painless.ir.TryNode;
 import org.elasticsearch.painless.ir.TypedCaptureReferenceNode;
@@ -85,330 +88,345 @@ import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode;
 import org.elasticsearch.painless.ir.UnaryMathNode;
 import org.elasticsearch.painless.ir.WhileLoopNode;
 
-public class IRTreeBaseVisitor<Input, Output> implements IRTreeVisitor<Input, Output> {
+public class IRTreeBaseVisitor<Scope> implements IRTreeVisitor<Scope> {
 
     @Override
-    public Output visitClass(ClassNode irClassNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitClass(ClassNode irClassNode, Scope scope) {
+        irClassNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitFunction(FunctionNode irFunctionNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitFunction(FunctionNode irFunctionNode, Scope scope) {
+        irFunctionNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitField(FieldNode irFieldNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitField(FieldNode irFieldNode, Scope scope) {
+        irFieldNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitBlock(BlockNode irBlockNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitBlock(BlockNode irBlockNode, Scope scope) {
+        irBlockNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitIf(IfNode irIfNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitIf(IfNode irIfNode, Scope scope) {
+        irIfNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitIfElse(IfElseNode irIfElseNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitIfElse(IfElseNode irIfElseNode, Scope scope) {
+        irIfElseNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitWhileLoop(WhileLoopNode irWhileLoopNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitWhileLoop(WhileLoopNode irWhileLoopNode, Scope scope) {
+        irWhileLoopNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitDoWhileLoop(DoWhileLoopNode irDoWhileLoopNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitDoWhileLoop(DoWhileLoopNode irDoWhileLoopNode, Scope scope) {
+        irDoWhileLoopNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitForLoop(ForLoopNode irForLoopNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitForLoop(ForLoopNode irForLoopNode, Scope scope) {
+        irForLoopNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitForEachLoop(ForEachLoopNode irForEachLoopNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitForEachLoop(ForEachLoopNode irForEachLoopNode, Scope scope) {
+        irForEachLoopNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitForEachSubArrayLoop(ForEachSubArrayNode irForEachSubArrayNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitForEachSubArrayLoop(ForEachSubArrayNode irForEachSubArrayNode, Scope scope) {
+        irForEachSubArrayNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitForEachSubIterableLoop(ForEachSubIterableNode irForEachSubIterableNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitForEachSubIterableLoop(ForEachSubIterableNode irForEachSubIterableNode, Scope scope) {
+        irForEachSubIterableNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitDeclarationBlock(DeclarationBlockNode irDeclarationBlockNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitDeclarationBlock(DeclarationBlockNode irDeclarationBlockNode, Scope scope) {
+        irDeclarationBlockNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitDeclaration(DeclarationNode irDeclarationNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitDeclaration(DeclarationNode irDeclarationNode, Scope scope) {
+        irDeclarationNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitReturn(ReturnNode irReturnNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitReturn(ReturnNode irReturnNode, Scope scope) {
+        irReturnNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStatementExpression(StatementExpressionNode irStatementExpressionNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStatementExpression(StatementExpressionNode irStatementExpressionNode, Scope scope) {
+        irStatementExpressionNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitTry(TryNode irTryNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitTry(TryNode irTryNode, Scope scope) {
+        irTryNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitCatch(CatchNode irCatchNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitCatch(CatchNode irCatchNode, Scope scope) {
+        irCatchNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitThrow(ThrowNode irThrowNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitThrow(ThrowNode irThrowNode, Scope scope) {
+        irThrowNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitContinue(ContinueNode irContinueNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitContinue(ContinueNode irContinueNode, Scope scope) {
+        irContinueNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitBreak(BreakNode irBreakNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitBreak(BreakNode irBreakNode, Scope scope) {
+        irBreakNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitUnaryMath(UnaryMathNode irUnaryMathNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitBinary(BinaryNode irBinaryNode, Scope scope) {
+        irBinaryNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitBinaryMath(BinaryMathNode irBinaryMathNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitUnaryMath(UnaryMathNode irUnaryMathNode, Scope scope) {
+        irUnaryMathNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitBoolean(BooleanNode irBoolNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitBinaryMath(BinaryMathNode irBinaryMathNode, Scope scope) {
+        irBinaryMathNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitComparison(ComparisonNode irComparisonNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStringConcatenation(StringConcatenationNode irStringConcatenationNode, Scope scope) {
+        irStringConcatenationNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitCast(CastNode irCastNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitBoolean(BooleanNode irBooleanNode, Scope scope) {
+        irBooleanNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitInstanceof(InstanceofNode irInstanceofNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitComparison(ComparisonNode irComparisonNode, Scope scope) {
+        irComparisonNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitConditional(ConditionalNode irConditionalNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitCast(CastNode irCastNode, Scope scope) {
+        irCastNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitElvis(ElvisNode irElvisNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitInstanceof(InstanceofNode irInstanceofNode, Scope scope) {
+        irInstanceofNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitListInitialization(ListInitializationNode irListInitializationNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitConditional(ConditionalNode irConditionalNode, Scope scope) {
+        irConditionalNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitMapInitialization(MapInitializationNode irMapInitializationNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitElvis(ElvisNode irElvisNode, Scope scope) {
+        irElvisNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitNewArray(NewArrayNode irNewArrayNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitListInitialization(ListInitializationNode irListInitializationNode, Scope scope) {
+        irListInitializationNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitNewObject(NewObjectNode irNewObjectNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitMapInitialization(MapInitializationNode irMapInitializationNode, Scope scope) {
+        irMapInitializationNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitConstant(ConstantNode irConstantNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitNewArray(NewArrayNode irNewArrayNode, Scope scope) {
+        irNewArrayNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitNull(NullNode irNullNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitNewObject(NewObjectNode irNewObjectNode, Scope scope) {
+        irNewObjectNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitDefInterfaceReference(DefInterfaceReferenceNode irDefInterfaceReferenceNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitConstant(ConstantNode irConstantNode, Scope scope) {
+        irConstantNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInterfaceReferenceNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitNull(NullNode irNullNode, Scope scope) {
+        irNullNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitTypeCaptureReference(TypedCaptureReferenceNode irTypedCaptureReferenceNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitDefInterfaceReference(DefInterfaceReferenceNode irDefInterfaceReferenceNode, Scope scope) {
+        irDefInterfaceReferenceNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStatic(StaticNode irStaticNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInterfaceReferenceNode, Scope scope) {
+        irTypedInterfaceReferenceNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadVariable(LoadVariableNode irLoadVariableNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitTypeCaptureReference(TypedCaptureReferenceNode irTypedCaptureReferenceNode, Scope scope) {
+        irTypedCaptureReferenceNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitNullSafeSub(NullSafeSubNode irNullSafeSubNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStatic(StaticNode irStaticNode, Scope scope) {
+        irStaticNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadDotArrayLengthNode(LoadDotArrayLengthNode irLoadDotArrayLengthNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadVariable(LoadVariableNode irLoadVariableNode, Scope scope) {
+        irLoadVariableNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadDotDef(LoadDotDefNode irLoadDotDefNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitNullSafeSub(NullSafeSubNode irNullSafeSubNode, Scope scope) {
+        irNullSafeSubNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadDot(LoadDotNode irLoadDotNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadDotArrayLengthNode(LoadDotArrayLengthNode irLoadDotArrayLengthNode, Scope scope) {
+        irLoadDotArrayLengthNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadDotShortcut(LoadDotShortcutNode irDotSubShortcutNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadDotDef(LoadDotDefNode irLoadDotDefNode, Scope scope) {
+        irLoadDotDefNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadListShortcut(LoadListShortcutNode irLoadListShortcutNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadDot(LoadDotNode irLoadDotNode, Scope scope) {
+        irLoadDotNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadDotShortcut(LoadDotShortcutNode irDotSubShortcutNode, Scope scope) {
+        irDotSubShortcutNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadListShortcut(LoadListShortcutNode irLoadListShortcutNode, Scope scope) {
+        irLoadListShortcutNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadBraceDef(LoadBraceDefNode irLoadBraceDefNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Scope scope) {
+        irLoadMapShortcutNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitLoadBrace(LoadBraceNode irLoadBraceNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, Scope scope) {
+        irLoadFieldMemberNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreDotDef(StoreDotDefNode irStoreDotDefNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadBraceDef(LoadBraceDefNode irLoadBraceDefNode, Scope scope) {
+        irLoadBraceDefNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreDot(StoreDotNode irStoreDotNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitLoadBrace(LoadBraceNode irLoadBraceNode, Scope scope) {
+        irLoadBraceNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreDotShortcut(StoreDotShortcutNode irDotSubShortcutNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreVariable(StoreVariableNode irStoreVariableNode, Scope scope) {
+        irStoreVariableNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreListShortcut(StoreListShortcutNode irStoreListShortcutNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreDotDef(StoreDotDefNode irStoreDotDefNode, Scope scope) {
+        irStoreDotDefNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreMapShortcut(StoreMapShortcutNode irStoreMapShortcutNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreDot(StoreDotNode irStoreDotNode, Scope scope) {
+        irStoreDotNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreFieldMember(StoreFieldMemberNode irStoreFieldMemberNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreDotShortcut(StoreDotShortcutNode irDotSubShortcutNode, Scope scope) {
+        irDotSubShortcutNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreBraceDef(StoreBraceDefNode irStoreBraceDefNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreListShortcut(StoreListShortcutNode irStoreListShortcutNode, Scope scope) {
+        irStoreListShortcutNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitStoreBrace(StoreBraceNode irStoreBraceNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreMapShortcut(StoreMapShortcutNode irStoreMapShortcutNode, Scope scope) {
+        irStoreMapShortcutNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitInvokeCallDef(InvokeCallDefNode irInvokeCallDefNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreFieldMember(StoreFieldMemberNode irStoreFieldMemberNode, Scope scope) {
+        irStoreFieldMemberNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitInvokeCall(InvokeCallNode irInvokeCallNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreBraceDef(StoreBraceDefNode irStoreBraceDefNode, Scope scope) {
+        irStoreBraceDefNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitStoreBrace(StoreBraceNode irStoreBraceNode, Scope scope) {
+        irStoreBraceNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitFlipArrayIndex(FlipArrayIndexNode irFlipArrayIndexNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitInvokeCallDef(InvokeCallDefNode irInvokeCallDefNode, Scope scope) {
+        irInvokeCallDefNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitFlipCollectionIndex(FlipCollectionIndexNode irFlipCollectionIndexNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitInvokeCall(InvokeCallNode irInvokeCallNode, Scope scope) {
+        irInvokeCallNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitFlipDefIndex(FlipDefIndexNode irFlipDefIndexNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, Scope scope) {
+        irInvokeCallMemberNode.visitChildren(this, scope);
     }
 
     @Override
-    public Output visitDup(DupNode dupNode, Input input) {
-        throw new UnsupportedOperationException();
+    public void visitFlipArrayIndex(FlipArrayIndexNode irFlipArrayIndexNode, Scope scope) {
+        irFlipArrayIndexNode.visitChildren(this, scope);
+    }
+
+    @Override
+    public void visitFlipCollectionIndex(FlipCollectionIndexNode irFlipCollectionIndexNode, Scope scope) {
+        irFlipCollectionIndexNode.visitChildren(this, scope);
+    }
+
+    @Override
+    public void visitFlipDefIndex(FlipDefIndexNode irFlipDefIndexNode, Scope scope) {
+        irFlipDefIndexNode.visitChildren(this, scope);
+    }
+
+    @Override
+    public void visitDup(DupNode irDupNode, Scope scope) {
+        irDupNode.visitChildren(this, scope);
     }
 }

+ 72 - 66
modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/IRTreeVisitor.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.phase;
 
 import org.elasticsearch.painless.ir.BinaryMathNode;
+import org.elasticsearch.painless.ir.BinaryNode;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.BooleanNode;
 import org.elasticsearch.painless.ir.BreakNode;
@@ -78,6 +79,8 @@ import org.elasticsearch.painless.ir.StoreDotShortcutNode;
 import org.elasticsearch.painless.ir.StoreFieldMemberNode;
 import org.elasticsearch.painless.ir.StoreListShortcutNode;
 import org.elasticsearch.painless.ir.StoreMapShortcutNode;
+import org.elasticsearch.painless.ir.StoreVariableNode;
+import org.elasticsearch.painless.ir.StringConcatenationNode;
 import org.elasticsearch.painless.ir.ThrowNode;
 import org.elasticsearch.painless.ir.TryNode;
 import org.elasticsearch.painless.ir.TypedCaptureReferenceNode;
@@ -85,73 +88,76 @@ import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode;
 import org.elasticsearch.painless.ir.UnaryMathNode;
 import org.elasticsearch.painless.ir.WhileLoopNode;
 
-public interface IRTreeVisitor<Input, Output> {
+public interface IRTreeVisitor<Scope> {
 
-    Output visitClass(ClassNode irClassNode, Input input);
-    Output visitFunction(FunctionNode irFunctionNode, Input input);
-    Output visitField(FieldNode irFieldNode, Input input);
+    void visitClass(ClassNode irClassNode, Scope scope);
+    void visitFunction(FunctionNode irFunctionNode, Scope scope);
+    void visitField(FieldNode irFieldNode, Scope scope);
 
-    Output visitBlock(BlockNode irBlockNode, Input input);
-    Output visitIf(IfNode irIfNode, Input input);
-    Output visitIfElse(IfElseNode irIfElseNode, Input input);
-    Output visitWhileLoop(WhileLoopNode irWhileLoopNode, Input input);
-    Output visitDoWhileLoop(DoWhileLoopNode irDoWhileLoopNode, Input input);
-    Output visitForLoop(ForLoopNode irForLoopNode, Input input);
-    Output visitForEachLoop(ForEachLoopNode irForEachLoopNode, Input input);
-    Output visitForEachSubArrayLoop(ForEachSubArrayNode irForEachSubArrayNode, Input input);
-    Output visitForEachSubIterableLoop(ForEachSubIterableNode irForEachSubIterableNode, Input input);
-    Output visitDeclarationBlock(DeclarationBlockNode irDeclarationBlockNode, Input input);
-    Output visitDeclaration(DeclarationNode irDeclarationNode, Input input);
-    Output visitReturn(ReturnNode irReturnNode, Input input);
-    Output visitStatementExpression(StatementExpressionNode irStatementExpressionNode, Input input);
-    Output visitTry(TryNode irTryNode, Input input);
-    Output visitCatch(CatchNode irCatchNode, Input input);
-    Output visitThrow(ThrowNode irThrowNode, Input input);
-    Output visitContinue(ContinueNode irContinueNode, Input input);
-    Output visitBreak(BreakNode irBreakNode, Input input);
+    void visitBlock(BlockNode irBlockNode, Scope scope);
+    void visitIf(IfNode irIfNode, Scope scope);
+    void visitIfElse(IfElseNode irIfElseNode, Scope scope);
+    void visitWhileLoop(WhileLoopNode irWhileLoopNode, Scope scope);
+    void visitDoWhileLoop(DoWhileLoopNode irDoWhileLoopNode, Scope scope);
+    void visitForLoop(ForLoopNode irForLoopNode, Scope scope);
+    void visitForEachLoop(ForEachLoopNode irForEachLoopNode, Scope scope);
+    void visitForEachSubArrayLoop(ForEachSubArrayNode irForEachSubArrayNode, Scope scope);
+    void visitForEachSubIterableLoop(ForEachSubIterableNode irForEachSubIterableNode, Scope scope);
+    void visitDeclarationBlock(DeclarationBlockNode irDeclarationBlockNode, Scope scope);
+    void visitDeclaration(DeclarationNode irDeclarationNode, Scope scope);
+    void visitReturn(ReturnNode irReturnNode, Scope scope);
+    void visitStatementExpression(StatementExpressionNode irStatementExpressionNode, Scope scope);
+    void visitTry(TryNode irTryNode, Scope scope);
+    void visitCatch(CatchNode irCatchNode, Scope scope);
+    void visitThrow(ThrowNode irThrowNode, Scope scope);
+    void visitContinue(ContinueNode irContinueNode, Scope scope);
+    void visitBreak(BreakNode irBreakNode, Scope scope);
 
-    Output visitUnaryMath(UnaryMathNode irUnaryMathNode, Input input);
-    Output visitBinaryMath(BinaryMathNode irBinaryMathNode, Input input);
-    Output visitBoolean(BooleanNode irBoolNode, Input input);
-    Output visitComparison(ComparisonNode irComparisonNode, Input input);
-    Output visitCast(CastNode irCastNode, Input input);
-    Output visitInstanceof(InstanceofNode irInstanceofNode, Input input);
-    Output visitConditional(ConditionalNode irConditionalNode, Input input);
-    Output visitElvis(ElvisNode irElvisNode, Input input);
-    Output visitListInitialization(ListInitializationNode irListInitializationNode, Input input);
-    Output visitMapInitialization(MapInitializationNode irMapInitializationNode, Input input);
-    Output visitNewArray(NewArrayNode irNewArrayNode, Input input);
-    Output visitNewObject(NewObjectNode irNewObjectNode, Input input);
-    Output visitConstant(ConstantNode irConstantNode, Input input);
-    Output visitNull(NullNode irNullNode, Input input);
-    Output visitDefInterfaceReference(DefInterfaceReferenceNode irDefInterfaceReferenceNode, Input input);
-    Output visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInterfaceReferenceNode, Input input);
-    Output visitTypeCaptureReference(TypedCaptureReferenceNode irTypedCaptureReferenceNode, Input input);
-    Output visitStatic(StaticNode irStaticNode, Input input);
-    Output visitLoadVariable(LoadVariableNode irLoadVariableNode, Input input);
-    Output visitNullSafeSub(NullSafeSubNode irNullSafeSubNode, Input input);
-    Output visitLoadDotArrayLengthNode(LoadDotArrayLengthNode irLoadDotArrayLengthNode, Input input);
-    Output visitLoadDotDef(LoadDotDefNode irLoadDotDefNode, Input input);
-    Output visitLoadDot(LoadDotNode irLoadDotNode, Input input);
-    Output visitLoadDotShortcut(LoadDotShortcutNode irDotSubShortcutNode, Input input);
-    Output visitLoadListShortcut(LoadListShortcutNode irLoadListShortcutNode, Input input);
-    Output visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Input input);
-    Output visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, Input input);
-    Output visitLoadBraceDef(LoadBraceDefNode irLoadBraceDefNode, Input input);
-    Output visitLoadBrace(LoadBraceNode irLoadBraceNode, Input input);
-    Output visitStoreDotDef(StoreDotDefNode irStoreDotDefNode, Input input);
-    Output visitStoreDot(StoreDotNode irStoreDotNode, Input input);
-    Output visitStoreDotShortcut(StoreDotShortcutNode irDotSubShortcutNode, Input input);
-    Output visitStoreListShortcut(StoreListShortcutNode irStoreListShortcutNode, Input input);
-    Output visitStoreMapShortcut(StoreMapShortcutNode irStoreMapShortcutNode, Input input);
-    Output visitStoreFieldMember(StoreFieldMemberNode irStoreFieldMemberNode, Input input);
-    Output visitStoreBraceDef(StoreBraceDefNode irStoreBraceDefNode, Input input);
-    Output visitStoreBrace(StoreBraceNode irStoreBraceNode, Input input);
-    Output visitInvokeCallDef(InvokeCallDefNode irInvokeCallDefNode, Input input);
-    Output visitInvokeCall(InvokeCallNode irInvokeCallNode, Input input);
-    Output visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, Input input);
-    Output visitFlipArrayIndex(FlipArrayIndexNode irFlipArrayIndexNode, Input input);
-    Output visitFlipCollectionIndex(FlipCollectionIndexNode irFlipCollectionIndexNode, Input input);
-    Output visitFlipDefIndex(FlipDefIndexNode irFlipDefIndexNode, Input input);
-    Output visitDup(DupNode dupNode, Input input);
+    void visitBinary(BinaryNode irBinaryNode, Scope scope);
+    void visitUnaryMath(UnaryMathNode irUnaryMathNode, Scope scope);
+    void visitBinaryMath(BinaryMathNode irBinaryMathNode, Scope scope);
+    void visitStringConcatenation(StringConcatenationNode irStringConcatenationNode, Scope scope);
+    void visitBoolean(BooleanNode irBooleanNode, Scope scope);
+    void visitComparison(ComparisonNode irComparisonNode, Scope scope);
+    void visitCast(CastNode irCastNode, Scope scope);
+    void visitInstanceof(InstanceofNode irInstanceofNode, Scope scope);
+    void visitConditional(ConditionalNode irConditionalNode, Scope scope);
+    void visitElvis(ElvisNode irElvisNode, Scope scope);
+    void visitListInitialization(ListInitializationNode irListInitializationNode, Scope scope);
+    void visitMapInitialization(MapInitializationNode irMapInitializationNode, Scope scope);
+    void visitNewArray(NewArrayNode irNewArrayNode, Scope scope);
+    void visitNewObject(NewObjectNode irNewObjectNode, Scope scope);
+    void visitConstant(ConstantNode irConstantNode, Scope scope);
+    void visitNull(NullNode irNullNode, Scope scope);
+    void visitDefInterfaceReference(DefInterfaceReferenceNode irDefInterfaceReferenceNode, Scope scope);
+    void visitTypedInterfaceReference(TypedInterfaceReferenceNode irTypedInterfaceReferenceNode, Scope scope);
+    void visitTypeCaptureReference(TypedCaptureReferenceNode irTypedCaptureReferenceNode, Scope scope);
+    void visitStatic(StaticNode irStaticNode, Scope scope);
+    void visitLoadVariable(LoadVariableNode irLoadVariableNode, Scope scope);
+    void visitNullSafeSub(NullSafeSubNode irNullSafeSubNode, Scope scope);
+    void visitLoadDotArrayLengthNode(LoadDotArrayLengthNode irLoadDotArrayLengthNode, Scope scope);
+    void visitLoadDotDef(LoadDotDefNode irLoadDotDefNode, Scope scope);
+    void visitLoadDot(LoadDotNode irLoadDotNode, Scope scope);
+    void visitLoadDotShortcut(LoadDotShortcutNode irDotSubShortcutNode, Scope scope);
+    void visitLoadListShortcut(LoadListShortcutNode irLoadListShortcutNode, Scope scope);
+    void visitLoadMapShortcut(LoadMapShortcutNode irLoadMapShortcutNode, Scope scope);
+    void visitLoadFieldMember(LoadFieldMemberNode irLoadFieldMemberNode, Scope scope);
+    void visitLoadBraceDef(LoadBraceDefNode irLoadBraceDefNode, Scope scope);
+    void visitLoadBrace(LoadBraceNode irLoadBraceNode, Scope scope);
+    void visitStoreVariable(StoreVariableNode irStoreVariableNode, Scope scope);
+    void visitStoreDotDef(StoreDotDefNode irStoreDotDefNode, Scope scope);
+    void visitStoreDot(StoreDotNode irStoreDotNode, Scope scope);
+    void visitStoreDotShortcut(StoreDotShortcutNode irDotSubShortcutNode, Scope scope);
+    void visitStoreListShortcut(StoreListShortcutNode irStoreListShortcutNode, Scope scope);
+    void visitStoreMapShortcut(StoreMapShortcutNode irStoreMapShortcutNode, Scope scope);
+    void visitStoreFieldMember(StoreFieldMemberNode irStoreFieldMemberNode, Scope scope);
+    void visitStoreBraceDef(StoreBraceDefNode irStoreBraceDefNode, Scope scope);
+    void visitStoreBrace(StoreBraceNode irStoreBraceNode, Scope scope);
+    void visitInvokeCallDef(InvokeCallDefNode irInvokeCallDefNode, Scope scope);
+    void visitInvokeCall(InvokeCallNode irInvokeCallNode, Scope scope);
+    void visitInvokeCallMember(InvokeCallMemberNode irInvokeCallMemberNode, Scope scope);
+    void visitFlipArrayIndex(FlipArrayIndexNode irFlipArrayIndexNode, Scope scope);
+    void visitFlipCollectionIndex(FlipCollectionIndexNode irFlipCollectionIndexNode, Scope scope);
+    void visitFlipDefIndex(FlipDefIndexNode irFlipDefIndexNode, Scope scope);
+    void visitDup(DupNode irDupNode, Scope scope);
 }