diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/AssignmentNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/AssignmentNode.java
index 8841143e84d53..4499a9f1f490e 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/AssignmentNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/AssignmentNode.java
@@ -28,6 +28,7 @@
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class AssignmentNode extends BinaryNode {
 
@@ -113,7 +114,7 @@ public PainlessCast getBack() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         // For the case where the assignment represents a String concatenation
@@ -127,7 +128,8 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             catElementStackSize = methodWriter.writeNewStrings();
         }
 
-        getLeftNode().setup(classWriter, methodWriter, globals); // call the setup method on the lhs to prepare for a load/store operation
+        // call the setup method on the lhs to prepare for a load/store operation
+        getLeftNode().setup(classWriter, methodWriter, globals, scopeTable);
 
         if (cat) {
             // Handle the case where we are doing a compound assignment
@@ -135,10 +137,10 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
             methodWriter.writeDup(getLeftNode().accessElementCount(), catElementStackSize); // dup the top element and insert it
                                                                                             // before concat helper on stack
-            getLeftNode().load(classWriter, methodWriter, globals);             // read the current lhs's value
+            getLeftNode().load(classWriter, methodWriter, globals, scopeTable);             // read the current lhs's value
             methodWriter.writeAppendStrings(getLeftNode().getExpressionType()); // append the lhs's value using the StringBuilder
 
-            getRightNode().write(classWriter, methodWriter, globals); // write the bytecode for the rhs
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable); // write the bytecode for the rhs
 
             // check to see if the rhs has already done a concatenation
             if (getRightNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getRightNode()).getCat() == false) {
@@ -156,14 +158,14 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             }
 
             // store the lhs's value from the stack in its respective variable/field/array
-            getLeftNode().store(classWriter, methodWriter, globals);
+            getLeftNode().store(classWriter, methodWriter, globals, scopeTable);
         } else if (operation != null) {
             // Handle the case where we are doing a compound assignment that
             // does not represent a String concatenation.
 
             methodWriter.writeDup(getLeftNode().accessElementCount(), 0); // if necessary, dup the previous lhs's value
                                                                           // to be both loaded from and stored to
-            getLeftNode().load(classWriter, methodWriter, globals); // load the current lhs's value
+            getLeftNode().load(classWriter, methodWriter, globals, scopeTable); // load the current lhs's value
 
             if (read && post) {
                 // dup the value if the lhs is also read from and is a post increment
@@ -173,7 +175,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
             methodWriter.writeCast(there); // if necessary cast the current lhs's value
                                            // to the promotion type between the lhs and rhs types
-            getRightNode().write(classWriter, methodWriter, globals); // write the bytecode for the rhs
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable); // write the bytecode for the rhs
 
             // XXX: fix these types, but first we need def compound assignment tests.
             // its tricky here as there are possibly explicit casts, too.
@@ -194,11 +196,11 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             }
 
             // store the lhs's value from the stack in its respective variable/field/array
-            getLeftNode().store(classWriter, methodWriter, globals);
+            getLeftNode().store(classWriter, methodWriter, globals, scopeTable);
         } else {
             // Handle the case for a simple write.
 
-            getRightNode().write(classWriter, methodWriter, globals); // write the bytecode for the rhs rhs
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable); // write the bytecode for the rhs rhs
 
             if (read) {
                 // dup the value if the lhs is also read from
@@ -207,7 +209,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             }
 
             // store the lhs's value from the stack in its respective variable/field/array
-            getLeftNode().store(classWriter, methodWriter, globals);
+            getLeftNode().store(classWriter, methodWriter, globals, scopeTable);
         }
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java
index cff2f89c3a676..cddb42dcee6e0 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java
@@ -28,6 +28,7 @@
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -98,7 +99,7 @@ public void setLocation(Location location) {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (getBinaryType() == String.class && operation == Operation.ADD) {
@@ -106,13 +107,13 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
                 methodWriter.writeNewStrings();
             }
 
-            getLeftNode().write(classWriter, methodWriter, globals);
+            getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
 
             if (getLeftNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getLeftNode()).getCat() == false) {
                 methodWriter.writeAppendStrings(getLeftNode().getExpressionType());
             }
 
-            getRightNode().write(classWriter, methodWriter, globals);
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable);
 
             if (getRightNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getRightNode()).getCat() == false) {
                 methodWriter.writeAppendStrings(getRightNode().getExpressionType());
@@ -122,8 +123,8 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
                 methodWriter.writeToStrings();
             }
         } else if (operation == Operation.FIND || operation == Operation.MATCH) {
-            getRightNode().write(classWriter, methodWriter, globals);
-            getLeftNode().write(classWriter, methodWriter, globals);
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable);
+            getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.invokeVirtual(org.objectweb.asm.Type.getType(Pattern.class), WriterConstants.PATTERN_MATCHER);
 
             if (operation == Operation.FIND) {
@@ -135,8 +136,8 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
                         "for type [" + getExpressionCanonicalTypeName() + "]");
             }
         } else {
-            getLeftNode().write(classWriter, methodWriter, globals);
-            getRightNode().write(classWriter, methodWriter, globals);
+            getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable);
 
             if (binaryType == def.class || (shiftType != null && shiftType == def.class)) {
                 // def calls adopt the wanted return value. if there was a narrowing cast,
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java
index fdc0c3e32530c..65bc2adc118ab 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,11 +65,11 @@ public int getStatementCount() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         for (StatementNode statementNode : statementNodes) {
             statementNode.continueLabel = continueLabel;
             statementNode.breakLabel = breakLabel;
-            statementNode.write(classWriter, methodWriter, globals);
+            statementNode.write(classWriter, methodWriter, globals, scopeTable);
         }
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java
index 65057de255fe9..2d2ab280e38c8 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -43,16 +44,16 @@ public Operation getOperation() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (operation == Operation.AND) {
             Label fals = new Label();
             Label end = new Label();
 
-            getLeftNode().write(classWriter, methodWriter, globals);
+            getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
-            getRightNode().write(classWriter, methodWriter, globals);
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
             methodWriter.push(true);
@@ -65,9 +66,9 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             Label fals = new Label();
             Label end = new Label();
 
-            getLeftNode().write(classWriter, methodWriter, globals);
+            getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFNE, tru);
-            getRightNode().write(classWriter, methodWriter, globals);
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
             methodWriter.mark(tru);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceNode.java
index 4a3dea6778622..dbc6f7cd95779 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceNode.java
@@ -22,13 +22,14 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class BraceNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getLeftNode().write(classWriter, methodWriter, globals);
-        getRightNode().write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
+        getRightNode().write(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
@@ -37,18 +38,18 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getLeftNode().write(classWriter, methodWriter, globals);
-        getRightNode().setup(classWriter, methodWriter, globals);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
+        getRightNode().setup(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getRightNode().load(classWriter, methodWriter, globals);
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getRightNode().load(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getRightNode().store(classWriter, methodWriter, globals);
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getRightNode().store(classWriter, methodWriter, globals, scopeTable);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubDefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubDefNode.java
index 2be964e9967d2..ebb99785106cb 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubDefNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubDefNode.java
@@ -23,14 +23,15 @@
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 
 public class BraceSubDefNode extends UnaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        setup(classWriter, methodWriter, globals);
-        load(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        setup(classWriter, methodWriter, globals, scopeTable);
+        load(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
@@ -39,16 +40,16 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.dup();
-        getChildNode().write(classWriter, methodWriter, globals);
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
         Type methodType = Type.getMethodType(MethodWriter.getType(
                 getChildNode().getExpressionType()), Type.getType(Object.class), MethodWriter.getType(getChildNode().getExpressionType()));
         methodWriter.invokeDefCall("normalizeIndex", methodType, DefBootstrap.INDEX_NORMALIZE);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(MethodWriter.getType(
@@ -57,7 +58,7 @@ protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(Type.getType(void.class), Type.getType(Object.class),
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubNode.java
index 3c8483b7e6099..181b35c0598a0 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubNode.java
@@ -22,15 +22,16 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class BraceSubNode extends UnaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        setup(classWriter, methodWriter, globals);
-        load(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        setup(classWriter, methodWriter, globals, scopeTable);
+        load(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
@@ -39,8 +40,8 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getChildNode().write(classWriter, methodWriter, globals);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
 
         Label noFlip = new Label();
         methodWriter.dup();
@@ -53,13 +54,13 @@ protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.arrayLoad(MethodWriter.getType(getExpressionType()));
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.arrayStore(MethodWriter.getType(getExpressionType()));
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java
index 405bbb77305b6..61f5d2e507cf0 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java
@@ -22,11 +22,12 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class BreakNode extends StatementNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.goTo(breakLabel);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallNode.java
index b24adc0c1915f..91138a7c8506b 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallNode.java
@@ -22,12 +22,13 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class CallNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getLeftNode().write(classWriter, methodWriter, globals);
-        getRightNode().write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
+        getRightNode().write(classWriter, methodWriter, globals, scopeTable);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java
index b6fd079767653..5e9124d1eb3e3 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 
 import java.util.ArrayList;
@@ -72,11 +73,11 @@ public List<Class<?>> getTypeParameters() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         for (ExpressionNode argumentNode : getArgumentNodes()) {
-            argumentNode.write(classWriter, methodWriter, globals);
+            argumentNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
         // create method type from return value and arguments
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubNode.java
index 6770640d9c857..961973c2ade12 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class CallSubNode extends ArgumentsNode {
 
@@ -50,7 +51,7 @@ public Class<?> getBox() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (box.isPrimitive()) {
@@ -58,7 +59,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
         }
 
         for (ExpressionNode argumentNode : getArgumentNodes()) {
-            argumentNode.write(classWriter, methodWriter, globals);
+            argumentNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
         methodWriter.invokeMethodCall(method);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java
index d00cb17557440..549fafa665943 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java
@@ -23,8 +23,9 @@
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
@@ -33,7 +34,7 @@ public class CapturingFuncRefNode extends ExpressionNode {
     /* ---- begin node data ---- */
 
     private String name;
-    private Variable captured;
+    private String capturedName;
     private FunctionRef funcRef;
     private String pointer;
 
@@ -45,12 +46,12 @@ public String getName() {
         return name;
     }
 
-    public void setCaptured(Variable captured) {
-        this.captured = captured;
+    public void setCapturedName(String capturedName) {
+        this.capturedName = capturedName;
     }
 
-    public Variable getCaptured() {
-        return captured;
+    public String getCapturedName() {
+        return capturedName;
     }
 
     public void setFuncRef(FunctionRef funcRef) {
@@ -72,20 +73,21 @@ public String getPointer() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
+        Variable captured = scopeTable.getVariable(capturedName);
         if (pointer != null) {
             // dynamic interface: placeholder for run-time lookup
             methodWriter.push((String)null);
-            methodWriter.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
+            methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot());
         } else if (funcRef == null) {
             // typed interface, dynamic implementation
-            methodWriter.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
-            Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), MethodWriter.getType(captured.clazz));
+            methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot());
+            Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), captured.getAsmType());
             methodWriter.invokeDefCall(name, methodType, DefBootstrap.REFERENCE, getExpressionCanonicalTypeName());
         } else {
             // typed interface, typed implementation
-            methodWriter.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
+            methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot());
             methodWriter.invokeLambdaCall(funcRef);
         }
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java
index 75b1bd2c32be5..b6475c5049dde 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class CastNode extends UnaryNode {
 
@@ -41,8 +42,8 @@ public PainlessCast getCast() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getChildNode().write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.writeDebugInfo(location);
         methodWriter.writeCast(cast);
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java
index 3bf0717757619..224a285f4d328 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java
@@ -22,6 +22,8 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -55,23 +57,24 @@ public BlockNode getBlockNode() {
     Label exception = null;
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
+        declarationNode.write(classWriter, methodWriter, globals, scopeTable);
+        Variable variable = scopeTable.getVariable(declarationNode.getName());
+
         Label jump = new Label();
 
         methodWriter.mark(jump);
-        methodWriter.visitVarInsn(MethodWriter.getType(
-                declarationNode.getVariable().clazz).getOpcode(Opcodes.ISTORE),
-                declarationNode.getVariable().getSlot());
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
 
         if (blockNode != null) {
             blockNode.continueLabel = continueLabel;
             blockNode.breakLabel = breakLabel;
-            blockNode.write(classWriter, methodWriter, globals);
+            blockNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
-        methodWriter.visitTryCatchBlock(begin, end, jump, MethodWriter.getType(declarationNode.getVariable().clazz).getInternalName());
+        methodWriter.visitTryCatchBlock(begin, end, jump, variable.getAsmType().getInternalName());
 
         if (exception != null && (blockNode == null || blockNode.doAllEscape() == false)) {
             methodWriter.goTo(exception);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java
index 1959902afc977..d1899ae9bc767 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java
@@ -22,17 +22,18 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Constant;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.ScriptClassInfo;
 import org.elasticsearch.painless.WriterConstants;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.elasticsearch.painless.symbol.ScriptRoot;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
 import org.objectweb.asm.util.Printer;
 
 import java.lang.invoke.MethodType;
@@ -107,10 +108,8 @@ public List<StatementNode> getStatementsNodes() {
     private String sourceText;
     private Printer debugStream;
     private ScriptRoot scriptRoot;
-    private Locals mainMethod;
     private boolean doesMethodEscape;
     private final Set<String> extractedVariables = new HashSet<>();
-    private final List<org.objectweb.asm.commons.Method> getMethods = new ArrayList<>();
 
     public void setScriptClassInfo(ScriptClassInfo scriptClassInfo) {
         this.scriptClassInfo = scriptClassInfo;
@@ -152,14 +151,6 @@ public ScriptRoot getScriptRoot() {
         return scriptRoot;
     }
 
-    public void setMainMethod(Locals mainMethod) {
-        this.mainMethod = mainMethod;
-    }
-
-    public Locals getMainMethod() {
-        return mainMethod;
-    }
-
     public void setMethodEscape(boolean doesMethodEscape) {
         this.doesMethodEscape = doesMethodEscape;
     }
@@ -180,10 +171,6 @@ public Set<String> getExtractedVariables() {
         return extractedVariables;
     }
 
-    public List<org.objectweb.asm.commons.Method> getGetMethods() {
-        return getMethods;
-    }
-
     /* ---- end node data ---- */
 
     protected Globals globals;
@@ -276,17 +263,17 @@ public Map<String, Object> write() {
         // Write the method defined in the interface:
         MethodWriter executeMethod = classWriter.newMethodWriter(Opcodes.ACC_PUBLIC, scriptClassInfo.getExecuteMethod());
         executeMethod.visitCode();
-        write(classWriter, executeMethod, globals);
+        write(classWriter, executeMethod, globals, new ScopeTable());
         executeMethod.endMethod();
 
         // Write all fields:
         for (FieldNode fieldNode : fieldNodes) {
-            fieldNode.write(classWriter, null, null);
+            fieldNode.write(classWriter, null, null, null);
         }
 
         // Write all functions:
         for (FunctionNode functionNode : functionNodes) {
-            functionNode.write(classWriter, null, globals);
+            functionNode.write(classWriter, null, globals, new ScopeTable());
         }
 
         // Write the constants
@@ -335,7 +322,7 @@ public Map<String, Object> write() {
     }
 
     @Override
-    protected void write(org.elasticsearch.painless.ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         // We wrap the whole method in a few try/catches to handle and/or convert other exceptions to ScriptException
         Label startTry = new Label();
         Label endTry = new Label();
@@ -344,28 +331,41 @@ protected void write(org.elasticsearch.painless.ClassWriter classWriter, MethodW
         Label endCatch = new Label();
         methodWriter.mark(startTry);
 
+        scopeTable.defineInternalVariable(Object.class, "this");
+
+        // Method arguments
+        for (ScriptClassInfo.MethodArgument arg : scriptClassInfo.getExecuteArguments()) {
+            scopeTable.defineVariable(arg.getClazz(), arg.getName());
+        }
+
         if (scriptRoot.getCompilerSettings().getMaxLoopCounter() > 0) {
             // if there is infinite loop protection, we do this once:
             // int #loop = settings.getMaxLoopCounter()
 
-            Variable loop = mainMethod.getVariable(null, Locals.LOOP);
+            Variable loop = scopeTable.defineInternalVariable(int.class, "loop");
 
             methodWriter.push(scriptRoot.getCompilerSettings().getMaxLoopCounter());
             methodWriter.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
         }
 
-        for (org.objectweb.asm.commons.Method method : getMethods) {
+        for (int getMethodIndex = 0; getMethodIndex < scriptClassInfo.getGetMethods().size(); ++getMethodIndex) {
+            Method method = scriptClassInfo.getGetMethods().get(getMethodIndex);
+            Class<?> returnType = scriptClassInfo.getGetReturns().get(getMethodIndex);
+
             String name = method.getName().substring(3);
             name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
-            Variable variable = mainMethod.getVariable(null, name);
 
-            methodWriter.loadThis();
-            methodWriter.invokeVirtual(Type.getType(scriptClassInfo.getBaseClass()), method);
-            methodWriter.visitVarInsn(method.getReturnType().getOpcode(Opcodes.ISTORE), variable.getSlot());
+            if (extractedVariables.contains(name)) {
+                Variable variable = scopeTable.defineVariable(returnType, name);
+
+                methodWriter.loadThis();
+                methodWriter.invokeVirtual(Type.getType(scriptClassInfo.getBaseClass()), method);
+                methodWriter.visitVarInsn(method.getReturnType().getOpcode(Opcodes.ISTORE), variable.getSlot());
+            }
         }
 
         for (StatementNode statementNode : statementNodes) {
-            statementNode.write(classWriter, methodWriter, globals);
+            statementNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
         if (doesMethodEscape == false) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java
index 75b6136fbe294..f21a210953f7b 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java
@@ -26,6 +26,7 @@
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Type;
 
@@ -62,13 +63,13 @@ public String getComparisonCanonicalTypeName() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
-        getLeftNode().write(classWriter, methodWriter, globals);
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
 
         if (getRightNode() instanceof NullNode == false) {
-            getRightNode().write(classWriter, methodWriter, globals);
+            getRightNode().write(classWriter, methodWriter, globals, scopeTable);
         }
 
         Label jump = new Label();
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java
index f903cc297e2ff..f62ceb6848960 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -42,19 +43,19 @@ public ExpressionNode getConditionNode() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Label fals = new Label();
         Label end = new Label();
 
-        conditionNode.write(classWriter, methodWriter, globals);
+        conditionNode.write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
-        getLeftNode().write(classWriter, methodWriter, globals);
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.goTo(end);
         methodWriter.mark(fals);
-        getRightNode().write(classWriter, methodWriter, globals);
+        getRightNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.mark(end);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java
index 3728cc020d92f..46052bdf1eee3 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class ConstantNode extends ExpressionNode {
 
@@ -40,7 +41,7 @@ public Object getConstant() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         if      (constant instanceof String)    methodWriter.push((String)constant);
         else if (constant instanceof Double)    methodWriter.push((double)constant);
         else if (constant instanceof Float)     methodWriter.push((float)constant);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java
index 82eeeaec9015f..f62a4df7261d5 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java
@@ -22,11 +22,12 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class ContinueNode extends StatementNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.goTo(continueLabel);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java
index 1decb6cf91f4f..c490364448fbc 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -43,9 +44,9 @@ public List<DeclarationNode> getDeclarationsNodes() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         for (DeclarationNode declarationNode : declarationNodes) {
-            declarationNode.write(classWriter, methodWriter, globals);
+            declarationNode.write(classWriter, methodWriter, globals, scopeTable);
         }
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java
index 2339a74c5788a..2e9ad4dfc6071 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java
@@ -21,8 +21,10 @@
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Opcodes;
 
 public class DeclarationNode extends StatementNode {
@@ -41,24 +43,39 @@ public ExpressionNode getExpressionNode() {
 
     /* ---- end tree structure, begin node data ---- */
 
-    private Variable variable;
+    protected String name;
+    protected Class<?> declarationType;
 
-    public void setVariable(Variable variable) {
-        this.variable = variable;
+    public void setName(String name) {
+        this.name = name;
     }
 
-    public Variable getVariable() {
-        return variable;
+    public String getName() {
+        return name;
+    }
+
+    public void setDeclarationType(Class<?> declarationType) {
+        this.declarationType = declarationType;
+    }
+
+    public Class<?> getDeclarationType() {
+        return declarationType;
+    }
+
+    public String getDeclarationCanonicalTypeName() {
+        return PainlessLookupUtility.typeToCanonicalTypeName(declarationType);
     }
 
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
+        Variable variable = scopeTable.defineVariable(declarationType, name);
+
         if (expressionNode == null) {
-            Class<?> sort = variable.clazz;
+            Class<?> sort = variable.getType();
 
             if (sort == void.class || sort == boolean.class || sort == byte.class ||
                 sort == short.class || sort == char.class || sort == int.class) {
@@ -73,9 +90,9 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
                 methodWriter.visitInsn(Opcodes.ACONST_NULL);
             }
         } else {
-            expressionNode.write(classWriter, methodWriter, globals);
+            expressionNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
-        methodWriter.visitVarInsn(MethodWriter.getType(variable.clazz).getOpcode(Opcodes.ISTORE), variable.getSlot());
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java
index 2fd104666ab70..89964578e9285 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java
@@ -22,15 +22,19 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class DoWhileLoopNode extends LoopNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
+        scopeTable = scopeTable.newScope();
+
         Label start = new Label();
         Label begin = new Label();
         Label end = new Label();
@@ -39,17 +43,19 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
         getBlockNode().continueLabel = begin;
         getBlockNode().breakLabel = end;
-        getBlockNode().write(classWriter, methodWriter, globals);
+        getBlockNode().write(classWriter, methodWriter, globals, scopeTable);
 
         methodWriter.mark(begin);
 
         if (isContinuous() == false) {
-            getConditionNode().write(classWriter, methodWriter, globals);
+            getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, end);
         }
 
-        if (getLoopCounter() != null) {
-            methodWriter.writeLoopCounter(getLoopCounter().getSlot(), Math.max(1, getBlockNode().getStatementCount()), location);
+        Variable loop = scopeTable.getInternalVariable("loop");
+
+        if (loop != null) {
+            methodWriter.writeLoopCounter(loop.getSlot(), Math.max(1, getBlockNode().getStatementCount()), location);
         }
 
         methodWriter.goTo(start);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotNode.java
index 2ea11e5f4d906..c50fa3680d5a6 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotNode.java
@@ -22,13 +22,14 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class DotNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getLeftNode().write(classWriter, methodWriter, globals);
-        getRightNode().write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
+        getRightNode().write(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
@@ -36,19 +37,18 @@ protected int accessElementCount() {
         return getRightNode().accessElementCount();
     }
 
-    @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getLeftNode().write(classWriter, methodWriter, globals);
-        getRightNode().setup(classWriter, methodWriter, globals);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
+        getRightNode().setup(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getRightNode().load(classWriter, methodWriter, globals);
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getRightNode().load(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getRightNode().store(classWriter, methodWriter, globals);
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getRightNode().store(classWriter, methodWriter, globals, scopeTable);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubArrayLengthNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubArrayLengthNode.java
index 2f32a73d74b31..4687ee14c57af 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubArrayLengthNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubArrayLengthNode.java
@@ -22,11 +22,12 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class DotSubArrayLengthNode extends ExpressionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.arrayLength();
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubDefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubDefNode.java
index 8be070de26e02..1e4e7820bfd2f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubDefNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubDefNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 
 public class DotSubDefNode extends ExpressionNode {
@@ -42,7 +43,7 @@ public String getValue() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), Type.getType(Object.class));
@@ -55,12 +56,12 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         // do nothing
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), Type.getType(Object.class));
@@ -68,7 +69,7 @@ protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubNode.java
index 5dc188419c286..faab60a07ee1c 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessField;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 
 public class DotSubNode extends ExpressionNode {
@@ -42,7 +43,7 @@ public PainlessField getField() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) {
@@ -60,12 +61,12 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter,Globals globals) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         // Do nothing.
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter,Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) {
@@ -78,7 +79,7 @@ protected void load(ClassWriter classWriter, MethodWriter methodWriter,Globals g
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter,Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubShortcutNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubShortcutNode.java
index 412aef5b07d69..8552667e720e4 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubShortcutNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubShortcutNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class DotSubShortcutNode extends ExpressionNode {
 
@@ -50,7 +51,7 @@ public PainlessMethod getGetter() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.invokeMethodCall(getter);
@@ -66,12 +67,12 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         // do nothing
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.invokeMethodCall(getter);
@@ -82,7 +83,7 @@ protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.invokeMethodCall(setter);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java
index 43c5fbc725d73..c5ec6959410fa 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java
@@ -22,21 +22,22 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 
 public class ElvisNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Label end = new Label();
 
-        getLeftNode().write(classWriter, methodWriter, globals);
+        getLeftNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.dup();
         methodWriter.ifNonNull(end);
         methodWriter.pop();
-        getRightNode().write(classWriter, methodWriter, globals);
+        getRightNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.mark(end);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java
index b9a70eaec27aa..aa9ee2dcbb37d 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 
 public class FieldNode extends IRNode {
@@ -73,7 +74,7 @@ public Object getInstance() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         classWriter.getClassVisitor().visitField(
                 ClassWriter.buildAccess(modifiers, true), name, Type.getType(fieldType).getDescriptor(), null, null).visitEnd();
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java
index 76d5140a024b8..b1ff46d14c358 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class ForEachLoopNode extends StatementNode {
 
@@ -40,7 +41,8 @@ public ConditionNode getConditionNode() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        conditionNode.write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        scopeTable = scopeTable.newScope();
+        conditionNode.write(classWriter, methodWriter, globals, scopeTable);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java
index 9dcb01cb1094b..3197aae1a9ac8 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java
@@ -21,10 +21,11 @@
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -32,18 +33,33 @@ public class ForEachSubArrayNode extends LoopNode {
 
     /* ---- begin node data ---- */
 
-    private Variable variable;
+    private Class<?> variableType;
+    private String variableName;
     private PainlessCast cast;
-    private Variable array;
-    private Variable index;
+    private Class<?> arrayType;
+    private String arrayName;
+    private Class<?> indexType;
+    private String indexName;
     private Class<?> indexedType;
 
-    public void setVariable(Variable variable) {
-        this.variable = variable;
+    public void setVariableType(Class<?> variableType) {
+        this.variableType = variableType;
     }
 
-    public Variable getVariable() {
-        return variable;
+    public Class<?> getVariableType() {
+        return variableType;
+    }
+
+    public String getVariableCanonicalTypeName() {
+        return PainlessLookupUtility.typeToCanonicalTypeName(variableType);
+    }
+
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
+    public String getVariableName() {
+        return variableName;
     }
 
     public void setCast(PainlessCast cast) {
@@ -54,22 +70,46 @@ public PainlessCast getCast() {
         return cast;
     }
 
-    public void setArray(Variable array) {
-        this.array = array;
+    public void setArrayType(Class<?> arrayType) {
+        this.arrayType = arrayType;
+    }
+
+    public Class<?> getArrayType() {
+        return arrayType;
+    }
+
+    public String getArrayCanonicalTypeName() {
+        return PainlessLookupUtility.typeToCanonicalTypeName(arrayType);
+    }
+
+    public void setArrayName(String arrayName) {
+        this.arrayName = arrayName;
+    }
+
+    public String getArrayName() {
+        return arrayName;
     }
 
-    public Variable getArray() {
-        return array;
+    public void setIndexType(Class<?> indexType) {
+        this.indexType = indexType;
     }
 
-    public void setIndex(Variable index) {
-        this.index = index;
+    public Class<?> getIndexType() {
+        return indexType;
     }
 
-    public Variable getIndex() {
-        return index;
+    public String getIndexCanonicalTypeName() {
+        return PainlessLookupUtility.typeToCanonicalTypeName(indexType);
     }
-    
+
+    public void setIndexName(String indexName) {
+        this.indexName = indexName;
+    }
+
+    public String getIndexName() {
+        return indexName;
+    }
+
     public void setIndexedType(Class<?> indexedType) {
         this.indexedType = indexedType;
     }
@@ -81,17 +121,21 @@ public Class<?> getIndexedType() {
     public String getIndexedCanonicalTypeName() {
         return PainlessLookupUtility.typeToCanonicalTypeName(indexedType);
     }
-    
+
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
-        getConditionNode().write(classWriter, methodWriter, globals);
-        methodWriter.visitVarInsn(MethodWriter.getType(array.clazz).getOpcode(Opcodes.ISTORE), array.getSlot());
+        Variable variable = scopeTable.defineVariable(variableType, variableName);
+        Variable array = scopeTable.defineInternalVariable(arrayType, arrayName);
+        Variable index = scopeTable.defineInternalVariable(indexType, indexName);
+
+        getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
+        methodWriter.visitVarInsn(array.getAsmType().getOpcode(Opcodes.ISTORE), array.getSlot());
         methodWriter.push(-1);
-        methodWriter.visitVarInsn(MethodWriter.getType(index.clazz).getOpcode(Opcodes.ISTORE), index.getSlot());
+        methodWriter.visitVarInsn(index.getAsmType().getOpcode(Opcodes.ISTORE), index.getSlot());
 
         Label begin = new Label();
         Label end = new Label();
@@ -99,24 +143,26 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
         methodWriter.mark(begin);
 
         methodWriter.visitIincInsn(index.getSlot(), 1);
-        methodWriter.visitVarInsn(MethodWriter.getType(index.clazz).getOpcode(Opcodes.ILOAD), index.getSlot());
-        methodWriter.visitVarInsn(MethodWriter.getType(array.clazz).getOpcode(Opcodes.ILOAD), array.getSlot());
+        methodWriter.visitVarInsn(index.getAsmType().getOpcode(Opcodes.ILOAD), index.getSlot());
+        methodWriter.visitVarInsn(array.getAsmType().getOpcode(Opcodes.ILOAD), array.getSlot());
         methodWriter.arrayLength();
         methodWriter.ifICmp(MethodWriter.GE, end);
 
-        methodWriter.visitVarInsn(MethodWriter.getType(array.clazz).getOpcode(Opcodes.ILOAD), array.getSlot());
-        methodWriter.visitVarInsn(MethodWriter.getType(index.clazz).getOpcode(Opcodes.ILOAD), index.getSlot());
-        methodWriter.arrayLoad(MethodWriter.getType(getIndexedType()));
+        methodWriter.visitVarInsn(array.getAsmType().getOpcode(Opcodes.ILOAD), array.getSlot());
+        methodWriter.visitVarInsn(index.getAsmType().getOpcode(Opcodes.ILOAD), index.getSlot());
+        methodWriter.arrayLoad(MethodWriter.getType(indexedType));
         methodWriter.writeCast(cast);
-        methodWriter.visitVarInsn(MethodWriter.getType(variable.clazz).getOpcode(Opcodes.ISTORE), variable.getSlot());
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
+
+        Variable loop = scopeTable.getInternalVariable("loop");
 
-        if (getLoopCounter() != null) {
-            methodWriter.writeLoopCounter(getLoopCounter().getSlot(), getBlockNode().getStatementCount(), location);
+        if (loop != null) {
+            methodWriter.writeLoopCounter(loop.getSlot(), getBlockNode().getStatementCount(), location);
         }
 
         getBlockNode().continueLabel = begin;
         getBlockNode().breakLabel = end;
-        getBlockNode().write(classWriter, methodWriter, globals);
+        getBlockNode().write(classWriter, methodWriter, globals, scopeTable);
 
         methodWriter.goTo(begin);
         methodWriter.mark(end);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java
index c6c7fe63cbf18..bdb1af41c3a41 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java
@@ -22,10 +22,11 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -42,19 +43,29 @@ public class ForEachSubIterableNode extends LoopNode {
 
     /* ---- begin node data ---- */
 
-    private Variable variable;
+    private Class<?> variableType;
+    private String variableName;
     private PainlessCast cast;
-    private Variable iterator;
+    private Class<?> iteratorType;
+    private String iteratorName;
     private PainlessMethod method;
 
-    public void setVariable(Variable variable) {
-        this.variable = variable;
+    public void setVariableType(Class<?> variableType) {
+        this.variableType = variableType;
     }
 
-    public Variable getVariable() {
-        return variable;
+    public Class<?> getVariableType() {
+        return variableType;
     }
 
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
+    public String getVariableName() {
+        return variableName;
+    }
+    
     public void setCast(PainlessCast cast) {
         this.cast = cast;
     }
@@ -63,12 +74,20 @@ public PainlessCast getCast() {
         return cast;
     }
 
-    public void setIterator(Variable iterator) {
-        this.iterator = iterator;
+    public void setIteratorType(Class<?> iteratorType) {
+        this.iteratorType = iteratorType;
+    }
+
+    public Class<?> getIteratorType() {
+        return iteratorType;
     }
 
-    public Variable getIterator() {
-        return iterator;
+    public void setIteratorName(String iteratorName) {
+        this.iteratorName = iteratorName;
+    }
+
+    public String getIteratorName() {
+        return iteratorName;
     }
 
     public void setMethod(PainlessMethod method) {
@@ -82,10 +101,13 @@ public PainlessMethod getMethod() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
-        getConditionNode().write(classWriter, methodWriter, globals);
+        Variable variable = scopeTable.defineVariable(variableType, variableName);
+        Variable iterator = scopeTable.defineInternalVariable(iteratorType, iteratorName);
+
+        getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
 
         if (method == null) {
             org.objectweb.asm.Type methodType = org.objectweb.asm.Type
@@ -95,29 +117,31 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             methodWriter.invokeMethodCall(method);
         }
 
-        methodWriter.visitVarInsn(MethodWriter.getType(iterator.clazz).getOpcode(Opcodes.ISTORE), iterator.getSlot());
+        methodWriter.visitVarInsn(iterator.getAsmType().getOpcode(Opcodes.ISTORE), iterator.getSlot());
 
         Label begin = new Label();
         Label end = new Label();
 
         methodWriter.mark(begin);
 
-        methodWriter.visitVarInsn(MethodWriter.getType(iterator.clazz).getOpcode(Opcodes.ILOAD), iterator.getSlot());
+        methodWriter.visitVarInsn(iterator.getAsmType().getOpcode(Opcodes.ILOAD), iterator.getSlot());
         methodWriter.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
         methodWriter.ifZCmp(MethodWriter.EQ, end);
 
-        methodWriter.visitVarInsn(MethodWriter.getType(iterator.clazz).getOpcode(Opcodes.ILOAD), iterator.getSlot());
+        methodWriter.visitVarInsn(iterator.getAsmType().getOpcode(Opcodes.ILOAD), iterator.getSlot());
         methodWriter.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
         methodWriter.writeCast(cast);
-        methodWriter.visitVarInsn(MethodWriter.getType(variable.clazz).getOpcode(Opcodes.ISTORE), variable.getSlot());
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
+
+        Variable loop = scopeTable.getInternalVariable("loop");
 
-        if (getLoopCounter() != null) {
-            methodWriter.writeLoopCounter(getLoopCounter().getSlot(), getBlockNode().getStatementCount(), location);
+        if (loop != null) {
+            methodWriter.writeLoopCounter(loop.getSlot(), getBlockNode().getStatementCount(), location);
         }
 
         getBlockNode().continueLabel = begin;
         getBlockNode().breakLabel = end;
-        getBlockNode().write(classWriter, methodWriter, globals);
+        getBlockNode().write(classWriter, methodWriter, globals, scopeTable);
 
         methodWriter.goTo(begin);
         methodWriter.mark(end);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java
index df800919f88dc..a1cdabf5b6cbe 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java
@@ -22,6 +22,8 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -48,28 +50,29 @@ public ExpressionNode getAfterthoughtNode() {
         return afterthoughtNode;
     }
 
-    /* ---- end tree structure ---- */
-
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
+        scopeTable = scopeTable.newScope();
+
         Label start = new Label();
         Label begin = afterthoughtNode == null ? start : new Label();
         Label end = new Label();
 
         if (initializerNode instanceof DeclarationBlockNode) {
-            initializerNode.write(classWriter, methodWriter, globals);
+            initializerNode.write(classWriter, methodWriter, globals, scopeTable);
         } else if (initializerNode instanceof ExpressionNode) {
             ExpressionNode initializer = (ExpressionNode)this.initializerNode;
-            initializer.write(classWriter, methodWriter, globals);
+
+            initializer.write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.writePop(MethodWriter.getType(initializer.getExpressionType()).getSize());
         }
 
         methodWriter.mark(start);
 
         if (getConditionNode() != null && isContinuous() == false) {
-            getConditionNode().write(classWriter, methodWriter, globals);
+            getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, end);
         }
 
@@ -84,26 +87,30 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
                 ++statementCount;
             }
 
-            if (getLoopCounter() != null) {
-                methodWriter.writeLoopCounter(getLoopCounter().getSlot(), statementCount, location);
+            Variable loop = scopeTable.getInternalVariable("loop");
+
+            if (loop != null) {
+                methodWriter.writeLoopCounter(loop.getSlot(), statementCount, location);
             }
 
             getBlockNode().continueLabel = begin;
             getBlockNode().breakLabel = end;
-            getBlockNode().write(classWriter, methodWriter, globals);
+            getBlockNode().write(classWriter, methodWriter, globals, scopeTable);
         } else {
-            if (getLoopCounter() != null) {
-                methodWriter.writeLoopCounter(getLoopCounter().getSlot(), 1, location);
+            Variable loop = scopeTable.getInternalVariable("loop");
+
+            if (loop != null) {
+                methodWriter.writeLoopCounter(loop.getSlot(), 1, location);
             }
         }
 
         if (afterthoughtNode != null) {
             methodWriter.mark(begin);
-            afterthoughtNode.write(classWriter, methodWriter, globals);
+            afterthoughtNode.write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.writePop(MethodWriter.getType(afterthoughtNode.getExpressionType()).getSize());
         }
 
-        if (afterthoughtNode != null || !allEscape) {
+        if (afterthoughtNode != null || allEscape == false) {
             methodWriter.goTo(start);
         }
 
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java
index 8c249a3cba703..45e56c3921b67 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class FuncRefNode extends ExpressionNode {
 
@@ -41,7 +42,7 @@ public FunctionRef getFuncRef() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         if (funcRef != null) {
             methodWriter.writeDebugInfo(location);
             methodWriter.invokeLambdaCall(funcRef);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java
index 1262f767a86be..700da1df83a0b 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java
@@ -21,8 +21,9 @@
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
@@ -48,10 +49,10 @@ public BlockNode getBlockNode() {
 
     private String name;
     private Class<?> returnType;
-    private final List<Class<?>> typeParameters = new ArrayList<>();
+    private List<Class<?>> typeParameters = new ArrayList<>();
+    private List<String> parameterNames = new ArrayList<>();
     private boolean isSynthetic;
     private boolean doesMethodEscape;
-    private Variable loopCounter;
     private int maxLoopCounter;
 
     public void setName(String name) {
@@ -78,6 +79,14 @@ public List<Class<?>> getTypeParameters() {
         return typeParameters;
     }
 
+    public void addParameterName(String parameterName) {
+        parameterNames.add(parameterName);
+    }
+
+    public List<String> getParameterNames() {
+        return parameterNames;
+    }
+
     public void setSynthetic(boolean isSythetic) {
         this.isSynthetic = isSythetic;
     }
@@ -94,14 +103,6 @@ public boolean doesMethodEscape() {
         return doesMethodEscape;
     }
 
-    public void setLoopCounter(Variable loopCounter) {
-        this.loopCounter = loopCounter;
-    }
-
-    public Variable getLoopCounter() {
-        return loopCounter;
-    }
-
     public void setMaxLoopCounter(int maxLoopCounter) {
         this.maxLoopCounter = maxLoopCounter;
     }
@@ -113,7 +114,7 @@ public int getMaxLoopCounter() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
 
         if (isSynthetic) {
@@ -124,6 +125,9 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
         Type[] asmParameterTypes = new Type[typeParameters.size()];
 
         for (int index = 0; index < asmParameterTypes.length; ++index) {
+            Class<?> type = typeParameters.get(index);
+            String name = parameterNames.get(index);
+            scopeTable.defineVariable(type, name);
             asmParameterTypes[index] = MethodWriter.getType(typeParameters.get(index));
         }
 
@@ -135,13 +139,16 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
         if (maxLoopCounter > 0) {
             // if there is infinite loop protection, we do this once:
             // int #loop = settings.getMaxLoopCounter()
+
+            Variable loop = scopeTable.defineInternalVariable(int.class, "loop");
+
             methodWriter.push(maxLoopCounter);
-            methodWriter.visitVarInsn(Opcodes.ISTORE, loopCounter.getSlot());
+            methodWriter.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
         }
 
-        blockNode.write(classWriter, methodWriter, globals);
+        blockNode.write(classWriter, methodWriter, globals, scopeTable.newScope());
 
-        if (!doesMethodEscape) {
+        if (doesMethodEscape == false) {
             if (returnType == void.class) {
                 methodWriter.returnValue();
             } else {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java
index 7a955e17aff13..e8624c5a1699a 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public abstract class IRNode {
 
@@ -40,10 +41,23 @@ public Location getLocation() {
 
     /* end node data */
 
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {throw new UnsupportedOperationException();}
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        throw new UnsupportedOperationException();
+    }
+
+    protected int accessElementCount() {
+        throw new UnsupportedOperationException();
+    }
+
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        throw new UnsupportedOperationException();
+    }
+
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        throw new UnsupportedOperationException();
+    }
 
-    protected int accessElementCount() {throw new UnsupportedOperationException();}
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {throw new UnsupportedOperationException();}
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {throw new UnsupportedOperationException();}
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {throw new UnsupportedOperationException();}
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java
index 267dd5d737328..ede3c583884d9 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -42,18 +43,18 @@ public BlockNode getElseBlockNode() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
         Label fals = new Label();
         Label end = new Label();
 
-        getConditionNode().write(classWriter, methodWriter, globals);
+        getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
         getBlockNode().continueLabel = continueLabel;
         getBlockNode().breakLabel = breakLabel;
-        getBlockNode().write(classWriter, methodWriter, globals);
+        getBlockNode().write(classWriter, methodWriter, globals, scopeTable.newScope());
 
         if (getBlockNode().doAllEscape() == false) {
             methodWriter.goTo(end);
@@ -63,7 +64,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
         elseBlockNode.continueLabel = continueLabel;
         elseBlockNode.breakLabel = breakLabel;
-        elseBlockNode.write(classWriter, methodWriter, globals);
+        elseBlockNode.write(classWriter, methodWriter, globals, scopeTable.newScope());
 
         methodWriter.mark(end);
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java
index d151a87f66c34..05639fe177627 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java
@@ -22,23 +22,24 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class IfNode extends ConditionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
         Label fals = new Label();
 
-        getConditionNode().write(classWriter, methodWriter, globals);
+        getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
         getBlockNode().continueLabel = continueLabel;
         getBlockNode().breakLabel = breakLabel;
-        getBlockNode().write(classWriter, methodWriter, globals);
+        getBlockNode().write(classWriter, methodWriter, globals, scopeTable.newScope());
 
         methodWriter.mark(fals);
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java
index 7dc8c3c8c50fd..c825d3c941ecf 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 
 public class InstanceofNode extends UnaryNode {
@@ -68,8 +69,8 @@ public boolean isPrimitiveResult() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getChildNode().write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
 
         // primitive types
         if (isPrimitiveResult) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java
index 267df6c54e1f0..1a5a3e016d855 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java
@@ -22,8 +22,9 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Opcodes;
 
 import java.util.ArrayList;
@@ -33,14 +34,14 @@ public class LambdaNode extends ExpressionNode {
 
     /* ---- begin node data ---- */
 
-    private final List<Variable> captures = new ArrayList<>();
+    private final List<String> captures = new ArrayList<>();
     private FunctionRef funcRef;
 
-    public void addCapture(Variable capture) {
+    public void addCapture(String capture) {
         captures.add(capture);
     }
 
-    public List<Variable> getCaptures() {
+    public List<String> getCaptures() {
         return captures;
     }
 
@@ -55,14 +56,15 @@ public FunctionRef getFuncRef() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (funcRef != null) {
             methodWriter.writeDebugInfo(location);
             // load captures
-            for (Variable capture : captures) {
-                methodWriter.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
+            for (String capture : captures) {
+                Variable variable = scopeTable.getVariable(capture);
+                methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
             }
 
             methodWriter.invokeLambdaCall(funcRef);
@@ -70,8 +72,9 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             // placeholder
             methodWriter.push((String)null);
             // load captures
-            for (Variable capture : captures) {
-                methodWriter.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
+            for (String capture : captures) {
+                Variable variable = scopeTable.getVariable(capture);
+                methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
             }
         }
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java
index a0cefdc4bfed4..304beade56265 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java
@@ -24,6 +24,7 @@
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -53,7 +54,7 @@ public PainlessMethod getMethod() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.newInstance(MethodWriter.getType(getExpressionType()));
@@ -63,7 +64,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
         for (ExpressionNode argument : getArgumentNodes()) {
             methodWriter.dup();
-            argument.write(classWriter, methodWriter, globals);
+            argument.write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.invokeMethodCall(method);
             methodWriter.pop();
         }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListSubShortcutNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListSubShortcutNode.java
index f902fca25a9be..7b3602c9cc5e8 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListSubShortcutNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListSubShortcutNode.java
@@ -24,6 +24,7 @@
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -53,9 +54,9 @@ public PainlessMethod getGetter() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        setup(classWriter, methodWriter, globals);
-        load(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        setup(classWriter, methodWriter, globals, scopeTable);
+        load(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
@@ -64,8 +65,8 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getChildNode().write(classWriter, methodWriter, globals);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
 
         Label noFlip = new Label();
         methodWriter.dup();
@@ -78,7 +79,7 @@ protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(getter);
 
@@ -88,7 +89,7 @@ protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(setter);
         methodWriter.writePop(MethodWriter.getType(setter.returnType).getSize());
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoopNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoopNode.java
index dc3a546d7de05..7822917009bd9 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoopNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LoopNode.java
@@ -19,14 +19,11 @@
 
 package org.elasticsearch.painless.ir;
 
-import org.elasticsearch.painless.Locals.Variable;
-
 public abstract class LoopNode extends ConditionNode {
 
     /* ---- begin node data ---- */
 
     private boolean isContinuous;
-    private Variable loopCounter;
 
     public void setContinuous(boolean isContinuous) {
         this.isContinuous = isContinuous;
@@ -36,14 +33,6 @@ public boolean isContinuous() {
         return isContinuous;
     }
 
-    public void setLoopCounter(Variable loopCounter) {
-        this.loopCounter = loopCounter;
-    }
-
-    public Variable getLoopCounter() {
-        return loopCounter;
-    }
-
     /* ---- end node data ---- */
 
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java
index f3411f837c5d8..788f157a95419 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java
@@ -24,6 +24,7 @@
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -86,7 +87,7 @@ public PainlessMethod getMethod() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.newInstance(MethodWriter.getType(getExpressionType()));
@@ -96,8 +97,8 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
         for (int index = 0; index < getArgumentsSize(); ++index) {
             methodWriter.dup();
-            getKeyNode(index).write(classWriter, methodWriter, globals);
-            getValueNode(index).write(classWriter, methodWriter, globals);
+            getKeyNode(index).write(classWriter, methodWriter, globals, scopeTable);
+            getValueNode(index).write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.invokeMethodCall(method);
             methodWriter.pop();
         }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapSubShortcutNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapSubShortcutNode.java
index f151ca89b2fa0..f1e012099a57a 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapSubShortcutNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapSubShortcutNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class MapSubShortcutNode extends UnaryNode {
 
@@ -50,8 +51,8 @@ public PainlessMethod getGetter() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getChildNode().write(classWriter, methodWriter, globals);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
 
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(getter);
@@ -67,12 +68,12 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        getChildNode().write(classWriter, methodWriter, globals);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(getter);
 
@@ -82,7 +83,7 @@ protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(setter);
         methodWriter.writePop(MethodWriter.getType(setter.returnType).getSize());
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java
index 74cbe5c32ad41..7055b82fd03ff 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class NewArrayFuncRefNode extends ExpressionNode {
 
@@ -41,7 +42,7 @@ public FunctionRef getFuncRef() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         if (funcRef != null) {
             methodWriter.writeDebugInfo(location);
             methodWriter.invokeLambdaCall(funcRef);
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java
index d2f26efe4c0ca..c63b811b5990c 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class NewArrayNode extends ArgumentsNode {
 
@@ -40,7 +41,7 @@ public boolean getInitialize() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (initialize) {
@@ -52,12 +53,12 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
                 methodWriter.dup();
                 methodWriter.push(index);
-                argumentNode.write(classWriter, methodWriter, globals);
+                argumentNode.write(classWriter, methodWriter, globals, scopeTable);
                 methodWriter.arrayStore(MethodWriter.getType(getExpressionType().getComponentType()));
             }
         } else {
             for (ExpressionNode argumentNode : getArgumentNodes()) {
-                argumentNode.write(classWriter, methodWriter, globals);
+                argumentNode.write(classWriter, methodWriter, globals, scopeTable);
             }
 
             if (getArgumentNodes().size() > 1) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java
index f92bb753e0a63..cb40eb7cb33e5 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java
@@ -23,6 +23,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -52,7 +53,7 @@ public boolean getRead() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.newInstance(MethodWriter.getType(getExpressionType()));
@@ -62,7 +63,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
         }
 
         for (ExpressionNode argumentNode : getArgumentNodes()) {
-            argumentNode.write(classWriter, methodWriter, globals);
+            argumentNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
         methodWriter.invokeConstructor(
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java
index 91baae23ab28a..60e9d43fa1cab 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java
@@ -22,12 +22,13 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Opcodes;
 
 public class NullNode extends ExpressionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.visitInsn(Opcodes.ACONST_NULL);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java
index 61585b13e926d..c89c0700d8060 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java
@@ -22,18 +22,19 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 
 public class NullSafeSubNode extends UnaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         Label end = new Label();
         methodWriter.dup();
         methodWriter.ifNull(end);
-        getChildNode().write(classWriter, methodWriter, globals);
+        getChildNode().write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.mark(end);
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/RegexNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/RegexNode.java
index dc4f042332b83..988f2c4bcc9af 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/RegexNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/RegexNode.java
@@ -24,6 +24,7 @@
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.WriterConstants;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 import java.util.regex.Pattern;
 
@@ -62,7 +63,7 @@ public Object getConstant() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.getStatic(WriterConstants.CLASS_TYPE, constant.name, org.objectweb.asm.Type.getType(Pattern.class));
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java
index 8752b6fd915c9..5a0d2687bcd13 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class ReturnNode extends StatementNode {
 
@@ -40,11 +41,11 @@ public ExpressionNode getExpressionNode() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
         if (expressionNode != null) {
-            expressionNode.write(classWriter, methodWriter, globals);
+            expressionNode.write(classWriter, methodWriter, globals, scopeTable);
         }
 
         methodWriter.returnValue();
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java
index 95c2195f62ae4..6d045bdb7f8c0 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class StatementExpressionNode extends StatementNode {
 
@@ -52,9 +53,9 @@ public boolean getMethodEscape() {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
-        expressionNode.write(classWriter, methodWriter, globals);
+        expressionNode.write(classWriter, methodWriter, globals, scopeTable);
 
         if (methodEscape) {
             methodWriter.returnValue();
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java
index f144f4e601d7b..2b0409982f88f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java
@@ -22,11 +22,12 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class StaticNode extends ExpressionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         // do nothing
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java
index dec6cec871b43..77f5dffea8bcf 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 
 public class ThrowNode extends StatementNode {
 
@@ -40,9 +41,9 @@ public ExpressionNode getExpressionNode() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
-        expressionNode.write(classWriter, methodWriter, globals);
+        expressionNode.write(classWriter, methodWriter, globals, scopeTable);
         methodWriter.throwException();
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java
index 7a89a6fadb600..c621e1348ec4a 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java
@@ -22,6 +22,7 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 
 import java.util.ArrayList;
@@ -53,7 +54,7 @@ public List<CatchNode> getCatchsNodes() {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
         Label begin = new Label();
@@ -64,7 +65,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
 
         blockNode.continueLabel = continueLabel;
         blockNode.breakLabel = breakLabel;
-        blockNode.write(classWriter, methodWriter, globals);
+        blockNode.write(classWriter, methodWriter, globals, scopeTable.newScope());
 
         if (blockNode.doAllEscape() == false) {
             methodWriter.goTo(exception);
@@ -76,7 +77,7 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals
             catchNode.begin = begin;
             catchNode.end = end;
             catchNode.exception = catchNodes.size() > 1 ? exception : null;
-            catchNode.write(classWriter, methodWriter, globals);
+            catchNode.write(classWriter, methodWriter, globals, scopeTable.newScope());
         }
 
         if (blockNode.doAllEscape() == false || catchNodes.size() > 1) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java
index ea6d08579039c..66369d8e202f0 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java
@@ -26,6 +26,7 @@
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -78,14 +79,14 @@ public boolean getOriginallyExplicit() {
     /* ---- end node data ---- */
 
     @Override
-    public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (operation == Operation.NOT) {
             Label fals = new Label();
             Label end = new Label();
 
-            getChildNode().write(classWriter, methodWriter, globals);
+            getChildNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
             methodWriter.push(false);
@@ -94,7 +95,7 @@ public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals gl
             methodWriter.push(true);
             methodWriter.mark(end);
         } else {
-            getChildNode().write(classWriter, methodWriter, globals);
+            getChildNode().write(classWriter, methodWriter, globals, scopeTable);
 
             // Def calls adopt the wanted return value. If there was a narrowing cast,
             // we need to flag that so that it's done at runtime.
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnboundCallNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnboundCallNode.java
index 9c2323b27cb73..3044b71c9bd59 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnboundCallNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnboundCallNode.java
@@ -26,6 +26,7 @@
 import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.symbol.FunctionTable.LocalFunction;
+import org.elasticsearch.painless.symbol.ScopeTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
@@ -94,18 +95,18 @@ public String getBindingName() {
     /* ---- end node data ---- */
 
     @Override
-    public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeDebugInfo(location);
 
         if (localFunction != null) {
             for (ExpressionNode argumentNode : getArgumentNodes()) {
-                argumentNode.write(classWriter, methodWriter, globals);
+                argumentNode.write(classWriter, methodWriter, globals, scopeTable);
             }
 
             methodWriter.invokeStatic(CLASS_TYPE, localFunction.getAsmMethod());
         } else if (importedMethod != null) {
             for (ExpressionNode argumentNode : getArgumentNodes()) {
-                argumentNode.write(classWriter, methodWriter, globals);
+                argumentNode.write(classWriter, methodWriter, globals, scopeTable);
             }
 
             methodWriter.invokeStatic(Type.getType(importedMethod.targetClass),
@@ -128,7 +129,7 @@ public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals gl
             }
 
             for (int argument = 0; argument < javaConstructorParameterCount; ++argument) {
-                getArgumentNodes().get(argument).write(classWriter, methodWriter, globals);
+                getArgumentNodes().get(argument).write(classWriter, methodWriter, globals, scopeTable);
             }
 
             methodWriter.invokeConstructor(type, Method.getMethod(classBinding.javaConstructor));
@@ -139,7 +140,7 @@ public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals gl
             methodWriter.getField(CLASS_TYPE, bindingName, type);
 
             for (int argument = 0; argument < classBinding.javaMethod.getParameterCount(); ++argument) {
-                getArgumentNodes().get(argument + javaConstructorParameterCount).write(classWriter, methodWriter, globals);
+                getArgumentNodes().get(argument + javaConstructorParameterCount).write(classWriter, methodWriter, globals, scopeTable);
             }
 
             methodWriter.invokeVirtual(type, Method.getMethod(classBinding.javaMethod));
@@ -150,7 +151,7 @@ public void write(ClassWriter classWriter, MethodWriter methodWriter, Globals gl
             methodWriter.getStatic(CLASS_TYPE, bindingName, type);
 
             for (int argument = 0; argument < instanceBinding.javaMethod.getParameterCount(); ++argument) {
-                getArgumentNodes().get(argument).write(classWriter, methodWriter, globals);
+                getArgumentNodes().get(argument).write(classWriter, methodWriter, globals, scopeTable);
             }
 
             methodWriter.invokeVirtual(type, Method.getMethod(instanceBinding.javaMethod));
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/VariableNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/VariableNode.java
index 37af99a09c3a9..68f2f08e2e52f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/VariableNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/VariableNode.java
@@ -21,29 +21,31 @@
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
-import org.elasticsearch.painless.Locals.Variable;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Opcodes;
 
 public class VariableNode extends ExpressionNode {
 
     /* ---- begin node data ---- */
 
-    private Variable variable;
+    private String name;
 
-    public void setVariable(Variable variable) {
-        this.variable = variable;
+    public void setName(String name) {
+        this.name = name;
     }
 
-    public Variable getVariable() {
-        return variable;
+    public String getName() {
+        return name;
     }
 
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        methodWriter.visitVarInsn(MethodWriter.getType(variable.clazz).getOpcode(Opcodes.ILOAD), variable.getSlot());
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        Variable variable = scopeTable.getVariable(name);
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
     }
 
     @Override
@@ -52,17 +54,19 @@ protected int accessElementCount() {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         // do nothing
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        methodWriter.visitVarInsn(MethodWriter.getType(variable.clazz).getOpcode(Opcodes.ILOAD), variable.getSlot());
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        Variable variable = scopeTable.getVariable(name);
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
-        methodWriter.visitVarInsn(MethodWriter.getType(variable.clazz).getOpcode(Opcodes.ISTORE), variable.getSlot());
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
+        Variable variable = scopeTable.getVariable(name);
+        methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
     }
 }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileNode.java
index 6f918e66f37fb..3e5df7d8a2e04 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileNode.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileNode.java
@@ -22,36 +22,44 @@
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.ScopeTable.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class WhileNode extends LoopNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals, ScopeTable scopeTable) {
         methodWriter.writeStatementOffset(location);
 
+        scopeTable = scopeTable.newScope();
+
         Label begin = new Label();
         Label end = new Label();
 
         methodWriter.mark(begin);
 
         if (isContinuous() == false) {
-            getConditionNode().write(classWriter, methodWriter, globals);
+            getConditionNode().write(classWriter, methodWriter, globals, scopeTable);
             methodWriter.ifZCmp(Opcodes.IFEQ, end);
         }
 
         if (getBlockNode() != null) {
-            if (getLoopCounter() != null) {
-                methodWriter.writeLoopCounter(getLoopCounter().getSlot(), Math.max(1, getBlockNode().getStatementCount()), location);
+            Variable loop = scopeTable.getInternalVariable("loop");
+
+            if (loop != null) {
+                methodWriter.writeLoopCounter(loop.getSlot(), Math.max(1, getBlockNode().getStatementCount()), location);
             }
 
             getBlockNode().continueLabel = begin;
             getBlockNode().breakLabel = end;
-            getBlockNode().write(classWriter, methodWriter, globals);
+            getBlockNode().write(classWriter, methodWriter, globals, scopeTable);
         } else {
-            if (getLoopCounter() != null) {
-                methodWriter.writeLoopCounter(getLoopCounter().getSlot(), 1, location);
+            Variable loop = scopeTable.getInternalVariable("loop");
+
+            if (loop != null) {
+                methodWriter.writeLoopCounter(loop.getSlot(), 1, location);
             }
         }
 
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java
index ed098df80fd31..58eee1c923d54 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java
@@ -85,13 +85,12 @@ CapturingFuncRefNode write() {
 
         capturingFuncRefNode.setLocation(location);
         capturingFuncRefNode.setExpressionType(actual);
-        capturingFuncRefNode.setCaptured(captured);
+        capturingFuncRefNode.setCapturedName(captured.name);
         capturingFuncRefNode.setName(call);
         capturingFuncRefNode.setPointer(defPointer);
         capturingFuncRefNode.setFuncRef(ref);;
 
         return capturingFuncRefNode;
-
     }
 
     @Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java
index 01341dee87a1c..dad16eaee3de8 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java
@@ -191,7 +191,10 @@ LambdaNode write() {
         lambdaNode.setLocation(location);
         lambdaNode.setExpressionType(actual);
         lambdaNode.setFuncRef(ref);
-        lambdaNode.getCaptures().addAll(captures);
+
+        for (Variable capture : captures) {
+            lambdaNode.addCapture(capture.name);
+        }
 
         return lambdaNode;
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java
index 62661210a6242..f0018bbc5d46f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java
@@ -65,7 +65,7 @@ VariableNode write() {
 
         variableNode.setLocation(location);
         variableNode.setExpressionType(actual);
-        variableNode.setVariable(variable);
+        variableNode.setName(name);
 
         return variableNode;
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java
index f343f0d1b443d..f69e7a52839fb 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java
@@ -192,16 +192,12 @@ public ClassNode writeClass() {
         classNode.setDebugStream(debugStream);
         classNode.setName(name);
         classNode.setSourceText(sourceText);
-        classNode.setMainMethod(mainMethod);
         classNode.setMethodEscape(methodEscape);
-        classNode.getGetMethods().addAll(getMethods);
         classNode.getExtractedVariables().addAll(extractedVariables);
 
         return classNode;
     }
 
-
-
     @Override
     public String toString() {
         List<Object> subs = new ArrayList<>(functions.size() + statements.size());
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
index 877c1dbd04911..d82bb09d91204 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
@@ -33,7 +33,7 @@
  */
 public final class SDeclaration extends AStatement {
 
-    private final DType type;
+    private DType type;
     private final String name;
     private AExpression expression;
 
@@ -59,6 +59,7 @@ void extractVariables(Set<String> variables) {
     @Override
     void analyze(ScriptRoot scriptRoot, Locals locals) {
         DResolvedType resolvedType = type.resolveType(scriptRoot.getPainlessLookup());
+        type = resolvedType;
 
         if (expression != null) {
             expression.expected = resolvedType.getType();
@@ -76,7 +77,8 @@ DeclarationNode write() {
         declarationNode.setExpressionNode(expression == null ? null : expression.write());
 
         declarationNode.setLocation(location);
-        declarationNode.setVariable(variable);
+        declarationNode.setDeclarationType(((DResolvedType)type).getType());
+        declarationNode.setName(name);
 
         return declarationNode;
     }
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
index 41a9da5afbe75..1e6e9ea4ba71a 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
@@ -101,7 +101,6 @@ DoWhileLoopNode write() {
         doWhileLoopNode.setBlockNode(block.write());
 
         doWhileLoopNode.setLocation(location);
-        doWhileLoopNode.setLoopCounter(loopCounter);
         doWhileLoopNode.setContinuous(continuous);
 
         return doWhileLoopNode;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
index 8ed1f0b1481b7..4c159c4c2e671 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
@@ -160,7 +160,6 @@ ForLoopNode write() {
         forLoopNode.setBlockNode(block == null ? null : block.write());
 
         forLoopNode.setLocation(location);
-        forLoopNode.setLoopCounter(loopCounter);
         forLoopNode.setContinuous(continuous);
 
         return forLoopNode;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java
index c1f135a344a8b..2cd2e57ee687c 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java
@@ -150,9 +150,9 @@ FunctionNode writeFunction() {
         functionNode.setName(name);
         functionNode.setReturnType(returnType);
         functionNode.getTypeParameters().addAll(typeParameters);
+        functionNode.getParameterNames().addAll(paramNameStrs);
         functionNode.setSynthetic(synthetic);
         functionNode.setMethodEscape(methodEscape);
-        functionNode.setLoopCounter(loopCounter);
         functionNode.setMaxLoopCounter(maxLoopCounter);
 
         return functionNode;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java
index 33a33d8a1b702..bb280f5c02e13 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java
@@ -75,12 +75,14 @@ ForEachSubArrayNode write() {
         forEachSubArrayNode.setBlockNode(block.write());
 
         forEachSubArrayNode.setLocation(location);
-        forEachSubArrayNode.setVariable(variable);
+        forEachSubArrayNode.setVariableType(variable.clazz);
+        forEachSubArrayNode.setVariableName(variable.name);
         forEachSubArrayNode.setCast(cast);
-        forEachSubArrayNode.setArray(array);
-        forEachSubArrayNode.setIndex(index);
+        forEachSubArrayNode.setArrayType(array.clazz);
+        forEachSubArrayNode.setArrayName(array.name.substring(1));
+        forEachSubArrayNode.setIndexType(index.clazz);
+        forEachSubArrayNode.setIndexName(index.name.substring(1));
         forEachSubArrayNode.setIndexedType(indexed);
-        forEachSubArrayNode.setLoopCounter(loopCounter);
         forEachSubArrayNode.setContinuous(false);
 
         return forEachSubArrayNode;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java
index 5ed45207361f9..afa96e8058e93 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java
@@ -90,11 +90,12 @@ ForEachSubIterableNode write() {
         forEachSubIterableNode.setBlockNode(block.write());
 
         forEachSubIterableNode.setLocation(location);
-        forEachSubIterableNode.setVariable(variable);
+        forEachSubIterableNode.setVariableType(variable.clazz);
+        forEachSubIterableNode.setVariableName(variable.name);
         forEachSubIterableNode.setCast(cast);
-        forEachSubIterableNode.setIterator(iterator);
+        forEachSubIterableNode.setIteratorType(iterator.clazz);
+        forEachSubIterableNode.setIteratorName(iterator.name.substring(1));
         forEachSubIterableNode.setMethod(method);
-        forEachSubIterableNode.setLoopCounter(loopCounter);
         forEachSubIterableNode.setContinuous(false);
 
         return forEachSubIterableNode;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java
index 18e4894bb29bd..a05046191274f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java
@@ -105,7 +105,6 @@ WhileNode write() {
         whileNode.setBlockNode(block == null ? null : block.write());
 
         whileNode.setLocation(location);
-        whileNode.setLoopCounter(loopCounter);
         whileNode.setContinuous(continuous);
 
         return whileNode;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScopeTable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScopeTable.java
new file mode 100644
index 0000000000000..78648ad893b53
--- /dev/null
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScopeTable.java
@@ -0,0 +1,118 @@
+/*
+ * 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.symbol;
+
+import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.objectweb.asm.Type;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ScopeTable {
+
+    public static class Variable {
+        protected final Class<?> type;
+        protected final Type asmType;
+        protected final String name;
+        protected final int slot;
+
+        public Variable(Class<?> type, String name, int slot) {
+            this.type = type;
+            this.asmType = MethodWriter.getType(type);
+            this.name = name;
+            this.slot = slot;
+        }
+
+        public Class<?> getType() {
+            return type;
+        }
+
+        public String getCanonicalTypeName() {
+            return PainlessLookupUtility.typeToCanonicalTypeName(type);
+        }
+
+        public Type getAsmType() {
+            return asmType;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public int getSlot() {
+            return slot;
+        }
+    }
+
+    protected final ScopeTable parent;
+    protected final Map<String, Variable> variables = new HashMap<>();
+    protected int nextSlot;
+
+    public ScopeTable() {
+        this.parent = null;
+        this.nextSlot = 0;
+    }
+
+    protected ScopeTable(ScopeTable parent, int nextSlot) {
+        this.parent = parent;
+        this.nextSlot = nextSlot;
+    }
+
+    public ScopeTable newScope() {
+        return new ScopeTable(this, nextSlot);
+    }
+
+    public Variable defineVariable(Class<?> type, String name) {
+        Variable variable = new Variable(type, name, nextSlot);
+        nextSlot += variable.getAsmType().getSize();
+        variables.put(name, variable);
+
+        return variable;
+    }
+
+    /**
+     * Prepends the character '#' to the variable name. The '#' is
+     * reserved and ensures that these internal variables aren't
+     * accessed by a normal consumer.
+     */
+    public Variable defineInternalVariable(Class<?> type, String name) {
+        return defineVariable(type, "#" + name);
+    }
+
+    public Variable getVariable(String name) {
+        Variable variable = variables.get(name);
+
+        if (variable == null && parent != null) {
+            variable = parent.getVariable(name);
+        }
+
+        return variable;
+    }
+
+    /**
+     * Prepends the character '#' to the variable name. The '#' is
+     * reserved and ensures that these internal variables aren't
+     * accessed by a normal consumer.
+     */
+    public Variable getInternalVariable(String name) {
+        return getVariable("#" + name);
+    }
+}