Browse Source

Add a ScriptRoot to consolidate global data necessary for multiple passes (#47532)

This PR is to get plumbing in for a ScriptRoot class that will consolidate 
several pieces of state required by potentially multiple passes including 
PainlessLookup, CompilerSettings, FunctionTable, the root class node, and a 
synthetic counter. It's possible more may be added to this as we move 
forward and slowly make the the nodes have less mutable state.
Jack Conradson 6 years ago
parent
commit
2a527dbba4
69 changed files with 520 additions and 479 deletions
  1. 8 41
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java
  2. 75 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptRoot.java
  3. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java
  4. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java
  5. 14 15
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java
  6. 75 75
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java
  7. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java
  8. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java
  9. 11 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java
  10. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java
  11. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java
  12. 50 50
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java
  13. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java
  14. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java
  15. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java
  16. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EElvis.java
  17. 7 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java
  18. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java
  19. 5 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java
  20. 11 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java
  21. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java
  22. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java
  23. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java
  24. 9 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java
  25. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java
  26. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java
  27. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java
  28. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java
  29. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java
  30. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EString.java
  31. 18 18
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java
  32. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java
  33. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PBrace.java
  34. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java
  35. 10 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java
  36. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubArrayLength.java
  37. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java
  38. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java
  39. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java
  40. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java
  41. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefField.java
  42. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java
  43. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java
  44. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java
  45. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeCallInvoke.java
  46. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeField.java
  47. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java
  48. 3 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java
  49. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java
  50. 4 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java
  51. 11 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java
  52. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java
  53. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java
  54. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
  55. 5 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
  56. 7 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java
  57. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java
  58. 10 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
  59. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java
  60. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java
  61. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java
  62. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java
  63. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java
  64. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java
  65. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java
  66. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java
  67. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java
  68. 2 2
      modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java
  69. 1 1
      modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionTests.java

+ 8 - 41
modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java

@@ -20,7 +20,6 @@
 package org.elasticsearch.painless;
 
 import org.elasticsearch.painless.ScriptClassInfo.MethodArgument;
-import org.elasticsearch.painless.lookup.PainlessLookup;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 
 import java.util.HashMap;
@@ -35,19 +34,6 @@ import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJava
  * Tracks user defined variables across compilation phases.
  */
 public final class Locals {
-    private int syntheticCounter = 0;
-
-    /**
-     * Returns a unique identifier for generating the name of a synthetic method.
-     */
-    public String getNextSyntheticName() {
-        Locals locals = this;
-        while (locals.getParent() != null) {
-            locals = locals.getParent();
-        }
-
-        return "lambda$" + locals.syntheticCounter++;
-    }
 
     /** Reserved word: loop counter */
     public static final String LOOP   = "#loop";
@@ -59,9 +45,7 @@ public final class Locals {
 
     /** Creates a new local variable scope (e.g. loop) inside the current scope */
     public static Locals newLocalScope(Locals currentScope) {
-        Locals locals = new Locals(currentScope);
-
-        return locals;
+        return new Locals(currentScope);
     }
 
     /**
@@ -71,7 +55,7 @@ public final class Locals {
      */
     public static Locals newLambdaScope(Locals programScope, String name, Class<?> returnType, List<Parameter> parameters,
                                         int captureCount, int maxLoopCounter) {
-        Locals locals = new Locals(programScope, programScope.painlessLookup, programScope.baseClass, returnType, KEYWORDS);
+        Locals locals = new Locals(programScope, returnType, KEYWORDS);
         List<Class<?>> typeParameters = parameters.stream().map(parameter -> typeToJavaType(parameter.clazz)).collect(Collectors.toList());
         for (int i = 0; i < parameters.size(); i++) {
             Parameter parameter = parameters.get(i);
@@ -91,7 +75,7 @@ public final class Locals {
 
     /** Creates a new function scope inside the current scope */
     public static Locals newFunctionScope(Locals programScope, Class<?> returnType, List<Parameter> parameters, int maxLoopCounter) {
-        Locals locals = new Locals(programScope, programScope.painlessLookup, programScope.baseClass, returnType, KEYWORDS);
+        Locals locals = new Locals(programScope, returnType, KEYWORDS);
         for (Parameter parameter : parameters) {
             locals.addVariable(parameter.location, parameter.clazz, parameter.name, false);
         }
@@ -104,8 +88,7 @@ public final class Locals {
 
     /** Creates a new main method scope */
     public static Locals newMainMethodScope(ScriptClassInfo scriptClassInfo, Locals programScope, int maxLoopCounter) {
-        Locals locals = new Locals(programScope, programScope.painlessLookup,
-                scriptClassInfo.getBaseClass(), scriptClassInfo.getExecuteMethodReturnType(), KEYWORDS);
+        Locals locals = new Locals(programScope, scriptClassInfo.getExecuteMethodReturnType(), KEYWORDS);
         // This reference. Internal use only.
         locals.defineVariable(null, Object.class, THIS, true);
 
@@ -122,8 +105,8 @@ public final class Locals {
     }
 
     /** Creates a new program scope as the root of all scopes */
-    public static Locals newProgramScope(ScriptClassInfo scriptClassInfo, PainlessLookup painlessLookup) {
-        return new Locals(null, painlessLookup, scriptClassInfo.getBaseClass(), null, null);
+    public static Locals newProgramScope() {
+        return new Locals(null, null, null);
     }
 
     /** Checks if a variable exists or not, in this scope or any parents. */
@@ -175,22 +158,8 @@ public final class Locals {
         return locals;
     }
 
-    /** Whitelist against which this script is being compiled. */
-    public PainlessLookup getPainlessLookup() {
-        return painlessLookup;
-    }
-
-    /** Base class for the compiled script. */
-    public Class<?> getBaseClass() {
-        return baseClass;
-    }
-
     ///// private impl
 
-    /** Whitelist against which this script is being compiled. */
-    private final PainlessLookup painlessLookup;
-    /** Base class for the compiled script. */
-    private final Class<?> baseClass;
     // parent scope
     private final Locals parent;
     // return type of this scope
@@ -206,16 +175,14 @@ public final class Locals {
      * Create a new Locals
      */
     private Locals(Locals parent) {
-        this(parent, parent.painlessLookup, parent.baseClass, parent.returnType, parent.keywords);
+        this(parent, parent.returnType, parent.keywords);
     }
 
     /**
      * Create a new Locals with specified return type
      */
-    private Locals(Locals parent, PainlessLookup painlessLookup, Class<?> baseClass, Class<?> returnType, Set<String> keywords) {
+    private Locals(Locals parent, Class<?> returnType, Set<String> keywords) {
         this.parent = parent;
-        this.painlessLookup = painlessLookup;
-        this.baseClass = baseClass;
         this.returnType = returnType;
         this.keywords = keywords;
         if (parent == null) {

+ 75 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptRoot.java

@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import org.elasticsearch.painless.lookup.PainlessLookup;
+import org.elasticsearch.painless.node.SClass;
+import org.elasticsearch.painless.symbol.FunctionTable;
+
+import java.util.Objects;
+
+/**
+ * Stores information for use across the entirety of compilation.
+ */
+public class ScriptRoot {
+
+    protected final PainlessLookup painlessLookup;
+    protected final CompilerSettings compilerSettings;
+    protected final ScriptClassInfo scriptClassInfo;
+
+    protected final SClass classNode;
+
+    protected final FunctionTable functionTable = new FunctionTable();
+    protected int syntheticCounter = 0;
+
+    public ScriptRoot(PainlessLookup painlessLookup, CompilerSettings compilerSettings, ScriptClassInfo scriptClassInfo, SClass classRoot) {
+        this.painlessLookup = Objects.requireNonNull(painlessLookup);
+        this.compilerSettings = Objects.requireNonNull(compilerSettings);
+        this.scriptClassInfo = Objects.requireNonNull(scriptClassInfo);
+        this.classNode = Objects.requireNonNull(classRoot);
+    }
+
+    public PainlessLookup getPainlessLookup() {
+        return painlessLookup;
+    }
+
+    public CompilerSettings getCompilerSettings() {
+        return compilerSettings;
+    }
+
+    public ScriptClassInfo getScriptClassInfo() {
+        return scriptClassInfo;
+    }
+
+    public SClass getClassNode() {
+        return classNode;
+    }
+
+    public FunctionTable getFunctionTable() {
+        return functionTable;
+    }
+
+    /**
+     * Returns a unique identifier for generating the name of a synthetic value.
+     */
+    public String getNextSyntheticName(String prefix) {
+        return prefix + "$synthetic$" + syntheticCounter++;
+    }
+}

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java

@@ -24,7 +24,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 
@@ -118,7 +118,7 @@ public abstract class AExpression extends ANode {
      * nodes with the constant variable set to a non-null value with {@link EConstant}.
      * @return The new child node for the parent node calling this method.
      */
-    AExpression cast(FunctionTable functions, Locals locals) {
+    AExpression cast(ScriptRoot scriptRoot, Locals locals) {
         PainlessCast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
 
         if (cast == null) {
@@ -136,7 +136,7 @@ public abstract class AExpression extends ANode {
                 // will already be the same.
 
                 EConstant econstant = new EConstant(location, constant);
-                econstant.analyze(functions, locals);
+                econstant.analyze(scriptRoot, locals);
 
                 if (!expected.equals(econstant.actual)) {
                     throw createError(new IllegalStateException("Illegal tree structure."));
@@ -170,7 +170,7 @@ public abstract class AExpression extends ANode {
                     constant = AnalyzerCaster.constCast(location, constant, cast);
 
                     EConstant econstant = new EConstant(location, constant);
-                    econstant.analyze(functions, locals);
+                    econstant.analyze(scriptRoot, locals);
 
                     if (!expected.equals(econstant.actual)) {
                         throw createError(new IllegalStateException("Illegal tree structure."));
@@ -201,7 +201,7 @@ public abstract class AExpression extends ANode {
                     // the EConstant will already be the same.
 
                     EConstant econstant = new EConstant(location, constant);
-                    econstant.analyze(functions, locals);
+                    econstant.analyze(scriptRoot, locals);
 
                     if (!actual.equals(econstant.actual)) {
                         throw createError(new IllegalStateException("Illegal tree structure."));

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -72,7 +72,7 @@ public abstract class ANode {
     /**
      * Checks for errors and collects data for the writing phase.
      */
-    abstract void analyze(FunctionTable functions, Locals locals);
+    abstract void analyze(ScriptRoot scriptRoot, Locals locals);
 
     /**
      * Writes ASM based on the data collected during the analysis phase.

+ 14 - 15
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java

@@ -31,7 +31,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -84,26 +84,26 @@ public final class EAssignment extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        analyzeLHS(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        analyzeLHS(scriptRoot, locals);
         analyzeIncrDecr();
 
         if (operation != null) {
-            analyzeCompound(functions, locals);
+            analyzeCompound(scriptRoot, locals);
         } else if (rhs != null) {
-            analyzeSimple(functions, locals);
+            analyzeSimple(scriptRoot, locals);
         } else {
             throw new IllegalStateException("Illegal tree structure.");
         }
     }
 
-    private void analyzeLHS(FunctionTable functions, Locals locals) {
+    private void analyzeLHS(ScriptRoot scriptRoot, Locals locals) {
         if (lhs instanceof AStoreable) {
             AStoreable lhs = (AStoreable)this.lhs;
 
             lhs.read = read;
             lhs.write = true;
-            lhs.analyze(functions, locals);
+            lhs.analyze(scriptRoot, locals);
         } else {
             throw new IllegalArgumentException("Left-hand side cannot be assigned a value.");
         }
@@ -147,9 +147,8 @@ public final class EAssignment extends AExpression {
         }
     }
 
-    private void analyzeCompound(FunctionTable functions, Locals locals) {
-        rhs.analyze(functions, locals);
-
+    private void analyzeCompound(ScriptRoot scriptRoot, Locals locals) {
+        rhs.analyze(scriptRoot, locals);
         boolean shift = false;
 
         if (operation == Operation.MUL) {
@@ -211,7 +210,7 @@ public final class EAssignment extends AExpression {
             rhs.expected = promote;
         }
 
-        rhs = rhs.cast(functions, locals);
+        rhs = rhs.cast(scriptRoot, locals);
 
         there = AnalyzerCaster.getLegalCast(location, lhs.actual, promote, false, false);
         back = AnalyzerCaster.getLegalCast(location, promote, lhs.actual, true, false);
@@ -220,12 +219,12 @@ public final class EAssignment extends AExpression {
         this.actual = read ? lhs.actual : void.class;
     }
 
-    private void analyzeSimple(FunctionTable functions, Locals locals) {
+    private void analyzeSimple(ScriptRoot scriptRoot, Locals locals) {
         AStoreable lhs = (AStoreable)this.lhs;
 
         // If the lhs node is a def optimized node we update the actual type to remove the need for a cast.
         if (lhs.isDefOptimized()) {
-            rhs.analyze(functions, locals);
+            rhs.analyze(scriptRoot, locals);
 
             if (rhs.actual == void.class) {
                 throw createError(new IllegalArgumentException("Right-hand side cannot be a [void] type for assignment."));
@@ -236,10 +235,10 @@ public final class EAssignment extends AExpression {
         // Otherwise, we must adapt the rhs type to the lhs type with a cast.
         } else {
             rhs.expected = lhs.actual;
-            rhs.analyze(functions, locals);
+            rhs.analyze(scriptRoot, locals);
         }
 
-        rhs = rhs.cast(functions, locals);
+        rhs = rhs.cast(scriptRoot, locals);
 
         this.statement = true;
         this.actual = read ? lhs.actual : void.class;

+ 75 - 75
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java

@@ -31,7 +31,7 @@ import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -73,43 +73,43 @@ public final class EBinary extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         originallyExplicit = explicit;
 
         if (operation == Operation.MUL) {
-            analyzeMul(functions, locals);
+            analyzeMul(scriptRoot, locals);
         } else if (operation == Operation.DIV) {
-            analyzeDiv(functions, locals);
+            analyzeDiv(scriptRoot, locals);
         } else if (operation == Operation.REM) {
-            analyzeRem(functions, locals);
+            analyzeRem(scriptRoot, locals);
         } else if (operation == Operation.ADD) {
-            analyzeAdd(functions, locals);
+            analyzeAdd(scriptRoot, locals);
         } else if (operation == Operation.SUB) {
-            analyzeSub(functions, locals);
+            analyzeSub(scriptRoot, locals);
         } else if (operation == Operation.FIND) {
-            analyzeRegexOp(functions, locals);
+            analyzeRegexOp(scriptRoot, locals);
         } else if (operation == Operation.MATCH) {
-            analyzeRegexOp(functions, locals);
+            analyzeRegexOp(scriptRoot, locals);
         } else if (operation == Operation.LSH) {
-            analyzeLSH(functions, locals);
+            analyzeLSH(scriptRoot, locals);
         } else if (operation == Operation.RSH) {
-            analyzeRSH(functions, locals);
+            analyzeRSH(scriptRoot, locals);
         } else if (operation == Operation.USH) {
-            analyzeUSH(functions, locals);
+            analyzeUSH(scriptRoot, locals);
         } else if (operation == Operation.BWAND) {
-            analyzeBWAnd(functions, locals);
+            analyzeBWAnd(scriptRoot, locals);
         } else if (operation == Operation.XOR) {
-            analyzeXor(functions, locals);
+            analyzeXor(scriptRoot, locals);
         } else if (operation == Operation.BWOR) {
-            analyzeBWOr(functions, locals);
+            analyzeBWOr(scriptRoot, locals);
         } else {
             throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
-    private void analyzeMul(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeMul(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -132,8 +132,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -150,9 +150,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeDiv(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeDiv(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -176,8 +176,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             try {
@@ -198,9 +198,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeRem(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeRem(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -224,8 +224,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             try {
@@ -246,9 +246,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeAdd(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeAdd(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteAdd(left.actual, right.actual);
 
@@ -284,8 +284,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -305,9 +305,9 @@ public final class EBinary extends AExpression {
 
     }
 
-    private void analyzeSub(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeSub(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -331,8 +331,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -349,23 +349,23 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeRegexOp(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeRegexOp(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         left.expected = String.class;
         right.expected = Pattern.class;
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         promote = boolean.class;
         actual = boolean.class;
     }
 
-    private void analyzeLSH(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeLSH(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         Class<?> lhspromote = AnalyzerCaster.promoteNumeric(left.actual, false);
         Class<?> rhspromote = AnalyzerCaster.promoteNumeric(right.actual, false);
@@ -397,8 +397,8 @@ public final class EBinary extends AExpression {
             }
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -411,9 +411,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeRSH(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeRSH(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         Class<?> lhspromote = AnalyzerCaster.promoteNumeric(left.actual, false);
         Class<?> rhspromote = AnalyzerCaster.promoteNumeric(right.actual, false);
@@ -445,8 +445,8 @@ public final class EBinary extends AExpression {
             }
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -459,9 +459,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeUSH(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeUSH(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         Class<?> lhspromote = AnalyzerCaster.promoteNumeric(left.actual, false);
         Class<?> rhspromote = AnalyzerCaster.promoteNumeric(right.actual, false);
@@ -493,8 +493,8 @@ public final class EBinary extends AExpression {
             }
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -507,9 +507,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeBWAnd(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeBWAnd(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, false);
 
@@ -533,8 +533,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {
@@ -547,9 +547,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeXor(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeXor(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteXor(left.actual, right.actual);
 
@@ -572,8 +572,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == boolean.class) {
@@ -588,9 +588,9 @@ public final class EBinary extends AExpression {
         }
     }
 
-    private void analyzeBWOr(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeBWOr(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, false);
 
@@ -613,8 +613,8 @@ public final class EBinary extends AExpression {
             right.expected = promote;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promote == int.class) {

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

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -63,14 +63,14 @@ public final class EBool extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         left.expected = boolean.class;
-        left.analyze(functions, locals);
-        left = left.cast(functions, locals);
+        left.analyze(scriptRoot, locals);
+        left = left.cast(scriptRoot, locals);
 
         right.expected = boolean.class;
-        right.analyze(functions, locals);
-        right = right.cast(functions, locals);
+        right.analyze(scriptRoot, locals);
+        right = right.cast(scriptRoot, locals);
 
         if (left.constant != null && right.constant != null) {
             if (operation == Operation.AND) {

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 
@@ -51,7 +51,7 @@ public final class EBoolean extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from constant [" + constant + "]."));
         }

+ 11 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java

@@ -28,6 +28,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessClassBinding;
 import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.ScriptRoot;
 import org.elasticsearch.painless.symbol.FunctionTable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Type;
@@ -76,8 +77,8 @@ public final class ECallLocal extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        localFunction = functions.getFunction(name, arguments.size());
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        localFunction = scriptRoot.getFunctionTable().getFunction(name, arguments.size());
 
         // user cannot call internal functions, reset to null if an internal function is found
         if (localFunction != null && localFunction.isInternal()) {
@@ -85,14 +86,14 @@ public final class ECallLocal extends AExpression {
         }
 
         if (localFunction == null) {
-            importedMethod = locals.getPainlessLookup().lookupImportedPainlessMethod(name, arguments.size());
+            importedMethod = scriptRoot.getPainlessLookup().lookupImportedPainlessMethod(name, arguments.size());
 
             if (importedMethod == null) {
-                classBinding = locals.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size());
+                classBinding = scriptRoot.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size());
 
                 // check to see if this class binding requires an implicit this reference
                 if (classBinding != null && classBinding.typeParameters.isEmpty() == false &&
-                        classBinding.typeParameters.get(0) == locals.getBaseClass()) {
+                        classBinding.typeParameters.get(0) == scriptRoot.getScriptClassInfo().getBaseClass()) {
                     classBinding = null;
                 }
 
@@ -103,11 +104,11 @@ public final class ECallLocal extends AExpression {
                     // will likely involve adding a class instance binding where any instance can have a class binding
                     // as part of its API.  However, the situation at run-time is difficult and will modifications that
                     // are a substantial change if even possible to do.
-                    classBinding = locals.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size() + 1);
+                    classBinding = scriptRoot.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size() + 1);
 
                     if (classBinding != null) {
                         if (classBinding.typeParameters.isEmpty() == false &&
-                                classBinding.typeParameters.get(0) == locals.getBaseClass()) {
+                                classBinding.typeParameters.get(0) == scriptRoot.getScriptClassInfo().getBaseClass()) {
                             classBindingOffset = 1;
                         } else {
                             classBinding = null;
@@ -115,7 +116,7 @@ public final class ECallLocal extends AExpression {
                     }
 
                     if (classBinding == null) {
-                        instanceBinding = locals.getPainlessLookup().lookupPainlessInstanceBinding(name, arguments.size());
+                        instanceBinding = scriptRoot.getPainlessLookup().lookupPainlessInstanceBinding(name, arguments.size());
 
                         if (instanceBinding == null) {
                             throw createError(new IllegalArgumentException(
@@ -152,8 +153,8 @@ public final class ECallLocal extends AExpression {
 
             expression.expected = typeParameters.get(argument + classBindingOffset);
             expression.internal = true;
-            expression.analyze(functions, locals);
-            arguments.set(argument, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            arguments.set(argument, expression.cast(scriptRoot, locals));
         }
 
         statement = true;

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java

@@ -30,7 +30,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
@@ -66,7 +66,7 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         captured = locals.getVariable(location, variable);
         if (expected == null) {
             if (captured.clazz == def.class) {
@@ -81,7 +81,7 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
             defPointer = null;
             // static case
             if (captured.clazz != def.class) {
-                ref = FunctionRef.create(locals.getPainlessLookup(), functions, location,
+                ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location,
                         expected, PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
             }
             actual = expected;

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -58,7 +58,7 @@ final class ECast extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         throw createError(new IllegalStateException("Illegal tree structure."));
     }
 

+ 50 - 50
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java

@@ -30,7 +30,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Type;
 
@@ -72,31 +72,31 @@ public final class EComp extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (operation == Operation.EQ) {
-            analyzeEq(functions, locals);
+            analyzeEq(scriptRoot, locals);
         } else if (operation == Operation.EQR) {
-            analyzeEqR(functions, locals);
+            analyzeEqR(scriptRoot, locals);
         } else if (operation == Operation.NE) {
-            analyzeNE(functions, locals);
+            analyzeNE(scriptRoot, locals);
         } else if (operation == Operation.NER) {
-            analyzeNER(functions, locals);
+            analyzeNER(scriptRoot, locals);
         } else if (operation == Operation.GTE) {
-            analyzeGTE(functions, locals);
+            analyzeGTE(scriptRoot, locals);
         } else if (operation == Operation.GT) {
-            analyzeGT(functions, locals);
+            analyzeGT(scriptRoot, locals);
         } else if (operation == Operation.LTE) {
-            analyzeLTE(functions, locals);
+            analyzeLTE(scriptRoot, locals);
         } else if (operation == Operation.LT) {
-            analyzeLT(functions, locals);
+            analyzeLT(scriptRoot, locals);
         } else {
             throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
-    private void analyzeEq(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeEq(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
@@ -114,8 +114,8 @@ public final class EComp extends AExpression {
             right.expected = promotedType;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.isNull && right.isNull) {
             throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
@@ -144,9 +144,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeEqR(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeEqR(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
@@ -159,8 +159,8 @@ public final class EComp extends AExpression {
         left.expected = promotedType;
         right.expected = promotedType;
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.isNull && right.isNull) {
             throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
@@ -185,9 +185,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeNE(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeNE(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
@@ -205,8 +205,8 @@ public final class EComp extends AExpression {
             right.expected = promotedType;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.isNull && right.isNull) {
             throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
@@ -235,9 +235,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeNER(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeNER(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
@@ -250,8 +250,8 @@ public final class EComp extends AExpression {
         left.expected = promotedType;
         right.expected = promotedType;
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.isNull && right.isNull) {
             throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
@@ -276,9 +276,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeGTE(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeGTE(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -296,8 +296,8 @@ public final class EComp extends AExpression {
             right.expected = promotedType;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promotedType == int.class) {
@@ -316,9 +316,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeGT(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeGT(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -336,8 +336,8 @@ public final class EComp extends AExpression {
             right.expected = promotedType;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promotedType == int.class) {
@@ -356,9 +356,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeLTE(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeLTE(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -376,8 +376,8 @@ public final class EComp extends AExpression {
             right.expected = promotedType;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promotedType == int.class) {
@@ -396,9 +396,9 @@ public final class EComp extends AExpression {
         actual = boolean.class;
     }
 
-    private void analyzeLT(FunctionTable functions, Locals variables) {
-        left.analyze(functions, variables);
-        right.analyze(functions, variables);
+    private void analyzeLT(ScriptRoot scriptRoot, Locals variables) {
+        left.analyze(scriptRoot, variables);
+        right.analyze(scriptRoot, variables);
 
         promotedType = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
@@ -416,8 +416,8 @@ public final class EComp extends AExpression {
             right.expected = promotedType;
         }
 
-        left = left.cast(functions, variables);
-        right = right.cast(functions, variables);
+        left = left.cast(scriptRoot, variables);
+        right = right.cast(scriptRoot, variables);
 
         if (left.constant != null && right.constant != null) {
             if (promotedType == int.class) {

+ 8 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -65,10 +65,10 @@ public final class EConditional extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         condition.expected = boolean.class;
-        condition.analyze(functions, locals);
-        condition = condition.cast(functions, locals);
+        condition.analyze(scriptRoot, locals);
+        condition = condition.cast(scriptRoot, locals);
 
         if (condition.constant != null) {
             throw createError(new IllegalArgumentException("Extraneous conditional statement."));
@@ -82,8 +82,8 @@ public final class EConditional extends AExpression {
         right.internal = internal;
         actual = expected;
 
-        left.analyze(functions, locals);
-        right.analyze(functions, locals);
+        left.analyze(scriptRoot, locals);
+        right.analyze(scriptRoot, locals);
 
         if (expected == null) {
             Class<?> promote = AnalyzerCaster.promoteConditional(left.actual, right.actual, left.constant, right.constant);
@@ -93,8 +93,8 @@ public final class EConditional extends AExpression {
             actual = promote;
         }
 
-        left = left.cast(functions, locals);
-        right = right.cast(functions, locals);
+        left = left.cast(scriptRoot, locals);
+        right = right.cast(scriptRoot, locals);
     }
 
     @Override

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 
@@ -52,7 +52,7 @@ final class EConstant extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (constant instanceof String) {
             actual = String.class;
         } else if (constant instanceof Double) {

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -54,7 +54,7 @@ public final class EDecimal extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from constant [" + value + "]."));
         }

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

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 
 import java.util.Set;
@@ -61,7 +61,7 @@ public class EElvis extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (expected != null && expected.isPrimitive()) {
             throw createError(new IllegalArgumentException("Elvis operator cannot return primitives"));
         }
@@ -72,8 +72,8 @@ public class EElvis extends AExpression {
         rhs.explicit = explicit;
         rhs.internal = internal;
         actual = expected;
-        lhs.analyze(functions, locals);
-        rhs.analyze(functions, locals);
+        lhs.analyze(scriptRoot, locals);
+        rhs.analyze(scriptRoot, locals);
 
         if (lhs.isNull) {
             throw createError(new IllegalArgumentException("Extraneous elvis operator. LHS is null."));
@@ -96,8 +96,8 @@ public class EElvis extends AExpression {
             actual = promote;
         }
 
-        lhs = lhs.cast(functions, locals);
-        rhs = rhs.cast(functions, locals);
+        lhs = lhs.cast(scriptRoot, locals);
+        rhs = rhs.cast(scriptRoot, locals);
     }
 
     @Override

+ 7 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -56,8 +56,8 @@ public final class EExplicit extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        actual = locals.getPainlessLookup().canonicalTypeNameToType(type);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        actual = scriptRoot.getPainlessLookup().canonicalTypeNameToType(type);
 
         if (actual == null) {
             throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
@@ -65,8 +65,8 @@ public final class EExplicit extends AExpression {
 
         child.expected = actual;
         child.explicit = true;
-        child.analyze(functions, locals);
-        child = child.cast(functions, locals);
+        child.analyze(scriptRoot, locals);
+        child = child.cast(scriptRoot, locals);
     }
 
     @Override
@@ -74,12 +74,12 @@ public final class EExplicit extends AExpression {
         throw createError(new IllegalStateException("Illegal tree structure."));
     }
 
-    AExpression cast(FunctionTable functions, Locals locals) {
+    AExpression cast(ScriptRoot scriptRoot, Locals locals) {
         child.expected = expected;
         child.explicit = explicit;
         child.internal = internal;
 
-        return child.cast(functions, locals);
+        return child.cast(scriptRoot, locals);
     }
 
     @Override

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 
 import java.util.Objects;
@@ -60,14 +60,14 @@ public final class EFunctionRef extends AExpression implements ILambda {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (expected == null) {
             ref = null;
             actual = String.class;
             defPointer = "S" + type + "." + call + ",0";
         } else {
             defPointer = null;
-            ref = FunctionRef.create(locals.getPainlessLookup(), functions, location, expected, type, call, 0);
+            ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location, expected, type, call, 0);
             actual = expected;
         }
     }

+ 5 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -61,10 +61,9 @@ public final class EInstanceof extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         // ensure the specified type is part of the definition
-        Class<?> clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type);
+        Class<?> clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
 
         if (clazz == null) {
             throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
@@ -75,9 +74,9 @@ public final class EInstanceof extends AExpression {
                 PainlessLookupUtility.typeToJavaType(clazz);
 
         // analyze and cast the expression
-        expression.analyze(functions, locals);
+        expression.analyze(scriptRoot, locals);
         expression.expected = expression.actual;
-        expression = expression.cast(functions, locals);
+        expression = expression.cast(scriptRoot, locals);
 
         // record if the expression returns a primitive
         primitiveExpression = expression.actual.isPrimitive();

+ 11 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java

@@ -30,7 +30,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Opcodes;
 
 import java.util.ArrayList;
@@ -111,7 +111,7 @@ public final class ELambda extends AExpression implements ILambda {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         Class<?> returnType;
         List<String> actualParamTypeStrs;
         PainlessMethod interfaceMethod;
@@ -132,7 +132,7 @@ public final class ELambda extends AExpression implements ILambda {
 
         } else {
             // we know the method statically, infer return type and any unknown/def types
-            interfaceMethod = locals.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(expected);
+            interfaceMethod = scriptRoot.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(expected);
             if (interfaceMethod == null) {
                 throw createError(new IllegalArgumentException("Cannot pass lambda to " +
                         "[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
@@ -176,14 +176,15 @@ public final class ELambda extends AExpression implements ILambda {
         paramNames.addAll(paramNameStrs);
 
         // desugar lambda body into a synthetic method
-        String name = locals.getNextSyntheticName();
-        desugared = new SFunction(location, PainlessLookupUtility.typeToCanonicalTypeName(returnType), name, paramTypes, paramNames,
+        String name = scriptRoot.getNextSyntheticName("lambda");
+        desugared = new SFunction(
+                location, PainlessLookupUtility.typeToCanonicalTypeName(returnType), name, paramTypes, paramNames,
                 new SBlock(location, statements), true);
         desugared.storeSettings(settings);
-        desugared.generateSignature(locals.getPainlessLookup());
-        desugared.analyze(functions, Locals.newLambdaScope(locals.getProgramScope(), desugared.name, returnType,
+        desugared.generateSignature(scriptRoot.getPainlessLookup());
+        desugared.analyze(scriptRoot, Locals.newLambdaScope(locals.getProgramScope(), desugared.name, returnType,
                                                 desugared.parameters, captures.size(), settings.getMaxLoopCounter()));
-        functions.addFunction(desugared.name, desugared.returnType, desugared.typeParameters, true);
+        scriptRoot.getFunctionTable().addFunction(desugared.name, desugared.returnType, desugared.typeParameters, true);
 
         // setup method reference to synthetic method
         if (expected == null) {
@@ -192,8 +193,8 @@ public final class ELambda extends AExpression implements ILambda {
             defPointer = "Sthis." + name + "," + captures.size();
         } else {
             defPointer = null;
-            ref = FunctionRef.create(
-                    locals.getPainlessLookup(), functions, location, expected, "this", desugared.name, captures.size());
+            ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(),
+                    location, expected, "this", desugared.name, captures.size());
             actual = expected;
         }
     }

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

@@ -28,7 +28,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -68,21 +68,21 @@ public final class EListInit extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from list initializer."));
         }
 
         actual = ArrayList.class;
 
-        constructor = locals.getPainlessLookup().lookupPainlessConstructor(actual, 0);
+        constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(actual, 0);
 
         if (constructor == null) {
             throw createError(new IllegalArgumentException(
                     "constructor [" + typeToCanonicalTypeName(actual) + ", <init>/0] not found"));
         }
 
-        method = locals.getPainlessLookup().lookupPainlessMethod(actual, false, "add", 1);
+        method = scriptRoot.getPainlessLookup().lookupPainlessMethod(actual, false, "add", 1);
 
         if (method == null) {
             throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(actual) + ", add/1] not found"));
@@ -93,8 +93,8 @@ public final class EListInit extends AExpression {
 
             expression.expected = def.class;
             expression.internal = true;
-            expression.analyze(functions, locals);
-            values.set(index, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            values.set(index, expression.cast(scriptRoot, locals));
         }
     }
 

+ 8 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java

@@ -28,7 +28,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -78,21 +78,21 @@ public final class EMapInit extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from map initializer."));
         }
 
         actual = HashMap.class;
 
-        constructor = locals.getPainlessLookup().lookupPainlessConstructor(actual, 0);
+        constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(actual, 0);
 
         if (constructor == null) {
             throw createError(new IllegalArgumentException(
                     "constructor [" + typeToCanonicalTypeName(actual) + ", <init>/0] not found"));
         }
 
-        method = locals.getPainlessLookup().lookupPainlessMethod(actual, false, "put", 2);
+        method = scriptRoot.getPainlessLookup().lookupPainlessMethod(actual, false, "put", 2);
 
         if (method == null) {
             throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(actual) + ", put/2] not found"));
@@ -107,8 +107,8 @@ public final class EMapInit extends AExpression {
 
             expression.expected = def.class;
             expression.internal = true;
-            expression.analyze(functions, locals);
-            keys.set(index, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            keys.set(index, expression.cast(scriptRoot, locals));
         }
 
         for (int index = 0; index < values.size(); ++index) {
@@ -116,8 +116,8 @@ public final class EMapInit extends AExpression {
 
             expression.expected = def.class;
             expression.internal = true;
-            expression.analyze(functions, locals);
-            values.set(index, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            values.set(index, expression.cast(scriptRoot, locals));
         }
     }
 

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.List;
 import java.util.Objects;
@@ -63,12 +63,12 @@ public final class ENewArray extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
              throw createError(new IllegalArgumentException("A newly created array must be read from."));
         }
 
-        Class<?> clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type);
+        Class<?> clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
 
         if (clazz == null) {
             throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
@@ -79,8 +79,8 @@ public final class ENewArray extends AExpression {
 
             expression.expected = initialize ? clazz.getComponentType() : int.class;
             expression.internal = true;
-            expression.analyze(functions, locals);
-            arguments.set(argument, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            arguments.set(argument, expression.cast(scriptRoot, locals));
         }
 
         actual = clazz;

+ 9 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 
 import java.util.Arrays;
@@ -63,17 +63,18 @@ public final class ENewArrayFunctionRef extends AExpression implements ILambda {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         SReturn code = new SReturn(location, new ENewArray(location, type, Arrays.asList(new EVariable(location, "size")), false));
-        function = new SFunction(location, type, locals.getNextSyntheticName(),
+        function = new SFunction(
+                location, type, scriptRoot.getNextSyntheticName("newarray"),
                 Collections.singletonList("int"), Collections.singletonList("size"),
                 new SBlock(location, Collections.singletonList(code)), true);
         function.storeSettings(settings);
-        function.generateSignature(locals.getPainlessLookup());
+        function.generateSignature(scriptRoot.getPainlessLookup());
         function.extractVariables(null);
-        function.analyze(functions, Locals.newLambdaScope(locals.getProgramScope(), function.name, function.returnType,
+        function.analyze(scriptRoot, Locals.newLambdaScope(locals.getProgramScope(), function.name, function.returnType,
                 function.parameters, 0, settings.getMaxLoopCounter()));
-        functions.addFunction(function.name, function.returnType, function.typeParameters, true);
+        scriptRoot.getFunctionTable().addFunction(function.name, function.returnType, function.typeParameters, true);
 
         if (expected == null) {
             ref = null;
@@ -81,7 +82,8 @@ public final class ENewArrayFunctionRef extends AExpression implements ILambda {
             defPointer = "Sthis." + function.name + ",0";
         } else {
             defPointer = null;
-            ref = FunctionRef.create(locals.getPainlessLookup(), functions, location, expected, "this", function.name, 0);
+            ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(),
+                    location, expected, "this", function.name, 0);
             actual = expected;
         }
     }

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -69,14 +69,14 @@ public final class ENewObj extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        actual = locals.getPainlessLookup().canonicalTypeNameToType(this.type);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        actual = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
 
         if (actual == null) {
             throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
         }
 
-        constructor = locals.getPainlessLookup().lookupPainlessConstructor(actual, arguments.size());
+        constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(actual, arguments.size());
 
         if (constructor == null) {
             throw createError(new IllegalArgumentException(
@@ -97,8 +97,8 @@ public final class ENewObj extends AExpression {
 
             expression.expected = types[argument];
             expression.internal = true;
-            expression.analyze(functions, locals);
-            arguments.set(argument, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            arguments.set(argument, expression.cast(scriptRoot, locals));
         }
 
         statement = true;

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

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Opcodes;
 
 import java.util.Set;
@@ -51,7 +51,7 @@ public final class ENull extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from null constant."));
         }

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -56,7 +56,7 @@ public final class ENumeric extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from constant [" + value + "]."));
         }

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.WriterConstants;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 import java.util.regex.Pattern;
@@ -69,7 +69,7 @@ public final class ERegex extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (false == settings.areRegexesEnabled()) {
             throw createError(new IllegalStateException("Regexes are disabled. Set [script.painless.regex.enabled] to [true] "
                     + "in elasticsearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep "

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -54,8 +54,8 @@ public final class EStatic extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        actual = locals.getPainlessLookup().canonicalTypeNameToType(type);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        actual = scriptRoot.getPainlessLookup().canonicalTypeNameToType(type);
 
         if (actual == null) {
             throw createError(new IllegalArgumentException("Not a type [" + type + "]."));

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -52,7 +52,7 @@ public final class EString extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!read) {
             throw createError(new IllegalArgumentException("Must read from constant [" + constant + "]."));
         }

+ 18 - 18
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java

@@ -30,7 +30,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -67,26 +67,26 @@ public final class EUnary extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         originallyExplicit = explicit;
 
         if (operation == Operation.NOT) {
-            analyzeNot(functions, locals);
+            analyzeNot(scriptRoot, locals);
         } else if (operation == Operation.BWNOT) {
-            analyzeBWNot(functions, locals);
+            analyzeBWNot(scriptRoot, locals);
         } else if (operation == Operation.ADD) {
-            analyzerAdd(functions, locals);
+            analyzerAdd(scriptRoot, locals);
         } else if (operation == Operation.SUB) {
-            analyzerSub(functions, locals);
+            analyzerSub(scriptRoot, locals);
         } else {
             throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
-    void analyzeNot(FunctionTable functions, Locals variables) {
+    void analyzeNot(ScriptRoot scriptRoot, Locals variables) {
         child.expected = boolean.class;
-        child.analyze(functions, variables);
-        child = child.cast(functions, variables);
+        child.analyze(scriptRoot, variables);
+        child = child.cast(scriptRoot, variables);
 
         if (child.constant != null) {
             constant = !(boolean)child.constant;
@@ -95,8 +95,8 @@ public final class EUnary extends AExpression {
         actual = boolean.class;
     }
 
-    void analyzeBWNot(FunctionTable functions, Locals variables) {
-        child.analyze(functions, variables);
+    void analyzeBWNot(ScriptRoot scriptRoot, Locals variables) {
+        child.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(child.actual, false);
 
@@ -106,7 +106,7 @@ public final class EUnary extends AExpression {
         }
 
         child.expected = promote;
-        child = child.cast(functions, variables);
+        child = child.cast(scriptRoot, variables);
 
         if (child.constant != null) {
             if (promote == int.class) {
@@ -125,8 +125,8 @@ public final class EUnary extends AExpression {
         }
     }
 
-    void analyzerAdd(FunctionTable functions, Locals variables) {
-        child.analyze(functions, variables);
+    void analyzerAdd(ScriptRoot scriptRoot, Locals variables) {
+        child.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(child.actual, true);
 
@@ -136,7 +136,7 @@ public final class EUnary extends AExpression {
         }
 
         child.expected = promote;
-        child = child.cast(functions, variables);
+        child = child.cast(scriptRoot, variables);
 
         if (child.constant != null) {
             if (promote == int.class) {
@@ -159,8 +159,8 @@ public final class EUnary extends AExpression {
         }
     }
 
-    void analyzerSub(FunctionTable functions, Locals variables) {
-        child.analyze(functions, variables);
+    void analyzerSub(ScriptRoot scriptRoot, Locals variables) {
+        child.analyze(scriptRoot, variables);
 
         promote = AnalyzerCaster.promoteNumeric(child.actual, true);
 
@@ -170,7 +170,7 @@ public final class EUnary extends AExpression {
         }
 
         child.expected = promote;
-        child = child.cast(functions, variables);
+        child = child.cast(scriptRoot, variables);
 
         if (child.constant != null) {
             if (promote == int.class) {

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

@@ -26,7 +26,7 @@ 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.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Opcodes;
 
 import java.util.Objects;
@@ -58,7 +58,7 @@ public final class EVariable extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         variable = locals.getVariable(location, name);
 
         if (write && variable.readonly) {

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PBrace.java

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.List;
 import java.util.Map;
@@ -62,10 +62,10 @@ public final class PBrace extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        prefix.analyze(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        prefix.analyze(scriptRoot, locals);
         prefix.expected = prefix.actual;
-        prefix = prefix.cast(functions, locals);
+        prefix = prefix.cast(scriptRoot, locals);
 
         if (prefix.actual.isArray()) {
             sub = new PSubBrace(location, prefix.actual, index);
@@ -84,7 +84,7 @@ public final class PBrace extends AStoreable {
         sub.read = read;
         sub.expected = expected;
         sub.explicit = explicit;
-        sub.analyze(functions, locals);
+        sub.analyze(scriptRoot, locals);
         actual = sub.actual;
     }
 

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.List;
 import java.util.Objects;
@@ -73,16 +73,16 @@ public final class PCallInvoke extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        prefix.analyze(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        prefix.analyze(scriptRoot, locals);
         prefix.expected = prefix.actual;
-        prefix = prefix.cast(functions, locals);
+        prefix = prefix.cast(scriptRoot, locals);
 
         if (prefix.actual == def.class) {
             sub = new PSubDefCall(location, name, arguments);
         } else {
             PainlessMethod method =
-                    locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, prefix instanceof EStatic, name, arguments.size());
+                    scriptRoot.getPainlessLookup().lookupPainlessMethod(prefix.actual, prefix instanceof EStatic, name, arguments.size());
 
             if (method == null) {
                 throw createError(new IllegalArgumentException(
@@ -98,7 +98,7 @@ public final class PCallInvoke extends AExpression {
 
         sub.expected = expected;
         sub.explicit = explicit;
-        sub.analyze(functions, locals);
+        sub.analyze(scriptRoot, locals);
         actual = sub.actual;
 
         statement = true;

+ 10 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java

@@ -29,7 +29,7 @@ import org.elasticsearch.painless.lookup.PainlessField;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.List;
 import java.util.Map;
@@ -66,38 +66,38 @@ public final class PField extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        prefix.analyze(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        prefix.analyze(scriptRoot, locals);
         prefix.expected = prefix.actual;
-        prefix = prefix.cast(functions, locals);
+        prefix = prefix.cast(scriptRoot, locals);
 
         if (prefix.actual.isArray()) {
             sub = new PSubArrayLength(location, PainlessLookupUtility.typeToCanonicalTypeName(prefix.actual), value);
         } else if (prefix.actual == def.class) {
             sub = new PSubDefField(location, value);
         } else {
-            PainlessField field = locals.getPainlessLookup().lookupPainlessField(prefix.actual, prefix instanceof EStatic, value);
+            PainlessField field = scriptRoot.getPainlessLookup().lookupPainlessField(prefix.actual, prefix instanceof EStatic, value);
 
             if (field == null) {
                 PainlessMethod getter;
                 PainlessMethod setter;
 
-                getter = locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, false,
+                getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(prefix.actual, false,
                         "get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0);
 
                 if (getter == null) {
-                    getter = locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, false,
+                    getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(prefix.actual, false,
                             "is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0);
                 }
 
-                setter = locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, false,
+                setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(prefix.actual, false,
                         "set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0);
 
                 if (getter != null || setter != null) {
                     sub = new PSubShortcut(location, value, PainlessLookupUtility.typeToCanonicalTypeName(prefix.actual), getter, setter);
                 } else {
                     EConstant index = new EConstant(location, value);
-                    index.analyze(functions, locals);
+                    index.analyze(scriptRoot, locals);
 
                     if (Map.class.isAssignableFrom(prefix.actual)) {
                         sub = new PSubMapShortcut(location, prefix.actual, index);
@@ -125,7 +125,7 @@ public final class PField extends AStoreable {
         sub.read = read;
         sub.expected = expected;
         sub.explicit = explicit;
-        sub.analyze(functions, locals);
+        sub.analyze(scriptRoot, locals);
         actual = sub.actual;
     }
 

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -56,7 +56,7 @@ final class PSubArrayLength extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if ("length".equals(value)) {
             if (write) {
                 throw createError(new IllegalArgumentException("Cannot write to read-only field [length] for an array."));

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -56,10 +56,10 @@ final class PSubBrace extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         index.expected = int.class;
-        index.analyze(functions, locals);
-        index = index.cast(functions, locals);
+        index.analyze(scriptRoot, locals);
+        index = index.cast(scriptRoot, locals);
 
         actual = clazz.getComponentType();
     }

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.List;
 import java.util.Objects;
@@ -60,14 +60,14 @@ final class PSubCallInvoke extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         for (int argument = 0; argument < arguments.size(); ++argument) {
             AExpression expression = arguments.get(argument);
 
             expression.expected = method.typeParameters.get(argument);
             expression.internal = true;
-            expression.analyze(functions, locals);
-            arguments.set(argument, expression.cast(functions, locals));
+            expression.analyze(scriptRoot, locals);
+            arguments.set(argument, expression.cast(scriptRoot, locals));
         }
 
         statement = true;

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 
 import java.time.ZonedDateTime;
@@ -57,10 +57,10 @@ final class PSubDefArray extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        index.analyze(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        index.analyze(scriptRoot, locals);
         index.expected = index.actual;
-        index = index.cast(functions, locals);
+        index = index.cast(scriptRoot, locals);
 
         // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
         actual = expected == null || expected == ZonedDateTime.class || explicit ? def.class : expected;

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 
 import java.time.ZonedDateTime;
@@ -66,7 +66,7 @@ final class PSubDefCall extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         recipe = new StringBuilder();
         int totalCaptures = 0;
 
@@ -74,7 +74,7 @@ final class PSubDefCall extends AExpression {
             AExpression expression = arguments.get(argument);
 
             expression.internal = true;
-            expression.analyze(functions, locals);
+            expression.analyze(scriptRoot, locals);
 
             if (expression instanceof ILambda) {
                 ILambda lambda = (ILambda) expression;
@@ -90,7 +90,7 @@ final class PSubDefCall extends AExpression {
             }
 
             expression.expected = expression.actual;
-            arguments.set(argument, expression.cast(functions, locals));
+            arguments.set(argument, expression.cast(scriptRoot, locals));
         }
 
         // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.time.ZonedDateTime;
 import java.util.Objects;
@@ -57,7 +57,7 @@ final class PSubDefField extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
         actual = expected == null || expected == ZonedDateTime.class || explicit ? def.class : expected;
     }

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessField;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Type;
 
 import java.lang.reflect.Modifier;
@@ -58,7 +58,7 @@ final class PSubField extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
          if (write && Modifier.isFinal(field.javaField.getModifiers())) {
              throw createError(new IllegalArgumentException("Cannot write to read-only field [" + field.javaField.getName() + "] " +
                      "for type [" + PainlessLookupUtility.typeToCanonicalTypeName(field.javaField.getDeclaringClass()) + "]."));

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

@@ -28,7 +28,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -62,11 +62,11 @@ final class PSubListShortcut extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
 
-        getter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1);
-        setter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "set", 2);
+        getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1);
+        setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "set", 2);
 
         if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 ||
             getter.typeParameters.get(0) != int.class)) {
@@ -84,8 +84,8 @@ final class PSubListShortcut extends AStoreable {
 
         if ((read || write) && (!read || getter != null) && (!write || setter != null)) {
             index.expected = int.class;
-            index.analyze(functions, locals);
-            index = index.cast(functions, locals);
+            index.analyze(scriptRoot, locals);
+            index = index.cast(scriptRoot, locals);
 
             actual = setter != null ? setter.typeParameters.get(1) : getter.returnType;
         } else {

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -61,11 +61,11 @@ final class PSubMapShortcut extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
 
-        getter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1);
-        setter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "put", 2);
+        getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1);
+        setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "put", 2);
 
         if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) {
             throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + canonicalClassName + "]."));
@@ -82,8 +82,8 @@ final class PSubMapShortcut extends AStoreable {
 
         if ((read || write) && (!read || getter != null) && (!write || setter != null)) {
             index.expected = setter != null ? setter.typeParameters.get(0) : getter.typeParameters.get(0);
-            index.analyze(functions, locals);
-            index = index.cast(functions, locals);
+            index.analyze(scriptRoot, locals);
+            index = index.cast(scriptRoot, locals);
 
             actual = setter != null ? setter.typeParameters.get(1) : getter.returnType;
         } else {

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeCallInvoke.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 
 import java.util.Set;
@@ -57,8 +57,8 @@ public class PSubNullSafeCallInvoke extends AExpression {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        guarded.analyze(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        guarded.analyze(scriptRoot, locals);
         actual = guarded.actual;
         if (actual.isPrimitive()) {
             throw new IllegalArgumentException("Result of null safe operator must be nullable");

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeField.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 
 import java.util.Set;
@@ -52,12 +52,12 @@ public class PSubNullSafeField extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (write) {
             throw createError(new IllegalArgumentException("Can't write to null safe reference"));
         }
         guarded.read = read;
-        guarded.analyze(functions, locals);
+        guarded.analyze(scriptRoot, locals);
         actual = guarded.actual;
         if (actual.isPrimitive()) {
             throw new IllegalArgumentException("Result of null safe operator must be nullable");

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

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 
@@ -60,7 +60,7 @@ final class PSubShortcut extends AStoreable {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (getter != null && (getter.returnType == void.class || !getter.typeParameters.isEmpty())) {
             throw createError(new IllegalArgumentException(
                 "Illegal get shortcut on field [" + value + "] for type [" + type + "]."));

+ 3 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Collections;
 import java.util.List;
@@ -61,7 +61,7 @@ public final class SBlock extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (statements == null || statements.isEmpty()) {
             throw createError(new IllegalArgumentException("A block must contain at least one statement."));
         }
@@ -78,8 +78,7 @@ public final class SBlock extends AStatement {
             statement.inLoop = inLoop;
             statement.lastSource = lastSource && statement == last;
             statement.lastLoop = (beginLoop || lastLoop) && statement == last;
-
-            statement.analyze(functions, locals);
+            statement.analyze(scriptRoot, locals);
 
             methodEscape = statement.methodEscape;
             loopEscape = statement.loopEscape;

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 
@@ -49,7 +49,7 @@ public final class SBreak extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!inLoop) {
             throw createError(new IllegalArgumentException("Break statement outside of a loop."));
         }

+ 4 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java

@@ -26,7 +26,7 @@ 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.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -73,8 +73,8 @@ public final class SCatch extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        Class<?> clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        Class<?> clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
 
         if (clazz == null) {
             throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
@@ -90,8 +90,7 @@ public final class SCatch extends AStatement {
             block.lastSource = lastSource;
             block.inLoop = inLoop;
             block.lastLoop = lastLoop;
-
-            block.analyze(functions, locals);
+            block.analyze(scriptRoot, locals);
 
             methodEscape = block.methodEscape;
             loopEscape = block.loopEscape;

+ 11 - 11
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java

@@ -30,6 +30,7 @@ import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.ScriptClassInfo;
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.lookup.PainlessLookup;
+import org.elasticsearch.painless.ScriptRoot;
 import org.elasticsearch.painless.symbol.FunctionTable;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.Label;
@@ -87,7 +88,7 @@ public final class SClass extends AStatement {
 
     private CompilerSettings settings;
 
-    private FunctionTable table;
+    private ScriptRoot table;
     private Locals mainMethod;
     private final Set<String> extractedVariables;
     private final List<org.objectweb.asm.commons.Method> getMethods;
@@ -134,30 +135,30 @@ public final class SClass extends AStatement {
     }
 
     public void analyze(PainlessLookup painlessLookup) {
-        table = new FunctionTable();
+        table = new ScriptRoot(painlessLookup, settings, scriptClassInfo, this);
 
         for (SFunction function : functions) {
             function.generateSignature(painlessLookup);
 
             String key = FunctionTable.buildLocalFunctionKey(function.name, function.parameters.size());
 
-            if (table.getFunction(key) != null) {
-                throw createError(new IllegalArgumentException("function [" + key + "] already defined"));
+            if (table.getFunctionTable().getFunction(key) != null) {
+                throw createError(new IllegalArgumentException("Illegal duplicate functions [" + key + "]."));
             }
 
-            table.addFunction(function.name, function.returnType, function.typeParameters, false);
+            table.getFunctionTable().addFunction(function.name, function.returnType, function.typeParameters, false);
         }
 
-        Locals locals = Locals.newProgramScope(scriptClassInfo, painlessLookup);
+        Locals locals = Locals.newProgramScope();
         analyze(table, locals);
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals program) {
+    void analyze(ScriptRoot scriptRoot, Locals program) {
         for (SFunction function : this.functions) {
             Locals functionLocals =
                 Locals.newFunctionScope(program, function.returnType, function.parameters, settings.getMaxLoopCounter());
-            function.analyze(functions, functionLocals);
+            function.analyze(scriptRoot, functionLocals);
         }
 
         if (statements == null || statements.isEmpty()) {
@@ -188,8 +189,7 @@ public final class SClass extends AStatement {
             }
 
             statement.lastSource = statement == last;
-
-            statement.analyze(functions, mainMethod);
+            statement.analyze(scriptRoot, mainMethod);
 
             methodEscape = statement.methodEscape;
             allEscape = statement.allEscape;
@@ -348,7 +348,7 @@ public final class SClass extends AStatement {
         bytes = classWriter.getClassBytes();
 
         Map<String, Object> statics = new HashMap<>();
-        statics.put("$FUNCTIONS", table);
+        statics.put("$FUNCTIONS", table.getFunctionTable());
 
         for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
             statics.put(instanceBinding.getValue(), instanceBinding.getKey());

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 
@@ -49,7 +49,7 @@ public final class SContinue extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (!inLoop) {
             throw createError(new IllegalArgumentException("Continue statement outside of a loop."));
         }

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Collections;
 import java.util.List;
@@ -61,9 +61,9 @@ public final class SDeclBlock extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         for (SDeclaration declaration : declarations) {
-            declaration.analyze(functions, locals);
+            declaration.analyze(scriptRoot, locals);
         }
 
         statementCount = declarations.size();

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java

@@ -26,7 +26,7 @@ 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.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Opcodes;
 
 import java.util.Objects;
@@ -68,8 +68,8 @@ public final class SDeclaration extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        Class<?> clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        Class<?> clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
 
         if (clazz == null) {
             throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
@@ -77,8 +77,8 @@ public final class SDeclaration extends AStatement {
 
         if (expression != null) {
             expression.expected = clazz;
-            expression.analyze(functions, locals);
-            expression = expression.cast(functions, locals);
+            expression.analyze(scriptRoot, locals);
+            expression = expression.cast(scriptRoot, locals);
         }
 
         variable = locals.addVariable(location, clazz, name, false);

+ 5 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -68,7 +68,7 @@ public final class SDo extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         locals = Locals.newLocalScope(locals);
 
         if (block == null) {
@@ -77,16 +77,15 @@ public final class SDo extends AStatement {
 
         block.beginLoop = true;
         block.inLoop = true;
-
-        block.analyze(functions, locals);
+        block.analyze(scriptRoot, locals);
 
         if (block.loopEscape && !block.anyContinue) {
             throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
         condition.expected = boolean.class;
-        condition.analyze(functions, locals);
-        condition = condition.cast(functions, locals);
+        condition.analyze(scriptRoot, locals);
+        condition = condition.cast(scriptRoot, locals);
 
         if (condition.constant != null) {
             continuous = (boolean)condition.constant;

+ 7 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java

@@ -28,7 +28,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -75,12 +75,12 @@ public class SEach extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
-        expression.analyze(functions, locals);
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
+        expression.analyze(scriptRoot, locals);
         expression.expected = expression.actual;
-        expression = expression.cast(functions, locals);
+        expression = expression.cast(scriptRoot, locals);
 
-        Class<?> clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type);
+        Class<?> clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
 
         if (clazz == null) {
             throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
@@ -98,7 +98,7 @@ public class SEach extends AStatement {
                     "[" + PainlessLookupUtility.typeToCanonicalTypeName(expression.actual) + "]."));
         }
 
-        sub.analyze(functions, locals);
+        sub.analyze(scriptRoot, locals);
 
         if (block == null) {
             throw createError(new IllegalArgumentException("Extraneous for each loop."));
@@ -106,7 +106,7 @@ public class SEach extends AStatement {
 
         block.beginLoop = true;
         block.inLoop = true;
-        block.analyze(functions, locals);
+        block.analyze(scriptRoot, locals);
         block.statementCount = Math.max(1, block.statementCount);
 
         if (block.loopEscape && !block.anyContinue) {

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -54,12 +54,12 @@ public final class SExpression extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         Class<?> rtnType = locals.getReturnType();
         boolean isVoid = rtnType == void.class;
 
         expression.read = lastSource && !isVoid;
-        expression.analyze(functions, locals);
+        expression.analyze(scriptRoot, locals);
 
         if (!lastSource && !expression.statement) {
             throw createError(new IllegalArgumentException("Not a statement."));
@@ -69,7 +69,7 @@ public final class SExpression extends AStatement {
 
         expression.expected = rtn ? rtnType : expression.actual;
         expression.internal = rtn;
-        expression = expression.cast(functions, locals);
+        expression = expression.cast(scriptRoot, locals);
 
         methodEscape = rtn;
         loopEscape = rtn;

+ 10 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -94,24 +94,24 @@ public final class SFor extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         locals = Locals.newLocalScope(locals);
 
         if (initializer != null) {
             if (initializer instanceof SDeclBlock) {
-                initializer.analyze(functions, locals);
+                initializer.analyze(scriptRoot, locals);
             } else if (initializer instanceof AExpression) {
                 AExpression initializer = (AExpression)this.initializer;
 
                 initializer.read = false;
-                initializer.analyze(functions, locals);
+                initializer.analyze(scriptRoot, locals);
 
                 if (!initializer.statement) {
                     throw createError(new IllegalArgumentException("Not a statement."));
                 }
 
                 initializer.expected = initializer.actual;
-                this.initializer = initializer.cast(functions, locals);
+                this.initializer = initializer.cast(scriptRoot, locals);
             } else {
                 throw createError(new IllegalStateException("Illegal tree structure."));
             }
@@ -119,8 +119,8 @@ public final class SFor extends AStatement {
 
         if (condition != null) {
             condition.expected = boolean.class;
-            condition.analyze(functions, locals);
-            condition = condition.cast(functions, locals);
+            condition.analyze(scriptRoot, locals);
+            condition = condition.cast(scriptRoot, locals);
 
             if (condition.constant != null) {
                 continuous = (boolean)condition.constant;
@@ -139,21 +139,21 @@ public final class SFor extends AStatement {
 
         if (afterthought != null) {
             afterthought.read = false;
-            afterthought.analyze(functions, locals);
+            afterthought.analyze(scriptRoot, locals);
 
             if (!afterthought.statement) {
                 throw createError(new IllegalArgumentException("Not a statement."));
             }
 
             afterthought.expected = afterthought.actual;
-            afterthought = afterthought.cast(functions, locals);
+            afterthought = afterthought.cast(scriptRoot, locals);
         }
 
         if (block != null) {
             block.beginLoop = true;
             block.inLoop = true;
 
-            block.analyze(functions, locals);
+            block.analyze(scriptRoot, locals);
 
             if (block.loopEscape && !block.anyContinue) {
                 throw createError(new IllegalArgumentException("Extraneous for loop."));

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java

@@ -29,7 +29,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookup;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Opcodes;
 
 import java.lang.invoke.MethodType;
@@ -127,7 +127,7 @@ public final class SFunction extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (block.statements.isEmpty()) {
             throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "]."));
         }
@@ -135,7 +135,7 @@ public final class SFunction extends AStatement {
         locals = Locals.newLocalScope(locals);
 
         block.lastSource = true;
-        block.analyze(functions, locals);
+        block.analyze(scriptRoot, locals);
         methodEscape = block.methodEscape;
 
         if (!methodEscape && returnType != void.class) {

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -66,10 +66,10 @@ public final class SIf extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         condition.expected = boolean.class;
-        condition.analyze(functions, locals);
-        condition = condition.cast(functions, locals);
+        condition.analyze(scriptRoot, locals);
+        condition = condition.cast(scriptRoot, locals);
 
         if (condition.constant != null) {
             throw createError(new IllegalArgumentException("Extraneous if statement."));
@@ -83,7 +83,7 @@ public final class SIf extends AStatement {
         ifblock.inLoop = inLoop;
         ifblock.lastLoop = lastLoop;
 
-        ifblock.analyze(functions, Locals.newLocalScope(locals));
+        ifblock.analyze(scriptRoot, Locals.newLocalScope(locals));
 
         anyContinue = ifblock.anyContinue;
         anyBreak = ifblock.anyBreak;

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -79,10 +79,10 @@ public final class SIfElse extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         condition.expected = boolean.class;
-        condition.analyze(functions, locals);
-        condition = condition.cast(functions, locals);
+        condition.analyze(scriptRoot, locals);
+        condition = condition.cast(scriptRoot, locals);
 
         if (condition.constant != null) {
             throw createError(new IllegalArgumentException("Extraneous if statement."));
@@ -96,7 +96,7 @@ public final class SIfElse extends AStatement {
         ifblock.inLoop = inLoop;
         ifblock.lastLoop = lastLoop;
 
-        ifblock.analyze(functions, Locals.newLocalScope(locals));
+        ifblock.analyze(scriptRoot, Locals.newLocalScope(locals));
 
         anyContinue = ifblock.anyContinue;
         anyBreak = ifblock.anyBreak;
@@ -110,7 +110,7 @@ public final class SIfElse extends AStatement {
         elseblock.inLoop = inLoop;
         elseblock.lastLoop = lastLoop;
 
-        elseblock.analyze(functions, Locals.newLocalScope(locals));
+        elseblock.analyze(scriptRoot, Locals.newLocalScope(locals));
 
         methodEscape = ifblock.methodEscape && elseblock.methodEscape;
         loopEscape = ifblock.loopEscape && elseblock.loopEscape;

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java

@@ -26,7 +26,7 @@ import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Set;
 
@@ -58,7 +58,7 @@ public final class SReturn extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (expression == null) {
             if (locals.getReturnType() != void.class) {
                 throw location.createError(new ClassCastException("Cannot cast from " +
@@ -68,8 +68,8 @@ public final class SReturn extends AStatement {
         } else {
             expression.expected = locals.getReturnType();
             expression.internal = true;
-            expression.analyze(functions, locals);
-            expression = expression.cast(functions, locals);
+            expression.analyze(scriptRoot, locals);
+            expression = expression.cast(scriptRoot, locals);
         }
 
         methodEscape = true;

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

@@ -29,7 +29,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -68,7 +68,7 @@ final class SSubEachArray extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         // We must store the array and index as variables for securing slots on the stack, and
         // also add the location offset to make the names unique in case of nested for each loops.
         array = locals.addVariable(location, expression.actual, "#array" + location.getOffset(), true);

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java

@@ -32,7 +32,7 @@ import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -77,7 +77,7 @@ final class SSubEachIterable extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         // We must store the iterator as a variable for securing a slot on the stack, and
         // also add the location offset to make the name unique in case of nested for each loops.
         iterator = locals.addVariable(location, Iterator.class, "#itr" + location.getOffset(), true);
@@ -85,7 +85,7 @@ final class SSubEachIterable extends AStatement {
         if (expression.actual == def.class) {
             method = null;
         } else {
-            method = locals.getPainlessLookup().lookupPainlessMethod(expression.actual, false, "iterator", 0);
+            method = scriptRoot.getPainlessLookup().lookupPainlessMethod(expression.actual, false, "iterator", 0);
 
             if (method == null) {
                     throw createError(new IllegalArgumentException(

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 
 import java.util.Objects;
 import java.util.Set;
@@ -54,10 +54,10 @@ public final class SThrow extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         expression.expected = Exception.class;
-        expression.analyze(functions, locals);
-        expression = expression.cast(functions, locals);
+        expression.analyze(scriptRoot, locals);
+        expression = expression.cast(scriptRoot, locals);
 
         methodEscape = true;
         loopEscape = true;

+ 4 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 
 import java.util.Collections;
@@ -71,7 +71,7 @@ public final class STry extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         if (block == null) {
             throw createError(new IllegalArgumentException("Extraneous try statement."));
         }
@@ -80,7 +80,7 @@ public final class STry extends AStatement {
         block.inLoop = inLoop;
         block.lastLoop = lastLoop;
 
-        block.analyze(functions, Locals.newLocalScope(locals));
+        block.analyze(scriptRoot, Locals.newLocalScope(locals));
 
         methodEscape = block.methodEscape;
         loopEscape = block.loopEscape;
@@ -95,7 +95,7 @@ public final class STry extends AStatement {
             catc.inLoop = inLoop;
             catc.lastLoop = lastLoop;
 
-            catc.analyze(functions, Locals.newLocalScope(locals));
+            catc.analyze(scriptRoot, Locals.newLocalScope(locals));
 
             methodEscape &= catc.methodEscape;
             loopEscape &= catc.loopEscape;

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.Globals;
 import org.elasticsearch.painless.Locals;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.ScriptRoot;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -67,12 +67,12 @@ public final class SWhile extends AStatement {
     }
 
     @Override
-    void analyze(FunctionTable functions, Locals locals) {
+    void analyze(ScriptRoot scriptRoot, Locals locals) {
         locals = Locals.newLocalScope(locals);
 
         condition.expected = boolean.class;
-        condition.analyze(functions, locals);
-        condition = condition.cast(functions, locals);
+        condition.analyze(scriptRoot, locals);
+        condition = condition.cast(scriptRoot, locals);
 
         if (condition.constant != null) {
             continuous = (boolean)condition.constant;
@@ -90,7 +90,7 @@ public final class SWhile extends AStatement {
             block.beginLoop = true;
             block.inLoop = true;
 
-            block.analyze(functions, locals);
+            block.analyze(scriptRoot, locals);
 
             if (block.loopEscape && !block.anyContinue) {
                 throw createError(new IllegalArgumentException("Extraneous while loop."));

+ 2 - 2
modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java

@@ -459,11 +459,11 @@ public class DefOptimizationTests extends ScriptTestCase {
     
     public void testLambdaReturnType() {
         assertBytecodeExists("List l = new ArrayList(); l.removeIf(x -> x < 10)",
-                             "synthetic lambda$0(Ljava/lang/Object;)Z");
+                             "synthetic lambda$synthetic$0(Ljava/lang/Object;)Z");
     }
     
     public void testLambdaArguments() {
         assertBytecodeExists("List l = new ArrayList(); l.stream().mapToDouble(Double::valueOf).map(x -> x + 1)",
-                             "synthetic lambda$0(D)D");
+                             "synthetic lambda$synthetic$0(D)D");
     }
 }

+ 1 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionTests.java

@@ -70,7 +70,7 @@ public class FunctionTests extends ScriptTestCase {
         Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("void test(int x) {x = 2;} void test(def y) {y = 3;} test()");
         });
-        assertThat(expected.getMessage(), containsString("already defined"));
+        assertThat(expected.getMessage(), containsString("Illegal duplicate functions"));
     }
 
     public void testBadCastFromMethod() {