浏览代码

Replace the Input/Output objects for the semantic phase with Decorations in Painless (#58506)

This change adds a decoration system for the phases used on the user tree. The Input and Output 
objects used during the semantic phase to support symbol existence, type checking, control flow 
checking, etc. are replaced by decorations on the "user" tree nodes instead.

Two new classes are introduced for this - Decorator and Decorations.

Decorator creates both a HashMap and HashSet for each node in the user tree as part of an array 
based on the user tree nodes' ids. (This avoids excessive hash lookups.) The HashMap contains 
decorations that require additional information such as types, while the HashSet contains conditions 
that are true if they exist within the HashSet and false, otherwise. (Many boolean conditions are set 
against the tree nodes during the semantic phase. This allows us to re-use condition objects instead of 
having to create a bunch of empty ones.) The decorations and conditions replace the data stored in 
the Input/Output objects.

Decorations contains all the small classes that replace each of the variables in the Input/Output 
objects in a 1 to 1 mapping.

The advantages of this decoration system are the following:

* Strict decoupling of the data collected during the semantic phase from the user tree.
* Allows us to split up the semantic phase into multiple phases easily. Each phase would have the 
expectation of using existing decorations from previous phases along with adding new decorations as 
more information is processed waling the tree. This means that we can add new phases easily for 
extensibility and split up the current semantic phase which does three separate things - gathering 
initial user function information, semantic validation, and creation of an IR tree.
* Allows us to add new decorations/conditions easily for extensibility. This is particularly useful for 
adding new features outside of standard Painless moving forward.

All the user tree nodes are updated to use this system, hence the larger change here.

Other changes include renaming ScopeTable to WriteTable for the ir tree to avoid any confusion of 
what's used where, and removing statement counting from the loop counting where iterations is 
nearly the same and a lot simpler.
Jack Conradson 5 年之前
父节点
当前提交
01a07d4e37
共有 100 个文件被更改,包括 1423 次插入1316 次删除
  1. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
  2. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java
  3. 11 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/AssignmentNode.java
  4. 9 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BinaryMathNode.java
  5. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BlockNode.java
  6. 6 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BooleanNode.java
  7. 11 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceNode.java
  8. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubDefNode.java
  9. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BraceSubNode.java
  10. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/BreakNode.java
  11. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallNode.java
  12. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java
  13. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubNode.java
  14. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CastNode.java
  15. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CatchNode.java
  16. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java
  17. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ComparisonNode.java
  18. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConditionalNode.java
  19. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ConstantNode.java
  20. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ContinueNode.java
  21. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationBlockNode.java
  22. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DeclarationNode.java
  23. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DefInterfaceReferenceNode.java
  24. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DoWhileLoopNode.java
  25. 11 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotNode.java
  26. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubArrayLengthNode.java
  27. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubDefNode.java
  28. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubNode.java
  29. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubShortcutNode.java
  30. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ElvisNode.java
  31. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java
  32. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachLoopNode.java
  33. 10 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubArrayNode.java
  34. 9 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForEachSubIterableNode.java
  35. 15 27
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java
  36. 7 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java
  37. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IRNode.java
  38. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfElseNode.java
  39. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/IfNode.java
  40. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/InstanceofNode.java
  41. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListInitializationNode.java
  42. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ListSubShortcutNode.java
  43. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapInitializationNode.java
  44. 7 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MapSubShortcutNode.java
  45. 7 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MemberCallNode.java
  46. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MemberFieldLoadNode.java
  47. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/MemberFieldStoreNode.java
  48. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayNode.java
  49. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewObjectNode.java
  50. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullNode.java
  51. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NullSafeSubNode.java
  52. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReturnNode.java
  53. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StatementExpressionNode.java
  54. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/StaticNode.java
  55. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ThrowNode.java
  56. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TryNode.java
  57. 4 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java
  58. 3 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedInterfaceReferenceNode.java
  59. 5 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/UnaryMathNode.java
  60. 9 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/VariableNode.java
  61. 11 17
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileNode.java
  62. 32 73
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java
  63. 2 66
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java
  64. 57 51
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java
  65. 52 47
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java
  66. 18 20
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java
  67. 9 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java
  68. 62 51
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBrace.java
  69. 73 47
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECall.java
  70. 22 18
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java
  71. 29 29
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java
  72. 52 44
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java
  73. 15 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java
  74. 116 88
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDot.java
  75. 39 31
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EElvis.java
  76. 18 15
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java
  77. 29 26
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java
  78. 16 20
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java
  79. 29 23
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java
  80. 22 18
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java
  81. 27 23
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java
  82. 18 16
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java
  83. 19 16
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java
  84. 23 19
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java
  85. 19 13
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java
  86. 23 16
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java
  87. 9 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java
  88. 9 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EString.java
  89. 24 21
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ESymbol.java
  90. 41 30
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java
  91. 59 19
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java
  92. 10 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java
  93. 19 13
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java
  94. 10 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java
  95. 3 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java
  96. 9 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
  97. 23 19
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
  98. 23 19
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java
  99. 31 16
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java
  100. 26 29
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java

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

@@ -210,7 +210,7 @@ final class Compiler {
         String scriptName = Location.computeSourceName(name);
         ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
         SClass root = Walker.buildPainlessTree(scriptClassInfo, scriptName, source, settings);
-        ScriptScope scriptScope = new ScriptScope(painlessLookup, settings, scriptClassInfo, scriptName, source);
+        ScriptScope scriptScope = new ScriptScope(painlessLookup, settings, scriptClassInfo, scriptName, source, root.getIdentifier() + 1);
         ClassNode classNode = root.analyze(scriptScope);
         DefBootstrapInjectionPhase.phase(classNode);
         ScriptInjectionPhase.phase(scriptScope, classNode);
@@ -240,7 +240,7 @@ final class Compiler {
         String scriptName = Location.computeSourceName(name);
         ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
         SClass root = Walker.buildPainlessTree(scriptClassInfo, scriptName, source, settings);
-        ScriptScope scriptScope = new ScriptScope(painlessLookup, settings, scriptClassInfo, scriptName, source);
+        ScriptScope scriptScope = new ScriptScope(painlessLookup, settings, scriptClassInfo, scriptName, source, root.getIdentifier() + 1);
         ClassNode classNode = root.analyze(scriptScope);
         classNode.setDebugStream(debugStream);
         DefBootstrapInjectionPhase.phase(classNode);

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

@@ -143,12 +143,12 @@ public final class MethodWriter extends GeneratorAdapter {
         visitLineNumber(location.getOffset() + 1, label);
     }
 
-    public void writeLoopCounter(int slot, int count, Location location) {
+    public void writeLoopCounter(int slot, Location location) {
         assert slot != -1;
         writeDebugInfo(location);
         final Label end = new Label();
 
-        iinc(slot, -count);
+        iinc(slot, -1);
         visitVarInsn(Opcodes.ILOAD, slot);
         push(0);
         ifICmp(GeneratorAdapter.GT, end);

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

@@ -27,7 +27,7 @@ import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class AssignmentNode extends BinaryNode {
 
@@ -104,7 +104,7 @@ public class AssignmentNode extends BinaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         // For the case where the assignment represents a String concatenation
@@ -119,7 +119,7 @@ public class AssignmentNode extends BinaryNode {
         }
 
         // call the setup method on the lhs to prepare for a load/store operation
-        getLeftNode().setup(classWriter, methodWriter, scopeTable);
+        getLeftNode().setup(classWriter, methodWriter, writeScope);
 
         if (cat) {
             // Handle the case where we are doing a compound assignment
@@ -127,10 +127,10 @@ public class AssignmentNode extends BinaryNode {
 
             methodWriter.writeDup(getLeftNode().accessElementCount(), catElementStackSize); // dup the top element and insert it
                                                                                             // before concat helper on stack
-            getLeftNode().load(classWriter, methodWriter, scopeTable);             // read the current lhs's value
+            getLeftNode().load(classWriter, methodWriter, writeScope);             // read the current lhs's value
             methodWriter.writeAppendStrings(getLeftNode().getExpressionType()); // append the lhs's value using the StringBuilder
 
-            getRightNode().write(classWriter, methodWriter, scopeTable); // write the bytecode for the rhs
+            getRightNode().write(classWriter, methodWriter, writeScope); // write the bytecode for the rhs
 
             // check to see if the rhs has already done a concatenation
             if (getRightNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getRightNode()).getCat() == false) {
@@ -148,7 +148,7 @@ public class AssignmentNode extends BinaryNode {
             }
 
             // store the lhs's value from the stack in its respective variable/field/array
-            getLeftNode().store(classWriter, methodWriter, scopeTable);
+            getLeftNode().store(classWriter, methodWriter, writeScope);
         } else if (operation != null) {
             // Handle the case where we are doing a compound assignment that
             // does not represent a String concatenation.
@@ -156,7 +156,7 @@ public class AssignmentNode extends BinaryNode {
             methodWriter.writeDup(getLeftNode().accessElementCount(), 0); // if necessary, dup the previous lhs's value
                                                                           // to be both loaded from and stored to
 
-            getLeftNode().load(classWriter, methodWriter, scopeTable); // load the current lhs's value
+            getLeftNode().load(classWriter, methodWriter, writeScope); // load the current lhs's value
 
             if (read && post) {
                 // dup the value if the lhs is also read from and is a post increment
@@ -167,7 +167,7 @@ public class AssignmentNode extends BinaryNode {
             methodWriter.writeCast(there); // if necessary cast the current lhs's value
                                            // to the promotion type between the lhs and rhs types
 
-            getRightNode().write(classWriter, methodWriter, scopeTable); // write the bytecode for the rhs
+            getRightNode().write(classWriter, methodWriter, writeScope); // write the bytecode for the rhs
 
             // XXX: fix these types, but first we need def compound assignment tests.
             // its tricky here as there are possibly explicit casts, too.
@@ -188,11 +188,11 @@ public class AssignmentNode extends BinaryNode {
             }
 
             // store the lhs's value from the stack in its respective variable/field/array
-            getLeftNode().store(classWriter, methodWriter, scopeTable);
+            getLeftNode().store(classWriter, methodWriter, writeScope);
         } else {
             // Handle the case for a simple write.
 
-            getRightNode().write(classWriter, methodWriter, scopeTable); // write the bytecode for the rhs rhs
+            getRightNode().write(classWriter, methodWriter, writeScope); // write the bytecode for the rhs rhs
 
             if (read) {
                 // dup the value if the lhs is also read from
@@ -201,7 +201,7 @@ public class AssignmentNode extends BinaryNode {
             }
 
             // store the lhs's value from the stack in its respective variable/field/array
-            getLeftNode().store(classWriter, methodWriter, scopeTable);
+            getLeftNode().store(classWriter, methodWriter, writeScope);
         }
     }
 }

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

@@ -27,7 +27,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.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -82,7 +82,7 @@ public class BinaryMathNode extends BinaryNode {
         return cat;
     }
 
-    public void setOriginallExplicit(boolean originallyExplicit) {
+    public void setOriginallyExplicit(boolean originallyExplicit) {
         this.originallyExplicit = originallyExplicit;
     }
 
@@ -98,7 +98,7 @@ public class BinaryMathNode extends BinaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (getBinaryType() == String.class && operation == Operation.ADD) {
@@ -106,13 +106,13 @@ public class BinaryMathNode extends BinaryNode {
                 methodWriter.writeNewStrings();
             }
 
-            getLeftNode().write(classWriter, methodWriter, scopeTable);
+            getLeftNode().write(classWriter, methodWriter, writeScope);
 
             if (getLeftNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getLeftNode()).getCat() == false) {
                 methodWriter.writeAppendStrings(getLeftNode().getExpressionType());
             }
 
-            getRightNode().write(classWriter, methodWriter, scopeTable);
+            getRightNode().write(classWriter, methodWriter, writeScope);
 
             if (getRightNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getRightNode()).getCat() == false) {
                 methodWriter.writeAppendStrings(getRightNode().getExpressionType());
@@ -122,8 +122,8 @@ public class BinaryMathNode extends BinaryNode {
                 methodWriter.writeToStrings();
             }
         } else if (operation == Operation.FIND || operation == Operation.MATCH) {
-            getRightNode().write(classWriter, methodWriter, scopeTable);
-            getLeftNode().write(classWriter, methodWriter, scopeTable);
+            getRightNode().write(classWriter, methodWriter, writeScope);
+            getLeftNode().write(classWriter, methodWriter, writeScope);
             methodWriter.invokeVirtual(org.objectweb.asm.Type.getType(Pattern.class), WriterConstants.PATTERN_MATCHER);
 
             if (operation == Operation.FIND) {
@@ -135,8 +135,8 @@ public class BinaryMathNode extends BinaryNode {
                         "for type [" + getExpressionCanonicalTypeName() + "]");
             }
         } else {
-            getLeftNode().write(classWriter, methodWriter, scopeTable);
-            getRightNode().write(classWriter, methodWriter, scopeTable);
+            getLeftNode().write(classWriter, methodWriter, writeScope);
+            getRightNode().write(classWriter, methodWriter, writeScope);
 
             if (binaryType == def.class || (shiftType != null && shiftType == def.class)) {
                 // def calls adopt the wanted return value. if there was a narrowing cast,

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,11 +64,11 @@ public class BlockNode extends StatementNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         for (StatementNode statementNode : statementNodes) {
             statementNode.continueLabel = continueLabel;
             statementNode.breakLabel = breakLabel;
-            statementNode.write(classWriter, methodWriter, scopeTable);
+            statementNode.write(classWriter, methodWriter, writeScope);
         }
     }
 }

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -43,16 +43,16 @@ public class BooleanNode extends BinaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (operation == Operation.AND) {
             Label fals = new Label();
             Label end = new Label();
 
-            getLeftNode().write(classWriter, methodWriter, scopeTable);
+            getLeftNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
-            getRightNode().write(classWriter, methodWriter, scopeTable);
+            getRightNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
             methodWriter.push(true);
@@ -65,9 +65,9 @@ public class BooleanNode extends BinaryNode {
             Label fals = new Label();
             Label end = new Label();
 
-            getLeftNode().write(classWriter, methodWriter, scopeTable);
+            getLeftNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFNE, tru);
-            getRightNode().write(classWriter, methodWriter, scopeTable);
+            getRightNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
             methodWriter.mark(tru);

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

@@ -21,14 +21,14 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class BraceNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
-        getRightNode().write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getLeftNode().write(classWriter, methodWriter, writeScope);
+        getRightNode().write(classWriter, methodWriter, writeScope);
     }
 
     @Override
@@ -37,18 +37,18 @@ public class BraceNode extends BinaryNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
-        getRightNode().setup(classWriter, methodWriter, scopeTable);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getLeftNode().write(classWriter, methodWriter, writeScope);
+        getRightNode().setup(classWriter, methodWriter, writeScope);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getRightNode().load(classWriter, methodWriter, scopeTable);
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getRightNode().load(classWriter, methodWriter, writeScope);
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getRightNode().store(classWriter, methodWriter, scopeTable);
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getRightNode().store(classWriter, methodWriter, writeScope);
     }
 }

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

@@ -22,15 +22,15 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 
 public class BraceSubDefNode extends UnaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        setup(classWriter, methodWriter, scopeTable);
-        load(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        setup(classWriter, methodWriter, writeScope);
+        load(classWriter, methodWriter, writeScope);
     }
 
     @Override
@@ -39,16 +39,16 @@ public class BraceSubDefNode extends UnaryNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.dup();
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+        getChildNode().write(classWriter, methodWriter, writeScope);
         Type methodType = Type.getMethodType(MethodWriter.getType(
                 getChildNode().getExpressionType()), Type.getType(Object.class), MethodWriter.getType(getChildNode().getExpressionType()));
         methodWriter.invokeDefCall("normalizeIndex", methodType, DefBootstrap.INDEX_NORMALIZE);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(MethodWriter.getType(
@@ -57,7 +57,7 @@ public class BraceSubDefNode extends UnaryNode {
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(Type.getType(void.class), Type.getType(Object.class),

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

@@ -21,16 +21,16 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class BraceSubNode extends UnaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        setup(classWriter, methodWriter, scopeTable);
-        load(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        setup(classWriter, methodWriter, writeScope);
+        load(classWriter, methodWriter, writeScope);
     }
 
     @Override
@@ -39,8 +39,8 @@ public class BraceSubNode extends UnaryNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getChildNode().write(classWriter, methodWriter, writeScope);
 
         Label noFlip = new Label();
         methodWriter.dup();
@@ -53,13 +53,13 @@ public class BraceSubNode extends UnaryNode {
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.arrayLoad(MethodWriter.getType(getExpressionType()));
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.arrayStore(MethodWriter.getType(getExpressionType()));
     }

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

@@ -21,12 +21,12 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class BreakNode extends StatementNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.goTo(breakLabel);
     }
 }

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

@@ -21,13 +21,13 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class CallNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
-        getRightNode().write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getLeftNode().write(classWriter, methodWriter, writeScope);
+        getRightNode().write(classWriter, methodWriter, writeScope);
     }
 }

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java

@@ -22,13 +22,13 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import static org.elasticsearch.painless.symbol.WriteScope.Variable;
 
 public class CallSubDefNode extends ArgumentsNode {
 
@@ -50,7 +50,7 @@ public class CallSubDefNode extends ArgumentsNode {
      * Writes an invokedynamic instruction for a call with an unknown receiver type.
      */
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         // its possible to have unknown functional interfaces
@@ -68,7 +68,7 @@ public class CallSubDefNode extends ArgumentsNode {
 
         for (int i = 0; i < getArgumentNodes().size(); ++i) {
             ExpressionNode argumentNode = getArgumentNodes().get(i);
-            argumentNode.write(classWriter, methodWriter, scopeTable);
+            argumentNode.write(classWriter, methodWriter, writeScope);
 
             typeParameters.add(argumentNode.getExpressionType());
 
@@ -88,7 +88,7 @@ public class CallSubDefNode extends ArgumentsNode {
                 capturedCount += defInterfaceReferenceNode.getCaptures().size();
 
                 for (String capturedName : defInterfaceReferenceNode.getCaptures()) {
-                    Variable capturedVariable = scopeTable.getVariable(capturedName);
+                    Variable capturedVariable = writeScope.getVariable(capturedName);
                     typeParameters.add(capturedVariable.getType());
                 }
             }

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class CallSubNode extends ArgumentsNode {
 
@@ -50,7 +50,7 @@ public class CallSubNode extends ArgumentsNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (box.isPrimitive()) {
@@ -58,7 +58,7 @@ public class CallSubNode extends ArgumentsNode {
         }
 
         for (ExpressionNode argumentNode : getArgumentNodes()) {
-            argumentNode.write(classWriter, methodWriter, scopeTable);
+            argumentNode.write(classWriter, methodWriter, writeScope);
         }
 
         methodWriter.invokeMethodCall(method);

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class CastNode extends UnaryNode {
 
@@ -41,8 +41,8 @@ public class CastNode extends UnaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getChildNode().write(classWriter, methodWriter, writeScope);
         methodWriter.writeDebugInfo(location);
         methodWriter.writeCast(cast);
     }

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

@@ -21,8 +21,8 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -65,10 +65,10 @@ public class CatchNode extends StatementNode {
     Label exception = null;
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        Variable variable = scopeTable.defineVariable(exceptionType, symbol);
+        Variable variable = writeScope.defineVariable(exceptionType, symbol);
 
         Label jump = new Label();
 
@@ -78,7 +78,7 @@ public class CatchNode extends StatementNode {
         if (blockNode != null) {
             blockNode.continueLabel = continueLabel;
             blockNode.breakLabel = breakLabel;
-            blockNode.write(classWriter, methodWriter, scopeTable);
+            blockNode.write(classWriter, methodWriter, writeScope);
         }
 
         methodWriter.visitTryCatchBlock(begin, end, jump, variable.getAsmType().getInternalName());

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

@@ -23,7 +23,7 @@ import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.ScriptClassInfo;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.elasticsearch.painless.symbol.ScriptScope;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.Opcodes;
@@ -137,7 +137,7 @@ public class ClassNode extends IRNode {
             MethodWriter methodWriter = classWriter.newMethodWriter(
                     Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
                     new Method("<clinit>", Type.getType(void.class), new Type[0]));
-            clinitBlockNode.write(classWriter, methodWriter, new ScopeTable());
+            clinitBlockNode.write(classWriter, methodWriter, new WriteScope());
             methodWriter.returnValue();
             methodWriter.endMethod();
         }
@@ -149,7 +149,7 @@ public class ClassNode extends IRNode {
 
         // Write all functions:
         for (FunctionNode functionNode : functionNodes) {
-            functionNode.write(classWriter, null, new ScopeTable());
+            functionNode.write(classWriter, null, new WriteScope());
         }
 
         // End writing the class and store the generated bytes.

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

@@ -25,7 +25,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.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Type;
 
@@ -62,13 +62,13 @@ public class ComparisonNode extends BinaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
+        getLeftNode().write(classWriter, methodWriter, writeScope);
 
         if (getRightNode() instanceof NullNode == false) {
-            getRightNode().write(classWriter, methodWriter, scopeTable);
+            getRightNode().write(classWriter, methodWriter, writeScope);
         }
 
         Label jump = new Label();

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -42,19 +42,19 @@ public class ConditionalNode extends BinaryNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Label fals = new Label();
         Label end = new Label();
 
-        conditionNode.write(classWriter, methodWriter, scopeTable);
+        conditionNode.write(classWriter, methodWriter, writeScope);
         methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
+        getLeftNode().write(classWriter, methodWriter, writeScope);
         methodWriter.goTo(end);
         methodWriter.mark(fals);
-        getRightNode().write(classWriter, methodWriter, scopeTable);
+        getRightNode().write(classWriter, methodWriter, writeScope);
         methodWriter.mark(end);
     }
 }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class ConstantNode extends ExpressionNode {
 
@@ -40,7 +40,7 @@ public class ConstantNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         if      (constant instanceof String)    methodWriter.push((String)constant);
         else if (constant instanceof Double)    methodWriter.push((double)constant);
         else if (constant instanceof Float)     methodWriter.push((float)constant);

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

@@ -21,12 +21,12 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class ContinueNode extends StatementNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.goTo(continueLabel);
     }
 }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -43,9 +43,9 @@ public final class DeclarationBlockNode extends StatementNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         for (DeclarationNode declarationNode : declarationNodes) {
-            declarationNode.write(classWriter, methodWriter, scopeTable);
+            declarationNode.write(classWriter, methodWriter, writeScope);
         }
     }
 }

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

@@ -22,8 +22,8 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Opcodes;
 
 public class DeclarationNode extends StatementNode {
@@ -68,10 +68,10 @@ public class DeclarationNode extends StatementNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        Variable variable = scopeTable.defineVariable(declarationType, name);
+        Variable variable = writeScope.defineVariable(declarationType, name);
 
         if (expressionNode == null) {
             Class<?> sort = variable.getType();
@@ -89,7 +89,7 @@ public class DeclarationNode extends StatementNode {
                 methodWriter.visitInsn(Opcodes.ACONST_NULL);
             }
         } else {
-            expressionNode.write(classWriter, methodWriter, scopeTable);
+            expressionNode.write(classWriter, methodWriter, writeScope);
         }
 
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Opcodes;
 
 public class DefInterfaceReferenceNode extends ReferenceNode {
@@ -41,7 +41,7 @@ public class DefInterfaceReferenceNode extends ReferenceNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         // place holder for functional interface receiver
@@ -49,7 +49,7 @@ public class DefInterfaceReferenceNode extends ReferenceNode {
         methodWriter.push((String)null);
 
         for (String capture : getCaptures()) {
-            ScopeTable.Variable variable = scopeTable.getVariable(capture);
+            WriteScope.Variable variable = writeScope.getVariable(capture);
             methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
         }
     }

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

@@ -21,18 +21,18 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class DoWhileLoopNode extends LoopNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        scopeTable = scopeTable.newScope();
+        writeScope = writeScope.newScope();
 
         Label start = new Label();
         Label begin = new Label();
@@ -42,19 +42,19 @@ public class DoWhileLoopNode extends LoopNode {
 
         getBlockNode().continueLabel = begin;
         getBlockNode().breakLabel = end;
-        getBlockNode().write(classWriter, methodWriter, scopeTable);
+        getBlockNode().write(classWriter, methodWriter, writeScope);
 
         methodWriter.mark(begin);
 
         if (isContinuous() == false) {
-            getConditionNode().write(classWriter, methodWriter, scopeTable);
+            getConditionNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFEQ, end);
         }
 
-        Variable loop = scopeTable.getInternalVariable("loop");
+        Variable loop = writeScope.getInternalVariable("loop");
 
         if (loop != null) {
-            methodWriter.writeLoopCounter(loop.getSlot(), Math.max(1, getBlockNode().getStatementCount()), location);
+            methodWriter.writeLoopCounter(loop.getSlot(), location);
         }
 
         methodWriter.goTo(start);

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

@@ -21,14 +21,14 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class DotNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
-        getRightNode().write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getLeftNode().write(classWriter, methodWriter, writeScope);
+        getRightNode().write(classWriter, methodWriter, writeScope);
     }
 
     @Override
@@ -36,18 +36,18 @@ public class DotNode extends BinaryNode {
         return getRightNode().accessElementCount();
     }
 
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
-        getRightNode().setup(classWriter, methodWriter, scopeTable);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getLeftNode().write(classWriter, methodWriter, writeScope);
+        getRightNode().setup(classWriter, methodWriter, writeScope);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getRightNode().load(classWriter, methodWriter, scopeTable);
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getRightNode().load(classWriter, methodWriter, writeScope);
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getRightNode().store(classWriter, methodWriter, scopeTable);
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getRightNode().store(classWriter, methodWriter, writeScope);
     }
 }

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

@@ -21,12 +21,12 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class DotSubArrayLengthNode extends ExpressionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.arrayLength();
     }

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubDefNode.java

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 
 public class DotSubDefNode extends ExpressionNode {
@@ -42,7 +42,7 @@ public class DotSubDefNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), Type.getType(Object.class));
@@ -55,12 +55,12 @@ public class DotSubDefNode extends ExpressionNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         // do nothing
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), Type.getType(Object.class));
@@ -68,7 +68,7 @@ public class DotSubDefNode extends ExpressionNode {
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Type methodType = Type.getMethodType(

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubNode.java

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessField;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 
 public class DotSubNode extends ExpressionNode {
@@ -42,7 +42,7 @@ public class DotSubNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) {
@@ -60,12 +60,12 @@ public class DotSubNode extends ExpressionNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         // Do nothing.
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) {
@@ -78,7 +78,7 @@ public class DotSubNode extends ExpressionNode {
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) {

+ 5 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DotSubShortcutNode.java

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class DotSubShortcutNode extends ExpressionNode {
 
@@ -50,7 +50,7 @@ public class DotSubShortcutNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.invokeMethodCall(getter);
@@ -66,12 +66,12 @@ public class DotSubShortcutNode extends ExpressionNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         // do nothing
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.invokeMethodCall(getter);
@@ -82,7 +82,7 @@ public class DotSubShortcutNode extends ExpressionNode {
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.invokeMethodCall(setter);

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

@@ -21,22 +21,22 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 
 public class ElvisNode extends BinaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Label end = new Label();
 
-        getLeftNode().write(classWriter, methodWriter, scopeTable);
+        getLeftNode().write(classWriter, methodWriter, writeScope);
         methodWriter.dup();
         methodWriter.ifNonNull(end);
         methodWriter.pop();
-        getRightNode().write(classWriter, methodWriter, scopeTable);
+        getRightNode().write(classWriter, methodWriter, writeScope);
         methodWriter.mark(end);
     }
 }

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 
 public class FieldNode extends IRNode {
@@ -64,7 +64,7 @@ public class FieldNode extends IRNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         classWriter.getClassVisitor().visitField(
                 ClassWriter.buildAccess(modifiers, true), name, Type.getType(fieldType).getDescriptor(), null, null).visitEnd();
     }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class ForEachLoopNode extends StatementNode {
 
@@ -40,8 +40,8 @@ public class ForEachLoopNode extends StatementNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        scopeTable = scopeTable.newScope();
-        conditionNode.write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        writeScope = writeScope.newScope();
+        conditionNode.write(classWriter, methodWriter, writeScope);
     }
 }

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

@@ -23,8 +23,8 @@ import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -124,14 +124,14 @@ public class ForEachSubArrayNode extends LoopNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        Variable variable = scopeTable.defineVariable(variableType, variableName);
-        Variable array = scopeTable.defineInternalVariable(arrayType, arrayName);
-        Variable index = scopeTable.defineInternalVariable(indexType, indexName);
+        Variable variable = writeScope.defineVariable(variableType, variableName);
+        Variable array = writeScope.defineInternalVariable(arrayType, arrayName);
+        Variable index = writeScope.defineInternalVariable(indexType, indexName);
 
-        getConditionNode().write(classWriter, methodWriter, scopeTable);
+        getConditionNode().write(classWriter, methodWriter, writeScope);
         methodWriter.visitVarInsn(array.getAsmType().getOpcode(Opcodes.ISTORE), array.getSlot());
         methodWriter.push(-1);
         methodWriter.visitVarInsn(index.getAsmType().getOpcode(Opcodes.ISTORE), index.getSlot());
@@ -153,15 +153,15 @@ public class ForEachSubArrayNode extends LoopNode {
         methodWriter.writeCast(cast);
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
 
-        Variable loop = scopeTable.getInternalVariable("loop");
+        Variable loop = writeScope.getInternalVariable("loop");
 
         if (loop != null) {
-            methodWriter.writeLoopCounter(loop.getSlot(), getBlockNode().getStatementCount(), location);
+            methodWriter.writeLoopCounter(loop.getSlot(), location);
         }
 
         getBlockNode().continueLabel = begin;
         getBlockNode().breakLabel = end;
-        getBlockNode().write(classWriter, methodWriter, scopeTable);
+        getBlockNode().write(classWriter, methodWriter, writeScope);
 
         methodWriter.goTo(begin);
         methodWriter.mark(end);

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

@@ -24,8 +24,8 @@ import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -100,13 +100,13 @@ public class ForEachSubIterableNode extends LoopNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        Variable variable = scopeTable.defineVariable(variableType, variableName);
-        Variable iterator = scopeTable.defineInternalVariable(iteratorType, iteratorName);
+        Variable variable = writeScope.defineVariable(variableType, variableName);
+        Variable iterator = writeScope.defineInternalVariable(iteratorType, iteratorName);
 
-        getConditionNode().write(classWriter, methodWriter, scopeTable);
+        getConditionNode().write(classWriter, methodWriter, writeScope);
 
         if (method == null) {
             org.objectweb.asm.Type methodType = org.objectweb.asm.Type
@@ -132,15 +132,15 @@ public class ForEachSubIterableNode extends LoopNode {
         methodWriter.writeCast(cast);
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
 
-        Variable loop = scopeTable.getInternalVariable("loop");
+        Variable loop = writeScope.getInternalVariable("loop");
 
         if (loop != null) {
-            methodWriter.writeLoopCounter(loop.getSlot(), getBlockNode().getStatementCount(), location);
+            methodWriter.writeLoopCounter(loop.getSlot(), location);
         }
 
         getBlockNode().continueLabel = begin;
         getBlockNode().breakLabel = end;
-        getBlockNode().write(classWriter, methodWriter, scopeTable);
+        getBlockNode().write(classWriter, methodWriter, writeScope);
 
         methodWriter.goTo(begin);
         methodWriter.mark(end);

+ 15 - 27
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ForLoopNode.java

@@ -21,8 +21,8 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -50,62 +50,50 @@ public class ForLoopNode extends LoopNode {
     }
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        scopeTable = scopeTable.newScope();
+        writeScope = writeScope.newScope();
 
         Label start = new Label();
         Label begin = afterthoughtNode == null ? start : new Label();
         Label end = new Label();
 
         if (initializerNode instanceof DeclarationBlockNode) {
-            initializerNode.write(classWriter, methodWriter, scopeTable);
+            initializerNode.write(classWriter, methodWriter, writeScope);
         } else if (initializerNode instanceof ExpressionNode) {
             ExpressionNode initializer = (ExpressionNode)this.initializerNode;
 
-            initializer.write(classWriter, methodWriter, scopeTable);
+            initializer.write(classWriter, methodWriter, writeScope);
             methodWriter.writePop(MethodWriter.getType(initializer.getExpressionType()).getSize());
         }
 
         methodWriter.mark(start);
 
         if (getConditionNode() != null && isContinuous() == false) {
-            getConditionNode().write(classWriter, methodWriter, scopeTable);
+            getConditionNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFEQ, end);
         }
 
+        Variable loop = writeScope.getInternalVariable("loop");
+
+        if (loop != null) {
+            methodWriter.writeLoopCounter(loop.getSlot(), location);
+        }
+
         boolean allEscape = false;
 
         if (getBlockNode() != null) {
             allEscape = getBlockNode().doAllEscape();
 
-            int statementCount = Math.max(1, getBlockNode().getStatementCount());
-
-            if (afterthoughtNode != null) {
-                ++statementCount;
-            }
-
-            Variable loop = scopeTable.getInternalVariable("loop");
-
-            if (loop != null) {
-                methodWriter.writeLoopCounter(loop.getSlot(), statementCount, location);
-            }
-
             getBlockNode().continueLabel = begin;
             getBlockNode().breakLabel = end;
-            getBlockNode().write(classWriter, methodWriter, scopeTable);
-        } else {
-            Variable loop = scopeTable.getInternalVariable("loop");
-
-            if (loop != null) {
-                methodWriter.writeLoopCounter(loop.getSlot(), 1, location);
-            }
+            getBlockNode().write(classWriter, methodWriter, writeScope);
         }
 
         if (afterthoughtNode != null) {
             methodWriter.mark(begin);
-            afterthoughtNode.write(classWriter, methodWriter, scopeTable);
+            afterthoughtNode.write(classWriter, methodWriter, writeScope);
             methodWriter.writePop(MethodWriter.getType(afterthoughtNode.getExpressionType()).getSize());
         }
 

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

@@ -21,8 +21,8 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
@@ -122,13 +122,13 @@ public class FunctionNode extends IRNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         int access = Opcodes.ACC_PUBLIC;
 
         if (isStatic) {
             access |= Opcodes.ACC_STATIC;
         } else {
-            scopeTable.defineInternalVariable(Object.class, "this");
+            writeScope.defineInternalVariable(Object.class, "this");
         }
 
         if (hasVarArgs) {
@@ -145,7 +145,7 @@ public class FunctionNode extends IRNode {
         for (int index = 0; index < asmParameterTypes.length; ++index) {
             Class<?> type = typeParameters.get(index);
             String name = parameterNames.get(index);
-            scopeTable.defineVariable(type, name);
+            writeScope.defineVariable(type, name);
             asmParameterTypes[index] = MethodWriter.getType(typeParameters.get(index));
         }
 
@@ -158,13 +158,13 @@ public class FunctionNode extends IRNode {
             // if there is infinite loop protection, we do this once:
             // int #loop = settings.getMaxLoopCounter()
 
-            Variable loop = scopeTable.defineInternalVariable(int.class, "loop");
+            Variable loop = writeScope.defineInternalVariable(int.class, "loop");
 
             methodWriter.push(maxLoopCounter);
             methodWriter.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
         }
 
-        blockNode.write(classWriter, methodWriter, scopeTable.newScope());
+        blockNode.write(classWriter, methodWriter, writeScope.newScope());
 
         methodWriter.endMethod();
     }

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public abstract class IRNode {
 
@@ -40,7 +40,7 @@ public abstract class IRNode {
 
     /* end node data */
 
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         throw new UnsupportedOperationException();
     }
 
@@ -48,15 +48,15 @@ public abstract class IRNode {
         throw new UnsupportedOperationException();
     }
 
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         throw new UnsupportedOperationException();
     }
 
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         throw new UnsupportedOperationException();
     }
 
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         throw new UnsupportedOperationException();
     }
 }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -42,18 +42,18 @@ public class IfElseNode extends ConditionNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
         Label fals = new Label();
         Label end = new Label();
 
-        getConditionNode().write(classWriter, methodWriter, scopeTable);
+        getConditionNode().write(classWriter, methodWriter, writeScope);
         methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
         getBlockNode().continueLabel = continueLabel;
         getBlockNode().breakLabel = breakLabel;
-        getBlockNode().write(classWriter, methodWriter, scopeTable.newScope());
+        getBlockNode().write(classWriter, methodWriter, writeScope.newScope());
 
         if (getBlockNode().doAllEscape() == false) {
             methodWriter.goTo(end);
@@ -63,7 +63,7 @@ public class IfElseNode extends ConditionNode {
 
         elseBlockNode.continueLabel = continueLabel;
         elseBlockNode.breakLabel = breakLabel;
-        elseBlockNode.write(classWriter, methodWriter, scopeTable.newScope());
+        elseBlockNode.write(classWriter, methodWriter, writeScope.newScope());
 
         methodWriter.mark(end);
     }

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

@@ -21,24 +21,24 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class IfNode extends ConditionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
         Label fals = new Label();
 
-        getConditionNode().write(classWriter, methodWriter, scopeTable);
+        getConditionNode().write(classWriter, methodWriter, writeScope);
         methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
         getBlockNode().continueLabel = continueLabel;
         getBlockNode().breakLabel = breakLabel;
-        getBlockNode().write(classWriter, methodWriter, scopeTable.newScope());
+        getBlockNode().write(classWriter, methodWriter, writeScope.newScope());
 
         methodWriter.mark(fals);
     }

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 
 public class InstanceofNode extends UnaryNode {
@@ -68,8 +68,8 @@ public class InstanceofNode extends UnaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getChildNode().write(classWriter, methodWriter, writeScope);
 
         // primitive types
         if (isPrimitiveResult) {

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

@@ -23,7 +23,7 @@ import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -53,7 +53,7 @@ public class ListInitializationNode extends ArgumentsNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.newInstance(MethodWriter.getType(getExpressionType()));
@@ -63,7 +63,7 @@ public class ListInitializationNode extends ArgumentsNode {
 
         for (ExpressionNode argument : getArgumentNodes()) {
             methodWriter.dup();
-            argument.write(classWriter, methodWriter, scopeTable);
+            argument.write(classWriter, methodWriter, writeScope);
             methodWriter.invokeMethodCall(method);
             methodWriter.pop();
         }

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

@@ -23,7 +23,7 @@ import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
@@ -53,9 +53,9 @@ public class ListSubShortcutNode extends UnaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        setup(classWriter, methodWriter, scopeTable);
-        load(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        setup(classWriter, methodWriter, writeScope);
+        load(classWriter, methodWriter, writeScope);
     }
 
     @Override
@@ -64,8 +64,8 @@ public class ListSubShortcutNode extends UnaryNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getChildNode().write(classWriter, methodWriter, writeScope);
 
         Label noFlip = new Label();
         methodWriter.dup();
@@ -78,7 +78,7 @@ public class ListSubShortcutNode extends UnaryNode {
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(getter);
 
@@ -88,7 +88,7 @@ public class ListSubShortcutNode extends UnaryNode {
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(setter);
         methodWriter.writePop(MethodWriter.getType(setter.returnType).getSize());

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

@@ -23,7 +23,7 @@ import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -86,7 +86,7 @@ public class MapInitializationNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.newInstance(MethodWriter.getType(getExpressionType()));
@@ -96,8 +96,8 @@ public class MapInitializationNode extends ExpressionNode {
 
         for (int index = 0; index < getArgumentsSize(); ++index) {
             methodWriter.dup();
-            getKeyNode(index).write(classWriter, methodWriter, scopeTable);
-            getValueNode(index).write(classWriter, methodWriter, scopeTable);
+            getKeyNode(index).write(classWriter, methodWriter, writeScope);
+            getValueNode(index).write(classWriter, methodWriter, writeScope);
             methodWriter.invokeMethodCall(method);
             methodWriter.pop();
         }

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessMethod;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class MapSubShortcutNode extends UnaryNode {
 
@@ -50,8 +50,8 @@ public class MapSubShortcutNode extends UnaryNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getChildNode().write(classWriter, methodWriter, writeScope);
 
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(getter);
@@ -67,12 +67,12 @@ public class MapSubShortcutNode extends UnaryNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        getChildNode().write(classWriter, methodWriter, writeScope);
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(getter);
 
@@ -82,7 +82,7 @@ public class MapSubShortcutNode extends UnaryNode {
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
         methodWriter.invokeMethodCall(setter);
         methodWriter.writePop(MethodWriter.getType(setter.returnType).getSize());

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

@@ -25,7 +25,7 @@ import org.elasticsearch.painless.lookup.PainlessClassBinding;
 import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.symbol.FunctionTable.LocalFunction;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
@@ -94,7 +94,7 @@ public class MemberCallNode extends ArgumentsNode {
     /* ---- end node data ---- */
 
     @Override
-    public void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (localFunction != null) {
@@ -103,7 +103,7 @@ public class MemberCallNode extends ArgumentsNode {
             }
 
             for (ExpressionNode argumentNode : getArgumentNodes()) {
-                argumentNode.write(classWriter, methodWriter, scopeTable);
+                argumentNode.write(classWriter, methodWriter, writeScope);
            }
 
             if (localFunction.isStatic()) {
@@ -113,7 +113,7 @@ public class MemberCallNode extends ArgumentsNode {
             }
         } else if (importedMethod != null) {
             for (ExpressionNode argumentNode : getArgumentNodes()) {
-                argumentNode.write(classWriter, methodWriter, scopeTable);
+                argumentNode.write(classWriter, methodWriter, writeScope);
            }
 
             methodWriter.invokeStatic(Type.getType(importedMethod.targetClass),
@@ -136,7 +136,7 @@ public class MemberCallNode extends ArgumentsNode {
             }
 
             for (int argument = 0; argument < javaConstructorParameterCount; ++argument) {
-                getArgumentNodes().get(argument).write(classWriter, methodWriter, scopeTable);
+                getArgumentNodes().get(argument).write(classWriter, methodWriter, writeScope);
            }
 
             methodWriter.invokeConstructor(type, Method.getMethod(classBinding.javaConstructor));
@@ -147,7 +147,7 @@ public class MemberCallNode extends ArgumentsNode {
             methodWriter.getField(CLASS_TYPE, bindingName, type);
 
             for (int argument = 0; argument < classBinding.javaMethod.getParameterCount(); ++argument) {
-                getArgumentNodes().get(argument + javaConstructorParameterCount).write(classWriter, methodWriter, scopeTable);
+                getArgumentNodes().get(argument + javaConstructorParameterCount).write(classWriter, methodWriter, writeScope);
             }
 
             methodWriter.invokeVirtual(type, Method.getMethod(classBinding.javaMethod));
@@ -158,7 +158,7 @@ public class MemberCallNode extends ArgumentsNode {
             methodWriter.getStatic(CLASS_TYPE, bindingName, type);
 
             for (int argument = 0; argument < instanceBinding.javaMethod.getParameterCount(); ++argument) {
-                getArgumentNodes().get(argument).write(classWriter, methodWriter, scopeTable);
+                getArgumentNodes().get(argument).write(classWriter, methodWriter, writeScope);
             }
 
             methodWriter.invokeVirtual(type, Method.getMethod(instanceBinding.javaMethod));

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
 
@@ -55,7 +55,7 @@ public class MemberFieldLoadNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    public void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (isStatic) {

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
 
@@ -70,12 +70,12 @@ public class MemberFieldStoreNode extends UnaryNode {
     /* ---- end node data ---- */
 
     @Override
-    public void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         if (isStatic == false) {
             methodWriter.loadThis();
         }
 
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+        getChildNode().write(classWriter, methodWriter, writeScope);
 
         methodWriter.writeDebugInfo(location);
 

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class NewArrayNode extends ArgumentsNode {
 
@@ -40,7 +40,7 @@ public class NewArrayNode extends ArgumentsNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (initialize) {
@@ -52,12 +52,12 @@ public class NewArrayNode extends ArgumentsNode {
 
                 methodWriter.dup();
                 methodWriter.push(index);
-                argumentNode.write(classWriter, methodWriter, scopeTable);
+                argumentNode.write(classWriter, methodWriter, writeScope);
                 methodWriter.arrayStore(MethodWriter.getType(getExpressionType().getComponentType()));
             }
         } else {
             for (ExpressionNode argumentNode : getArgumentNodes()) {
-                argumentNode.write(classWriter, methodWriter, scopeTable);
+                argumentNode.write(classWriter, methodWriter, writeScope);
             }
 
             if (getArgumentNodes().size() > 1) {

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.Method;
 
@@ -52,7 +52,7 @@ public final class NewObjectNode extends ArgumentsNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         methodWriter.newInstance(MethodWriter.getType(getExpressionType()));
@@ -62,7 +62,7 @@ public final class NewObjectNode extends ArgumentsNode {
         }
 
         for (ExpressionNode argumentNode : getArgumentNodes()) {
-            argumentNode.write(classWriter, methodWriter, scopeTable);
+            argumentNode.write(classWriter, methodWriter, writeScope);
         }
 
         methodWriter.invokeConstructor(

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

@@ -21,13 +21,13 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Opcodes;
 
 public class NullNode extends ExpressionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.visitInsn(Opcodes.ACONST_NULL);
     }
 }

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

@@ -21,19 +21,19 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 
 public class NullSafeSubNode extends UnaryNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         Label end = new Label();
         methodWriter.dup();
         methodWriter.ifNull(end);
-        getChildNode().write(classWriter, methodWriter, scopeTable);
+        getChildNode().write(classWriter, methodWriter, writeScope);
         methodWriter.mark(end);
     }
 }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class ReturnNode extends StatementNode {
 
@@ -40,11 +40,11 @@ public class ReturnNode extends StatementNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
         if (expressionNode != null) {
-            expressionNode.write(classWriter, methodWriter, scopeTable);
+            expressionNode.write(classWriter, methodWriter, writeScope);
         }
 
         methodWriter.returnValue();

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class StatementExpressionNode extends StatementNode {
 
@@ -40,9 +40,9 @@ public class StatementExpressionNode extends StatementNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
-        expressionNode.write(classWriter, methodWriter, scopeTable);
+        expressionNode.write(classWriter, methodWriter, writeScope);
         methodWriter.writePop(MethodWriter.getType(expressionNode.getExpressionType()).getSize());
     }
 }

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

@@ -21,12 +21,12 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class StaticNode extends ExpressionNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         // do nothing
     }
 }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 
 public class ThrowNode extends StatementNode {
 
@@ -40,9 +40,9 @@ public class ThrowNode extends StatementNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
-        expressionNode.write(classWriter, methodWriter, scopeTable);
+        expressionNode.write(classWriter, methodWriter, writeScope);
         methodWriter.throwException();
     }
 }

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

@@ -21,7 +21,7 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 
 import java.util.ArrayList;
@@ -53,7 +53,7 @@ public class TryNode extends StatementNode {
     /* ---- end tree structure ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
         Label begin = new Label();
@@ -64,7 +64,7 @@ public class TryNode extends StatementNode {
 
         blockNode.continueLabel = continueLabel;
         blockNode.breakLabel = breakLabel;
-        blockNode.write(classWriter, methodWriter, scopeTable.newScope());
+        blockNode.write(classWriter, methodWriter, writeScope.newScope());
 
         if (blockNode.doAllEscape() == false) {
             methodWriter.goTo(exception);
@@ -76,7 +76,7 @@ public class TryNode extends StatementNode {
             catchNode.begin = begin;
             catchNode.end = end;
             catchNode.exception = catchNodes.size() > 1 ? exception : null;
-            catchNode.write(classWriter, methodWriter, scopeTable.newScope());
+            catchNode.write(classWriter, methodWriter, writeScope.newScope());
         }
 
         if (blockNode.doAllEscape() == false || catchNodes.size() > 1) {

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

@@ -22,8 +22,8 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
@@ -44,9 +44,9 @@ public class TypedCaptureReferenceNode extends ReferenceNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
-        Variable captured = scopeTable.getVariable(getCaptures().get(0));
+        Variable captured = writeScope.getVariable(getCaptures().get(0));
 
         methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot());
         Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), captured.getAsmType());

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

@@ -22,7 +22,7 @@ package org.elasticsearch.painless.ir;
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Opcodes;
 
 public class TypedInterfaceReferenceNode extends ReferenceNode {
@@ -42,11 +42,11 @@ public class TypedInterfaceReferenceNode extends ReferenceNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         for (String capture : getCaptures()) {
-            ScopeTable.Variable variable = scopeTable.getVariable(capture);
+            WriteScope.Variable variable = writeScope.getVariable(capture);
             methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
         }
 

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

@@ -25,7 +25,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.ScopeTable;
+import org.elasticsearch.painless.symbol.WriteScope;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -67,7 +67,7 @@ public class UnaryMathNode extends UnaryNode {
         return cat;
     }
 
-    public void setOriginallExplicit(boolean originallyExplicit) {
+    public void setOriginallyExplicit(boolean originallyExplicit) {
         this.originallyExplicit = originallyExplicit;
     }
 
@@ -78,14 +78,14 @@ public class UnaryMathNode extends UnaryNode {
     /* ---- end node data ---- */
 
     @Override
-    public void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    public void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeDebugInfo(location);
 
         if (operation == Operation.NOT) {
             Label fals = new Label();
             Label end = new Label();
 
-            getChildNode().write(classWriter, methodWriter, scopeTable);
+            getChildNode().write(classWriter, methodWriter, writeScope);
 
             methodWriter.ifZCmp(Opcodes.IFEQ, fals);
 
@@ -95,7 +95,7 @@ public class UnaryMathNode extends UnaryNode {
             methodWriter.push(true);
             methodWriter.mark(end);
         } else {
-            getChildNode().write(classWriter, methodWriter, scopeTable);
+            getChildNode().write(classWriter, methodWriter, writeScope);
 
             // Def calls adopt the wanted return value. If there was a narrowing cast,
             // we need to flag that so that it's done at runtime.

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

@@ -21,8 +21,8 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Opcodes;
 
 public class VariableNode extends ExpressionNode {
@@ -42,8 +42,8 @@ public class VariableNode extends ExpressionNode {
     /* ---- end node data ---- */
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        Variable variable = scopeTable.getVariable(name);
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        Variable variable = writeScope.getVariable(name);
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
     }
 
@@ -53,19 +53,19 @@ public class VariableNode extends ExpressionNode {
     }
 
     @Override
-    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void setup(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         // do nothing
     }
 
     @Override
-    protected void load(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        Variable variable = scopeTable.getVariable(name);
+    protected void load(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        Variable variable = writeScope.getVariable(name);
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot());
     }
 
     @Override
-    protected void store(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
-        Variable variable = scopeTable.getVariable(name);
+    protected void store(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
+        Variable variable = writeScope.getVariable(name);
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
     }
 }

+ 11 - 17
modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/WhileNode.java

@@ -21,18 +21,18 @@ package org.elasticsearch.painless.ir;
 
 import org.elasticsearch.painless.ClassWriter;
 import org.elasticsearch.painless.MethodWriter;
-import org.elasticsearch.painless.symbol.ScopeTable;
-import org.elasticsearch.painless.symbol.ScopeTable.Variable;
+import org.elasticsearch.painless.symbol.WriteScope;
+import org.elasticsearch.painless.symbol.WriteScope.Variable;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
 public class WhileNode extends LoopNode {
 
     @Override
-    protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) {
+    protected void write(ClassWriter classWriter, MethodWriter methodWriter, WriteScope writeScope) {
         methodWriter.writeStatementOffset(location);
 
-        scopeTable = scopeTable.newScope();
+        writeScope = writeScope.newScope();
 
         Label begin = new Label();
         Label end = new Label();
@@ -40,26 +40,20 @@ public class WhileNode extends LoopNode {
         methodWriter.mark(begin);
 
         if (isContinuous() == false) {
-            getConditionNode().write(classWriter, methodWriter, scopeTable);
+            getConditionNode().write(classWriter, methodWriter, writeScope);
             methodWriter.ifZCmp(Opcodes.IFEQ, end);
         }
 
-        if (getBlockNode() != null) {
-            Variable loop = scopeTable.getInternalVariable("loop");
+        Variable loop = writeScope.getInternalVariable("loop");
 
-            if (loop != null) {
-                methodWriter.writeLoopCounter(loop.getSlot(), Math.max(1, getBlockNode().getStatementCount()), location);
-            }
+        if (loop != null) {
+            methodWriter.writeLoopCounter(loop.getSlot(), location);
+        }
 
+        if (getBlockNode() != null) {
             getBlockNode().continueLabel = begin;
             getBlockNode().breakLabel = end;
-            getBlockNode().write(classWriter, methodWriter, scopeTable);
-        } else {
-            Variable loop = scopeTable.getInternalVariable("loop");
-
-            if (loop != null) {
-                methodWriter.writeLoopCounter(loop.getSlot(), 1, location);
-            }
+            getBlockNode().write(classWriter, methodWriter, writeScope);
         }
 
         if (getBlockNode() == null || getBlockNode().doAllEscape() == false) {

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

@@ -19,12 +19,18 @@
 
 package org.elasticsearch.painless.node;
 
+import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.ir.CastNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ExpressionNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
-import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.PartialCanonicalTypeName;
+import org.elasticsearch.painless.symbol.Decorations.StaticType;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
 import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
@@ -32,70 +38,8 @@ import org.elasticsearch.painless.symbol.SemanticScope;
  */
 public abstract class AExpression extends ANode {
 
-    public static class Input {
-
-        /**
-         * Set to false when an expression will not be read from such as
-         * a basic assignment.  Note this variable is always set by the parent
-         * as input.
-         */
-        boolean read = true;
-
-        /**
-         * Set to true when this node is an lhs-expression and will be storing
-         * a value from an rhs-expression.
-         */
-        boolean write = false;
-
-        /**
-         * Set to the expected type this node needs to be.  Note this variable
-         * is always set by the parent as input and should never be read from.
-         */
-        Class<?> expected = null;
-
-        /**
-         * Set by {@link EExplicit} if a cast made on an expression node should be
-         * explicit.
-         */
-        boolean explicit = false;
-
-        /**
-         * Set to true if a cast is allowed to boxed/unboxed.  This is used
-         * for method arguments because casting may be required.
-         */
-        boolean internal = false;
-    }
-
     public static class Output {
 
-        /**
-         * Set to the actual type this node is.  Note this variable is always
-         * set by the node as output and should only be read from outside of the
-         * node itself. Also, actual can always be read after a cast is
-         * called on this node to get the type of the node after the cast.
-         */
-        Class<?> actual = null;
-
-        /**
-         * Set to {@code true} when actual represents a static type and
-         * this expression does not generate a value. Set to {@code false}
-         * when actual is the type of value this expression generates.
-         */
-        boolean isStaticType = false;
-
-        /**
-         * Used to build a fully-qualified type name when the name comes
-         * in as pieces since x.y.z may get broken down into multiples nodes
-         * with the dot as a delimiter.
-         */
-        String partialCanonicalTypeName = null;
-
-        /**
-         * {@code true} if this node or a sub-node of this node can be optimized with
-         * rhs actual type to avoid an unnecessary cast.
-         */
-        boolean isDefOptimized = false;
-
         /**
          * The {@link ExpressionNode}(s) generated from this expression.
          */
@@ -105,14 +49,14 @@ public abstract class AExpression extends ANode {
     /**
      * Standard constructor with location used for error tracking.
      */
-    AExpression(int indentifier, Location location) {
-        super(indentifier, location);
+    AExpression(int identifier, Location location) {
+        super(identifier, location);
     }
 
     /**
      * Checks for errors and collects data for the writing phase.
      */
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         throw new UnsupportedOperationException();
     }
 
@@ -120,21 +64,36 @@ public abstract class AExpression extends ANode {
      * Checks for errors and collects data for the writing phase. Adds additional, common
      * error checking for conditions related to static types and partially constructed static types.
      */
-    static Output analyze(AExpression expression, ClassNode classNode, SemanticScope semanticScope, Input input) {
-        Output output = expression.analyze(classNode, semanticScope, input);
+    static Output analyze(AExpression expression, ClassNode classNode, SemanticScope semanticScope) {
+        Output output = expression.analyze(classNode, semanticScope);
+
+        if (semanticScope.hasDecoration(expression, PartialCanonicalTypeName.class)) {
+            throw expression.createError(new IllegalArgumentException("cannot resolve symbol " +
+                    "[" + semanticScope.getDecoration(expression, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "]"));
+        }
 
-        if (output.partialCanonicalTypeName != null) {
-            throw expression.createError(new IllegalArgumentException("cannot resolve symbol [" + output.partialCanonicalTypeName + "]"));
+        if (semanticScope.hasDecoration(expression, StaticType.class)) {
+            throw expression.createError(new IllegalArgumentException("value required: instead found unexpected type " +
+                    "[" + semanticScope.getDecoration(expression, StaticType.class).getStaticCanonicalTypeName() + "]"));
         }
 
-        if (output.isStaticType) {
-            throw expression.createError(new IllegalArgumentException("value required: " +
-                    "instead found unexpected type [" + PainlessLookupUtility.typeToCanonicalTypeName(output.actual) + "]"));
+        if (semanticScope.hasDecoration(expression, ValueType.class) == false) {
+            throw expression.createError(new IllegalStateException("value required: instead found no value"));
         }
 
         return output;
     }
 
+    // TODO: move this somewhere more appropriate
+    public PainlessCast cast(SemanticScope semanticScope) {
+        Class<?> valueType = semanticScope.getDecoration(this, ValueType.class).getValueType();
+        Class<?> targetType = semanticScope.getDecoration(this, TargetType.class).getTargetType();
+        boolean isExplicitCast = semanticScope.getCondition(this, Explicit.class);
+        boolean isInternalCast = semanticScope.getCondition(this, Internal.class);
+
+        return AnalyzerCaster.getLegalCast(getLocation(), valueType, targetType, isExplicitCast, isInternalCast);
+    }
+
     static ExpressionNode cast(ExpressionNode expressionNode, PainlessCast painlessCast) {
         if (painlessCast == null) {
             return expressionNode;

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

@@ -20,81 +20,17 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.StatementNode;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
  * The superclass for all S* (statement) nodes.
  */
 public abstract class AStatement extends ANode {
 
-    public static class Input {
-
-        /**
-         * Set to true when the final statement in an {@link SClass} is reached.
-         * Used to determine whether or not an auto-return is necessary.
-         */
-        boolean lastSource = false;
-
-        /**
-         * Set to true when a loop begins.  Used by {@link SBlock} to help determine
-         * when the final statement of a loop is reached.
-         */
-        boolean beginLoop = false;
-
-        /**
-         * Set to true when inside a loop.  Used by {@link SBreak} and {@link SContinue}
-         * to determine if a break/continue statement is legal.
-         */
-        boolean inLoop = false;
-
-        /**
-         * Set to true when on the last statement of a loop.  Used by {@link SContinue}
-         * to prevent extraneous continue statements.
-         */
-        boolean lastLoop = false;
-    }
-
     public static class Output {
 
-        /**
-         * Set to true if a statement would cause the method to exit.  Used to
-         * determine whether or not an auto-return is necessary.
-         */
-        boolean methodEscape = false;
-
-        /**
-         * Set to true if a statement would cause a loop to exit.  Used to
-         * prevent unreachable statements.
-         */
-        boolean loopEscape = false;
-
-        /**
-         * Set to true if all current paths escape from the current {@link SBlock}.
-         * Used during the analysis phase to prevent unreachable statements and
-         * the writing phase to prevent extraneous bytecode gotos from being written.
-         */
-        boolean allEscape = false;
-
-        /**
-         * Set to true if any continue statement occurs in a loop.  Used to prevent
-         * unnecessary infinite loops.
-         */
-        boolean anyContinue = false;
-
-        /**
-         * Set to true if any break statement occurs in a loop.  Used to prevent
-         * extraneous loops.
-         */
-        boolean anyBreak = false;
-
-        /**
-         * Set to the approximate number of statements in a loop block to prevent
-         * infinite loops during runtime.
-         */
-        int statementCount = 1;
-
         /**
          * The {@link StatementNode}(s) generated from this expression.
          */
@@ -111,7 +47,7 @@ public abstract class AStatement extends ANode {
     /**
      * Checks for errors and collects data for the writing phase.
      */
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         throw new UnsupportedOperationException();
     }
 }

+ 57 - 51
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java

@@ -23,7 +23,6 @@ package org.elasticsearch.painless.node;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.AssignmentNode;
 import org.elasticsearch.painless.ir.BinaryMathNode;
 import org.elasticsearch.painless.ir.BraceNode;
@@ -34,6 +33,14 @@ import org.elasticsearch.painless.ir.DotSubDefNode;
 import org.elasticsearch.painless.ir.ExpressionNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations;
+import org.elasticsearch.painless.symbol.Decorations.DefOptimized;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -75,63 +82,64 @@ public class EAssignment extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
 
         boolean cat = false;
         Class<?> promote = null;
         Class<?> shiftDistance = null;
-        PainlessCast rightCast;
+        PainlessCast rightCast = null;
         PainlessCast there = null;
         PainlessCast back = null;
 
-        Input leftInput = new Input();
-        leftInput.read = input.read;
-        leftInput.write = true;
-        Output leftOutput = analyze(leftNode, classNode, semanticScope, leftInput);
+        semanticScope.replicateCondition(this, leftNode, Read.class);
+        semanticScope.setCondition(leftNode, Write.class);
+        Output leftOutput = analyze(leftNode, classNode, semanticScope);
+        Class<?> leftValueType = semanticScope.getDecoration(leftNode, Decorations.ValueType.class).getValueType();
 
-        Input rightInput = new Input();
+        semanticScope.setCondition(rightNode, Read.class);
         Output rightOutput;
 
         if (operation != null) {
-            rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+            rightOutput = analyze(rightNode, classNode, semanticScope);
+            Class<?> rightValueType = semanticScope.getDecoration(rightNode, ValueType.class).getValueType();
             boolean shift = false;
 
             if (operation == Operation.MUL) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
             } else if (operation == Operation.DIV) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
             } else if (operation == Operation.REM) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
             } else if (operation == Operation.ADD) {
-                promote = AnalyzerCaster.promoteAdd(leftOutput.actual, rightOutput.actual);
+                promote = AnalyzerCaster.promoteAdd(leftValueType, rightValueType);
             } else if (operation == Operation.SUB) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
             } else if (operation == Operation.LSH) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, false);
-                shiftDistance = AnalyzerCaster.promoteNumeric(rightOutput.actual, false);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, false);
+                shiftDistance = AnalyzerCaster.promoteNumeric(rightValueType, false);
                 shift = true;
             } else if (operation == Operation.RSH) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, false);
-                shiftDistance = AnalyzerCaster.promoteNumeric(rightOutput.actual, false);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, false);
+                shiftDistance = AnalyzerCaster.promoteNumeric(rightValueType, false);
                 shift = true;
             } else if (operation == Operation.USH) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, false);
-                shiftDistance = AnalyzerCaster.promoteNumeric(rightOutput.actual, false);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, false);
+                shiftDistance = AnalyzerCaster.promoteNumeric(rightValueType, false);
                 shift = true;
             } else if (operation == Operation.BWAND) {
-                promote = AnalyzerCaster.promoteXor(leftOutput.actual, rightOutput.actual);
+                promote = AnalyzerCaster.promoteXor(leftValueType, rightValueType);
             } else if (operation == Operation.XOR) {
-                promote = AnalyzerCaster.promoteXor(leftOutput.actual, rightOutput.actual);
+                promote = AnalyzerCaster.promoteXor(leftValueType, rightValueType);
             } else if (operation == Operation.BWOR) {
-                promote = AnalyzerCaster.promoteXor(leftOutput.actual, rightOutput.actual);
+                promote = AnalyzerCaster.promoteXor(leftValueType, rightValueType);
             } else {
                 throw createError(new IllegalStateException("Illegal tree structure."));
             }
 
             if (promote == null || (shift && shiftDistance == null)) {
                 throw createError(new ClassCastException("Cannot apply compound assignment " +
-                        "[" + operation.symbol + "=] to types [" + leftOutput.actual + "] and [" + rightOutput.actual + "]."));
+                        "[" + operation.symbol + "=] to types [" + leftValueType + "] and [" + rightValueType + "]."));
             }
 
             cat = operation == Operation.ADD && promote == String.class;
@@ -139,7 +147,7 @@ public class EAssignment extends AExpression {
             if (cat && rightOutput.expressionNode instanceof BinaryMathNode) {
                 BinaryMathNode binaryMathNode = (BinaryMathNode)rightOutput.expressionNode;
 
-                if (binaryMathNode.getOperation() == Operation.ADD && rightOutput.actual == String.class) {
+                if (binaryMathNode.getOperation() == Operation.ADD && rightValueType == String.class) {
                     ((BinaryMathNode)rightOutput.expressionNode).setCat(true);
                 }
             }
@@ -147,53 +155,51 @@ public class EAssignment extends AExpression {
             if (shift) {
                 if (promote == def.class) {
                     // shifts are promoted independently, but for the def type, we need object.
-                    rightInput.expected = promote;
+                    semanticScope.putDecoration(rightNode, new TargetType(def.class));
                 } else if (shiftDistance == long.class) {
-                    rightInput.expected = int.class;
-                    rightInput.explicit = true;
+                    semanticScope.putDecoration(rightNode, new TargetType(int.class));
+                    semanticScope.setCondition(rightNode, Explicit.class);
                 } else {
-                    rightInput.expected = shiftDistance;
+                    semanticScope.putDecoration(rightNode, new TargetType(shiftDistance));
                 }
             } else {
-                rightInput.expected = promote;
+                semanticScope.putDecoration(rightNode, new TargetType(promote));
             }
 
-            rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                    rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
+            rightCast = rightNode.cast(semanticScope);
 
-            there = AnalyzerCaster.getLegalCast(getLocation(), leftOutput.actual, promote, false, false);
-            back = AnalyzerCaster.getLegalCast(getLocation(), promote, leftOutput.actual, true, false);
+            there = AnalyzerCaster.getLegalCast(getLocation(), leftValueType, promote, false, false);
+            back = AnalyzerCaster.getLegalCast(getLocation(), promote, leftValueType, true, false);
         } else {
             // If the lhs node is a def optimized node we update the actual type to remove the need for a cast.
-            if (leftOutput.isDefOptimized) {
-                rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+            if (semanticScope.getCondition(leftNode, DefOptimized.class)) {
+                rightOutput = analyze(rightNode, classNode, semanticScope);
+                Class<?> rightValueType = semanticScope.getDecoration(rightNode, ValueType.class).getValueType();
 
-                if (rightOutput.actual == void.class) {
+                if (rightValueType == void.class) {
                     throw createError(new IllegalArgumentException("Right-hand side cannot be a [void] type for assignment."));
                 }
 
-                rightInput.expected = rightOutput.actual;
-                leftOutput.actual = rightOutput.actual;
-                leftOutput.expressionNode.setExpressionType(rightOutput.actual);
-
+                leftValueType = rightValueType;
+                leftOutput.expressionNode.setExpressionType(rightValueType);
                 ExpressionNode expressionNode = leftOutput.expressionNode;
 
                 if (expressionNode instanceof DotNode && ((DotNode)expressionNode).getRightNode() instanceof DotSubDefNode) {
-                    ((DotNode)expressionNode).getRightNode().setExpressionType(leftOutput.actual);
+                    ((DotNode)expressionNode).getRightNode().setExpressionType(leftValueType);
                 } else if (expressionNode instanceof BraceNode && ((BraceNode)expressionNode).getRightNode() instanceof BraceSubDefNode) {
-                    ((BraceNode)expressionNode).getRightNode().setExpressionType(leftOutput.actual);
+                    ((BraceNode)expressionNode).getRightNode().setExpressionType(leftValueType);
                 }
             // Otherwise, we must adapt the rhs type to the lhs type with a cast.
             } else {
-                rightInput.expected = leftOutput.actual;
-                rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+                semanticScope.putDecoration(rightNode, new TargetType(leftValueType));
+                rightOutput = analyze(rightNode, classNode, semanticScope);
+                rightCast = rightNode.cast(semanticScope);
             }
-
-            rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                    rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
         }
 
-        output.actual = input.read ? leftOutput.actual : void.class;
+        boolean read = semanticScope.getCondition(this, Read.class);
+        ValueType valueType = new ValueType(read ? leftValueType : void.class);
+        semanticScope.putDecoration(this, valueType);
 
         AssignmentNode assignmentNode = new AssignmentNode();
 
@@ -201,11 +207,11 @@ public class EAssignment extends AExpression {
         assignmentNode.setRightNode(cast(rightOutput.expressionNode, rightCast));
 
         assignmentNode.setLocation(getLocation());
-        assignmentNode.setExpressionType(output.actual);
+        assignmentNode.setExpressionType(valueType.getValueType());
         assignmentNode.setCompoundType(promote);
         assignmentNode.setPost(postIfRead);
         assignmentNode.setOperation(operation);
-        assignmentNode.setRead(input.read);
+        assignmentNode.setRead(read);
         assignmentNode.setCat(cat);
         assignmentNode.setThere(there);
         assignmentNode.setBack(back);

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

@@ -22,12 +22,17 @@ package org.elasticsearch.painless.node;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BinaryMathNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 import java.util.regex.Pattern;
@@ -62,51 +67,58 @@ public class EBinary extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
-        Class<?> promote = null;            // promoted type
-        Class<?> shiftDistance = null;      // for shifts, the rhs is promoted independently
-        boolean originallyExplicit = input.explicit; // record whether there was originally an explicit cast
+        Class<?> promote;
+        Class<?> shiftDistance = null;
 
-        Input leftInput = new Input();
-        Output leftOutput = analyze(leftNode, classNode, semanticScope, leftInput);
+        semanticScope.setCondition(leftNode, Read.class);
+        Output leftOutput = analyze(leftNode, classNode, semanticScope);
+        Class<?> leftValueType = semanticScope.getDecoration(leftNode, ValueType.class).getValueType();
+
+        semanticScope.setCondition(rightNode, Read.class);
+        Output rightOutput = analyze(rightNode, classNode, semanticScope);
+        Class<?> rightValueType = semanticScope.getDecoration(rightNode, ValueType.class).getValueType();
 
         Output output = new Output();
-        Input rightInput = new Input();
-        Output rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+        Class<?> valueType;
+        PainlessCast leftCast = null;
+        PainlessCast rightCast = null;
 
         if (operation == Operation.FIND || operation == Operation.MATCH) {
-            leftInput.expected = String.class;
-            rightInput.expected = Pattern.class;
+            semanticScope.putDecoration(leftNode, new TargetType(String.class));
+            semanticScope.putDecoration(rightNode, new TargetType(Pattern.class));
+            leftCast = leftNode.cast(semanticScope);
+            rightCast = rightNode.cast(semanticScope);
             promote = boolean.class;
-            output.actual = boolean.class;
+            valueType = boolean.class;
         } else {
             if (operation == Operation.MUL || operation == Operation.DIV || operation == Operation.REM) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
             } else if (operation == Operation.ADD) {
-                promote = AnalyzerCaster.promoteAdd(leftOutput.actual, rightOutput.actual);
+                promote = AnalyzerCaster.promoteAdd(leftValueType, rightValueType);
             } else if (operation == Operation.SUB) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
             } else if (operation == Operation.LSH || operation == Operation.RSH || operation == Operation.USH) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, false);
-                shiftDistance = AnalyzerCaster.promoteNumeric(rightOutput.actual, false);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, false);
+                shiftDistance = AnalyzerCaster.promoteNumeric(rightValueType, false);
 
                 if (shiftDistance == null) {
                     promote = null;
                 }
             } else if (operation == Operation.BWOR || operation == Operation.BWAND) {
-                promote = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, false);
+                promote = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, false);
             } else if (operation == Operation.XOR) {
-                promote = AnalyzerCaster.promoteXor(leftOutput.actual, rightOutput.actual);
+                promote = AnalyzerCaster.promoteXor(leftValueType, rightValueType);
             } else {
                 throw createError(new IllegalStateException("unexpected binary operation [" + operation.name + "]"));
             }
@@ -114,20 +126,17 @@ public class EBinary extends AExpression {
             if (promote == null) {
                 throw createError(new ClassCastException("cannot apply the " + operation.name + " operator " +
                         "[" + operation.symbol + "] to the types " +
-                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftOutput.actual) + "] and " +
-                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightOutput.actual) + "]"));
+                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftValueType) + "] and " +
+                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightValueType) + "]"));
             }
 
-            output.actual = promote;
+            valueType = promote;
 
             if (operation == Operation.ADD && promote == String.class) {
-                leftInput.expected = leftOutput.actual;
-                rightInput.expected = rightOutput.actual;
-
                 if (leftOutput.expressionNode instanceof BinaryMathNode) {
                     BinaryMathNode binaryMathNode = (BinaryMathNode)leftOutput.expressionNode;
 
-                    if (binaryMathNode.getOperation() == Operation.ADD && leftOutput.actual == String.class) {
+                    if (binaryMathNode.getOperation() == Operation.ADD && leftValueType == String.class) {
                         ((BinaryMathNode)leftOutput.expressionNode).setCat(true);
                     }
                 }
@@ -135,51 +144,47 @@ public class EBinary extends AExpression {
                 if (rightOutput.expressionNode instanceof BinaryMathNode) {
                     BinaryMathNode binaryMathNode = (BinaryMathNode)rightOutput.expressionNode;
 
-                    if (binaryMathNode.getOperation() == Operation.ADD && rightOutput.actual == String.class) {
+                    if (binaryMathNode.getOperation() == Operation.ADD && rightValueType == String.class) {
                         ((BinaryMathNode)rightOutput.expressionNode).setCat(true);
                     }
                 }
             } else if (promote == def.class || shiftDistance == def.class) {
-                leftInput.expected = leftOutput.actual;
-                rightInput.expected = rightOutput.actual;
+                TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
 
-                if (input.expected != null) {
-                    output.actual = input.expected;
+                if (targetType != null) {
+                    valueType = targetType.getTargetType();
                 }
             } else {
-                leftInput.expected = promote;
+                semanticScope.putDecoration(leftNode, new TargetType(promote));
 
                 if (operation == Operation.LSH || operation == Operation.RSH || operation == Operation.USH) {
                     if (shiftDistance == long.class) {
-                        rightInput.expected = int.class;
-                        rightInput.explicit = true;
+                        semanticScope.putDecoration(rightNode, new TargetType(int.class));
+                        semanticScope.setCondition(rightNode, Explicit.class);
                     } else {
-                        rightInput.expected = shiftDistance;
+                        semanticScope.putDecoration(rightNode, new TargetType(shiftDistance));
                     }
                 } else {
-                    rightInput.expected = promote;
+                    semanticScope.putDecoration(rightNode, new TargetType(promote));
                 }
+
+                leftCast = leftNode.cast(semanticScope);
+                rightCast = rightNode.cast(semanticScope);
             }
         }
 
-        PainlessCast leftCast = AnalyzerCaster.getLegalCast(leftNode.getLocation(),
-                leftOutput.actual, leftInput.expected, leftInput.explicit, leftInput.internal);
-        PainlessCast rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
         BinaryMathNode binaryMathNode = new BinaryMathNode();
-
         binaryMathNode.setLeftNode(cast(leftOutput.expressionNode, leftCast));
         binaryMathNode.setRightNode(cast(rightOutput.expressionNode, rightCast));
-
         binaryMathNode.setLocation(getLocation());
-        binaryMathNode.setExpressionType(output.actual);
+        binaryMathNode.setExpressionType(valueType);
         binaryMathNode.setBinaryType(promote);
         binaryMathNode.setShiftType(shiftDistance);
         binaryMathNode.setOperation(operation);
         binaryMathNode.setCat(false);
-        binaryMathNode.setOriginallExplicit(originallyExplicit);
-
+        binaryMathNode.setOriginallyExplicit(semanticScope.getCondition(this, Explicit.class));
         output.expressionNode = binaryMathNode;
 
         return output;

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

@@ -19,13 +19,16 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BooleanNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -59,42 +62,37 @@ public class EBool extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
         Output output = new Output();
 
-        Input leftInput = new Input();
-        leftInput.expected = boolean.class;
-        Output leftOutput = analyze(leftNode, classNode, semanticScope, leftInput);
-        PainlessCast leftCast = AnalyzerCaster.getLegalCast(leftNode.getLocation(),
-                leftOutput.actual, leftInput.expected, leftInput.explicit, leftInput.internal);
+        semanticScope.setCondition(leftNode, Read.class);
+        semanticScope.putDecoration(leftNode, new TargetType(boolean.class));
+        Output leftOutput = analyze(leftNode, classNode, semanticScope);
+        PainlessCast leftCast = leftNode.cast(semanticScope);
 
-        Input rightInput = new Input();
-        rightInput.expected = boolean.class;
-        Output rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
-        PainlessCast rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
+        semanticScope.setCondition(rightNode, Read.class);
+        semanticScope.putDecoration(rightNode, new TargetType(boolean.class));
+        Output rightOutput = analyze(rightNode, classNode, semanticScope);
+        PainlessCast rightCast = rightNode.cast(semanticScope);
 
-        output.actual = boolean.class;
+        semanticScope.putDecoration(this, new ValueType(boolean.class));
 
         BooleanNode booleanNode = new BooleanNode();
-
         booleanNode.setLeftNode(cast(leftOutput.expressionNode, leftCast));
         booleanNode.setRightNode(cast(rightOutput.expressionNode, rightCast));
-
         booleanNode.setLocation(getLocation());
-        booleanNode.setExpressionType(output.actual);
+        booleanNode.setExpressionType(boolean.class);
         booleanNode.setOperation(operation);
-
         output.expressionNode = booleanNode;
 
         return output;

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

@@ -20,9 +20,12 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ConstantNode;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
  * Represents a boolean constant.
@@ -42,25 +45,24 @@ public class EBoolean extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to boolean constant [" + bool + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: boolean constant [" + bool + "] not used"));
         }
 
         Output output = new Output();
 
-        output.actual = boolean.class;
+        semanticScope.putDecoration(this, new ValueType(boolean.class));
 
         ConstantNode constantNode = new ConstantNode();
         constantNode.setLocation(getLocation());
-        constantNode.setExpressionType(output.actual);
+        constantNode.setExpressionType(boolean.class);
         constantNode.setConstant(bool);
-
         output.expressionNode = constantNode;
 
         return output;

+ 62 - 51
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBrace.java

@@ -19,9 +19,7 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BraceNode;
 import org.elasticsearch.painless.ir.BraceSubDefNode;
 import org.elasticsearch.painless.ir.BraceSubNode;
@@ -33,6 +31,13 @@ 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.Decorations.DefOptimized;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.time.ZonedDateTime;
 import java.util.List;
@@ -63,49 +68,56 @@ public class EBrace extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.read == false && input.write == false) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        boolean read = semanticScope.getCondition(this, Read.class);
+        boolean write = semanticScope.getCondition(this, Write.class);
+
+        if (read == false && write == false) {
             throw createError(new IllegalArgumentException("not a statement: result of brace operator not used"));
         }
 
-        Output prefixOutput = analyze(prefixNode, classNode, semanticScope, new Input());
+        semanticScope.setCondition(prefixNode, Read.class);
+        Output prefixOutput = analyze(prefixNode, classNode, semanticScope);
+        Class<?> prefixValueType = semanticScope.getDecoration(prefixNode, ValueType.class).getValueType();
 
         ExpressionNode expressionNode;
         Output output = new Output();
+        Class<?> valueType;
 
-        if (prefixOutput.actual.isArray()) {
-            Input indexInput = new Input();
-            indexInput.expected = int.class;
-            Output indexOutput = analyze(indexNode, classNode, semanticScope, indexInput);
-            PainlessCast indexCast = AnalyzerCaster.getLegalCast(indexNode.getLocation(),
-                    indexOutput.actual, indexInput.expected, indexInput.explicit, indexInput.internal);
-
-            output.actual = prefixOutput.actual.getComponentType();
+        if (prefixValueType.isArray()) {
+            semanticScope.setCondition(indexNode, Read.class);
+            semanticScope.putDecoration(indexNode, new TargetType(int.class));
+            Output indexOutput = analyze(indexNode, classNode, semanticScope);
+            PainlessCast indexCast = indexNode.cast(semanticScope);
+            valueType = prefixValueType.getComponentType();
 
             BraceSubNode braceSubNode = new BraceSubNode();
             braceSubNode.setChildNode(cast(indexOutput.expressionNode, indexCast));
             braceSubNode.setLocation(getLocation());
-            braceSubNode.setExpressionType(output.actual);
+            braceSubNode.setExpressionType(valueType);
             expressionNode = braceSubNode;
-        } else if (prefixOutput.actual == def.class) {
-            Input indexInput = new Input();
-            Output indexOutput = analyze(indexNode, classNode, semanticScope, indexInput);
+        } else if (prefixValueType == def.class) {
+            semanticScope.setCondition(indexNode, Read.class);
+            Output indexOutput = analyze(indexNode, classNode, semanticScope);
 
+            TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
             // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
-            output.actual = input.expected == null || input.expected == ZonedDateTime.class || input.explicit ? def.class : input.expected;
-            output.isDefOptimized = true;
+            valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class ||
+                    semanticScope.getCondition(this, Explicit.class) ? def.class : targetType.getTargetType();
+            semanticScope.setCondition(this, DefOptimized.class);
 
             BraceSubDefNode braceSubDefNode = new BraceSubDefNode();
             braceSubDefNode.setChildNode(indexOutput.expressionNode);
             braceSubDefNode.setLocation(getLocation());
-            braceSubDefNode.setExpressionType(output.actual);
+            braceSubDefNode.setExpressionType(valueType);
             expressionNode = braceSubDefNode;
-        } else if (Map.class.isAssignableFrom(prefixOutput.actual)) {
-            Class<?> targetClass = prefixOutput.actual;
-            String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
+        } else if (Map.class.isAssignableFrom(prefixValueType)) {
+            String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(prefixValueType);
 
-            PainlessMethod getter = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1);
-            PainlessMethod setter = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(targetClass, false, "put", 2);
+            PainlessMethod getter =
+                    semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(prefixValueType, false, "get", 1);
+            PainlessMethod setter =
+                    semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(prefixValueType, 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 + "]."));
@@ -123,14 +135,13 @@ public class EBrace extends AExpression {
             Output indexOutput;
             PainlessCast indexCast;
 
-            if ((input.read == false || getter != null) && (input.write == false || setter != null)) {
-                Input indexInput = new Input();
-                indexInput.expected = setter != null ? setter.typeParameters.get(0) : getter.typeParameters.get(0);
-                indexOutput = analyze(indexNode, classNode, semanticScope, indexInput);
-                indexCast = AnalyzerCaster.getLegalCast(indexNode.getLocation(),
-                        indexOutput.actual, indexInput.expected, indexInput.explicit, indexInput.internal);
-
-                output.actual = setter != null ? setter.typeParameters.get(1) : getter.returnType;
+            if ((read == false || getter != null) && (write == false || setter != null)) {
+                semanticScope.setCondition(indexNode, Read.class);
+                semanticScope.putDecoration(indexNode,
+                        new TargetType(setter != null ? setter.typeParameters.get(0) : getter.typeParameters.get(0)));
+                indexOutput = analyze(indexNode, classNode, semanticScope);
+                indexCast = indexNode.cast(semanticScope);
+                valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
             } else {
                 throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + canonicalClassName + "]."));
             }
@@ -138,16 +149,17 @@ public class EBrace extends AExpression {
             MapSubShortcutNode mapSubShortcutNode = new MapSubShortcutNode();
             mapSubShortcutNode.setChildNode(cast(indexOutput.expressionNode, indexCast));
             mapSubShortcutNode.setLocation(getLocation());
-            mapSubShortcutNode.setExpressionType(output.actual);
+            mapSubShortcutNode.setExpressionType(valueType);
             mapSubShortcutNode.setGetter(getter);
             mapSubShortcutNode.setSetter(setter);
             expressionNode = mapSubShortcutNode;
-        } else if (List.class.isAssignableFrom(prefixOutput.actual)) {
-            Class<?> targetClass = prefixOutput.actual;
-            String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
+        } else if (List.class.isAssignableFrom(prefixValueType)) {
+            String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(prefixValueType);
 
-            PainlessMethod getter = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1);
-            PainlessMethod setter = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(targetClass, false, "set", 2);
+            PainlessMethod getter =
+                    semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(prefixValueType, false, "get", 1);
+            PainlessMethod setter =
+                    semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(prefixValueType, false, "set", 2);
 
             if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 ||
                     getter.typeParameters.get(0) != int.class)) {
@@ -166,14 +178,12 @@ public class EBrace extends AExpression {
             Output indexOutput;
             PainlessCast indexCast;
 
-            if ((input.read == false || getter != null) && (input.write == false || setter != null)) {
-                Input indexInput = new Input();
-                indexInput.expected = int.class;
-                indexOutput = analyze(indexNode, classNode, semanticScope, indexInput);
-                indexCast = AnalyzerCaster.getLegalCast(indexNode.getLocation(),
-                        indexOutput.actual, indexInput.expected, indexInput.explicit, indexInput.internal);
-
-                output.actual = setter != null ? setter.typeParameters.get(1) : getter.returnType;
+            if ((read == false || getter != null) && (write == false || setter != null)) {
+                semanticScope.setCondition(indexNode, Read.class);
+                semanticScope.putDecoration(indexNode, new TargetType(int.class));
+                indexOutput = analyze(indexNode, classNode, semanticScope);
+                indexCast = indexNode.cast(semanticScope);
+                valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
             } else {
                 throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + canonicalClassName + "]."));
             }
@@ -181,21 +191,22 @@ public class EBrace extends AExpression {
             ListSubShortcutNode listSubShortcutNode = new ListSubShortcutNode();
             listSubShortcutNode.setChildNode(cast(indexOutput.expressionNode, indexCast));
             listSubShortcutNode.setLocation(getLocation());
-            listSubShortcutNode.setExpressionType(output.actual);
+            listSubShortcutNode.setExpressionType(valueType);
             listSubShortcutNode.setGetter(getter);
             listSubShortcutNode.setSetter(setter);
             expressionNode = listSubShortcutNode;
         } else {
             throw createError(new IllegalArgumentException("Illegal array access on type " +
-                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(prefixOutput.actual) + "]."));
+                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(prefixValueType) + "]."));
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         BraceNode braceNode = new BraceNode();
         braceNode.setLeftNode(prefixOutput.expressionNode);
         braceNode.setRightNode(expressionNode);
         braceNode.setLocation(getLocation());
-        braceNode.setExpressionType(output.actual);
-
+        braceNode.setExpressionType(valueType);
         output.expressionNode = braceNode;
 
         return output;

+ 73 - 47
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECall.java

@@ -19,9 +19,7 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.CallNode;
 import org.elasticsearch.painless.ir.CallSubDefNode;
 import org.elasticsearch.painless.ir.CallSubNode;
@@ -29,10 +27,18 @@ import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ExpressionNode;
 import org.elasticsearch.painless.ir.NullSafeSubNode;
 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.spi.annotation.NonDeterministicAnnotation;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.PartialCanonicalTypeName;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.StaticType;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
@@ -40,8 +46,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName;
-
 /**
  * Represents a method call and defers to a child subnode.
  */
@@ -80,45 +84,52 @@ public class ECall extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to method call [" + methodName + "/" + argumentNodes.size() + "]"));
         }
 
-        Output output = new Output();
+        semanticScope.setCondition(prefixNode, Read.class);
+        Output prefixOutput = prefixNode.analyze(classNode, semanticScope);
+        ValueType prefixValueType = semanticScope.getDecoration(prefixNode, ValueType.class);
+        StaticType prefixStaticType = semanticScope.getDecoration(prefixNode, StaticType.class);
 
-        Input prefixInput = new Input();
-        Output prefixOutput = prefixNode.analyze(classNode, semanticScope, prefixInput);
+        if (prefixValueType != null && prefixStaticType != null) {
+            throw createError(new IllegalStateException("cannot have both " +
+                    "value [" + prefixValueType.getValueCanonicalTypeName() + "] " +
+                    "and type [" + prefixStaticType.getStaticCanonicalTypeName() + "]"));
+        }
 
-        if (prefixOutput.partialCanonicalTypeName != null) {
-            throw createError(new IllegalArgumentException("cannot resolve symbol [" + prefixOutput.partialCanonicalTypeName + "]"));
+        if (semanticScope.hasDecoration(prefixNode, PartialCanonicalTypeName.class)) {
+            throw createError(new IllegalArgumentException("cannot resolve symbol " +
+                    "[" + semanticScope.getDecoration(prefixNode, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "]"));
         }
 
+        Output output = new Output();
+        Class<?> valueType;
         ExpressionNode expressionNode;
 
-        if (prefixOutput.actual == def.class) {
-            if (output.isStaticType) {
-                throw createError(new IllegalArgumentException("value required: " +
-                        "instead found unexpected type [" + PainlessLookupUtility.typeToCanonicalTypeName(output.actual) + "]"));
-            }
-
+        if (prefixValueType != null && prefixValueType.getValueType() == def.class) {
             List<Output> argumentOutputs = new ArrayList<>(argumentNodes.size());
 
             for (AExpression argument : argumentNodes) {
-                Input expressionInput = new Input();
-                expressionInput.internal = true;
-                Output expressionOutput = analyze(argument, classNode, semanticScope, expressionInput);
+                semanticScope.setCondition(argument, Read.class);
+                semanticScope.setCondition(argument, Internal.class);
+                Output expressionOutput = analyze(argument, classNode, semanticScope);
+                Class<?> argumentValueType = semanticScope.getDecoration(argument, ValueType.class).getValueType();
                 argumentOutputs.add(expressionOutput);
 
-                if (expressionOutput.actual == void.class) {
+                if (argumentValueType == void.class) {
                     throw createError(new IllegalArgumentException(
                             "Argument(s) cannot be of [void] type when calling method [" + methodName + "]."));
                 }
             }
 
+            TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
             // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
-            output.actual = input.expected == null || input.expected == ZonedDateTime.class || input.explicit ? def.class : input.expected;
+            valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class ||
+                    semanticScope.getCondition(this, Explicit.class) ? def.class : targetType.getTargetType();
 
             CallSubDefNode callSubDefNode = new CallSubDefNode();
 
@@ -127,17 +138,35 @@ public class ECall extends AExpression {
             }
 
             callSubDefNode.setLocation(getLocation());
-            callSubDefNode.setExpressionType(output.actual);
+            callSubDefNode.setExpressionType(valueType);
             callSubDefNode.setName(methodName);
-
             expressionNode = callSubDefNode;
         } else {
-            PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(
-                    prefixOutput.actual, prefixOutput.isStaticType, methodName, argumentNodes.size());
-
-            if (method == null) {
-                throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(prefixOutput.actual) + ", " +
-                        "" + methodName + "/" + argumentNodes.size() + "] not found"));
+            PainlessMethod method;
+            Class<?> boxType;
+
+            if (prefixValueType != null) {
+                method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(
+                        prefixValueType.getValueType(), false, methodName, argumentNodes.size());
+                boxType = prefixValueType.getValueType();
+
+                if (method == null) {
+                    throw createError(new IllegalArgumentException("member method " +
+                            "[" + prefixValueType.getValueCanonicalTypeName() + ", " + methodName + "/" + argumentNodes.size() + "] " +
+                            "not found"));
+                }
+            } else if (prefixStaticType != null) {
+                method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(
+                        prefixStaticType.getStaticType(), true, methodName, argumentNodes.size());
+                boxType = prefixStaticType.getStaticType();
+
+                if (method == null) {
+                    throw createError(new IllegalArgumentException("static method " +
+                            "[" + prefixStaticType.getStaticCanonicalTypeName() + ", " + methodName + "/" + argumentNodes.size() + "] " +
+                            "not found"));
+                }
+            } else {
+                throw createError(new IllegalStateException("value required: instead found no value"));
             }
 
             semanticScope.getScriptScope().markNonDeterministic(method.annotations.containsKey(NonDeterministicAnnotation.class));
@@ -148,17 +177,15 @@ public class ECall extends AExpression {
             for (int argument = 0; argument < argumentNodes.size(); ++argument) {
                 AExpression expression = argumentNodes.get(argument);
 
-                Input expressionInput = new Input();
-                expressionInput.expected = method.typeParameters.get(argument);
-                expressionInput.internal = true;
-                Output expressionOutput = analyze(expression, classNode, semanticScope, expressionInput);
+                semanticScope.setCondition(expression, Read.class);
+                semanticScope.putDecoration(expression, new TargetType(method.typeParameters.get(argument)));
+                semanticScope.setCondition(expression, Internal.class);
+                Output expressionOutput = analyze(expression, classNode, semanticScope);
                 argumentOutputs.add(expressionOutput);
-                argumentCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                        expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal));
-
+                argumentCasts.add(expression.cast(semanticScope));
             }
 
-            output.actual = method.returnType;
+            valueType = method.returnType;
 
             CallSubNode callSubNode = new CallSubNode();
 
@@ -167,32 +194,31 @@ public class ECall extends AExpression {
             }
 
             callSubNode.setLocation(getLocation());
-            callSubNode.setExpressionType(output.actual);
+            callSubNode.setExpressionType(valueType);
             callSubNode.setMethod(method);
-            callSubNode.setBox(prefixOutput.actual);
+            callSubNode.setBox(boxType);
             expressionNode = callSubNode;
         }
 
         if (isNullSafe) {
-            if (output.actual.isPrimitive()) {
+            if (valueType.isPrimitive()) {
                 throw new IllegalArgumentException("Result of null safe operator must be nullable");
             }
 
             NullSafeSubNode nullSafeSubNode = new NullSafeSubNode();
             nullSafeSubNode.setChildNode(expressionNode);
             nullSafeSubNode.setLocation(getLocation());
-            nullSafeSubNode.setExpressionType(output.actual);
+            nullSafeSubNode.setExpressionType(valueType);
             expressionNode = nullSafeSubNode;
         }
 
-        CallNode callNode = new CallNode();
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
+        CallNode callNode = new CallNode();
         callNode.setLeftNode(prefixOutput.expressionNode);
         callNode.setRightNode(expressionNode);
-
         callNode.setLocation(getLocation());
-        callNode.setExpressionType(output.actual);
-
+        callNode.setExpressionType(valueType);
         output.expressionNode = callNode;
 
         return output;

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

@@ -19,10 +19,7 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.FieldNode;
 import org.elasticsearch.painless.ir.MemberCallNode;
@@ -31,7 +28,14 @@ import org.elasticsearch.painless.lookup.PainlessClassBinding;
 import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.spi.annotation.NonDeterministicAnnotation;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
 import org.elasticsearch.painless.symbol.FunctionTable;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
@@ -63,8 +67,8 @@ public class ECallLocal extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to function call [" + methodName + "/" + argumentNodes.size() + "]"));
         }
@@ -79,6 +83,7 @@ public class ECallLocal extends AExpression {
         String bindingName = null;
 
         Output output = new Output();
+        Class<?> valueType;
 
         localFunction = scriptScope.getFunctionTable().getFunction(methodName, argumentNodes.size());
 
@@ -133,15 +138,15 @@ public class ECallLocal extends AExpression {
 
         if (localFunction != null) {
             typeParameters = new ArrayList<>(localFunction.getTypeParameters());
-            output.actual = localFunction.getReturnType();
+            valueType = localFunction.getReturnType();
         } else if (importedMethod != null) {
             scriptScope.markNonDeterministic(importedMethod.annotations.containsKey(NonDeterministicAnnotation.class));
             typeParameters = new ArrayList<>(importedMethod.typeParameters);
-            output.actual = importedMethod.returnType;
+            valueType = importedMethod.returnType;
         } else if (classBinding != null) {
             scriptScope.markNonDeterministic(classBinding.annotations.containsKey(NonDeterministicAnnotation.class));
             typeParameters = new ArrayList<>(classBinding.typeParameters);
-            output.actual = classBinding.returnType;
+            valueType = classBinding.returnType;
             bindingName = scriptScope.getNextSyntheticName("class_binding");
 
             FieldNode fieldNode = new FieldNode();
@@ -153,7 +158,7 @@ public class ECallLocal extends AExpression {
             classNode.addFieldNode(fieldNode);
         } else if (instanceBinding != null) {
             typeParameters = new ArrayList<>(instanceBinding.typeParameters);
-            output.actual = instanceBinding.returnType;
+            valueType = instanceBinding.returnType;
             bindingName = scriptScope.getNextSyntheticName("instance_binding");
 
             FieldNode fieldNode = new FieldNode();
@@ -177,16 +182,16 @@ public class ECallLocal extends AExpression {
         for (int argument = 0; argument < argumentNodes.size(); ++argument) {
             AExpression expression = argumentNodes.get(argument);
 
-            Input argumentInput = new Input();
-            argumentInput.expected = typeParameters.get(argument + classBindingOffset);
-            argumentInput.internal = true;
-            Output argumentOutput = analyze(expression, classNode, semanticScope, argumentInput);
+            semanticScope.setCondition(expression, Read.class);
+            semanticScope.putDecoration(expression, new TargetType(typeParameters.get(argument + classBindingOffset)));
+            semanticScope.setCondition(expression, Internal.class);
+            Output argumentOutput = analyze(expression, classNode, semanticScope);
             argumentOutputs.add(argumentOutput);
-            argumentCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                    argumentOutput.actual, argumentInput.expected, argumentInput.explicit, argumentInput.internal));
-
+            argumentCasts.add(expression.cast(semanticScope));
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         MemberCallNode memberCallNode = new MemberCallNode();
 
         for (int argument = 0; argument < argumentNodes.size(); ++argument) {
@@ -194,14 +199,13 @@ public class ECallLocal extends AExpression {
         }
 
         memberCallNode.setLocation(getLocation());
-        memberCallNode.setExpressionType(output.actual);
+        memberCallNode.setExpressionType(valueType);
         memberCallNode.setLocalFunction(localFunction);
         memberCallNode.setImportedMethod(importedMethod);
         memberCallNode.setClassBinding(classBinding);
         memberCallNode.setClassBindingOffset(classBindingOffset);
         memberCallNode.setBindingName(bindingName);
         memberCallNode.setInstanceBinding(instanceBinding);
-
         output.expressionNode = memberCallNode;
 
         return output;

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

@@ -22,12 +22,16 @@ package org.elasticsearch.painless.node;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ComparisonNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -61,13 +65,13 @@ public class EComp extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
@@ -76,16 +80,18 @@ public class EComp extends AExpression {
 
         Output output = new Output();
 
-        Input leftInput = new Input();
-        Output leftOutput = analyze(leftNode, classNode, semanticScope, leftInput);
+        semanticScope.setCondition(leftNode, Read.class);
+        Output leftOutput = analyze(leftNode, classNode, semanticScope);
+        Class<?> leftValueType = semanticScope.getDecoration(leftNode, ValueType.class).getValueType();
 
-        Input rightInput = new Input();
-        Output rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+        semanticScope.setCondition(rightNode, Read.class);
+        Output rightOutput = analyze(rightNode, classNode, semanticScope);
+        Class<?> rightValueType = semanticScope.getDecoration(rightNode, ValueType.class).getValueType();
 
         if (operation == Operation.EQ || operation == Operation.EQR || operation == Operation.NE || operation == Operation.NER) {
-            promotedType = AnalyzerCaster.promoteEquality(leftOutput.actual, rightOutput.actual);
+            promotedType = AnalyzerCaster.promoteEquality(leftValueType, rightValueType);
         } else if (operation == Operation.GT || operation == Operation.GTE || operation == Operation.LT || operation == Operation.LTE) {
-            promotedType = AnalyzerCaster.promoteNumeric(leftOutput.actual, rightOutput.actual, true);
+            promotedType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true);
         } else {
             throw createError(new IllegalStateException("unexpected binary operation [" + operation.name + "]"));
         }
@@ -93,16 +99,8 @@ public class EComp extends AExpression {
         if (promotedType == null) {
             throw createError(new ClassCastException("cannot apply the " + operation.name + " operator " +
                     "[" + operation.symbol + "] to the types " +
-                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftOutput.actual) + "] and " +
-                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightOutput.actual) + "]"));
-        }
-
-        if (operation != Operation.EQR && operation != Operation.NER && promotedType == def.class) {
-            leftInput.expected = leftOutput.actual;
-            rightInput.expected = rightOutput.actual;
-        } else {
-            leftInput.expected = promotedType;
-            rightInput.expected = promotedType;
+                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftValueType) + "] and " +
+                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightValueType) + "]"));
         }
 
         if ((operation == Operation.EQ || operation == Operation.EQR || operation == Operation.NE || operation == Operation.NER)
@@ -110,23 +108,25 @@ public class EComp extends AExpression {
             throw createError(new IllegalArgumentException("extraneous comparison of [null] constants"));
         }
 
-        PainlessCast leftCast = AnalyzerCaster.getLegalCast(leftNode.getLocation(),
-                leftOutput.actual, leftInput.expected, leftInput.explicit, leftInput.internal);
-        PainlessCast rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
+        PainlessCast leftCast = null;
+        PainlessCast rightCast = null;
 
-        output.actual = boolean.class;
+        if (operation == Operation.EQR || operation == Operation.NER || promotedType != def.class) {
+            semanticScope.putDecoration(leftNode, new TargetType(promotedType));
+            semanticScope.putDecoration(rightNode, new TargetType(promotedType));
+            leftCast = leftNode.cast(semanticScope);
+            rightCast = rightNode.cast(semanticScope);
+        }
 
-        ComparisonNode comparisonNode = new ComparisonNode();
+        semanticScope.putDecoration(this, new ValueType(boolean.class));
 
+        ComparisonNode comparisonNode = new ComparisonNode();
         comparisonNode.setLeftNode(cast(leftOutput.expressionNode, leftCast));
         comparisonNode.setRightNode(cast(rightOutput.expressionNode, rightCast));
-
         comparisonNode.setLocation(getLocation());
-        comparisonNode.setExpressionType(output.actual);
+        comparisonNode.setExpressionType(boolean.class);
         comparisonNode.setComparisonType(promotedType);
         comparisonNode.setOperation(operation);
-
         output.expressionNode = comparisonNode;
 
         return output;

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

@@ -21,11 +21,17 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ConditionalNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -35,89 +41,91 @@ import java.util.Objects;
 public class EConditional extends AExpression {
 
     private final AExpression conditionNode;
-    private final AExpression leftNode;
-    private final AExpression rightNode;
+    private final AExpression trueNode;
+    private final AExpression falseNode;
 
-    public EConditional(int identifier, Location location, AExpression conditionNode, AExpression leftNode, AExpression rightNode) {
+    public EConditional(int identifier, Location location, AExpression conditionNode, AExpression trueNode, AExpression falseNode) {
         super(identifier, location);
 
         this.conditionNode = Objects.requireNonNull(conditionNode);
-        this.leftNode = Objects.requireNonNull(leftNode);
-        this.rightNode = Objects.requireNonNull(rightNode);
+        this.trueNode = Objects.requireNonNull(trueNode);
+        this.falseNode = Objects.requireNonNull(falseNode);
     }
 
     public AExpression getConditionNode() {
         return conditionNode;
     }
 
-    public AExpression getLeftNode() {
-        return leftNode;
+    public AExpression getTrueNode() {
+        return trueNode;
     }
 
-    public AExpression getRightNode() {
-        return rightNode;
+    public AExpression getFalseNode() {
+        return falseNode;
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to conditional operation [?:]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: result not used from conditional operation [?:]"));
         }
 
         Output output = new Output();
 
-        Input conditionInput = new Input();
-        conditionInput.expected = boolean.class;
-        Output conditionOutput = analyze(conditionNode, classNode, semanticScope, conditionInput);
-        PainlessCast conditionCast = AnalyzerCaster.getLegalCast(classNode.getLocation(),
-                conditionOutput.actual, conditionInput.expected, conditionInput.explicit, conditionInput.internal);
 
-        Input leftInput = new Input();
-        leftInput.expected = input.expected;
-        leftInput.explicit = input.explicit;
-        leftInput.internal = input.internal;
-        Output leftOutput = analyze(leftNode, classNode, semanticScope, leftInput);
+        semanticScope.setCondition(conditionNode, Read.class);
+        semanticScope.putDecoration(conditionNode, new TargetType(boolean.class));
+        Output conditionOutput = analyze(conditionNode, classNode, semanticScope);
+        PainlessCast conditionCast = conditionNode.cast(semanticScope);
 
-        Input rightInput = new Input();
-        rightInput.expected = input.expected;
-        rightInput.explicit = input.explicit;
-        rightInput.internal = input.internal;
-        Output rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+        semanticScope.setCondition(trueNode, Read.class);
+        semanticScope.copyDecoration(this, trueNode, TargetType.class);
+        semanticScope.replicateCondition(this, trueNode, Explicit.class);
+        semanticScope.replicateCondition(this, trueNode, Internal.class);
+        Output leftOutput = analyze(trueNode, classNode, semanticScope);
+        Class<?> leftValueType = semanticScope.getDecoration(trueNode, ValueType.class).getValueType();
 
-        output.actual = input.expected;
+        semanticScope.setCondition(falseNode, Read.class);
+        semanticScope.copyDecoration(this, falseNode, TargetType.class);
+        semanticScope.replicateCondition(this, falseNode, Explicit.class);
+        semanticScope.replicateCondition(this, falseNode, Internal.class);
+        Output rightOutput = analyze(falseNode, classNode, semanticScope);
+        Class<?> rightValueType = semanticScope.getDecoration(falseNode, ValueType.class).getValueType();
 
-        if (input.expected == null) {
-            Class<?> promote = AnalyzerCaster.promoteConditional(leftOutput.actual, rightOutput.actual);
+        TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
+        Class<?> valueType;
+
+        if (targetType == null) {
+            Class<?> promote = AnalyzerCaster.promoteConditional(leftValueType, rightValueType);
 
             if (promote == null) {
                 throw createError(new ClassCastException("cannot apply the conditional operator [?:] to the types " +
-                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftOutput.actual) + "] and " +
-                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightOutput.actual) + "]"));
+                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftValueType) + "] and " +
+                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightValueType) + "]"));
             }
 
-            leftInput.expected = promote;
-            rightInput.expected = promote;
-            output.actual = promote;
+            semanticScope.putDecoration(trueNode, new TargetType(promote));
+            semanticScope.putDecoration(falseNode, new TargetType(promote));
+            valueType = promote;
+        } else {
+            valueType = targetType.getTargetType();
         }
 
-        PainlessCast leftCast = AnalyzerCaster.getLegalCast(leftNode.getLocation(),
-                leftOutput.actual, leftInput.expected, leftInput.explicit, leftInput.internal);
-        PainlessCast rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
+        PainlessCast leftCast = trueNode.cast(semanticScope);
+        PainlessCast rightCast = falseNode.cast(semanticScope);
 
-        ConditionalNode conditionalNode = new ConditionalNode();
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
+        ConditionalNode conditionalNode = new ConditionalNode();
         conditionalNode.setLeftNode(cast(leftOutput.expressionNode, leftCast));
         conditionalNode.setRightNode(cast(rightOutput.expressionNode, rightCast));
         conditionalNode.setConditionNode(cast(conditionOutput.expressionNode, conditionCast));
-
         conditionalNode.setLocation(getLocation());
-        conditionalNode.setExpressionType(output.actual);
-
+        conditionalNode.setExpressionType(valueType);
         output.expressionNode = conditionalNode;
 
         return output;

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

@@ -20,9 +20,12 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ConstantNode;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -44,21 +47,22 @@ public class EDecimal extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        return analyze(input, false);
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        return analyze(semanticScope, false);
     }
 
-    Output analyze(Input input, boolean negate) {
-        if (input.write) {
+    Output analyze(SemanticScope semanticScope, boolean negate) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to decimal constant [" + decimal + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: decimal constant [" + decimal + "] not used"));
         }
 
         Output output = new Output();
+        Class<?> valueType;
         Object constant;
 
         String decimal = negate ? "-" + this.decimal : this.decimal;
@@ -66,7 +70,7 @@ public class EDecimal extends AExpression {
         if (decimal.endsWith("f") || decimal.endsWith("F")) {
             try {
                 constant = Float.parseFloat(decimal.substring(0, decimal.length() - 1));
-                output.actual = float.class;
+                valueType = float.class;
             } catch (NumberFormatException exception) {
                 throw createError(new IllegalArgumentException("Invalid float constant [" + decimal + "]."));
             }
@@ -77,15 +81,17 @@ public class EDecimal extends AExpression {
             }
             try {
                 constant = Double.parseDouble(toParse);
-                output.actual = double.class;
+                valueType = double.class;
             } catch (NumberFormatException exception) {
                 throw createError(new IllegalArgumentException("Invalid double constant [" + decimal + "]."));
             }
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         ConstantNode constantNode = new ConstantNode();
         constantNode.setLocation(getLocation());
-        constantNode.setExpressionType(output.actual);
+        constantNode.setExpressionType(valueType);
         constantNode.setConstant(constant);
 
         output.expressionNode = constantNode;

+ 116 - 88
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDot.java

@@ -20,8 +20,6 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ConstantNode;
 import org.elasticsearch.painless.ir.DotNode;
@@ -38,6 +36,16 @@ 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.Decorations.DefOptimized;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.PartialCanonicalTypeName;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.StaticType;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.lang.reflect.Modifier;
 import java.time.ZonedDateTime;
@@ -45,8 +53,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
-import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName;
-
 /**
  * Represents a field load/store and defers to a child subnode.
  */
@@ -77,152 +83,167 @@ public class EDot extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.read == false && input.write == false) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        boolean read = semanticScope.getCondition(this, Read.class);
+        boolean write = semanticScope.getCondition(this, Write.class);
+
+        if (read == false && write == false) {
             throw createError(new IllegalArgumentException("not a statement: result of dot operator [.] not used"));
         }
 
         ScriptScope scriptScope = semanticScope.getScriptScope();
 
         Output output = new Output();
-        Output prefixOutput = prefixNode.analyze(classNode, semanticScope, new Input());
 
-        if (prefixOutput.partialCanonicalTypeName != null) {
-            if (output.isStaticType) {
-                throw createError(new IllegalArgumentException("value required: " +
-                        "instead found unexpected type [" + PainlessLookupUtility.typeToCanonicalTypeName(output.actual) + "]"));
+        semanticScope.setCondition(prefixNode, Read.class);
+        Output prefixOutput = prefixNode.analyze(classNode, semanticScope);
+        ValueType prefixValueType = semanticScope.getDecoration(prefixNode, ValueType.class);
+        StaticType prefixStaticType = semanticScope.getDecoration(prefixNode, StaticType.class);
+
+        if (prefixValueType != null && prefixStaticType != null) {
+            throw createError(new IllegalStateException("cannot have both " +
+                    "value [" + prefixValueType.getValueCanonicalTypeName() + "] " +
+                    "and type [" + prefixStaticType.getStaticCanonicalTypeName() + "]"));
+        }
+
+        if (semanticScope.hasDecoration(prefixNode, PartialCanonicalTypeName.class)) {
+            if (prefixValueType != null) {
+                throw createError(new IllegalArgumentException("value required: instead found unexpected type " +
+                        "[" + prefixValueType.getValueCanonicalTypeName() + "]"));
+            }
+
+            if (prefixStaticType != null) {
+                throw createError(new IllegalArgumentException("value required: instead found unexpected type " +
+                        "[" + prefixStaticType.getStaticType() + "]"));
             }
 
-            String canonicalTypeName = prefixOutput.partialCanonicalTypeName + "." + index;
+            String canonicalTypeName =
+                    semanticScope.getDecoration(prefixNode, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "." + index;
             Class<?> type = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
 
             if (type == null) {
-                output.partialCanonicalTypeName = canonicalTypeName;
+                semanticScope.putDecoration(this, new PartialCanonicalTypeName(canonicalTypeName));
             } else {
-                if (input.write) {
+                if (write) {
                     throw createError(new IllegalArgumentException("invalid assignment: " +
                             "cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "]"));
                 }
 
-                if (input.read == false) {
-                    throw createError(new IllegalArgumentException(
-                            "not a statement: static type [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "] not used"));
-                }
-
-                output.actual = type;
-                output.isStaticType = true;
+                semanticScope.putDecoration(this, new StaticType(type));
 
                 StaticNode staticNode = new StaticNode();
-
                 staticNode.setLocation(getLocation());
-                staticNode.setExpressionType(output.actual);
-
+                staticNode.setExpressionType(type);
                 output.expressionNode = staticNode;
             }
         } else {
-            Class<?> targetType = prefixOutput.actual;
-            String targetCanonicalTypeName = PainlessLookupUtility.typeToCanonicalTypeName(targetType);
-
             ExpressionNode expressionNode = null;
+            Class<?> valueType = null;
 
-            if (prefixOutput.actual.isArray()) {
-                if (output.isStaticType) {
-                    throw createError(new IllegalArgumentException("value required: " +
-                            "instead found unexpected type [" + PainlessLookupUtility.typeToCanonicalTypeName(output.actual) + "]"));
-                }
-
+            if (prefixValueType != null && prefixValueType.getValueType().isArray()) {
                 if ("length".equals(index)) {
-                    if (input.write) {
+                    if (write) {
                         throw createError(new IllegalArgumentException(
                                 "invalid assignment: cannot assign a value write to read-only field [length] for an array."));
                     }
 
-                    output.actual = int.class;
+                    valueType = int.class;
                 } else {
                     throw createError(new IllegalArgumentException(
-                            "Field [" + index + "] does not exist for type [" + targetCanonicalTypeName + "]."));
+                            "Field [" + index + "] does not exist for type [" + prefixValueType.getValueCanonicalTypeName() + "]."));
                 }
 
                 DotSubArrayLengthNode dotSubArrayLengthNode = new DotSubArrayLengthNode();
                 dotSubArrayLengthNode.setLocation(getLocation());
-                dotSubArrayLengthNode.setExpressionType(output.actual);
+                dotSubArrayLengthNode.setExpressionType(int.class);
                 expressionNode = dotSubArrayLengthNode;
-            } else if (prefixOutput.actual == def.class) {
-                if (output.isStaticType) {
-                    throw createError(new IllegalArgumentException("value required: " +
-                            "instead found unexpected type [" + PainlessLookupUtility.typeToCanonicalTypeName(output.actual) + "]"));
-                }
-
+            } else if (prefixValueType != null && prefixValueType.getValueType() == def.class) {
+                TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
                 // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
-                output.actual =
-                        input.expected == null || input.expected == ZonedDateTime.class || input.explicit ? def.class : input.expected;
-                output.isDefOptimized = true;
+                valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class ||
+                        semanticScope.getCondition(this, Explicit.class) ? def.class : targetType.getTargetType();
+                semanticScope.setCondition(this, DefOptimized.class);
 
                 DotSubDefNode dotSubDefNode = new DotSubDefNode();
                 dotSubDefNode.setLocation(getLocation());
-                dotSubDefNode.setExpressionType(output.actual);
+                dotSubDefNode.setExpressionType(valueType);
                 dotSubDefNode.setValue(index);
                 expressionNode = dotSubDefNode;
             } else {
-                PainlessField field =
-                        scriptScope.getPainlessLookup().lookupPainlessField(prefixOutput.actual, prefixOutput.isStaticType, index);
+                Class<?> prefixType;
+                String prefixCanonicalTypeName;
+                boolean isStatic;
+
+                if (prefixValueType != null) {
+                    prefixType = prefixValueType.getValueType();
+                    prefixCanonicalTypeName = prefixValueType.getValueCanonicalTypeName();
+                    isStatic = false;
+                } else if (prefixStaticType != null) {
+                    prefixType = prefixStaticType.getStaticType();
+                    prefixCanonicalTypeName = prefixStaticType.getStaticCanonicalTypeName();
+                    isStatic = true;
+                } else {
+                    throw createError(new IllegalStateException("value required: instead found no value"));
+                }
+
+                PainlessField field = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessField(prefixType, isStatic, index);
 
                 if (field == null) {
                     PainlessMethod getter;
                     PainlessMethod setter;
 
-                    getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixOutput.actual, false,
+                    getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
                             "get" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
 
                     if (getter == null) {
-                        getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixOutput.actual, false,
+                        getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
                                 "is" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
                     }
 
-                    setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixOutput.actual, false,
+                    setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
                             "set" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
 
                     if (getter != null || setter != null) {
                         if (getter != null && (getter.returnType == void.class || !getter.typeParameters.isEmpty())) {
                             throw createError(new IllegalArgumentException(
-                                    "Illegal get shortcut on field [" + index + "] for type [" + targetCanonicalTypeName + "]."));
+                                    "Illegal get shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
                         }
 
                         if (setter != null && (setter.returnType != void.class || setter.typeParameters.size() != 1)) {
                             throw createError(new IllegalArgumentException(
-                                    "Illegal set shortcut on field [" + index + "] for type [" + targetCanonicalTypeName + "]."));
+                                    "Illegal set shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
                         }
 
                         if (getter != null && setter != null && setter.typeParameters.get(0) != getter.returnType) {
                             throw createError(new IllegalArgumentException("Shortcut argument types must match."));
                         }
 
-                        if ((input.read == false || getter != null) && (input.write == false || setter != null)) {
-                            output.actual = setter != null ? setter.typeParameters.get(0) : getter.returnType;
+                        if ((read == false || getter != null) && (write == false || setter != null)) {
+                            valueType = setter != null ? setter.typeParameters.get(0) : getter.returnType;
                         } else {
                             throw createError(new IllegalArgumentException(
-                                    "Illegal shortcut on field [" + index + "] for type [" + targetCanonicalTypeName + "]."));
+                                    "Illegal shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
                         }
 
                         DotSubShortcutNode dotSubShortcutNode = new DotSubShortcutNode();
                         dotSubShortcutNode.setLocation(getLocation());
-                        dotSubShortcutNode.setExpressionType(output.actual);
+                        dotSubShortcutNode.setExpressionType(valueType);
                         dotSubShortcutNode.setGetter(getter);
                         dotSubShortcutNode.setSetter(setter);
                         expressionNode = dotSubShortcutNode;
-                    } else {
-                        if (Map.class.isAssignableFrom(prefixOutput.actual)) {
-                            getter = scriptScope.getPainlessLookup().lookupPainlessMethod(targetType, false, "get", 1);
-                            setter = scriptScope.getPainlessLookup().lookupPainlessMethod(targetType, false, "put", 2);
+                    } else if (isStatic == false) {
+                        if (Map.class.isAssignableFrom(prefixValueType.getValueType())) {
+                            getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "get", 1);
+                            setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "put", 2);
 
                             if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) {
                                 throw createError(new IllegalArgumentException(
-                                        "Illegal map get shortcut for type [" + targetCanonicalTypeName + "]."));
+                                        "Illegal map get shortcut for type [" + prefixCanonicalTypeName + "]."));
                             }
 
                             if (setter != null && setter.typeParameters.size() != 2) {
                                 throw createError(new IllegalArgumentException(
-                                        "Illegal map set shortcut for type [" + targetCanonicalTypeName + "]."));
+                                        "Illegal map set shortcut for type [" + prefixCanonicalTypeName + "]."));
                             }
 
                             if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) ||
@@ -230,11 +251,11 @@ public class EDot extends AExpression {
                                 throw createError(new IllegalArgumentException("Shortcut argument types must match."));
                             }
 
-                            if ((input.read == false || getter != null) && (input.write == false || setter != null)) {
-                                output.actual = setter != null ? setter.typeParameters.get(1) : getter.returnType;
+                            if ((read == false || getter != null) && (write == false || setter != null)) {
+                                valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
                             } else {
                                 throw createError(new IllegalArgumentException(
-                                        "Illegal map shortcut for type [" + targetCanonicalTypeName + "]."));
+                                        "Illegal map shortcut for type [" + prefixCanonicalTypeName + "]."));
                             }
 
                             ConstantNode constantNode = new ConstantNode();
@@ -245,13 +266,13 @@ public class EDot extends AExpression {
                             MapSubShortcutNode mapSubShortcutNode = new MapSubShortcutNode();
                             mapSubShortcutNode.setChildNode(constantNode);
                             mapSubShortcutNode.setLocation(getLocation());
-                            mapSubShortcutNode.setExpressionType(output.actual);
+                            mapSubShortcutNode.setExpressionType(valueType);
                             mapSubShortcutNode.setGetter(getter);
                             mapSubShortcutNode.setSetter(setter);
                             expressionNode = mapSubShortcutNode;
                         }
 
-                        if (List.class.isAssignableFrom(prefixOutput.actual)) {
+                        if (List.class.isAssignableFrom(prefixType)) {
                             int index;
 
                             try {
@@ -260,30 +281,30 @@ public class EDot extends AExpression {
                                 throw createError(new IllegalArgumentException("invalid list index [" + this.index + "]"));
                             }
 
-                            getter = scriptScope.getPainlessLookup().lookupPainlessMethod(targetType, false, "get", 1);
-                            setter = scriptScope.getPainlessLookup().lookupPainlessMethod(targetType, false, "set", 2);
+                            getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "get", 1);
+                            setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "set", 2);
 
                             if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 ||
                                     getter.typeParameters.get(0) != int.class)) {
                                 throw createError(new IllegalArgumentException(
-                                        "Illegal list get shortcut for type [" + targetCanonicalTypeName + "]."));
+                                        "Illegal list get shortcut for type [" + prefixCanonicalTypeName + "]."));
                             }
 
                             if (setter != null && (setter.typeParameters.size() != 2 || setter.typeParameters.get(0) != int.class)) {
                                 throw createError(new IllegalArgumentException(
-                                        "Illegal list set shortcut for type [" + targetCanonicalTypeName + "]."));
+                                        "Illegal list set shortcut for type [" + prefixCanonicalTypeName + "]."));
                             }
 
                             if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0))
                                     || !getter.returnType.equals(setter.typeParameters.get(1)))) {
                                 throw createError(new IllegalArgumentException("Shortcut argument types must match."));
                             }
-
-                            if ((input.read == false || getter != null) && (input.write == false || setter != null)) {
-                                output.actual = setter != null ? setter.typeParameters.get(1) : getter.returnType;
+                            
+                            if ((read == false || getter != null) && (write == false || setter != null)) {
+                                valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
                             } else {
                                 throw createError(new IllegalArgumentException(
-                                        "Illegal list shortcut for type [" + targetCanonicalTypeName + "]."));
+                                        "Illegal list shortcut for type [" + prefixCanonicalTypeName + "]."));
                             }
 
                             ConstantNode constantNode = new ConstantNode();
@@ -294,47 +315,54 @@ public class EDot extends AExpression {
                             ListSubShortcutNode listSubShortcutNode = new ListSubShortcutNode();
                             listSubShortcutNode.setChildNode(constantNode);
                             listSubShortcutNode.setLocation(getLocation());
-                            listSubShortcutNode.setExpressionType(output.actual);
+                            listSubShortcutNode.setExpressionType(valueType);
                             listSubShortcutNode.setGetter(getter);
                             listSubShortcutNode.setSetter(setter);
                             expressionNode = listSubShortcutNode;
                         }
                     }
 
-                    if (expressionNode == null) {
-                        throw createError(new IllegalArgumentException(
-                                "field [" + typeToCanonicalTypeName(prefixOutput.actual) + ", " + index + "] not found"));
+                    if (valueType == null) {
+                        if (prefixValueType != null) {
+                            throw createError(new IllegalArgumentException(
+                                    "field [" + prefixValueType.getValueCanonicalTypeName() + ", " + index + "] not found"));
+                        } else {
+                            throw createError(new IllegalArgumentException(
+                                    "field [" + prefixStaticType.getStaticCanonicalTypeName() + ", " + index + "] not found"));
+                        }
                     }
                 } else {
-                    if (input.write && Modifier.isFinal(field.javaField.getModifiers())) {
+                    if (write && Modifier.isFinal(field.javaField.getModifiers())) {
                         throw createError(new IllegalArgumentException(
                                 "invalid assignment: cannot assign a value to read-only field [" + field.javaField.getName() + "]"));
                     }
 
-                    output.actual = field.typeParameter;
+                    valueType = field.typeParameter;
 
                     DotSubNode dotSubNode = new DotSubNode();
                     dotSubNode.setLocation(getLocation());
-                    dotSubNode.setExpressionType(output.actual);
+                    dotSubNode.setExpressionType(valueType);
                     dotSubNode.setField(field);
                     expressionNode = dotSubNode;
                 }
             }
 
+            semanticScope.putDecoration(this, new ValueType(valueType));
+
             if (isNullSafe) {
-                if (input.write) {
+                if (write) {
                     throw createError(new IllegalArgumentException(
                             "invalid assignment: cannot assign a value to a null safe operation [?.]"));
                 }
 
-                if (output.actual.isPrimitive()) {
+                if (valueType.isPrimitive()) {
                     throw new IllegalArgumentException("Result of null safe operator must be nullable");
                 }
 
                 NullSafeSubNode nullSafeSubNode = new NullSafeSubNode();
                 nullSafeSubNode.setChildNode(expressionNode);
                 nullSafeSubNode.setLocation(getLocation());
-                nullSafeSubNode.setExpressionType(output.actual);
+                nullSafeSubNode.setExpressionType(valueType);
                 expressionNode = nullSafeSubNode;
             }
 
@@ -342,7 +370,7 @@ public class EDot extends AExpression {
             dotNode.setLeftNode(prefixOutput.expressionNode);
             dotNode.setRightNode(expressionNode);
             dotNode.setLocation(getLocation());
-            dotNode.setExpressionType(output.actual);
+            dotNode.setExpressionType(valueType);
             output.expressionNode = dotNode;
         }
 

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

@@ -21,10 +21,16 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ElvisNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import static java.util.Objects.requireNonNull;
 
@@ -53,34 +59,37 @@ public class EElvis extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to elvis operation [?:]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: result not used from elvis operation [?:]"));
         }
 
         Output output = new Output();
+        Class<?> valueType;
 
-        if (input.expected != null && input.expected.isPrimitive()) {
+        TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
+
+        if (targetType != null && targetType.getTargetType().isPrimitive()) {
             throw createError(new IllegalArgumentException("Elvis operator cannot return primitives"));
         }
 
-        Input leftInput = new Input();
-        leftInput.expected = input.expected;
-        leftInput.explicit = input.explicit;
-        leftInput.internal = input.internal;
-        Output leftOutput = analyze(leftNode, classNode, semanticScope, leftInput);
-
-        Input rightInput = new Input();
-        rightInput.expected = input.expected;
-        rightInput.explicit = input.explicit;
-        rightInput.internal = input.internal;
-        Output rightOutput = analyze(rightNode, classNode, semanticScope, rightInput);
+        semanticScope.setCondition(leftNode, Read.class);
+        semanticScope.copyDecoration(this, leftNode, TargetType.class);
+        semanticScope.replicateCondition(this, leftNode, Explicit.class);
+        semanticScope.replicateCondition(this, leftNode, Internal.class);
+        Output leftOutput = analyze(leftNode, classNode, semanticScope);
+        Class<?> leftValueType = semanticScope.getDecoration(leftNode, ValueType.class).getValueType();
 
-        output.actual = input.expected;
+        semanticScope.setCondition(rightNode, Read.class);
+        semanticScope.copyDecoration(this, rightNode, TargetType.class);
+        semanticScope.replicateCondition(this, rightNode, Explicit.class);
+        semanticScope.replicateCondition(this, rightNode, Internal.class);
+        Output rightOutput = analyze(rightNode, classNode, semanticScope);
+        Class<?> rightValueType = semanticScope.getDecoration(rightNode, ValueType.class).getValueType();
 
         if (leftNode instanceof ENull) {
             throw createError(new IllegalArgumentException("Extraneous elvis operator. LHS is null."));
@@ -88,34 +97,33 @@ public class EElvis extends AExpression {
         if (leftNode instanceof EBoolean || leftNode instanceof ENumeric || leftNode instanceof EDecimal || leftNode instanceof EString) {
             throw createError(new IllegalArgumentException("Extraneous elvis operator. LHS is a constant."));
         }
-        if (leftOutput.actual.isPrimitive()) {
+        if (leftValueType.isPrimitive()) {
             throw createError(new IllegalArgumentException("Extraneous elvis operator. LHS is a primitive."));
         }
         if (rightNode instanceof ENull) {
             throw createError(new IllegalArgumentException("Extraneous elvis operator. RHS is null."));
         }
 
-        if (input.expected == null) {
-            Class<?> promote = AnalyzerCaster.promoteConditional(leftOutput.actual, rightOutput.actual);
+        if (targetType == null) {
+            Class<?> promote = AnalyzerCaster.promoteConditional(leftValueType, rightValueType);
 
-            leftInput.expected = promote;
-            rightInput.expected = promote;
-            output.actual = promote;
+            semanticScope.putDecoration(leftNode, new TargetType(promote));
+            semanticScope.putDecoration(rightNode, new TargetType(promote));
+            valueType = promote;
+        } else {
+            valueType = targetType.getTargetType();
         }
 
-        PainlessCast leftCast = AnalyzerCaster.getLegalCast(leftNode.getLocation(),
-                leftOutput.actual, leftInput.expected, leftInput.explicit, leftInput.internal);
-        PainlessCast rightCast = AnalyzerCaster.getLegalCast(rightNode.getLocation(),
-                rightOutput.actual, rightInput.expected, rightInput.explicit, rightInput.internal);
+        PainlessCast leftCast = leftNode.cast(semanticScope);
+        PainlessCast rightCast = rightNode.cast(semanticScope);
 
-        ElvisNode elvisNode = new ElvisNode();
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
+        ElvisNode elvisNode = new ElvisNode();
         elvisNode.setLeftNode(cast(leftOutput.expressionNode, leftCast));
         elvisNode.setRightNode(cast(rightOutput.expressionNode, rightCast));
-
         elvisNode.setLocation(getLocation());
-        elvisNode.setExpressionType(output.actual);
-
+        elvisNode.setExpressionType(valueType);
         output.expressionNode = elvisNode;
 
         return output;

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

@@ -19,11 +19,15 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -51,33 +55,32 @@ public class EExplicit extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to an explicit cast with target type [" + canonicalTypeName + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: result not used from explicit cast with target type [" + canonicalTypeName + "]"));
         }
 
-        Class<?> type = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
+        Class<?> valueType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
 
-        if (type == null) {
+        if (valueType == null) {
             throw createError(new IllegalArgumentException("cannot resolve type [" + canonicalTypeName + "]"));
         }
 
-        Output output = new Output();
-        output.actual = type;
+        semanticScope.setCondition(childNode, Read.class);
+        semanticScope.putDecoration(childNode, new TargetType(valueType));
+        semanticScope.setCondition(childNode, Explicit.class);
+        Output childOutput = analyze(childNode, classNode, semanticScope);
+        PainlessCast childCast = childNode.cast(semanticScope);
 
-        Input childInput = new Input();
-        childInput.expected = output.actual;
-        childInput.explicit = true;
-        Output childOutput = analyze(childNode, classNode, semanticScope, childInput);
-        PainlessCast childCast = AnalyzerCaster.getLegalCast(childNode.getLocation(),
-                childOutput.actual, childInput.expected, childInput.explicit, childInput.internal);
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
+        Output output = new Output();
         output.expressionNode = cast(childOutput.expressionNode, childCast);
 
         return output;

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

@@ -21,13 +21,17 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.DefInterfaceReferenceNode;
 import org.elasticsearch.painless.ir.TypedCaptureReferenceNode;
 import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -55,59 +59,61 @@ public class EFunctionRef extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         ScriptScope scriptScope = semanticScope.getScriptScope();
+        boolean read = semanticScope.getCondition(this, Read.class);
+        TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
 
         Output output = new Output();
+        Class<?> valueType;
         Class<?> type = scriptScope.getPainlessLookup().canonicalTypeNameToType(symbol);
 
         if (symbol.equals("this") || type != null)  {
-            if (input.write) {
+            if (semanticScope.getCondition(this, Write.class)) {
                 throw createError(new IllegalArgumentException(
                         "invalid assignment: cannot assign a value to function reference [" + symbol + ":" + methodName + "]"));
             }
 
-            if (input.read == false) {
+            if (read == false) {
                 throw createError(new IllegalArgumentException(
                         "not a statement: function reference [" + symbol + ":" + methodName + "] not used"));
             }
 
-            if (input.expected == null) {
-                output.actual = String.class;
+            if (targetType == null) {
+                valueType = String.class;
                 String defReferenceEncoding = "S" + symbol + "." + methodName + ",0";
 
                 DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode();
 
                 defInterfaceReferenceNode.setLocation(getLocation());
-                defInterfaceReferenceNode.setExpressionType(output.actual);
+                defInterfaceReferenceNode.setExpressionType(valueType);
                 defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding);
 
                 output.expressionNode = defInterfaceReferenceNode;
             } else {
                 FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(),
-                        getLocation(), input.expected, symbol, methodName, 0);
-                output.actual = input.expected;
+                        getLocation(), targetType.getTargetType(), symbol, methodName, 0);
+                valueType = targetType.getTargetType();
 
                 TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode();
                 typedInterfaceReferenceNode.setLocation(getLocation());
-                typedInterfaceReferenceNode.setExpressionType(output.actual);
+                typedInterfaceReferenceNode.setExpressionType(valueType);
                 typedInterfaceReferenceNode.setReference(ref);
-
                 output.expressionNode = typedInterfaceReferenceNode;
             }
         } else {
-            if (input.write) {
+            if (semanticScope.getCondition(this, Write.class)) {
                 throw createError(new IllegalArgumentException(
                         "invalid assignment: cannot assign a value to capturing function reference [" + symbol + ":"  + methodName + "]"));
             }
 
-            if (input.read == false) {
+            if (read == false) {
                 throw createError(new IllegalArgumentException(
                         "not a statement: capturing function reference [" + symbol + ":"  + methodName + "] not used"));
             }
 
             SemanticScope.Variable captured = semanticScope.getVariable(getLocation(), symbol);
-            if (input.expected == null) {
+            if (targetType == null) {
                 String defReferenceEncoding;
                 if (captured.getType() == def.class) {
                     // dynamic implementation
@@ -116,44 +122,41 @@ public class EFunctionRef extends AExpression {
                     // typed implementation
                     defReferenceEncoding = "S" + captured.getCanonicalTypeName() + "." + methodName + ",1";
                 }
-                output.actual = String.class;
+                valueType = String.class;
 
                 DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode();
 
                 defInterfaceReferenceNode.setLocation(getLocation());
-                defInterfaceReferenceNode.setExpressionType(output.actual);
+                defInterfaceReferenceNode.setExpressionType(valueType);
                 defInterfaceReferenceNode.addCapture(captured.getName());
                 defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding);
-
                 output.expressionNode = defInterfaceReferenceNode;
             } else {
-                output.actual = input.expected;
+                valueType = targetType.getTargetType();
                 // static case
                 if (captured.getType() != def.class) {
                     FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(), getLocation(),
-                            input.expected, captured.getCanonicalTypeName(), methodName, 1);
+                            targetType.getTargetType(), captured.getCanonicalTypeName(), methodName, 1);
 
                     TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode();
                     typedInterfaceReferenceNode.setLocation(getLocation());
-                    typedInterfaceReferenceNode.setExpressionType(output.actual);
+                    typedInterfaceReferenceNode.setExpressionType(valueType);
                     typedInterfaceReferenceNode.addCapture(captured.getName());
                     typedInterfaceReferenceNode.setReference(ref);
-
                     output.expressionNode = typedInterfaceReferenceNode;
                 } else {
                     TypedCaptureReferenceNode typedCaptureReferenceNode = new TypedCaptureReferenceNode();
                     typedCaptureReferenceNode.setLocation(getLocation());
-                    typedCaptureReferenceNode.setExpressionType(output.actual);
+                    typedCaptureReferenceNode.setExpressionType(valueType);
                     typedCaptureReferenceNode.addCapture(captured.getName());
                     typedCaptureReferenceNode.setMethodName(methodName);
-
                     output.expressionNode = typedCaptureReferenceNode;
                 }
             }
-
-            return output;
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         return output;
     }
 }

+ 16 - 20
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java

@@ -19,13 +19,14 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.InstanceofNode;
-import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -55,13 +56,13 @@ public class EInstanceof extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to instanceof with target type [" + canonicalTypeName + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: result not used from instanceof with target type [" + canonicalTypeName + "]"));
         }
@@ -84,30 +85,25 @@ public class EInstanceof extends AExpression {
                 PainlessLookupUtility.typeToJavaType(clazz);
 
         // analyze and cast the expression
-        Input expressionInput = new Input();
-        Output expressionOutput = analyze(expressionNode, classNode, semanticScope, expressionInput);
-        expressionInput.expected = expressionOutput.actual;
-        PainlessCast expressionCast = AnalyzerCaster.getLegalCast(expressionNode.getLocation(),
-                expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal);
+        semanticScope.setCondition(expressionNode, Read.class);
+        Output expressionOutput = analyze(expressionNode, classNode, semanticScope);
+        Class<?> expressionValueType = semanticScope.getDecoration(expressionNode, ValueType.class).getValueType();
 
         // record if the expression returns a primitive
-        primitiveExpression = expressionOutput.actual.isPrimitive();
+        primitiveExpression = expressionValueType.isPrimitive();
         // map to wrapped type for primitive types
-        expressionType = expressionOutput.actual.isPrimitive() ?
-            PainlessLookupUtility.typeToBoxedType(expressionOutput.actual) : PainlessLookupUtility.typeToJavaType(clazz);
+        expressionType = expressionValueType.isPrimitive() ?
+            PainlessLookupUtility.typeToBoxedType(expressionValueType) : PainlessLookupUtility.typeToJavaType(clazz);
 
-        output.actual = boolean.class;
+        semanticScope.putDecoration(this, new ValueType(boolean.class));
 
         InstanceofNode instanceofNode = new InstanceofNode();
-
-        instanceofNode.setChildNode(cast(expressionOutput.expressionNode, expressionCast));
-
+        instanceofNode.setChildNode(expressionOutput.expressionNode);
         instanceofNode.setLocation(getLocation());
-        instanceofNode.setExpressionType(output.actual);
+        instanceofNode.setExpressionType(boolean.class);
         instanceofNode.setInstanceType(expressionType);
         instanceofNode.setResolvedType(resolvedType);
         instanceofNode.setPrimitiveResult(primitiveExpression);
-
         output.expressionNode = instanceofNode;
 
         return output;

+ 29 - 23
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java

@@ -21,19 +21,24 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
-import org.elasticsearch.painless.symbol.SemanticScope.LambdaScope;
-import org.elasticsearch.painless.symbol.SemanticScope.Variable;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.DefInterfaceReferenceNode;
 import org.elasticsearch.painless.ir.FunctionNode;
 import org.elasticsearch.painless.ir.ReferenceNode;
 import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode;
-import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.LastSource;
+import org.elasticsearch.painless.symbol.Decorations.MethodEscape;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
+import org.elasticsearch.painless.symbol.SemanticScope.LambdaScope;
+import org.elasticsearch.painless.symbol.SemanticScope.Variable;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -92,16 +97,17 @@ public class ELambda extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to a lambda"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: lambda not used"));
         }
 
         ScriptScope scriptScope = semanticScope.getScriptScope();
+        TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
 
         String name;
         Class<?> returnType;
@@ -110,12 +116,12 @@ public class ELambda extends AExpression {
         int maxLoopCounter;
 
         Output output = new Output();
+        Class<?> valueType;
 
         List<Class<?>> typeParameters;
         PainlessMethod interfaceMethod;
         // inspect the target first, set interface method if we know it.
-        if (input.expected == null) {
-            interfaceMethod = null;
+        if (targetType == null) {
             // we don't know anything: treat as def
             returnType = def.class;
             // don't infer any types, replace any null types with def
@@ -133,18 +139,17 @@ public class ELambda extends AExpression {
                     typeParameters.add(typeParameter);
                 }
             }
-
         } else {
             // we know the method statically, infer return type and any unknown/def types
-            interfaceMethod = scriptScope.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(input.expected);
+            interfaceMethod = scriptScope.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(targetType.getTargetType());
             if (interfaceMethod == null) {
                 throw createError(new IllegalArgumentException("Cannot pass lambda to " +
-                        "[" + PainlessLookupUtility.typeToCanonicalTypeName(input.expected) + "], not a functional interface"));
+                        "[" + targetType.getTargetCanonicalTypeName() + "], not a functional interface"));
             }
             // check arity before we manipulate parameters
             if (interfaceMethod.typeParameters.size() != canonicalTypeNameParameters.size())
                 throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.javaMethod.getName() +
-                        "] in [" + PainlessLookupUtility.typeToCanonicalTypeName(input.expected) + "]");
+                        "] in [" + targetType.getTargetCanonicalTypeName() + "]");
             // for method invocation, its allowed to ignore the return value
             if (interfaceMethod.returnType == void.class) {
                 returnType = def.class;
@@ -180,11 +185,10 @@ public class ELambda extends AExpression {
         if (blockNode.getStatementNodes().isEmpty()) {
             throw createError(new IllegalArgumentException("cannot generate empty lambda"));
         }
-        AStatement.Input blockInput = new AStatement.Input();
-        blockInput.lastSource = true;
-        AStatement.Output blockOutput = blockNode.analyze(classNode, lambdaScope, blockInput);
+        semanticScope.setCondition(blockNode, LastSource.class);
+        AStatement.Output blockOutput = blockNode.analyze(classNode, lambdaScope);
 
-        if (blockOutput.methodEscape == false) {
+        if (semanticScope.getCondition(blockNode, MethodEscape.class) == false) {
             throw createError(new IllegalArgumentException("not all paths return a value for lambda"));
         }
 
@@ -208,8 +212,8 @@ public class ELambda extends AExpression {
         ReferenceNode referenceNode;
 
         // setup method reference to synthetic method
-        if (input.expected == null) {
-            output.actual = String.class;
+        if (targetType == null) {
+            valueType = String.class;
             String defReferenceEncoding = "Sthis." + name + "," + captures.size();
 
             DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode();
@@ -217,14 +221,16 @@ public class ELambda extends AExpression {
             referenceNode = defInterfaceReferenceNode;
         } else {
             FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(),
-                    getLocation(), input.expected, "this", name, captures.size());
-            output.actual = input.expected;
+                    getLocation(), targetType.getTargetType(), "this", name, captures.size());
+            valueType = targetType.getTargetType();
 
             TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode();
             typedInterfaceReferenceNode.setReference(ref);
             referenceNode = typedInterfaceReferenceNode;
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         FunctionNode functionNode = new FunctionNode();
         functionNode.setBlockNode((BlockNode)blockOutput.statementNode);
         functionNode.setLocation(getLocation());
@@ -240,7 +246,7 @@ public class ELambda extends AExpression {
         classNode.addFunctionNode(functionNode);
 
         referenceNode.setLocation(getLocation());
-        referenceNode.setExpressionType(output.actual);
+        referenceNode.setExpressionType(valueType);
 
         for (Variable capture : captures) {
             referenceNode.addCapture(capture.getName());

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

@@ -19,15 +19,19 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ListInitializationNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -54,44 +58,45 @@ public class EListInit extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to list initializer"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: result not used from list initializer"));
         }
 
         Output output = new Output();
-        output.actual = ArrayList.class;
+        Class<?> valueType = ArrayList.class;
 
-        PainlessConstructor constructor = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessConstructor(output.actual, 0);
+        PainlessConstructor constructor = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessConstructor(valueType, 0);
 
         if (constructor == null) {
             throw createError(new IllegalArgumentException(
-                    "constructor [" + typeToCanonicalTypeName(output.actual) + ", <init>/0] not found"));
+                    "constructor [" + typeToCanonicalTypeName(valueType) + ", <init>/0] not found"));
         }
 
-        PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(output.actual, false, "add", 1);
+        PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(valueType, false, "add", 1);
 
         if (method == null) {
-            throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(output.actual) + ", add/1] not found"));
+            throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(valueType) + ", add/1] not found"));
         }
 
         List<Output> valueOutputs = new ArrayList<>(valueNodes.size());
         List<PainlessCast> valueCasts = new ArrayList<>(valueNodes.size());
 
         for (AExpression expression : valueNodes) {
-            Input expressionInput = new Input();
-            expressionInput.expected = def.class;
-            expressionInput.internal = true;
-            Output expressionOutput = analyze(expression, classNode, semanticScope, expressionInput);
+            semanticScope.setCondition(expression, Read.class);
+            semanticScope.putDecoration(expression, new TargetType(def.class));
+            semanticScope.setCondition(expression, Internal.class);
+            Output expressionOutput = analyze(expression, classNode, semanticScope);
             valueOutputs.add(expressionOutput);
-            valueCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                    expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal));
+            valueCasts.add(expression.cast(semanticScope));
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+        
         ListInitializationNode listInitializationNode = new ListInitializationNode();
 
         for (int i = 0; i < valueNodes.size(); ++i) {
@@ -99,10 +104,9 @@ public class EListInit extends AExpression {
         }
 
         listInitializationNode.setLocation(getLocation());
-        listInitializationNode.setExpressionType(output.actual);
+        listInitializationNode.setExpressionType(valueType);
         listInitializationNode.setConstructor(constructor);
         listInitializationNode.setMethod(method);
-
         output.expressionNode = listInitializationNode;
 
         return output;

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

@@ -19,15 +19,19 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.MapInitializationNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -61,29 +65,29 @@ public class EMapInit extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to map initializer"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: result not used from map initializer"));
         }
 
         Output output = new Output();
-        output.actual = HashMap.class;
+        Class<?> valueType = HashMap.class;
 
-        PainlessConstructor constructor = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessConstructor(output.actual, 0);
+        PainlessConstructor constructor = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessConstructor(valueType, 0);
 
         if (constructor == null) {
             throw createError(new IllegalArgumentException(
-                    "constructor [" + typeToCanonicalTypeName(output.actual) + ", <init>/0] not found"));
+                    "constructor [" + typeToCanonicalTypeName(valueType) + ", <init>/0] not found"));
         }
 
-        PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(output.actual, false, "put", 2);
+        PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(valueType, false, "put", 2);
 
         if (method == null) {
-            throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(output.actual) + ", put/2] not found"));
+            throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(valueType) + ", put/2] not found"));
         }
 
         if (keyNodes.size() != valueNodes.size()) {
@@ -97,25 +101,25 @@ public class EMapInit extends AExpression {
 
         for (int i = 0; i < keyNodes.size(); ++i) {
             AExpression expression = keyNodes.get(i);
-            Input expressionInput = new Input();
-            expressionInput.expected = def.class;
-            expressionInput.internal = true;
-            Output expressionOutput = analyze(expression, classNode, semanticScope, expressionInput);
+            semanticScope.setCondition(expression, Read.class);
+            semanticScope.putDecoration(expression, new TargetType(def.class));
+            semanticScope.setCondition(expression, Internal.class);
+            Output expressionOutput = analyze(expression, classNode, semanticScope);
             keyOutputs.add(expressionOutput);
-            keyCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                    expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal));
+            keyCasts.add(expression.cast(semanticScope));
 
             expression = valueNodes.get(i);
-            expressionInput = new Input();
-            expressionInput.expected = def.class;
-            expressionInput.internal = true;
-            expressionOutput = analyze(expression, classNode, semanticScope, expressionInput);
-            valueCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                    expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal));
+            semanticScope.setCondition(expression, Read.class);
+            semanticScope.putDecoration(expression, new TargetType(def.class));
+            semanticScope.setCondition(expression, Internal.class);
+            expressionOutput = analyze(expression, classNode, semanticScope);
+            valueCasts.add(expression.cast(semanticScope));
 
             valueOutputs.add(expressionOutput);
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         MapInitializationNode mapInitializationNode = new MapInitializationNode();
 
         for (int i = 0; i < keyNodes.size(); ++i) {
@@ -125,7 +129,7 @@ public class EMapInit extends AExpression {
         }
 
         mapInitializationNode.setLocation(getLocation());
-        mapInitializationNode.setExpressionType(output.actual);
+        mapInitializationNode.setExpressionType(valueType);
         mapInitializationNode.setConstructor(constructor);
         mapInitializationNode.setMethod(method);
 

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

@@ -19,12 +19,16 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.NewArrayNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -61,20 +65,20 @@ public class ENewArray extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to new array"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: result not used from new array"));
         }
 
         Output output = new Output();
 
-        Class<?> clazz = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
+        Class<?> valueType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
 
-        if (clazz == null) {
+        if (valueType == null) {
             throw createError(new IllegalArgumentException("Not a type [" + canonicalTypeName + "]."));
         }
 
@@ -82,16 +86,15 @@ public class ENewArray extends AExpression {
         List<PainlessCast> argumentCasts = new ArrayList<>();
 
         for (AExpression expression : valueNodes) {
-            Input expressionInput = new Input();
-            expressionInput.expected = isInitializer ? clazz.getComponentType() : int.class;
-            expressionInput.internal = true;
-            Output expressionOutput = analyze(expression, classNode, semanticScope, expressionInput);
+            semanticScope.setCondition(expression, Read.class);
+            semanticScope.putDecoration(expression, new TargetType(isInitializer ? valueType.getComponentType() : int.class));
+            semanticScope.setCondition(expression, Internal.class);
+            Output expressionOutput = analyze(expression, classNode, semanticScope);
             argumentOutputs.add(expressionOutput);
-            argumentCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                    expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal));
+            argumentCasts.add(expression.cast(semanticScope));
         }
 
-        output.actual = clazz;
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
         NewArrayNode newArrayNode = new NewArrayNode();
 
@@ -100,9 +103,8 @@ public class ENewArray extends AExpression {
         }
 
         newArrayNode.setLocation(getLocation());
-        newArrayNode.setExpressionType(output.actual);
+        newArrayNode.setExpressionType(valueType);
         newArrayNode.setInitialize(isInitializer);
-
         output.expressionNode = newArrayNode;
 
         return output;

+ 19 - 16
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java

@@ -21,8 +21,6 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.FunctionRef;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.DefInterfaceReferenceNode;
@@ -31,6 +29,12 @@ import org.elasticsearch.painless.ir.NewArrayNode;
 import org.elasticsearch.painless.ir.ReturnNode;
 import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode;
 import org.elasticsearch.painless.ir.VariableNode;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Collections;
 import java.util.Objects;
@@ -49,21 +53,22 @@ public class ENewArrayFunctionRef extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "cannot assign a value to new array function reference with target type [ + " + canonicalTypeName  + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: new array function reference with target type [" + canonicalTypeName + "] not used"));
         }
 
         ScriptScope scriptScope = semanticScope.getScriptScope();
+        TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
 
         Output output = new Output();
-
+        Class<?> valueType;
         Class<?> clazz = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
 
         if (clazz == null) {
@@ -73,31 +78,29 @@ public class ENewArrayFunctionRef extends AExpression {
         String name = scriptScope.getNextSyntheticName("newarray");
         scriptScope.getFunctionTable().addFunction(name, clazz, Collections.singletonList(int.class), true, true);
 
-        if (input.expected == null) {
-            output.actual = String.class;
+        if (targetType == null) {
+            valueType = String.class;
             String defReferenceEncoding = "Sthis." + name + ",0";
 
             DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode();
-
             defInterfaceReferenceNode.setLocation(getLocation());
-            defInterfaceReferenceNode.setExpressionType(output.actual);
+            defInterfaceReferenceNode.setExpressionType(valueType);
             defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding);
-
             output.expressionNode = defInterfaceReferenceNode;
         } else {
             FunctionRef ref = FunctionRef.create(scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(),
-                    getLocation(), input.expected, "this", name, 0);
-            output.actual = input.expected;
+                    getLocation(), targetType.getTargetType(), "this", name, 0);
+            valueType = targetType.getTargetType();
 
             TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode();
-
             typedInterfaceReferenceNode.setLocation(getLocation());
-            typedInterfaceReferenceNode.setExpressionType(output.actual);
+            typedInterfaceReferenceNode.setExpressionType(valueType);
             typedInterfaceReferenceNode.setReference(ref);
-
             output.expressionNode = typedInterfaceReferenceNode;
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         VariableNode variableNode = new VariableNode();
         variableNode.setLocation(getLocation());
         variableNode.setExpressionType(int.class);

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

@@ -19,16 +19,20 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.NewObjectNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessConstructor;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.spi.annotation.NonDeterministicAnnotation;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -61,8 +65,8 @@ public class ENewObj extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment cannot assign a value to new object with constructor " +
                     "[" + canonicalTypeName + "/" + argumentNodes.size() + "]"));
         }
@@ -70,18 +74,17 @@ public class ENewObj extends AExpression {
         ScriptScope scriptScope = semanticScope.getScriptScope();
 
         Output output = new Output();
+        Class<?> valueType = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
 
-        output.actual = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
-
-        if (output.actual == null) {
+        if (valueType == null) {
             throw createError(new IllegalArgumentException("Not a type [" + canonicalTypeName + "]."));
         }
 
-        PainlessConstructor constructor = scriptScope.getPainlessLookup().lookupPainlessConstructor(output.actual, argumentNodes.size());
+        PainlessConstructor constructor = scriptScope.getPainlessLookup().lookupPainlessConstructor(valueType, argumentNodes.size());
 
         if (constructor == null) {
             throw createError(new IllegalArgumentException(
-                    "constructor [" + typeToCanonicalTypeName(output.actual) + ", <init>/" + argumentNodes.size() + "] not found"));
+                    "constructor [" + typeToCanonicalTypeName(valueType) + ", <init>/" + argumentNodes.size() + "] not found"));
         }
 
         scriptScope.markNonDeterministic(constructor.annotations.containsKey(NonDeterministicAnnotation.class));
@@ -91,7 +94,7 @@ public class ENewObj extends AExpression {
 
         if (constructor.typeParameters.size() != argumentNodes.size()) {
             throw createError(new IllegalArgumentException(
-                    "When calling constructor on type [" + PainlessLookupUtility.typeToCanonicalTypeName(output.actual) + "] " +
+                    "When calling constructor on type [" + PainlessLookupUtility.typeToCanonicalTypeName(valueType) + "] " +
                     "expected [" + constructor.typeParameters.size() + "] arguments, but found [" + argumentNodes.size() + "]."));
         }
 
@@ -101,15 +104,16 @@ public class ENewObj extends AExpression {
         for (int i = 0; i < argumentNodes.size(); ++i) {
             AExpression expression = argumentNodes.get(i);
 
-            Input expressionInput = new Input();
-            expressionInput.expected = types[i];
-            expressionInput.internal = true;
-            Output expressionOutput = analyze(expression, classNode, semanticScope, expressionInput);
+            semanticScope.setCondition(expression, Read.class);
+            semanticScope.putDecoration(expression, new TargetType(types[i]));
+            semanticScope.setCondition(expression, Internal.class);
+            Output expressionOutput = analyze(expression, classNode, semanticScope);
             argumentOutputs.add(expressionOutput);
-            argumentCasts.add(AnalyzerCaster.getLegalCast(expression.getLocation(),
-                    expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal));
+            argumentCasts.add(expression.cast(semanticScope));
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         NewObjectNode newObjectNode = new NewObjectNode();
 
         for (int i = 0; i < argumentNodes.size(); ++ i) {
@@ -117,8 +121,8 @@ public class ENewObj extends AExpression {
         }
 
         newObjectNode.setLocation(getLocation());
-        newObjectNode.setExpressionType(output.actual);
-        newObjectNode.setRead(input.read);
+        newObjectNode.setExpressionType(valueType);
+        newObjectNode.setRead(semanticScope.getCondition(this, Read.class));
         newObjectNode.setConstructor(constructor);
 
         output.expressionNode = newObjectNode;

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

@@ -20,10 +20,13 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.NullNode;
-import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
  * Represents a null constant.
@@ -35,33 +38,36 @@ public class ENull extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException("invalid assignment: cannot assign a value to null constant"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: null constant not used"));
         }
 
+        TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
+
         Output output = new Output();
+        Class<?> valueType;
 
-        if (input.expected != null) {
-            if (input.expected.isPrimitive()) {
+        if (targetType != null) {
+            if (targetType.getTargetType().isPrimitive()) {
                 throw createError(new IllegalArgumentException(
-                    "Cannot cast null to a primitive type [" + PainlessLookupUtility.typeToCanonicalTypeName(input.expected) + "]."));
+                        "Cannot cast null to a primitive type [" + targetType.getTargetCanonicalTypeName() + "]."));
             }
 
-            output.actual = input.expected;
+            valueType = targetType.getTargetType();
         } else {
-            output.actual = Object.class;
+            valueType = Object.class;
         }
 
-        NullNode nullNode = new NullNode();
+        semanticScope.putDecoration(this, new ValueType(valueType));
 
+        NullNode nullNode = new NullNode();
         nullNode.setLocation(getLocation());
-        nullNode.setExpressionType(output.actual);
-
+        nullNode.setExpressionType(valueType);
         output.expressionNode = nullNode;
 
         return output;

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

@@ -20,9 +20,13 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ConstantNode;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -50,21 +54,22 @@ public class ENumeric extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        return analyze(input, false);
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        return analyze(semanticScope, false);
     }
 
-    Output analyze(Input input, boolean negate) {
-        if (input.write) {
+    Output analyze(SemanticScope semanticScope, boolean negate) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to numeric constant [" + numeric + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: numeric constant [" + numeric + "] not used"));
         }
 
         Output output = new Output();
+        Class<?> valueType;
         Object constant;
 
         String numeric = negate ? "-" + this.numeric : this.numeric;
@@ -76,7 +81,7 @@ public class ENumeric extends AExpression {
 
             try {
                 constant = Double.parseDouble(numeric.substring(0, numeric.length() - 1));
-                output.actual = double.class;
+                valueType = double.class;
             } catch (NumberFormatException exception) {
                 throw createError(new IllegalArgumentException("Invalid double constant [" + numeric + "]."));
             }
@@ -87,34 +92,35 @@ public class ENumeric extends AExpression {
 
             try {
                 constant = Float.parseFloat(numeric.substring(0, numeric.length() - 1));
-                output.actual = float.class;
+                valueType = float.class;
             } catch (NumberFormatException exception) {
                 throw createError(new IllegalArgumentException("Invalid float constant [" + numeric + "]."));
             }
         } else if (numeric.endsWith("l") || numeric.endsWith("L")) {
             try {
                 constant = Long.parseLong(numeric.substring(0, numeric.length() - 1), radix);
-                output.actual = long.class;
+                valueType = long.class;
             } catch (NumberFormatException exception) {
                 throw createError(new IllegalArgumentException("Invalid long constant [" + numeric + "]."));
             }
         } else {
             try {
-                Class<?> sort = input.expected == null ? int.class : input.expected;
+                TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
+                Class<?> sort = targetType == null ? int.class : targetType.getTargetType();
                 int integer = Integer.parseInt(numeric, radix);
 
                 if (sort == byte.class && integer >= Byte.MIN_VALUE && integer <= Byte.MAX_VALUE) {
                     constant = (byte)integer;
-                    output.actual = byte.class;
+                    valueType = byte.class;
                 } else if (sort == char.class && integer >= Character.MIN_VALUE && integer <= Character.MAX_VALUE) {
                     constant = (char)integer;
-                    output.actual = char.class;
+                    valueType = char.class;
                 } else if (sort == short.class && integer >= Short.MIN_VALUE && integer <= Short.MAX_VALUE) {
                     constant = (short)integer;
-                    output.actual = short.class;
+                    valueType = short.class;
                 } else {
                     constant = integer;
-                    output.actual = int.class;
+                    valueType = int.class;
                 }
             } catch (NumberFormatException exception) {
                 try {
@@ -129,11 +135,12 @@ public class ENumeric extends AExpression {
             }
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         ConstantNode constantNode = new ConstantNode();
         constantNode.setLocation(getLocation());
-        constantNode.setExpressionType(output.actual);
+        constantNode.setExpressionType(valueType);
         constantNode.setConstant(constant);
-
         output.expressionNode = constantNode;
 
         return output;

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

@@ -20,7 +20,6 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.CallNode;
 import org.elasticsearch.painless.ir.CallSubNode;
@@ -32,6 +31,10 @@ import org.elasticsearch.painless.ir.MemberFieldStoreNode;
 import org.elasticsearch.painless.ir.StatementExpressionNode;
 import org.elasticsearch.painless.ir.StaticNode;
 import org.elasticsearch.painless.lookup.PainlessMethod;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
@@ -63,13 +66,13 @@ public class ERegex extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to regex constant [" + pattern + "] with flags [" + flags + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: regex constant [" + pattern + "] with flags [" + flags + "] not used"));
         }
@@ -95,8 +98,9 @@ public class ERegex extends AExpression {
                     new IllegalArgumentException("Error compiling regex: " + e.getDescription()));
         }
 
+        semanticScope.putDecoration(this, new ValueType(Pattern.class));
+
         String name = semanticScope.getScriptScope().getNextSyntheticName("regex");
-        output.actual = Pattern.class;
 
         FieldNode fieldNode = new FieldNode();
         fieldNode.setLocation(getLocation());
@@ -173,7 +177,6 @@ public class ERegex extends AExpression {
         memberFieldLoadNode.setExpressionType(Pattern.class);
         memberFieldLoadNode.setName(name);
         memberFieldLoadNode.setStatic(true);
-
         output.expressionNode = memberFieldLoadNode;
 
         return output;

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

@@ -20,9 +20,12 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ConstantNode;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -44,22 +47,22 @@ public class EString extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to string constant [" + string + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException("not a statement: string constant [" + string + "] not used"));
         }
 
         Output output = new Output();
-        output.actual = String.class;
+        semanticScope.putDecoration(this, new ValueType(String.class));
 
         ConstantNode constantNode = new ConstantNode();
         constantNode.setLocation(getLocation());
-        constantNode.setExpressionType(output.actual);
+        constantNode.setExpressionType(String.class);
         constantNode.setConstant(string);
 
         output.expressionNode = constantNode;

+ 24 - 21
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ESymbol.java

@@ -20,12 +20,17 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
-import org.elasticsearch.painless.symbol.SemanticScope.Variable;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.StaticNode;
 import org.elasticsearch.painless.ir.VariableNode;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.Decorations.PartialCanonicalTypeName;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.StaticType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
+import org.elasticsearch.painless.symbol.SemanticScope.Variable;
 
 import java.util.Objects;
 
@@ -47,52 +52,50 @@ public class ESymbol extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
-        Class<?> type = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(symbol);
+        Class<?> staticType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(symbol);
+        boolean read = semanticScope.getCondition(this, Read.class);
+        boolean write = semanticScope.getCondition(this, Write.class);
 
-        if (type != null)  {
-            if (input.write) {
+        if (staticType != null)  {
+            if (write) {
                 throw createError(new IllegalArgumentException("invalid assignment: " +
-                        "cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "]"));
+                        "cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "]"));
             }
 
-            if (input.read == false) {
+            if (read == false) {
                 throw createError(new IllegalArgumentException("not a statement: " +
-                        "static type [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "] not used"));
+                        "static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "] not used"));
             }
 
-            output.actual = type;
-            output.isStaticType = true;
+            semanticScope.putDecoration(this, new StaticType(staticType));
 
             StaticNode staticNode = new StaticNode();
-
             staticNode.setLocation(getLocation());
-            staticNode.setExpressionType(output.actual);
-
+            staticNode.setExpressionType(staticType);
             output.expressionNode = staticNode;
         } else if (semanticScope.isVariableDefined(symbol)) {
-            if (input.read == false && input.write == false) {
+            if (read == false && write == false) {
                 throw createError(new IllegalArgumentException("not a statement: variable [" + symbol + "] not used"));
             }
 
             Variable variable = semanticScope.getVariable(getLocation(), symbol);
 
-            if (input.write && variable.isFinal()) {
+            if (write && variable.isFinal()) {
                 throw createError(new IllegalArgumentException("Variable [" + variable.getName() + "] is read-only."));
             }
 
-            output.actual = variable.getType();
+            Class<?> valueType = variable.getType();
+            semanticScope.putDecoration(this, new ValueType(valueType));
 
             VariableNode variableNode = new VariableNode();
-
             variableNode.setLocation(getLocation());
-            variableNode.setExpressionType(output.actual);
+            variableNode.setExpressionType(valueType);
             variableNode.setName(symbol);
-
             output.expressionNode = variableNode;
         } else {
-            output.partialCanonicalTypeName = symbol;
+            semanticScope.putDecoration(this, new PartialCanonicalTypeName(symbol));
         }
 
         return output;

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

@@ -22,12 +22,18 @@ package org.elasticsearch.painless.node;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.UnaryMathNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
 import org.elasticsearch.painless.lookup.def;
+import org.elasticsearch.painless.symbol.Decorations.Explicit;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.Decorations.Write;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -55,82 +61,85 @@ public class EUnary extends AExpression {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
-        if (input.write) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
+        if (semanticScope.getCondition(this, Write.class)) {
             throw createError(new IllegalArgumentException(
                     "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
-        if (input.read == false) {
+        if (semanticScope.getCondition(this, Read.class) == false) {
             throw createError(new IllegalArgumentException(
                     "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]"));
         }
 
         Output output = new Output();
+        Class<?> valueType;
 
         Class<?> promote = null;
-        boolean originallyExplicit = input.explicit;
 
-        Input childInput = new Input();
         Output childOutput;
 
         if ((operation == Operation.SUB || operation == Operation.ADD) &&
                 (childNode instanceof ENumeric || childNode instanceof EDecimal)) {
-            childInput.expected = input.expected;
-            childInput.explicit = input.explicit;
-            childInput.internal = input.internal;
+            semanticScope.setCondition(childNode, Read.class);
+            semanticScope.copyDecoration(this, childNode, TargetType.class);
+            semanticScope.replicateCondition(this, childNode, Explicit.class);
+            semanticScope.replicateCondition(this, childNode, Internal.class);
 
             if (childNode instanceof ENumeric) {
                 ENumeric numeric = (ENumeric)childNode;
 
                 if (operation == Operation.SUB) {
-                    childOutput = numeric.analyze(childInput, numeric.getNumeric().charAt(0) != '-');
+                    childOutput = numeric.analyze(semanticScope, numeric.getNumeric().charAt(0) != '-');
                 } else {
-                    childOutput = childNode.analyze(classNode, semanticScope, childInput);
+                    childOutput = childNode.analyze(classNode, semanticScope);
                 }
             } else if (childNode instanceof EDecimal) {
                 EDecimal decimal = (EDecimal)childNode;
 
                 if (operation == Operation.SUB) {
-                    childOutput = decimal.analyze(childInput, decimal.getDecimal().charAt(0) != '-');
+                    childOutput = decimal.analyze(semanticScope, decimal.getDecimal().charAt(0) != '-');
                 } else {
-                    childOutput = childNode.analyze(classNode, semanticScope, childInput);
+                    childOutput = childNode.analyze(classNode, semanticScope);
                 }
             } else {
                 throw createError(new IllegalArgumentException("illegal tree structure"));
             }
 
-            output.actual = childOutput.actual;
+            valueType = semanticScope.getDecoration(childNode, ValueType.class).getValueType();
             output.expressionNode = childOutput.expressionNode;
         } else {
             PainlessCast childCast;
 
             if (operation == Operation.NOT) {
-                childInput.expected = boolean.class;
-                childOutput = analyze(childNode, classNode, semanticScope, childInput);
-                childCast = AnalyzerCaster.getLegalCast(childNode.getLocation(),
-                        childOutput.actual, childInput.expected, childInput.explicit, childInput.internal);
+                semanticScope.setCondition(childNode, Read.class);
+                semanticScope.putDecoration(childNode, new TargetType(boolean.class));
+                childOutput = analyze(childNode, classNode, semanticScope);
+                childCast = childNode.cast(semanticScope);
 
-                output.actual = boolean.class;
+                valueType = boolean.class;
             } else if (operation == Operation.BWNOT || operation == Operation.ADD || operation == Operation.SUB) {
-                childOutput = analyze(childNode, classNode, semanticScope, new Input());
+                semanticScope.setCondition(childNode, Read.class);
+                childOutput = analyze(childNode, classNode, semanticScope);
+                Class<?> childValueType = semanticScope.getDecoration(childNode, ValueType.class).getValueType();
 
-                promote = AnalyzerCaster.promoteNumeric(childOutput.actual, operation != Operation.BWNOT);
+                promote = AnalyzerCaster.promoteNumeric(childValueType, operation != Operation.BWNOT);
 
                 if (promote == null) {
                     throw createError(new ClassCastException("cannot apply the " + operation.name + " operator " +
                             "[" + operation.symbol + "] to the type " +
-                            "[" + PainlessLookupUtility.typeToCanonicalTypeName(childOutput.actual) + "]"));
+                            "[" + PainlessLookupUtility.typeToCanonicalTypeName(childValueType) + "]"));
                 }
 
-                childInput.expected = promote;
-                childCast = AnalyzerCaster.getLegalCast(childNode.getLocation(),
-                        childOutput.actual, childInput.expected, childInput.explicit, childInput.internal);
+                semanticScope.putDecoration(childNode, new TargetType(promote));
+                childCast = childNode.cast(semanticScope);
 
-                if (promote == def.class && input.expected != null) {
-                    output.actual = input.expected;
+                TargetType targetType = semanticScope.getDecoration(this, TargetType.class);
+
+                if (promote == def.class && targetType != null) {
+                    valueType = targetType.getTargetType();
                 } else {
-                    output.actual = promote;
+                    valueType = promote;
                 }
             } else {
                 throw createError(new IllegalStateException("unexpected unary operation [" + operation.name + "]"));
@@ -139,14 +148,16 @@ public class EUnary extends AExpression {
             UnaryMathNode unaryMathNode = new UnaryMathNode();
             unaryMathNode.setChildNode(cast(childOutput.expressionNode, childCast));
             unaryMathNode.setLocation(getLocation());
-            unaryMathNode.setExpressionType(output.actual);
+            unaryMathNode.setExpressionType(valueType);
             unaryMathNode.setUnaryType(promote);
             unaryMathNode.setOperation(operation);
-            unaryMathNode.setOriginallExplicit(originallyExplicit);
+            unaryMathNode.setOriginallyExplicit(semanticScope.getCondition(this, Explicit.class));
 
             output.expressionNode = unaryMathNode;
         }
 
+        semanticScope.putDecoration(this, new ValueType(valueType));
+
         return output;
     }
 }

+ 59 - 19
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java

@@ -20,9 +20,18 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.ClassNode;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.AnyBreak;
+import org.elasticsearch.painless.symbol.Decorations.AnyContinue;
+import org.elasticsearch.painless.symbol.Decorations.BeginLoop;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LastLoop;
+import org.elasticsearch.painless.symbol.Decorations.LastSource;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.Decorations.MethodEscape;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -47,7 +56,7 @@ public class SBlock extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
 
         if (statementNodes.isEmpty()) {
@@ -58,30 +67,62 @@ public class SBlock extends AStatement {
 
         List<Output> statementOutputs = new ArrayList<>(statementNodes.size());
 
+        boolean lastSource = semanticScope.getCondition(this, LastSource.class);
+        boolean beginLoop = semanticScope.getCondition(this, BeginLoop.class);
+        boolean inLoop = semanticScope.getCondition(this, InLoop.class);
+        boolean lastLoop = semanticScope.getCondition(this, LastLoop.class);
+
+        boolean allEscape = false;
+        boolean anyContinue = false;
+        boolean anyBreak = false;
+
         for (AStatement statement : statementNodes) {
-            // Note that we do not need to check after the last statement because
-            // there is no statement that can be unreachable after the last.
-            if (output.allEscape) {
-                throw createError(new IllegalArgumentException("Unreachable statement."));
+            if (inLoop) {
+                semanticScope.setCondition(statement, InLoop.class);
             }
 
-            Input statementInput = new Input();
-            statementInput.inLoop = input.inLoop;
-            statementInput.lastSource = input.lastSource && statement == last;
-            statementInput.lastLoop = (input.beginLoop || input.lastLoop) && statement == last;
+            if (statement == last) {
+                if (beginLoop || lastLoop) {
+                    semanticScope.setCondition(statement, LastLoop.class);
+                }
 
-            Output statementOutput = statement.analyze(classNode, semanticScope, statementInput);
+                if (lastSource) {
+                    semanticScope.setCondition(statement, LastSource.class);
+                }
+            }
 
-            output.methodEscape = statementOutput.methodEscape;
-            output.loopEscape = statementOutput.loopEscape;
-            output.allEscape = statementOutput.allEscape;
-            output.anyContinue |= statementOutput.anyContinue;
-            output.anyBreak |= statementOutput.anyBreak;
-            output.statementCount += statementOutput.statementCount;
+            Output statementOutput = statement.analyze(classNode, semanticScope);
+            allEscape = semanticScope.getCondition(statement, AllEscape.class);
+
+            if (statement == last) {
+                semanticScope.replicateCondition(statement, this, MethodEscape.class);
+                semanticScope.replicateCondition(statement, this, LoopEscape.class);
+
+                if (allEscape) {
+                    semanticScope.setCondition(this, AllEscape.class);
+                }
+            } else {
+                // Note that we do not need to check after the last statement because
+                // there is no statement that can be unreachable after the last.
+                if (allEscape) {
+                    throw createError(new IllegalArgumentException("Unreachable statement."));
+                }
+            }
+
+            anyContinue |= semanticScope.getCondition(statement, AnyContinue.class);
+            anyBreak |= semanticScope.getCondition(statement, AnyBreak.class);;
 
             statementOutputs.add(statementOutput);
         }
 
+        if (anyContinue) {
+            semanticScope.setCondition(this, AnyContinue.class);
+        }
+
+        if (anyBreak) {
+            semanticScope.setCondition(this, AnyBreak.class);
+        }
+
         BlockNode blockNode = new BlockNode();
 
         for (Output statementOutput : statementOutputs) {
@@ -89,8 +130,7 @@ public class SBlock extends AStatement {
         }
 
         blockNode.setLocation(getLocation());
-        blockNode.setAllEscape(output.allEscape);
-        blockNode.setStatementCount(output.statementCount);
+        blockNode.setAllEscape(allEscape);
 
         output.statementNode = blockNode;
 

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

@@ -20,9 +20,13 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BreakNode;
 import org.elasticsearch.painless.ir.ClassNode;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.AnyBreak;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
  * Represents a break statement.
@@ -34,17 +38,16 @@ public class SBreak extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
 
-        if (input.inLoop == false) {
+        if (semanticScope.getCondition(this, InLoop.class) == false) {
             throw createError(new IllegalArgumentException("Break statement outside of a loop."));
         }
 
-        output.loopEscape = true;
-        output.allEscape = true;
-        output.anyBreak = true;
-        output.statementCount = 1;
+        semanticScope.setCondition(this, AllEscape.class);
+        semanticScope.setCondition(this, LoopEscape.class);
+        semanticScope.setCondition(this, AnyBreak.class);
 
         BreakNode breakNode = new BreakNode();
         breakNode.setLocation(getLocation());

+ 19 - 13
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java

@@ -24,6 +24,14 @@ import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.CatchNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.AnyBreak;
+import org.elasticsearch.painless.symbol.Decorations.AnyContinue;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LastLoop;
+import org.elasticsearch.painless.symbol.Decorations.LastSource;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.Decorations.MethodEscape;
 import org.elasticsearch.painless.symbol.ScriptScope;
 import org.elasticsearch.painless.symbol.SemanticScope;
 
@@ -65,7 +73,7 @@ public class SCatch extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         ScriptScope scriptScope = semanticScope.getScriptScope();
 
         Output output = new Output();
@@ -91,18 +99,16 @@ public class SCatch extends AStatement {
         Output blockOutput = null;
 
         if (blockNode != null) {
-            Input blockInput = new Input();
-            blockInput.lastSource = input.lastSource;
-            blockInput.inLoop = input.inLoop;
-            blockInput.lastLoop = input.lastLoop;
-            blockOutput = blockNode.analyze(classNode, semanticScope, blockInput);
-
-            output.methodEscape = blockOutput.methodEscape;
-            output.loopEscape = blockOutput.loopEscape;
-            output.allEscape = blockOutput.allEscape;
-            output.anyContinue = blockOutput.anyContinue;
-            output.anyBreak = blockOutput.anyBreak;
-            output.statementCount = blockOutput.statementCount;
+            semanticScope.replicateCondition(this, blockNode, LastSource.class);
+            semanticScope.replicateCondition(this, blockNode, InLoop.class);
+            semanticScope.replicateCondition(this, blockNode, LastLoop.class);
+            blockOutput = blockNode.analyze(classNode, semanticScope);
+
+            semanticScope.setCondition(this, MethodEscape.class);
+            semanticScope.setCondition(this, LoopEscape.class);
+            semanticScope.setCondition(this, AllEscape.class);
+            semanticScope.setCondition(this, AnyContinue.class);
+            semanticScope.setCondition(this, AnyBreak.class);
         }
 
         CatchNode catchNode = new CatchNode();

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

@@ -20,9 +20,13 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ContinueNode;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.AnyContinue;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LastLoop;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
  * Represents a continue statement.
@@ -34,20 +38,19 @@ public class SContinue extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
 
-        if (input.inLoop == false) {
+        if (semanticScope.getCondition(this, InLoop.class) == false) {
             throw createError(new IllegalArgumentException("Continue statement outside of a loop."));
         }
 
-        if (input.lastLoop) {
+        if (semanticScope.getCondition(this, LastLoop.class)) {
             throw createError(new IllegalArgumentException("Extraneous continue statement."));
         }
 
-        output.allEscape = true;
-        output.anyContinue = true;
-        output.statementCount = 1;
+        semanticScope.setCondition(this, AllEscape.class);
+        semanticScope.setCondition(this, AnyContinue.class);
 
         ContinueNode continueNode = new ContinueNode();
         continueNode.setLocation(getLocation());

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

@@ -20,10 +20,10 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.DeclarationBlockNode;
 import org.elasticsearch.painless.ir.DeclarationNode;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -47,17 +47,15 @@ public class SDeclBlock extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
 
         List<Output> declarationOutputs = new ArrayList<>(declarationNodes.size());
 
         for (SDeclaration declaration : declarationNodes) {
-            declarationOutputs.add(declaration.analyze(classNode, semanticScope, new Input()));
+            declarationOutputs.add(declaration.analyze(classNode, semanticScope));
         }
 
-        output.statementCount = declarationNodes.size();
-
         DeclarationBlockNode declarationBlockNode = new DeclarationBlockNode();
 
         for (Output declarationOutput : declarationOutputs) {

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

@@ -19,13 +19,14 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.ScriptScope;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.DeclarationNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.ScriptScope;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -59,7 +60,7 @@ public class SDeclaration extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         ScriptScope scriptScope = semanticScope.getScriptScope();
 
         if (scriptScope.getPainlessLookup().isValidCanonicalClassName(symbol)) {
@@ -76,11 +77,10 @@ public class SDeclaration extends AStatement {
         PainlessCast expressionCast = null;
 
         if (valueNode != null) {
-            AExpression.Input expressionInput = new AExpression.Input();
-            expressionInput.expected = type;
-            expressionOutput = AExpression.analyze(valueNode, classNode, semanticScope, expressionInput);
-            expressionCast = AnalyzerCaster.getLegalCast(valueNode.getLocation(),
-                    expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal);
+            semanticScope.setCondition(valueNode, Read.class);
+            semanticScope.putDecoration(valueNode, new TargetType(type));
+            expressionOutput = AExpression.analyze(valueNode, classNode, semanticScope);
+            expressionCast = valueNode.cast(semanticScope);
         }
 
         semanticScope.defineVariable(getLocation(), type, symbol, false);

+ 23 - 19
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java

@@ -19,13 +19,21 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.DoWhileLoopNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.AnyBreak;
+import org.elasticsearch.painless.symbol.Decorations.AnyContinue;
+import org.elasticsearch.painless.symbol.Decorations.BeginLoop;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.Decorations.MethodEscape;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -53,7 +61,7 @@ public class SDo extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
         semanticScope = semanticScope.newLocalScope();
 
@@ -61,21 +69,19 @@ public class SDo extends AStatement {
             throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
-        Input blockInput = new Input();
-        blockInput.beginLoop = true;
-        blockInput.inLoop = true;
-        Output blockOutput = blockNode.analyze(classNode, semanticScope, blockInput);
+        semanticScope.setCondition(blockNode, BeginLoop.class);
+        semanticScope.setCondition(blockNode, InLoop.class);
+        Output blockOutput = blockNode.analyze(classNode, semanticScope);
 
-        if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
+        if (semanticScope.getCondition(blockNode, LoopEscape.class) &&
+                semanticScope.getCondition(blockNode, AnyContinue.class) == false) {
             throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
-        AExpression.Input conditionInput = new AExpression.Input();
-        conditionInput.expected = boolean.class;
-        AExpression.Output conditionOutput = AExpression.analyze(conditionNode, classNode, semanticScope, conditionInput);
-        PainlessCast conditionCast = AnalyzerCaster.getLegalCast(conditionNode.getLocation(),
-                conditionOutput.actual, conditionInput.expected, conditionInput.explicit, conditionInput.internal);
-
+        semanticScope.setCondition(conditionNode, Read.class);
+        semanticScope.putDecoration(conditionNode, new TargetType(boolean.class));
+        AExpression.Output conditionOutput = AExpression.analyze(conditionNode, classNode, semanticScope);
+        PainlessCast conditionCast = conditionNode.cast(semanticScope);
 
         boolean continuous = false;
 
@@ -86,14 +92,12 @@ public class SDo extends AStatement {
                 throw createError(new IllegalArgumentException("Extraneous do while loop."));
             }
 
-            if (blockOutput.anyBreak == false) {
-                output.methodEscape = true;
-                output.allEscape = true;
+            if (semanticScope.getCondition(blockNode, AnyBreak.class) == false) {
+                semanticScope.setCondition(this, MethodEscape.class);
+                semanticScope.setCondition(this, AllEscape.class);
             }
         }
 
-        output.statementCount = 1;
-
         DoWhileLoopNode doWhileLoopNode = new DoWhileLoopNode();
         doWhileLoopNode.setConditionNode(AExpression.cast(conditionOutput.expressionNode, conditionCast));
         doWhileLoopNode.setBlockNode((BlockNode)blockOutput.statementNode);

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

@@ -31,6 +31,12 @@ 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.Decorations.AnyContinue;
+import org.elasticsearch.painless.symbol.Decorations.BeginLoop;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
 import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.symbol.SemanticScope.Variable;
 
@@ -75,11 +81,12 @@ public class SEach extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Output output = new Output();
 
-        AExpression.Input expressionInput = new AExpression.Input();
-        AExpression.Output expressionOutput = AExpression.analyze(iterableNode, classNode, semanticScope, expressionInput);
+        semanticScope.setCondition(iterableNode, Read.class);
+        AExpression.Output expressionOutput = AExpression.analyze(iterableNode, classNode, semanticScope);
+        Class<?> iterableValueType = semanticScope.getDecoration(iterableNode, ValueType.class).getValueType();
 
         Class<?> clazz = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
 
@@ -94,23 +101,22 @@ public class SEach extends AStatement {
             throw createError(new IllegalArgumentException("Extraneous for each loop."));
         }
 
-        Input blockInput = new Input();
-        blockInput.beginLoop = true;
-        blockInput.inLoop = true;
-        Output blockOutput = blockNode.analyze(classNode, semanticScope, blockInput);
-        blockOutput.statementCount = Math.max(1, blockOutput.statementCount);
+        semanticScope.setCondition(blockNode, BeginLoop.class);
+        semanticScope.setCondition(blockNode, InLoop.class);
+        Output blockOutput = blockNode.analyze(classNode, semanticScope);
 
-        if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
+        if (semanticScope.getCondition(blockNode, LoopEscape.class) &&
+                semanticScope.getCondition(blockNode, AnyContinue.class) == false) {
             throw createError(new IllegalArgumentException("Extraneous for loop."));
         }
 
         ConditionNode conditionNode;
 
-        if (expressionOutput.actual.isArray()) {
+        if (iterableValueType.isArray()) {
             Variable array =
-                    semanticScope.defineVariable(getLocation(), expressionOutput.actual, "#array" + getLocation().getOffset(), true);
+                    semanticScope.defineVariable(getLocation(), iterableValueType, "#array" + getLocation().getOffset(), true);
             Variable index = semanticScope.defineVariable(getLocation(), int.class, "#index" + getLocation().getOffset(), true);
-            Class<?> indexed = expressionOutput.actual.getComponentType();
+            Class<?> indexed = iterableValueType.getComponentType();
             PainlessCast cast = AnalyzerCaster.getLegalCast(getLocation(), indexed, variable.getType(), true, true);
 
             ForEachSubArrayNode forEachSubArrayNode = new ForEachSubArrayNode();
@@ -127,22 +133,22 @@ public class SEach extends AStatement {
             forEachSubArrayNode.setIndexedType(indexed);
             forEachSubArrayNode.setContinuous(false);
             conditionNode = forEachSubArrayNode;
-        } else if (expressionOutput.actual == def.class || Iterable.class.isAssignableFrom(expressionOutput.actual)) {
+        } else if (iterableValueType == def.class || Iterable.class.isAssignableFrom(iterableValueType)) {
             // 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.
             Variable iterator = semanticScope.defineVariable(getLocation(), Iterator.class, "#itr" + getLocation().getOffset(), true);
 
             PainlessMethod method;
 
-            if (expressionOutput.actual == def.class) {
+            if (iterableValueType == def.class) {
                 method = null;
             } else {
                 method = semanticScope.getScriptScope().getPainlessLookup().
-                        lookupPainlessMethod(expressionOutput.actual, false, "iterator", 0);
+                        lookupPainlessMethod(iterableValueType, false, "iterator", 0);
 
                 if (method == null) {
                     throw createError(new IllegalArgumentException(
-                            "method [" + typeToCanonicalTypeName(expressionOutput.actual) + ", iterator/0] not found"));
+                            "method [" + typeToCanonicalTypeName(iterableValueType) + ", iterator/0] not found"));
                 }
             }
 
@@ -162,11 +168,9 @@ public class SEach extends AStatement {
             conditionNode = forEachSubIterableNode;
         } else {
             throw createError(new IllegalArgumentException("Illegal for each type " +
-                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(expressionOutput.actual) + "]."));
+                    "[" + PainlessLookupUtility.typeToCanonicalTypeName(iterableValueType) + "]."));
         }
 
-        output.statementCount = 1;
-
         ForEachLoopNode forEachLoopNode = new ForEachLoopNode();
         forEachLoopNode.setConditionNode(conditionNode);
         forEachLoopNode.setLocation(getLocation());

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

@@ -19,14 +19,21 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ExpressionNode;
 import org.elasticsearch.painless.ir.ReturnNode;
 import org.elasticsearch.painless.ir.StatementExpressionNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.Internal;
+import org.elasticsearch.painless.symbol.Decorations.LastSource;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.Decorations.MethodEscape;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.Decorations.ValueType;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 import java.util.Objects;
 
@@ -48,30 +55,38 @@ public class SExpression extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         Class<?> rtnType = semanticScope.getReturnType();
         boolean isVoid = rtnType == void.class;
+        boolean lastSource = semanticScope.getCondition(this, LastSource.class);
+        
+        if (lastSource && !isVoid) {
+            semanticScope.setCondition(expressionNode, Read.class);
+        }
+        
+        AExpression.Output expressionOutput = AExpression.analyze(expressionNode, classNode, semanticScope);
+        Class<?> expressionValueType = semanticScope.getDecoration(expressionNode, ValueType.class).getValueType();
 
-        AExpression.Input expressionInput = new AExpression.Input();
-        expressionInput.read = input.lastSource && !isVoid;
-        AExpression.Output expressionOutput = AExpression.analyze(expressionNode, classNode, semanticScope, expressionInput);
+        boolean rtn = lastSource && isVoid == false && expressionValueType != void.class;
+        semanticScope.putDecoration(expressionNode, new TargetType(rtn ? rtnType : expressionValueType));
 
-        boolean rtn = input.lastSource && isVoid == false && expressionOutput.actual != void.class;
+        if (rtn) {
+            semanticScope.setCondition(expressionNode, Internal.class);
+        }
 
-        expressionInput.expected = rtn ? rtnType : expressionOutput.actual;
-        expressionInput.internal = rtn;
-        PainlessCast expressionCast = AnalyzerCaster.getLegalCast(expressionNode.getLocation(),
-                expressionOutput.actual, expressionInput.expected, expressionInput.explicit, expressionInput.internal);
+        PainlessCast expressionCast = expressionNode.cast(semanticScope);
 
         Output output = new Output();
-        output.methodEscape = rtn;
-        output.loopEscape = rtn;
-        output.allEscape = rtn;
-        output.statementCount = 1;
+
+        if (rtn) {
+            semanticScope.setCondition(this, MethodEscape.class);
+            semanticScope.setCondition(this, LoopEscape.class);
+            semanticScope.setCondition(this, AllEscape.class);
+        }
 
         ExpressionNode expressionNode = AExpression.cast(expressionOutput.expressionNode, expressionCast);
 
-        if (output.methodEscape) {
+        if (rtn) {
             ReturnNode returnNode = new ReturnNode();
             returnNode.setExpressionNode(expressionNode);
             returnNode.setLocation(getLocation());

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

@@ -19,13 +19,21 @@
 
 package org.elasticsearch.painless.node;
 
-import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Location;
-import org.elasticsearch.painless.symbol.SemanticScope;
 import org.elasticsearch.painless.ir.BlockNode;
 import org.elasticsearch.painless.ir.ClassNode;
 import org.elasticsearch.painless.ir.ForLoopNode;
 import org.elasticsearch.painless.lookup.PainlessCast;
+import org.elasticsearch.painless.symbol.Decorations.AllEscape;
+import org.elasticsearch.painless.symbol.Decorations.AnyBreak;
+import org.elasticsearch.painless.symbol.Decorations.AnyContinue;
+import org.elasticsearch.painless.symbol.Decorations.BeginLoop;
+import org.elasticsearch.painless.symbol.Decorations.InLoop;
+import org.elasticsearch.painless.symbol.Decorations.LoopEscape;
+import org.elasticsearch.painless.symbol.Decorations.MethodEscape;
+import org.elasticsearch.painless.symbol.Decorations.Read;
+import org.elasticsearch.painless.symbol.Decorations.TargetType;
+import org.elasticsearch.painless.symbol.SemanticScope;
 
 /**
  * Represents a for loop.
@@ -65,7 +73,7 @@ public class SFor extends AStatement {
     }
 
     @Override
-    Output analyze(ClassNode classNode, SemanticScope semanticScope, Input input) {
+    Output analyze(ClassNode classNode, SemanticScope semanticScope) {
         semanticScope = semanticScope.newLocalScope();
 
         Output initializerStatementOutput = null;
@@ -73,13 +81,10 @@ public class SFor extends AStatement {
 
         if (initializerNode != null) {
             if (initializerNode instanceof SDeclBlock) {
-                initializerStatementOutput = ((SDeclBlock)initializerNode).analyze(classNode, semanticScope, new Input());
+                initializerStatementOutput = ((SDeclBlock)initializerNode).analyze(classNode, semanticScope);
             } else if (initializerNode instanceof AExpression) {
                 AExpression initializer = (AExpression)this.initializerNode;
-
-                AExpression.Input initializerInput = new AExpression.Input();
-                initializerInput.read = false;
-                initializerExpressionOutput = AExpression.analyze(initializer, classNode, semanticScope, initializerInput);
+                initializerExpressionOutput = AExpression.analyze(initializer, classNode, semanticScope);
             } else {
                 throw createError(new IllegalStateException("Illegal tree structure."));
             }
@@ -91,11 +96,10 @@ public class SFor extends AStatement {
         PainlessCast conditionCast = null;
 
         if (conditionNode != null) {
-            AExpression.Input conditionInput = new AExpression.Input();
-            conditionInput.expected = boolean.class;
-            conditionOutput = AExpression.analyze(conditionNode, classNode, semanticScope, conditionInput);
-            conditionCast = AnalyzerCaster.getLegalCast(conditionNode.getLocation(),
-                    conditionOutput.actual, conditionInput.expected, conditionInput.explicit, conditionInput.internal);
+            semanticScope.setCondition(conditionNode, Read.class);
+            semanticScope.putDecoration(conditionNode, new TargetType(boolean.class));
+            conditionOutput = AExpression.analyze(conditionNode, classNode, semanticScope);
+            conditionCast = conditionNode.cast(semanticScope);
 
             if (conditionNode instanceof EBoolean) {
                 continuous = ((EBoolean)conditionNode).getBool();
@@ -115,35 +119,28 @@ public class SFor extends AStatement {
         AExpression.Output afterthoughtOutput = null;
 
         if (afterthoughtNode != null) {
-            AExpression.Input afterthoughtInput = new AExpression.Input();
-            afterthoughtInput.read = false;
-            afterthoughtOutput = AExpression.analyze(afterthoughtNode, classNode, semanticScope, afterthoughtInput);
+            afterthoughtOutput = AExpression.analyze(afterthoughtNode, classNode, semanticScope);
         }
 
         Output output = new Output();
         Output blockOutput = null;
 
         if (blockNode != null) {
-            Input blockInput = new Input();
-            blockInput.beginLoop = true;
-            blockInput.inLoop = true;
+            semanticScope.setCondition(blockNode, BeginLoop.class);
+            semanticScope.setCondition(blockNode, InLoop.class);
+            blockOutput = blockNode.analyze(classNode, semanticScope);
 
-            blockOutput = blockNode.analyze(classNode, semanticScope, blockInput);
-
-            if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
+            if (semanticScope.getCondition(blockNode, LoopEscape.class) &&
+                    semanticScope.getCondition(blockNode, AnyContinue.class) == false) {
                 throw createError(new IllegalArgumentException("Extraneous for loop."));
             }
 
-            if (continuous && blockOutput.anyBreak == false) {
-                output.methodEscape = true;
-                output.allEscape = true;
+            if (continuous && semanticScope.getCondition(blockNode, AnyBreak.class) == false) {
+                semanticScope.setCondition(this, MethodEscape.class);
+                semanticScope.setCondition(this, AllEscape.class);
             }
-
-            blockOutput.statementCount = Math.max(1, blockOutput.statementCount);
         }
 
-        output.statementCount = 1;
-
         ForLoopNode forLoopNode = new ForLoopNode();
         forLoopNode.setInitialzerNode(initializerNode == null ? null : initializerNode instanceof AExpression ?
                 initializerExpressionOutput.expressionNode : initializerStatementOutput.statementNode);

部分文件因为文件数量过多而无法显示