diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index ab09010d248d4..e9dcdb1ef409d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -210,8 +210,7 @@ ScriptRoot compile(Loader loader, String name, String source, CompilerSettings s ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass); SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, null); ScriptRoot scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, root); - root.analyze(scriptRoot); - ClassNode classNode = root.writeClass(); + ClassNode classNode = root.writeClass(scriptRoot); DefBootstrapInjectionPhase.phase(classNode); ScriptInjectionPhase.phase(scriptRoot, classNode); byte[] bytes = classNode.write(); @@ -240,8 +239,7 @@ byte[] compile(String name, String source, CompilerSettings settings, Printer de ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass); SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, debugStream); ScriptRoot scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, root); - root.analyze(scriptRoot); - ClassNode classNode = root.writeClass(); + ClassNode classNode = root.writeClass(scriptRoot); DefBootstrapInjectionPhase.phase(classNode); ScriptInjectionPhase.phase(scriptRoot, classNode); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java index 189cb94dcd70b..e7fb9e951c59e 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java @@ -79,6 +79,18 @@ public static class Output { * called on this node to get the type of the node after the cast. */ Class actual = null; + + /** + * The {@link PainlessCast} to convert this expression's actual type + * to the parent expression's expected type. {@code null} if no cast + * is required. + */ + PainlessCast painlessCast = null; + + /** + * The {@link ExpressionNode}(s) generated from this expression. + */ + ExpressionNode expressionNode = null; } /** @@ -90,16 +102,6 @@ public static class Output { */ AExpression prefix; - // TODO: remove placeholders once analysis and write are combined into build - // TODO: https://github.com/elastic/elasticsearch/issues/53561 - // This are used to support the transition from a mutable to immutable state. - // Currently, the IR tree is built during the user tree "write" phase, so - // these are stored on the node to set during the "semantic" phase and then - // use during the "write" phase. - Input input = null; - Output output = null; - PainlessCast cast = null; - /** * Standard constructor with location used for error tracking. */ @@ -121,29 +123,24 @@ public static class Output { /** * Checks for errors and collects data for the writing phase. */ - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { throw new UnsupportedOperationException(); } - /** - * Writes ASM based on the data collected during the analysis phase. - */ - abstract ExpressionNode write(ClassNode classNode); - - void cast() { - cast = AnalyzerCaster.getLegalCast(location, output.actual, input.expected, input.explicit, input.internal); + void cast(Input input, Output output) { + output.painlessCast = AnalyzerCaster.getLegalCast(location, output.actual, input.expected, input.explicit, input.internal); } - ExpressionNode cast(ExpressionNode expressionNode) { - if (cast == null) { - return expressionNode; + ExpressionNode cast(Output output) { + if (output.painlessCast == null) { + return output.expressionNode; } CastNode castNode = new CastNode(); castNode.setLocation(location); - castNode.setExpressionType(cast.targetType); - castNode.setCast(cast); - castNode.setChildNode(expressionNode); + castNode.setExpressionType(output.painlessCast.targetType); + castNode.setCast(output.painlessCast); + castNode.setChildNode(output.expressionNode); return castNode; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java index 0566399095565..4790e222e99d7 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java @@ -20,8 +20,6 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.Location; -import org.elasticsearch.painless.ir.ClassNode; -import org.elasticsearch.painless.ir.IRNode; import java.util.ArrayList; import java.util.Arrays; @@ -51,12 +49,6 @@ public abstract class ANode { this.location = Objects.requireNonNull(location); } - /** - * Writes ASM based on the data collected during the analysis phase. - * @param classNode the root {@link ClassNode} - */ - abstract IRNode write(ClassNode classNode); - /** * Create an error with location information pointing to this node. */ diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java index d8d12d8a14698..06c1affc4a4a7 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java @@ -94,13 +94,13 @@ public static class Output { * Set to the approximate number of statements in a loop block to prevent * infinite loops during runtime. */ - int statementCount = 0; - } + int statementCount = 1; - // TODO: remove placeholders once analysis and write are combined into build - // TODO: https://github.com/elastic/elasticsearch/issues/53561 - Input input; - Output output; + /** + * The {@link StatementNode}(s) generated from this expression. + */ + StatementNode statementNode = null; + } /** * Standard constructor with location used for error tracking. @@ -112,12 +112,7 @@ public static class Output { /** * Checks for errors and collects data for the writing phase. */ - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { throw new UnsupportedOperationException(); } - - /** - * Writes ASM based on the data collected during the analysis phase. - */ - abstract StatementNode write(ClassNode classNode); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStoreable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStoreable.java index cc7262f670a94..4bca9bb37cbc5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStoreable.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStoreable.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -57,7 +58,7 @@ public static class Input extends AExpression.Input { this.prefix = Objects.requireNonNull(prefix); } - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { throw new UnsupportedOperationException(); } @@ -66,11 +67,4 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { * rhs actual type to avoid an unnecessary cast. */ abstract boolean isDefOptimized(); - - /** - * If this node or a sub-node of this node uses dynamic calls then - * actual will be set to this value. This is used for an optimization - * during assignment to def type targets. - */ - abstract void updateActual(Class actual); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java index e995d637520c7..970d31935831b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java @@ -25,7 +25,13 @@ import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.AssignmentNode; +import org.elasticsearch.painless.ir.BinaryMathNode; +import org.elasticsearch.painless.ir.BraceNode; +import org.elasticsearch.painless.ir.BraceSubDefNode; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.DotNode; +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.ScriptRoot; @@ -37,19 +43,13 @@ /** * Represents an assignment with the lhs and rhs as child nodes. */ -public final class EAssignment extends AExpression { +public class EAssignment extends AExpression { - private AExpression lhs; - private AExpression rhs; - private final boolean pre; - private final boolean post; - private Operation operation; - - private boolean cat = false; - private Class promote = null; - private Class shiftDistance; // for shifts, the RHS is promoted independently - private PainlessCast there = null; - private PainlessCast back = null; + protected final AExpression lhs; + protected final AExpression rhs; + protected final boolean pre; + protected final boolean post; + protected final Operation operation; public EAssignment(Location location, AExpression lhs, AExpression rhs, boolean pre, boolean post, Operation operation) { super(location); @@ -62,11 +62,20 @@ public EAssignment(Location location, AExpression lhs, AExpression rhs, boolean } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + AExpression rhs = this.rhs; + Operation operation = this.operation; + boolean cat = false; + Class promote = null; + Class shiftDistance = null; + PainlessCast there = null; + PainlessCast back = null; Output leftOutput; + + Input rightInput = new Input(); Output rightOutput; if (lhs instanceof AStoreable) { @@ -75,7 +84,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { leftInput.read = input.read; leftInput.write = true; - leftOutput = lhs.analyze(scriptRoot, scope, leftInput); + leftOutput = lhs.analyze(classNode, scriptRoot, scope, leftInput); } else { throw new IllegalArgumentException("Left-hand side cannot be assigned a value."); } @@ -117,7 +126,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } if (operation != null) { - rightOutput = rhs.analyze(scriptRoot, scope, new Input()); + rightOutput = rhs.analyze(classNode, scriptRoot, scope, rightInput); boolean shift = false; if (operation == Operation.MUL) { @@ -161,25 +170,25 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { if (cat) { if (rhs instanceof EBinary && ((EBinary)rhs).operation == Operation.ADD && rightOutput.actual == String.class) { - ((EBinary)rhs).cat = true; + ((BinaryMathNode)rightOutput.expressionNode).setCat(true); } } if (shift) { if (promote == def.class) { // shifts are promoted independently, but for the def type, we need object. - rhs.input.expected = promote; + rightInput.expected = promote; } else if (shiftDistance == long.class) { - rhs.input.expected = int.class; - rhs.input.explicit = true; + rightInput.expected = int.class; + rightInput.explicit = true; } else { - rhs.input.expected = shiftDistance; + rightInput.expected = shiftDistance; } } else { - rhs.input.expected = promote; + rightInput.expected = promote; } - rhs.cast(); + rhs.cast(rightInput, rightOutput); there = AnalyzerCaster.getLegalCast(location, leftOutput.actual, promote, false, false); back = AnalyzerCaster.getLegalCast(location, promote, leftOutput.actual, true, false); @@ -188,24 +197,33 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } else if (rhs != null) { AStoreable lhs = (AStoreable)this.lhs; + // TODO: move this optimization to a later phase // If the lhs node is a def optimized node we update the actual type to remove the need for a cast. if (lhs.isDefOptimized()) { - rightOutput = rhs.analyze(scriptRoot, scope, new Input()); + rightOutput = rhs.analyze(classNode, scriptRoot, scope, rightInput); if (rightOutput.actual == void.class) { throw createError(new IllegalArgumentException("Right-hand side cannot be a [void] type for assignment.")); } - rhs.input.expected = rightOutput.actual; - lhs.updateActual(rightOutput.actual); + rightInput.expected = rightOutput.actual; + leftOutput.actual = rightOutput.actual; + leftOutput.expressionNode.setExpressionType(rightOutput.actual); + + ExpressionNode expressionNode = leftOutput.expressionNode; + + if (expressionNode instanceof DotNode && ((DotNode)expressionNode).getRightNode() instanceof DotSubDefNode) { + ((DotNode)expressionNode).getRightNode().setExpressionType(leftOutput.actual); + } else if (expressionNode instanceof BraceNode && ((BraceNode)expressionNode).getRightNode() instanceof BraceSubDefNode) { + ((BraceNode)expressionNode).getRightNode().setExpressionType(leftOutput.actual); + } // Otherwise, we must adapt the rhs type to the lhs type with a cast. } else { - Input rightInput = new Input(); rightInput.expected = leftOutput.actual; - rhs.analyze(scriptRoot, scope, rightInput); + rightOutput = rhs.analyze(classNode, scriptRoot, scope, rightInput); } - rhs.cast(); + rhs.cast(rightInput, rightOutput); } else { throw new IllegalStateException("Illegal tree structure."); } @@ -213,21 +231,10 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.statement = true; output.actual = input.read ? leftOutput.actual : void.class; - return output; - } - - /** - * Handles writing byte code for variable/method chains for all given possibilities - * including String concatenation, compound assignment, regular assignment, and simple - * reads. Includes proper duplication for chained assignments and assignments that are - * also read from. - */ - @Override - AssignmentNode write(ClassNode classNode) { AssignmentNode assignmentNode = new AssignmentNode(); - assignmentNode.setLeftNode(lhs.write(classNode)); - assignmentNode.setRightNode(rhs.cast(rhs.write(classNode))); + assignmentNode.setLeftNode(leftOutput.expressionNode); + assignmentNode.setRightNode(rhs.cast(rightOutput)); assignmentNode.setLocation(location); assignmentNode.setExpressionType(output.actual); @@ -240,7 +247,9 @@ AssignmentNode write(ClassNode classNode) { assignmentNode.setThere(there); assignmentNode.setBack(back); - return assignmentNode; + output.expressionNode = assignmentNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java index 5a65d74d4158c..9f6fc56f97ff2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java @@ -35,16 +35,11 @@ /** * Represents a binary math expression. */ -public final class EBinary extends AExpression { +public class EBinary extends AExpression { - final Operation operation; - private AExpression left; - private AExpression right; - - private Class promote = null; // promoted type - private Class shiftDistance = null; // for shifts, the rhs is promoted independently - boolean cat = false; - private boolean originallyExplicit = false; // record whether there was originally an explicit cast + protected final Operation operation; + protected final AExpression left; + protected final AExpression right; public EBinary(Location location, Operation operation, AExpression left, AExpression right) { super(location); @@ -55,18 +50,22 @@ public EBinary(Location location, Operation operation, AExpression left, AExpres } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + 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 + + Output output = new Output(); - originallyExplicit = input.explicit; + Input leftInput = new Input(); + Output leftOutput = left.analyze(classNode, scriptRoot, scope, leftInput); - Output leftOutput = left.analyze(scriptRoot, scope, new Input()); - Output rightOutput = right.analyze(scriptRoot, scope, new Input()); + Input rightInput = new Input(); + Output rightOutput = right.analyze(classNode, scriptRoot, scope, rightInput); if (operation == Operation.FIND || operation == Operation.MATCH) { - left.input.expected = String.class; - right.input.expected = Pattern.class; + leftInput.expected = String.class; + rightInput.expected = Pattern.class; promote = boolean.class; output.actual = boolean.class; } else { @@ -99,61 +98,58 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = promote; if (operation == Operation.ADD && promote == String.class) { - left.input.expected = leftOutput.actual; - right.input.expected = rightOutput.actual; + leftInput.expected = leftOutput.actual; + rightInput.expected = rightOutput.actual; if (left instanceof EBinary && ((EBinary) left).operation == Operation.ADD && leftOutput.actual == String.class) { - ((EBinary) left).cat = true; + ((BinaryMathNode)leftOutput.expressionNode).setCat(true); } if (right instanceof EBinary && ((EBinary) right).operation == Operation.ADD && rightOutput.actual == String.class) { - ((EBinary) right).cat = true; + ((BinaryMathNode)rightOutput.expressionNode).setCat(true); } - } else if (promote == def.class || shiftDistance != null && shiftDistance == def.class) { - left.input.expected = leftOutput.actual; - right.input.expected = rightOutput.actual; + } else if (promote == def.class || shiftDistance == def.class) { + leftInput.expected = leftOutput.actual; + rightInput.expected = rightOutput.actual; if (input.expected != null) { output.actual = input.expected; } } else { - left.input.expected = promote; + leftInput.expected = promote; if (operation == Operation.LSH || operation == Operation.RSH || operation == Operation.USH) { if (shiftDistance == long.class) { - right.input.expected = int.class; - right.input.explicit = true; + rightInput.expected = int.class; + rightInput.explicit = true; } else { - right.input.expected = shiftDistance; + rightInput.expected = shiftDistance; } } else { - right.input.expected = promote; + rightInput.expected = promote; } } } - left.cast(); - right.cast(); - - return output; - } + left.cast(leftInput, leftOutput); + right.cast(rightInput, rightOutput); - @Override - BinaryMathNode write(ClassNode classNode) { BinaryMathNode binaryMathNode = new BinaryMathNode(); - binaryMathNode.setLeftNode(left.cast(left.write(classNode))); - binaryMathNode.setRightNode(right.cast(right.write(classNode))); + binaryMathNode.setLeftNode(left.cast(leftOutput)); + binaryMathNode.setRightNode(right.cast(rightOutput)); binaryMathNode.setLocation(location); binaryMathNode.setExpressionType(output.actual); binaryMathNode.setBinaryType(promote); binaryMathNode.setShiftType(shiftDistance); binaryMathNode.setOperation(operation); - binaryMathNode.setCat(cat); + binaryMathNode.setCat(false); binaryMathNode.setOriginallExplicit(originallyExplicit); - return binaryMathNode; + output.expressionNode = binaryMathNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java index f2acae9299f20..e9dac2123c6cd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java @@ -31,11 +31,11 @@ /** * Represents a boolean expression. */ -public final class EBool extends AExpression { +public class EBool extends AExpression { - private final Operation operation; - private AExpression left; - private AExpression right; + protected final Operation operation; + protected final AExpression left; + protected final AExpression right; public EBool(Location location, Operation operation, AExpression left, AExpression right) { super(location); @@ -46,37 +46,33 @@ public EBool(Location location, Operation operation, AExpression left, AExpressi } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); Input leftInput = new Input(); leftInput.expected = boolean.class; - left.analyze(scriptRoot, scope, leftInput); - left.cast(); + Output leftOutput = left.analyze(classNode, scriptRoot, scope, leftInput); + left.cast(leftInput, leftOutput); Input rightInput = new Input(); rightInput.expected = boolean.class; - right.analyze(scriptRoot, scope, rightInput); - right.cast(); + Output rightOutput = right.analyze(classNode, scriptRoot, scope, rightInput); + right.cast(rightInput, rightOutput); output.actual = boolean.class; - return output; - } - - @Override - BooleanNode write(ClassNode classNode) { BooleanNode booleanNode = new BooleanNode(); - booleanNode.setLeftNode(left.cast(left.write(classNode))); - booleanNode.setRightNode(right.cast(right.write(classNode))); + booleanNode.setLeftNode(left.cast(leftOutput)); + booleanNode.setRightNode(right.cast(rightOutput)); booleanNode.setLocation(location); booleanNode.setExpressionType(output.actual); booleanNode.setOperation(operation); - return booleanNode; + output.expressionNode = booleanNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java index 6e78fb972008c..b1362e99cb2a5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java @@ -23,13 +23,12 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.ConstantNode; -import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.symbol.ScriptRoot; /** * Represents a boolean constant. */ -public final class EBoolean extends AExpression { +public class EBoolean extends AExpression { protected boolean constant; @@ -40,9 +39,8 @@ public EBoolean(Location location, boolean constant) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from constant [" + constant + "].")); @@ -50,17 +48,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = boolean.class; - return output; - } - - @Override - ExpressionNode write(ClassNode classNode) { ConstantNode constantNode = new ConstantNode(); constantNode.setLocation(location); constantNode.setExpressionType(output.actual); constantNode.setConstant(constant); - return constantNode; + output.expressionNode = constantNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java index ad4dce45d4d7a..c86966df03f60 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java @@ -23,6 +23,7 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.MemberCallNode; +import org.elasticsearch.painless.ir.FieldNode; import org.elasticsearch.painless.lookup.PainlessClassBinding; import org.elasticsearch.painless.lookup.PainlessInstanceBinding; import org.elasticsearch.painless.lookup.PainlessMethod; @@ -32,35 +33,35 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; /** * Represents a user-defined call. */ -public final class ECallLocal extends AExpression { +public class ECallLocal extends AExpression { - private final String name; - private final List arguments; - - private FunctionTable.LocalFunction localFunction = null; - private PainlessMethod importedMethod = null; - private PainlessClassBinding classBinding = null; - private int classBindingOffset = 0; - private PainlessInstanceBinding instanceBinding = null; - private String bindingName = null; + protected final String name; + protected final List arguments; public ECallLocal(Location location, String name, List arguments) { super(location); this.name = Objects.requireNonNull(name); - this.arguments = Objects.requireNonNull(arguments); + this.arguments = Collections.unmodifiableList(Objects.requireNonNull(arguments)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + FunctionTable.LocalFunction localFunction = null; + PainlessMethod importedMethod = null; + PainlessClassBinding classBinding = null; + int classBindingOffset = 0; + PainlessInstanceBinding instanceBinding = null; + String bindingName = null; + + Output output = new Output(); localFunction = scriptRoot.getFunctionTable().getFunction(name, arguments.size()); @@ -125,43 +126,53 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { typeParameters = new ArrayList<>(classBinding.typeParameters); output.actual = classBinding.returnType; bindingName = scriptRoot.getNextSyntheticName("class_binding"); - scriptRoot.getClassNode().addField(new SField(location, - Modifier.PRIVATE, bindingName, classBinding.javaConstructor.getDeclaringClass())); + + FieldNode fieldNode = new FieldNode(); + fieldNode.setLocation(location); + fieldNode.setModifiers(Modifier.PRIVATE); + fieldNode.setFieldType(classBinding.javaConstructor.getDeclaringClass()); + fieldNode.setName(bindingName); + + classNode.addFieldNode(fieldNode); } else if (instanceBinding != null) { typeParameters = new ArrayList<>(instanceBinding.typeParameters); output.actual = instanceBinding.returnType; bindingName = scriptRoot.getNextSyntheticName("instance_binding"); - scriptRoot.getClassNode().addField(new SField(location, Modifier.STATIC | Modifier.PUBLIC, - bindingName, instanceBinding.targetInstance.getClass())); + + FieldNode fieldNode = new FieldNode(); + fieldNode.setLocation(location); + fieldNode.setModifiers(Modifier.PUBLIC | Modifier.STATIC); + fieldNode.setFieldType(instanceBinding.targetInstance.getClass()); + fieldNode.setName(bindingName); + + classNode.addFieldNode(fieldNode); + scriptRoot.addStaticConstant(bindingName, instanceBinding.targetInstance); } else { throw new IllegalStateException("Illegal tree structure."); } + List argumentOutputs = new ArrayList<>(arguments.size()); // if the class binding is using an implicit this reference then the arguments counted must // be incremented by 1 as the this reference will not be part of the arguments passed into // the class binding call for (int argument = 0; argument < arguments.size(); ++argument) { AExpression expression = arguments.get(argument); - Input expressionInput = new Input(); - expressionInput.expected = typeParameters.get(argument + classBindingOffset); - expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + Input argumentInput = new Input(); + argumentInput.expected = typeParameters.get(argument + classBindingOffset); + argumentInput.internal = true; + Output argumentOutput = expression.analyze(classNode, scriptRoot, scope, argumentInput); + expression.cast(argumentInput, argumentOutput); + argumentOutputs.add(argumentOutput); } output.statement = true; - return output; - } - - @Override - MemberCallNode write(ClassNode classNode) { MemberCallNode memberCallNode = new MemberCallNode(); - for (AExpression argument : arguments) { - memberCallNode.addArgumentNode(argument.cast(argument.write(classNode))); + for (int argument = 0; argument < arguments.size(); ++argument) { + memberCallNode.addArgumentNode(arguments.get(argument).cast(argumentOutputs.get(argument))); } memberCallNode.setLocation(location); @@ -173,7 +184,9 @@ MemberCallNode write(ClassNode classNode) { memberCallNode.setBindingName(bindingName); memberCallNode.setInstanceBinding(instanceBinding); - return memberCallNode; + output.expressionNode = memberCallNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java index fa8b25cd861f9..e5f3b11910d18 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java @@ -35,11 +35,12 @@ /** * Represents a capturing function reference. For member functions that require a this reference, ie not static. */ -public final class ECapturingFunctionRef extends AExpression implements ILambda { - private final String variable; - private final String call; +public class ECapturingFunctionRef extends AExpression implements ILambda { - private FunctionRef ref; + protected final String variable; + protected final String call; + + // TODO: #54015 private Variable captured; private String defPointer; @@ -51,9 +52,10 @@ public ECapturingFunctionRef(Location location, String variable, String call) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + FunctionRef ref = null; + + Output output = new Output(); captured = scope.getVariable(location, variable); if (input.expected == null) { @@ -75,11 +77,6 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = input.expected; } - return output; - } - - @Override - CapturingFuncRefNode write(ClassNode classNode) { CapturingFuncRefNode capturingFuncRefNode = new CapturingFuncRefNode(); capturingFuncRefNode.setLocation(location); @@ -87,9 +84,11 @@ CapturingFuncRefNode write(ClassNode classNode) { capturingFuncRefNode.setCapturedName(captured.getName()); capturingFuncRefNode.setName(call); capturingFuncRefNode.setPointer(defPointer); - capturingFuncRefNode.setFuncRef(ref);; + capturingFuncRefNode.setFuncRef(ref); - return capturingFuncRefNode; + output.expressionNode = capturingFuncRefNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java index 32555c18625f7..359546e805a40 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java @@ -34,13 +34,11 @@ /** * Represents a comparison expression. */ -public final class EComp extends AExpression { +public class EComp extends AExpression { - private final Operation operation; - private AExpression left; - private AExpression right; - - private Class promotedType; + protected final Operation operation; + protected final AExpression left; + protected final AExpression right; public EComp(Location location, Operation operation, AExpression left, AExpression right) { super(location); @@ -51,12 +49,16 @@ public EComp(Location location, Operation operation, AExpression left, AExpressi } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Class promotedType; + + Output output = new Output(); + + Input leftInput = new Input(); + Output leftOutput = left.analyze(classNode, scriptRoot, scope, leftInput); - Output leftOutput = left.analyze(scriptRoot, scope, new Input()); - Output rightOutput = right.analyze(scriptRoot, scope, new Input()); + Input rightInput = new Input(); + Output rightOutput = right.analyze(classNode, scriptRoot, scope, rightInput); if (operation == Operation.EQ || operation == Operation.EQR || operation == Operation.NE || operation == Operation.NER) { promotedType = AnalyzerCaster.promoteEquality(leftOutput.actual, rightOutput.actual); @@ -74,11 +76,11 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } if (operation != Operation.EQR && operation != Operation.NER && promotedType == def.class) { - left.input.expected = leftOutput.actual; - right.input.expected = rightOutput.actual; + leftInput.expected = leftOutput.actual; + rightInput.expected = rightOutput.actual; } else { - left.input.expected = promotedType; - right.input.expected = promotedType; + leftInput.expected = promotedType; + rightInput.expected = promotedType; } if ((operation == Operation.EQ || operation == Operation.EQR || operation == Operation.NE || operation == Operation.NER) @@ -86,27 +88,24 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { throw createError(new IllegalArgumentException("extraneous comparison of [null] constants")); } - left.cast(); - right.cast(); + left.cast(leftInput, leftOutput); + right.cast(rightInput, rightOutput); output.actual = boolean.class; - return output; - } - - @Override - ComparisonNode write(ClassNode classNode) { ComparisonNode comparisonNode = new ComparisonNode(); - comparisonNode.setLeftNode(left.cast(left.write(classNode))); - comparisonNode.setRightNode(right.cast(right.write(classNode))); + comparisonNode.setLeftNode(left.cast(leftOutput)); + comparisonNode.setRightNode(right.cast(rightOutput)); comparisonNode.setLocation(location); comparisonNode.setExpressionType(output.actual); comparisonNode.setComparisonType(promotedType); comparisonNode.setOperation(operation); - return comparisonNode; + output.expressionNode = comparisonNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java index d523838ecf66d..2b4440787347b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java @@ -32,11 +32,11 @@ /** * Represents a conditional expression. */ -public final class EConditional extends AExpression { +public class EConditional extends AExpression { - private AExpression condition; - private AExpression left; - private AExpression right; + protected final AExpression condition; + protected final AExpression left; + protected final AExpression right; public EConditional(Location location, AExpression condition, AExpression left, AExpression right) { super(location); @@ -47,30 +47,28 @@ public EConditional(Location location, AExpression condition, AExpression left, } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); Input conditionInput = new Input(); conditionInput.expected = boolean.class; - condition.analyze(scriptRoot, scope, conditionInput); - condition.cast(); + Output conditionOutput = condition.analyze(classNode, scriptRoot, scope, conditionInput); + condition.cast(conditionInput, conditionOutput); Input leftInput = new Input(); leftInput.expected = input.expected; leftInput.explicit = input.explicit; leftInput.internal = input.internal; + Output leftOutput = left.analyze(classNode, scriptRoot, scope, leftInput); Input rightInput = new Input(); rightInput.expected = input.expected; rightInput.explicit = input.explicit; rightInput.internal = input.internal; + Output rightOutput = right.analyze(classNode, scriptRoot, scope, rightInput); output.actual = input.expected; - Output leftOutput = left.analyze(scriptRoot, scope, leftInput); - Output rightOutput = right.analyze(scriptRoot, scope, rightInput); - if (input.expected == null) { Class promote = AnalyzerCaster.promoteConditional(leftOutput.actual, rightOutput.actual); @@ -80,29 +78,26 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightOutput.actual) + "]")); } - left.input.expected = promote; - right.input.expected = promote; + leftInput.expected = promote; + rightInput.expected = promote; output.actual = promote; } - left.cast(); - right.cast(); - - return output; - } + left.cast(leftInput, leftOutput); + right.cast(rightInput, rightOutput); - @Override - ConditionalNode write(ClassNode classNode) { ConditionalNode conditionalNode = new ConditionalNode(); - conditionalNode.setLeftNode(left.cast(left.write(classNode))); - conditionalNode.setRightNode(right.cast(right.write(classNode))); - conditionalNode.setConditionNode(condition.cast(condition.write(classNode))); + conditionalNode.setLeftNode(left.cast(leftOutput)); + conditionalNode.setRightNode(right.cast(rightOutput)); + conditionalNode.setConditionNode(condition.cast(conditionOutput)); conditionalNode.setLocation(location); conditionalNode.setExpressionType(output.actual); - return conditionalNode; + output.expressionNode = conditionalNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java index 5caa6bca92895..317a9dae3bd3d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java @@ -30,7 +30,7 @@ * Represents a constant inserted into the tree replacing * other constants during constant folding. (Internal only.) */ -final class EConstant extends AExpression { +public class EConstant extends AExpression { protected Object constant; @@ -41,9 +41,8 @@ final class EConstant extends AExpression { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (constant instanceof String) { output.actual = String.class; @@ -69,17 +68,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { "for constant node")); } - return output; - } - - @Override - ConstantNode write(ClassNode classNode) { ConstantNode constantNode = new ConstantNode(); constantNode.setLocation(location); constantNode.setExpressionType(output.actual); constantNode.setConstant(constant); - return constantNode; + output.expressionNode = constantNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java index 405f90b356f1a..35388db216cb5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java @@ -23,7 +23,6 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.ConstantNode; -import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -31,11 +30,9 @@ /** * Represents a decimal constant. */ -public final class EDecimal extends AExpression { +public class EDecimal extends AExpression { - private final String value; - - protected Object constant; + protected final String value; public EDecimal(Location location, String value) { super(location); @@ -44,9 +41,10 @@ public EDecimal(Location location, String value) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Object constant; + + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from constant [" + value + "].")); @@ -72,17 +70,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } } - return output; - } - - @Override - ExpressionNode write(ClassNode classNode) { ConstantNode constantNode = new ConstantNode(); constantNode.setLocation(location); constantNode.setExpressionType(output.actual); constantNode.setConstant(constant); - return constantNode; + output.expressionNode = constantNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EElvis.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EElvis.java index 6c771af47caf4..6ccb1a3f397b8 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EElvis.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EElvis.java @@ -33,8 +33,9 @@ * non null. If the first expression is null then it evaluates the second expression and returns it. */ public class EElvis extends AExpression { - private AExpression lhs; - private AExpression rhs; + + protected AExpression lhs; + protected AExpression rhs; public EElvis(Location location, AExpression lhs, AExpression rhs) { super(location); @@ -44,24 +45,26 @@ public EElvis(Location location, AExpression lhs, AExpression rhs) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.expected != null && input.expected.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 = lhs.analyze(classNode, scriptRoot, scope, leftInput); + Input rightInput = new Input(); rightInput.expected = input.expected; rightInput.explicit = input.explicit; rightInput.internal = input.internal; + Output rightOutput = rhs.analyze(classNode, scriptRoot, scope, rightInput); + output.actual = input.expected; - Output leftOutput = lhs.analyze(scriptRoot, scope, leftInput); - Output rightOutput = rhs.analyze(scriptRoot, scope, rightInput); if (lhs instanceof ENull) { throw createError(new IllegalArgumentException("Extraneous elvis operator. LHS is null.")); @@ -83,28 +86,25 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { if (input.expected == null) { Class promote = AnalyzerCaster.promoteConditional(leftOutput.actual, rightOutput.actual); - lhs.input.expected = promote; - rhs.input.expected = promote; + leftInput.expected = promote; + rightInput.expected = promote; output.actual = promote; } - lhs.cast(); - rhs.cast(); - - return output; - } + lhs.cast(leftInput, leftOutput); + rhs.cast(rightInput, rightOutput); - @Override - ElvisNode write(ClassNode classNode) { ElvisNode elvisNode = new ElvisNode(); - elvisNode.setLeftNode(lhs.cast(lhs.write(classNode))); - elvisNode.setRightNode(rhs.cast(rhs.write(classNode))); + elvisNode.setLeftNode(lhs.cast(leftOutput)); + elvisNode.setRightNode(rhs.cast(rightOutput)); elvisNode.setLocation(location); elvisNode.setExpressionType(output.actual); - return elvisNode; + output.expressionNode = elvisNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java index b76dcd946b3b3..532e9607fb7a6 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java @@ -22,7 +22,6 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; -import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -30,10 +29,10 @@ /** * Represents an explicit cast. */ -public final class EExplicit extends AExpression { +public class EExplicit extends AExpression { - private final String type; - private AExpression child; + protected final String type; + protected final AExpression child; public EExplicit(Location location, String type, AExpression child) { super(location); @@ -43,9 +42,8 @@ public EExplicit(Location location, String type, AExpression child) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); output.actual = scriptRoot.getPainlessLookup().canonicalTypeNameToType(type); @@ -56,15 +54,12 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { Input childInput = new Input(); childInput.expected = output.actual; childInput.explicit = true; - child.analyze(scriptRoot, scope, childInput); - child.cast(); + Output childOutput = child.analyze(classNode, scriptRoot, scope, childInput); + child.cast(childInput, childOutput); - return output; - } + output.expressionNode = child.cast(childOutput); - @Override - ExpressionNode write(ClassNode classNode) { - return child.cast(child.write(classNode)); + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java index 62bb34c215590..a9cb7876fe11a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java @@ -33,11 +33,12 @@ /** * Represents a function reference. */ -public final class EFunctionRef extends AExpression implements ILambda { - private final String type; - private final String call; +public class EFunctionRef extends AExpression implements ILambda { - private FunctionRef ref; + protected final String type; + protected final String call; + + // TODO: #54015 private String defPointer; public EFunctionRef(Location location, String type, String call) { @@ -48,9 +49,10 @@ public EFunctionRef(Location location, String type, String call) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + FunctionRef ref; + + Output output = new Output(); if (input.expected == null) { ref = null; @@ -63,18 +65,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = input.expected; } - return output; - } - - @Override - FuncRefNode write(ClassNode classNode) { FuncRefNode funcRefNode = new FuncRefNode(); funcRefNode.setLocation(location); funcRefNode.setExpressionType(output.actual); funcRefNode.setFuncRef(ref); - return funcRefNode; + output.expressionNode = funcRefNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java index e6ee1a03602b8..9bf0a00e610ca 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java @@ -33,13 +33,10 @@ *

* Unlike java's, this works for primitive types too. */ -public final class EInstanceof extends AExpression { - private AExpression expression; - private final String type; +public class EInstanceof extends AExpression { - private Class resolvedType; - private Class expressionType; - private boolean primitiveExpression; + protected final AExpression expression; + protected final String type; public EInstanceof(Location location, AExpression expression, String type) { super(location); @@ -48,9 +45,12 @@ public EInstanceof(Location location, AExpression expression, String type) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Class resolvedType; + Class expressionType; + boolean primitiveExpression; + + Output output = new Output(); // ensure the specified type is part of the definition Class clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type); @@ -64,9 +64,10 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { PainlessLookupUtility.typeToJavaType(clazz); // analyze and cast the expression - Output expressionOutput = expression.analyze(scriptRoot, scope, new Input()); - expression.input.expected = expressionOutput.actual; - expression.cast(); + Input expressionInput = new Input(); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expressionInput.expected = expressionOutput.actual; + expression.cast(expressionInput, expressionOutput); // record if the expression returns a primitive primitiveExpression = expressionOutput.actual.isPrimitive(); @@ -76,14 +77,9 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = boolean.class; - return output; - } - - @Override - InstanceofNode write(ClassNode classNode) { InstanceofNode instanceofNode = new InstanceofNode(); - instanceofNode.setChildNode(expression.cast(expression.write(classNode))); + instanceofNode.setChildNode(expression.cast(expressionOutput)); instanceofNode.setLocation(location); instanceofNode.setExpressionType(output.actual); @@ -91,7 +87,9 @@ InstanceofNode write(ClassNode classNode) { instanceofNode.setResolvedType(resolvedType); instanceofNode.setPrimitiveResult(primitiveExpression); - return instanceofNode; + output.expressionNode = instanceofNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java index a4cb19d048c62..41ffd62df044f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java @@ -24,6 +24,7 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.Scope.LambdaScope; import org.elasticsearch.painless.Scope.Variable; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.FunctionNode; import org.elasticsearch.painless.ir.LambdaNode; @@ -59,27 +60,16 @@ *
* {@code sort(list, lambda$0(capture))} */ -public final class ELambda extends AExpression implements ILambda { +public class ELambda extends AExpression implements ILambda { - private final List paramTypeStrs; - private final List paramNameStrs; - private final List statements; + protected final List paramTypeStrs; + protected final List paramNameStrs; + protected final List statements; - // captured variables + // TODO: #54015 private List captures; - // static parent, static lambda - private FunctionRef ref; - // dynamic parent, deferred until link time private String defPointer; - private String name; - private Class returnType; - private List> typeParameters; - private List parameterNames; - private SBlock block; - private boolean methodEscape; - private int maxLoopCounter; - public ELambda(Location location, List paramTypes, List paramNames, List statements) { @@ -91,11 +81,18 @@ public ELambda(Location location, } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); - - List> typeParameters = new ArrayList<>(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + String name; + Class returnType; + List> typeParametersWithCaptures; + List parameterNames; + SBlock block; + int maxLoopCounter; + FunctionRef ref; + + Output output = new Output(); + + List> typeParameters; PainlessMethod interfaceMethod; // inspect the target first, set interface method if we know it. if (input.expected == null) { @@ -157,8 +154,8 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { for (int index = 0; index < typeParameters.size(); ++index) { Class type = typeParameters.get(index); - String name = paramNameStrs.get(index); - lambdaScope.defineVariable(location, type, name, true); + String paramName = paramNameStrs.get(index); + lambdaScope.defineVariable(location, type, paramName, true); } block = new SBlock(location, statements); @@ -167,7 +164,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } AStatement.Input blockInput = new AStatement.Input(); blockInput.lastSource = true; - AStatement.Output blockOutput = block.analyze(scriptRoot, lambdaScope, blockInput); + AStatement.Output blockOutput = block.analyze(classNode, scriptRoot, lambdaScope, blockInput); if (blockOutput.methodEscape == false) { throw createError(new IllegalArgumentException("not all paths return a value for lambda")); @@ -177,18 +174,18 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { // prepend capture list to lambda's arguments captures = new ArrayList<>(lambdaScope.getCaptures()); - this.typeParameters = new ArrayList<>(captures.size() + typeParameters.size()); + typeParametersWithCaptures = new ArrayList<>(captures.size() + typeParameters.size()); parameterNames = new ArrayList<>(captures.size() + paramNameStrs.size()); for (Variable var : captures) { - this.typeParameters.add(var.getType()); + typeParametersWithCaptures.add(var.getType()); parameterNames.add(var.getName()); } - this.typeParameters.addAll(typeParameters); + typeParametersWithCaptures.addAll(typeParameters); parameterNames.addAll(paramNameStrs); // desugar lambda body into a synthetic method name = scriptRoot.getNextSyntheticName("lambda"); - scriptRoot.getFunctionTable().addFunction(name, returnType, this.typeParameters, true, true); + scriptRoot.getFunctionTable().addFunction(name, returnType, typeParametersWithCaptures, true, true); // setup method reference to synthetic method if (input.expected == null) { @@ -202,19 +199,12 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = input.expected; } - return output; - } - - @Override - LambdaNode write(ClassNode classNode) { FunctionNode functionNode = new FunctionNode(); - - functionNode.setBlockNode(block.write(classNode)); - + functionNode.setBlockNode((BlockNode)blockOutput.statementNode); functionNode.setLocation(location); functionNode.setName(name); functionNode.setReturnType(returnType); - functionNode.getTypeParameters().addAll(typeParameters); + functionNode.getTypeParameters().addAll(typeParametersWithCaptures); functionNode.getParameterNames().addAll(parameterNames); functionNode.setStatic(true); functionNode.setVarArgs(false); @@ -224,7 +214,6 @@ LambdaNode write(ClassNode classNode) { classNode.addFunctionNode(functionNode); LambdaNode lambdaNode = new LambdaNode(); - lambdaNode.setLocation(location); lambdaNode.setExpressionType(output.actual); lambdaNode.setFuncRef(ref); @@ -233,7 +222,9 @@ LambdaNode write(ClassNode classNode) { lambdaNode.addCapture(capture.getName()); } - return lambdaNode; + output.expressionNode = lambdaNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java index cb49bce8a11c3..c60a9d1bea78b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java @@ -29,29 +29,28 @@ import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; /** * Represents a list initialization shortcut. */ -public final class EListInit extends AExpression { - private final List values; +public class EListInit extends AExpression { - private PainlessConstructor constructor = null; - private PainlessMethod method = null; + protected final List values; public EListInit(Location location, List values) { super(location); - this.values = values; + this.values = Collections.unmodifiableList(Objects.requireNonNull(values)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from list initializer.")); @@ -59,38 +58,34 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = ArrayList.class; - constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(output.actual, 0); + PainlessConstructor constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(output.actual, 0); if (constructor == null) { throw createError(new IllegalArgumentException( "constructor [" + typeToCanonicalTypeName(output.actual) + ", /0] not found")); } - method = scriptRoot.getPainlessLookup().lookupPainlessMethod(output.actual, false, "add", 1); + PainlessMethod method = scriptRoot.getPainlessLookup().lookupPainlessMethod(output.actual, false, "add", 1); if (method == null) { throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(output.actual) + ", add/1] not found")); } - for (int index = 0; index < values.size(); ++index) { - AExpression expression = values.get(index); + List valueOutputs = new ArrayList<>(values.size()); + for (AExpression expression : values) { Input expressionInput = new Input(); expressionInput.expected = def.class; expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); + valueOutputs.add(expressionOutput); } - return output; - } - - @Override - ListInitializationNode write(ClassNode classNode) { ListInitializationNode listInitializationNode = new ListInitializationNode(); - for (AExpression value : values) { - listInitializationNode.addArgumentNode(value.cast(value.write(classNode))); + for (int i = 0; i < values.size(); ++i) { + listInitializationNode.addArgumentNode(values.get(i).cast(valueOutputs.get(i))); } listInitializationNode.setLocation(location); @@ -98,7 +93,9 @@ ListInitializationNode write(ClassNode classNode) { listInitializationNode.setConstructor(constructor); listInitializationNode.setMethod(method); - return listInitializationNode; + output.expressionNode = listInitializationNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java index f846a18eb89f9..fbe500c027dab 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java @@ -28,32 +28,32 @@ import org.elasticsearch.painless.lookup.def; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Objects; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; /** * Represents a map initialization shortcut. */ -public final class EMapInit extends AExpression { - private final List keys; - private final List values; +public class EMapInit extends AExpression { - private PainlessConstructor constructor = null; - private PainlessMethod method = null; + protected final List keys; + protected final List values; public EMapInit(Location location, List keys, List values) { super(location); - this.keys = keys; - this.values = values; + this.keys = Collections.unmodifiableList(Objects.requireNonNull(keys)); + this.values = Collections.unmodifiableList(Objects.requireNonNull(values)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from map initializer.")); @@ -61,14 +61,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = HashMap.class; - constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(output.actual, 0); + PainlessConstructor constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(output.actual, 0); if (constructor == null) { throw createError(new IllegalArgumentException( "constructor [" + typeToCanonicalTypeName(output.actual) + ", /0] not found")); } - method = scriptRoot.getPainlessLookup().lookupPainlessMethod(output.actual, false, "put", 2); + PainlessMethod method = scriptRoot.getPainlessLookup().lookupPainlessMethod(output.actual, false, "put", 2); if (method == null) { throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(output.actual) + ", put/2] not found")); @@ -78,37 +78,33 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { throw createError(new IllegalStateException("Illegal tree structure.")); } - for (int index = 0; index < keys.size(); ++index) { - AExpression expression = keys.get(index); + List keyOutputs = new ArrayList<>(keys.size()); + List valueOutputs = new ArrayList<>(values.size()); + for (int i = 0; i < keys.size(); ++i) { + AExpression expression = keys.get(i); Input expressionInput = new Input(); expressionInput.expected = def.class; expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); - } - - for (int index = 0; index < values.size(); ++index) { - AExpression expression = values.get(index); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); + keyOutputs.add(expressionOutput); - Input expressionInput = new Input(); + expression = values.get(i); + expressionInput = new Input(); expressionInput.expected = def.class; expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); + valueOutputs.add(expressionOutput); } - return output; - } - - @Override - MapInitializationNode write(ClassNode classNode) { MapInitializationNode mapInitializationNode = new MapInitializationNode(); - for (int index = 0; index < keys.size(); ++index) { + for (int i = 0; i < keys.size(); ++i) { mapInitializationNode.addArgumentNode( - keys.get(index).cast(keys.get(index).write(classNode)), - values.get(index).cast(values.get(index).write(classNode))); + keys.get(i).cast(keyOutputs.get(i)), + values.get(i).cast(valueOutputs.get(i))); } mapInitializationNode.setLocation(location); @@ -116,7 +112,9 @@ MapInitializationNode write(ClassNode classNode) { mapInitializationNode.setConstructor(constructor); mapInitializationNode.setMethod(method); - return mapInitializationNode; + output.expressionNode = mapInitializationNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java index 73603d79e6c89..a4ce05d57cf98 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java @@ -25,30 +25,31 @@ import org.elasticsearch.painless.ir.NewArrayNode; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; /** * Represents an array instantiation. */ -public final class ENewArray extends AExpression { +public class ENewArray extends AExpression { - private final String type; - private final List arguments; - private final boolean initialize; + protected final String type; + protected final List arguments; + protected final boolean initialize; public ENewArray(Location location, String type, List arguments, boolean initialize) { super(location); this.type = Objects.requireNonNull(type); - this.arguments = Objects.requireNonNull(arguments); + this.arguments = Collections.unmodifiableList(Objects.requireNonNull(arguments)); this.initialize = initialize; } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("A newly created array must be read from.")); @@ -60,34 +61,32 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } - for (int argument = 0; argument < arguments.size(); ++argument) { - AExpression expression = arguments.get(argument); + List argumentOutputs = new ArrayList<>(); + for (AExpression expression : arguments) { Input expressionInput = new Input(); expressionInput.expected = initialize ? clazz.getComponentType() : int.class; expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); + argumentOutputs.add(expressionOutput); } output.actual = clazz; - return output; - } - - @Override - NewArrayNode write(ClassNode classNode) { NewArrayNode newArrayNode = new NewArrayNode(); - for (AExpression argument : arguments) { - newArrayNode.addArgumentNode(argument.cast(argument.write(classNode))); + for (int i = 0; i < arguments.size(); ++ i) { + newArrayNode.addArgumentNode(arguments.get(i).cast(argumentOutputs.get(i))); } newArrayNode.setLocation(location); newArrayNode.setExpressionType(output.actual); newArrayNode.setInitialize(initialize); - return newArrayNode; + output.expressionNode = newArrayNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java index 0a132c12e5adb..16dc2ee729827 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java @@ -23,6 +23,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.FunctionNode; import org.elasticsearch.painless.ir.NewArrayFuncRefNode; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -34,11 +35,11 @@ /** * Represents a function reference. */ -public final class ENewArrayFunctionRef extends AExpression implements ILambda { - private final String type; +public class ENewArrayFunctionRef extends AExpression implements ILambda { - private SFunction function; - private FunctionRef ref; + protected final String type; + + // TODO: #54015 private String defPointer; public ENewArrayFunctionRef(Location location, String type) { @@ -48,19 +49,20 @@ public ENewArrayFunctionRef(Location location, String type) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); SReturn code = new SReturn(location, new ENewArray(location, type, Arrays.asList(new EVariable(location, "size")), false)); - function = new SFunction( + SFunction function = new SFunction( location, type, scriptRoot.getNextSyntheticName("newarray"), Collections.singletonList("int"), Collections.singletonList("size"), new SBlock(location, Collections.singletonList(code)), true, true, true, false); function.generateSignature(scriptRoot.getPainlessLookup()); - function.analyze(scriptRoot); + FunctionNode functionNode = function.writeFunction(classNode, scriptRoot); scriptRoot.getFunctionTable().addFunction(function.name, function.returnType, function.typeParameters, true, true); + FunctionRef ref; + if (input.expected == null) { ref = null; output.actual = String.class; @@ -72,12 +74,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = input.expected; } - return output; - } - - @Override - NewArrayFuncRefNode write(ClassNode classNode) { - classNode.addFunctionNode(function.write(classNode)); + classNode.addFunctionNode(functionNode); NewArrayFuncRefNode newArrayFuncRefNode = new NewArrayFuncRefNode(); @@ -85,7 +82,9 @@ NewArrayFuncRefNode write(ClassNode classNode) { newArrayFuncRefNode.setExpressionType(output.actual); newArrayFuncRefNode.setFuncRef(ref); - return newArrayFuncRefNode; + output.expressionNode = newArrayFuncRefNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java index dda96482a472d..1659910986173 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java @@ -28,6 +28,8 @@ import org.elasticsearch.painless.spi.annotation.NonDeterministicAnnotation; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -36,24 +38,21 @@ /** * Represents and object instantiation. */ -public final class ENewObj extends AExpression { +public class ENewObj extends AExpression { - private final String type; - private final List arguments; - - private PainlessConstructor constructor; + protected final String type; + protected final List arguments; public ENewObj(Location location, String type, List arguments) { super(location); this.type = Objects.requireNonNull(type); - this.arguments = Objects.requireNonNull(arguments); + this.arguments = Collections.unmodifiableList(Objects.requireNonNull(arguments)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); output.actual = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type); @@ -61,7 +60,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } - constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(output.actual, arguments.size()); + PainlessConstructor constructor = scriptRoot.getPainlessLookup().lookupPainlessConstructor(output.actual, arguments.size()); if (constructor == null) { throw createError(new IllegalArgumentException( @@ -79,27 +78,25 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { "expected [" + constructor.typeParameters.size() + "] arguments, but found [" + arguments.size() + "].")); } - for (int argument = 0; argument < arguments.size(); ++argument) { - AExpression expression = arguments.get(argument); + List argumentOutputs = new ArrayList<>(); + + for (int i = 0; i < arguments.size(); ++i) { + AExpression expression = arguments.get(i); Input expressionInput = new Input(); - expressionInput.expected = types[argument]; + expressionInput.expected = types[i]; expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); + argumentOutputs.add(expressionOutput); } output.statement = true; - return output; - } - - @Override - NewObjectNode write(ClassNode classNode) { NewObjectNode newObjectNode = new NewObjectNode(); - for (AExpression argument : arguments) { - newObjectNode.addArgumentNode(argument.cast(argument.write(classNode))); + for (int i = 0; i < arguments.size(); ++ i) { + newObjectNode.addArgumentNode(arguments.get(i).cast(argumentOutputs.get(i))); } newObjectNode.setLocation(location); @@ -107,7 +104,9 @@ NewObjectNode write(ClassNode classNode) { newObjectNode.setRead(input.read); newObjectNode.setConstructor(constructor); - return newObjectNode; + output.expressionNode = newObjectNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java index 841bd09586bed..1b61a05a68501 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java @@ -29,16 +29,15 @@ /** * Represents a null constant. */ -public final class ENull extends AExpression { +public class ENull extends AExpression { public ENull(Location location) { super(location); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from null constant.")); @@ -55,17 +54,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = Object.class; } - return output; - } - - @Override - NullNode write(ClassNode classNode) { NullNode nullNode = new NullNode(); nullNode.setLocation(location); nullNode.setExpressionType(output.actual); - return nullNode; + output.expressionNode = nullNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java index f7cbe681b639d..1e548252f1a74 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java @@ -23,7 +23,6 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.ConstantNode; -import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -31,12 +30,10 @@ /** * Represents a non-decimal numeric constant. */ -public final class ENumeric extends AExpression { +public class ENumeric extends AExpression { - private final String value; - private int radix; - - protected Object constant; + protected final String value; + protected final int radix; public ENumeric(Location location, String value, int radix) { super(location); @@ -46,9 +43,10 @@ public ENumeric(Location location, String value, int radix) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Object constant; + + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from constant [" + value + "].")); @@ -114,17 +112,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } } - return output; - } - - @Override - ExpressionNode write(ClassNode classNode) { ConstantNode constantNode = new ConstantNode(); constantNode.setLocation(location); constantNode.setExpressionType(output.actual); constantNode.setConstant(constant); - return constantNode; + output.expressionNode = constantNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java index 3847397407dd8..23e92f018c371 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java @@ -42,11 +42,10 @@ /** * Represents a regex constant. All regexes are constants. */ -public final class ERegex extends AExpression { +public class ERegex extends AExpression { - private final String pattern; - private final int flags; - private String name; + protected final String pattern; + protected final int flags; public ERegex(Location location, String pattern, String flagsString) { super(location); @@ -63,10 +62,8 @@ public ERegex(Location location, String pattern, String flagsString) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); - + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (scriptRoot.getCompilerSettings().areRegexesEnabled() == false) { throw createError(new IllegalStateException("Regexes are disabled. Set [script.painless.regex.enabled] to [true] " @@ -85,14 +82,9 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { new IllegalArgumentException("Error compiling regex: " + e.getDescription())); } - name = scriptRoot.getNextSyntheticName("regex"); + String name = scriptRoot.getNextSyntheticName("regex"); output.actual = Pattern.class; - return output; - } - - @Override - MemberFieldLoadNode write(ClassNode classNode) { FieldNode fieldNode = new FieldNode(); fieldNode.setLocation(location); fieldNode.setModifiers(Modifier.FINAL | Modifier.STATIC | Modifier.PRIVATE); @@ -169,7 +161,9 @@ MemberFieldLoadNode write(ClassNode classNode) { memberFieldLoadNode.setName(name); memberFieldLoadNode.setStatic(true); - return memberFieldLoadNode; + output.expressionNode = memberFieldLoadNode; + + return output; } private int flagForChar(char c) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java index 0706e321e5312..b45ba741ae1a3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java @@ -30,9 +30,9 @@ /** * Represents a static type target. */ -public final class EStatic extends AExpression { +public class EStatic extends AExpression { - private final String type; + protected final String type; public EStatic(Location location, String type) { super(location); @@ -41,9 +41,8 @@ public EStatic(Location location, String type) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); output.actual = scriptRoot.getPainlessLookup().canonicalTypeNameToType(type); @@ -51,17 +50,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { throw createError(new IllegalArgumentException("Not a type [" + type + "].")); } - return output; - } - - @Override - StaticNode write(ClassNode classNode) { StaticNode staticNode = new StaticNode(); staticNode.setLocation(location); staticNode.setExpressionType(output.actual); - return staticNode; + output.expressionNode = staticNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EString.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EString.java index 5cc5b67fe511b..77b9fb857bc08 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EString.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EString.java @@ -23,7 +23,6 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.ConstantNode; -import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -31,7 +30,7 @@ /** * Represents a string constant. */ -public final class EString extends AExpression { +public class EString extends AExpression { protected String constant; @@ -42,9 +41,8 @@ public EString(Location location, String string) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.read == false) { throw createError(new IllegalArgumentException("Must read from constant [" + constant + "].")); @@ -52,17 +50,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.actual = String.class; - return output; - } - - @Override - ExpressionNode write(ClassNode classNode) { ConstantNode constantNode = new ConstantNode(); constantNode.setLocation(location); constantNode.setExpressionType(output.actual); constantNode.setConstant(constant); - return constantNode; + output.expressionNode = constantNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java index e498ceb3578c8..cafcda3b7dd2a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java @@ -25,7 +25,6 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.UnaryMathNode; -import org.elasticsearch.painless.ir.UnaryNode; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.def; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -35,13 +34,10 @@ /** * Represents a unary math expression. */ -public final class EUnary extends AExpression { +public class EUnary extends AExpression { - private final Operation operation; - private AExpression child; - - private Class promote; - private boolean originallyExplicit = false; // record whether there was originally an explicit cast + protected final Operation operation; + protected final AExpression child; public EUnary(Location location, Operation operation, AExpression child) { super(location); @@ -51,21 +47,24 @@ public EUnary(Location location, Operation operation, AExpression child) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + Class promote = null; + boolean originallyExplicit = input.explicit; - originallyExplicit = input.explicit; + Input childInput = new Input(); + Output childOutput; if (operation == Operation.NOT) { - Input childInput = new Input(); + childInput.expected = boolean.class; - child.analyze(scriptRoot, scope, childInput); - child.cast(); + childOutput = child.analyze(classNode, scriptRoot, scope, childInput); + child.cast(childInput, childOutput); output.actual = boolean.class; } else if (operation == Operation.BWNOT || operation == Operation.ADD || operation == Operation.SUB) { - Output childOutput = child.analyze(scriptRoot, scope, new Input()); + childOutput = child.analyze(classNode, scriptRoot, scope, new Input()); promote = AnalyzerCaster.promoteNumeric(childOutput.actual, operation != Operation.BWNOT); @@ -75,8 +74,8 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { "[" + PainlessLookupUtility.typeToCanonicalTypeName(childOutput.actual) + "]")); } - child.input.expected = promote; - child.cast(); + childInput.expected = promote; + child.cast(childInput, childOutput); if (promote == def.class && input.expected != null) { output.actual = input.expected; @@ -87,14 +86,9 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { throw createError(new IllegalStateException("unexpected unary operation [" + operation.name + "]")); } - return output; - } - - @Override - UnaryNode write(ClassNode classNode) { UnaryMathNode unaryMathNode = new UnaryMathNode(); - unaryMathNode.setChildNode(child.cast(child.write(classNode))); + unaryMathNode.setChildNode(child.cast(childOutput)); unaryMathNode.setLocation(location); unaryMathNode.setExpressionType(output.actual); @@ -102,7 +96,9 @@ UnaryNode write(ClassNode classNode) { unaryMathNode.setOperation(operation); unaryMathNode.setOriginallExplicit(originallyExplicit); - return unaryMathNode; + output.expressionNode = unaryMathNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java index 2f1a4d6f09a72..0c345dfa46735 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EVariable.java @@ -31,9 +31,9 @@ /** * Represents a variable load/store. */ -public final class EVariable extends AStoreable { +public class EVariable extends AStoreable { - private final String name; + protected final String name; public EVariable(Location location, String name) { super(location); @@ -42,20 +42,19 @@ public EVariable(Location location, String name) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AExpression.Input input) { + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AExpression.Input input) { AStoreable.Input storeableInput = new AStoreable.Input(); storeableInput.read = input.read; storeableInput.expected = input.expected; storeableInput.explicit = input.explicit; storeableInput.internal = input.internal; - return analyze(scriptRoot, scope, storeableInput); + return analyze(classNode, scriptRoot, scope, storeableInput); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); Variable variable = scope.getVariable(location, name); @@ -65,18 +64,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { output.actual = variable.getType(); - return output; - } - - @Override - VariableNode write(ClassNode classNode) { VariableNode variableNode = new VariableNode(); variableNode.setLocation(location); variableNode.setExpressionType(output.actual); variableNode.setName(name); - return variableNode; + output.expressionNode = variableNode; + + return output; } @Override @@ -84,11 +80,6 @@ boolean isDefOptimized() { return false; } - @Override - void updateActual(Class actual) { - throw new IllegalArgumentException("Illegal tree structure."); - } - @Override public String toString() { return singleLineToString(name); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PBrace.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PBrace.java index 684b015aa835e..664a9e1c0048a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PBrace.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PBrace.java @@ -34,11 +34,12 @@ /** * Represents an array load/store and defers to a child subnode. */ -public final class PBrace extends AStoreable { +public class PBrace extends AStoreable { - private AExpression index; + protected final AExpression index; - private AStoreable sub = null; + // TODO: #54015 + private boolean isDefOptimized = false; public PBrace(Location location, AExpression prefix, AExpression index) { super(location, prefix); @@ -47,24 +48,26 @@ public PBrace(Location location, AExpression prefix, AExpression index) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AExpression.Input input) { + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AExpression.Input input) { AStoreable.Input storeableInput = new AStoreable.Input(); storeableInput.read = input.read; storeableInput.expected = input.expected; storeableInput.explicit = input.explicit; storeableInput.internal = input.internal; - return analyze(scriptRoot, scope, storeableInput); + return analyze(classNode, scriptRoot, scope, storeableInput); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); - Output prefixOutput = prefix.analyze(scriptRoot, scope, new Input()); - prefix.input.expected = prefixOutput.actual; - prefix.cast(); + Input prefixInput = new Input(); + Output prefixOutput = prefix.analyze(classNode, scriptRoot, scope, prefixInput); + prefixInput.expected = prefixOutput.actual; + prefix.cast(prefixInput, prefixOutput); + + AStoreable sub; if (prefixOutput.actual.isArray()) { sub = new PSubBrace(location, prefixOutput.actual, index); @@ -79,39 +82,32 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { "[" + PainlessLookupUtility.typeToCanonicalTypeName(prefixOutput.actual) + "].")); } + isDefOptimized = sub.isDefOptimized(); + Input subInput = new Input(); subInput.write = input.write; subInput.read = input.read; subInput.expected = input.expected; subInput.explicit = input.explicit; - Output subOutput = sub.analyze(scriptRoot, scope, subInput); + Output subOutput = sub.analyze(classNode, scriptRoot, scope, subInput); output.actual = subOutput.actual; - return output; - } - - @Override - BraceNode write(ClassNode classNode) { BraceNode braceNode = new BraceNode(); - braceNode.setLeftNode(prefix.cast(prefix.write(classNode))); - braceNode.setRightNode(sub.write(classNode)); + braceNode.setLeftNode(prefix.cast(prefixOutput)); + braceNode.setRightNode(subOutput.expressionNode); braceNode.setLocation(location); braceNode.setExpressionType(output.actual); - return braceNode; - } + output.expressionNode = braceNode; - @Override - boolean isDefOptimized() { - return sub.isDefOptimized(); + return output; } @Override - void updateActual(Class actual) { - sub.updateActual(actual); - this.output.actual = actual; + boolean isDefOptimized() { + return isDefOptimized; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java index 4a0254ef96253..036c04f3878b5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java @@ -28,6 +28,7 @@ import org.elasticsearch.painless.spi.annotation.NonDeterministicAnnotation; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -36,30 +37,30 @@ /** * Represents a method call and defers to a child subnode. */ -public final class PCallInvoke extends AExpression { +public class PCallInvoke extends AExpression { - private final String name; - private final boolean nullSafe; - private final List arguments; - - private AExpression sub = null; + protected final String name; + protected final boolean nullSafe; + protected final List arguments; public PCallInvoke(Location location, AExpression prefix, String name, boolean nullSafe, List arguments) { super(location, prefix); this.name = Objects.requireNonNull(name); this.nullSafe = nullSafe; - this.arguments = Objects.requireNonNull(arguments); + this.arguments = Collections.unmodifiableList(Objects.requireNonNull(arguments)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + Input prefixInput = new Input(); + Output prefixOutput = prefix.analyze(classNode, scriptRoot, scope, prefixInput); + prefixInput.expected = prefixOutput.actual; + prefix.cast(prefixInput, prefixOutput); - Output prefixOutput = prefix.analyze(scriptRoot, scope, new Input()); - prefix.input.expected = prefixOutput.actual; - prefix.cast(); + AExpression sub; if (prefixOutput.actual == def.class) { sub = new PSubDefCall(location, name, arguments); @@ -84,25 +85,22 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { Input subInput = new Input(); subInput.expected = input.expected; subInput.explicit = input.explicit; - Output subOutput = sub.analyze(scriptRoot, scope, subInput); + Output subOutput = sub.analyze(classNode, scriptRoot, scope, subInput); output.actual = subOutput.actual; output.statement = true; - return output; - } - - @Override - CallNode write(ClassNode classNode) { CallNode callNode = new CallNode(); - callNode.setLeftNode(prefix.cast(prefix.write(classNode))); - callNode.setRightNode(sub.write(classNode)); + callNode.setLeftNode(prefix.cast(prefixOutput)); + callNode.setRightNode(subOutput.expressionNode); callNode.setLocation(location); callNode.setExpressionType(output.actual); - return callNode; + output.expressionNode = callNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java index 2380aa536edde..9751786e1515b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java @@ -38,12 +38,13 @@ /** * Represents a field load/store and defers to a child subnode. */ -public final class PField extends AStoreable { +public class PField extends AStoreable { - private final boolean nullSafe; - private final String value; + protected final boolean nullSafe; + protected final String value; - private AStoreable sub = null; + // TODO: #54015 + private boolean isDefOptimized; public PField(Location location, AExpression prefix, boolean nullSafe, String value) { super(location, prefix); @@ -53,24 +54,26 @@ public PField(Location location, AExpression prefix, boolean nullSafe, String va } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AExpression.Input input) { + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AExpression.Input input) { AStoreable.Input storeableInput = new AStoreable.Input(); storeableInput.read = input.read; storeableInput.expected = input.expected; storeableInput.explicit = input.explicit; storeableInput.internal = input.internal; - return analyze(scriptRoot, scope, storeableInput); + return analyze(classNode, scriptRoot, scope, storeableInput); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); - Output prefixOutput = prefix.analyze(scriptRoot, scope, new Input()); - prefix.input.expected = prefixOutput.actual; - prefix.cast(); + Input prefixInput = new Input(); + Output prefixOutput = prefix.analyze(classNode, scriptRoot, scope, prefixInput); + prefixInput.expected = prefixOutput.actual; + prefix.cast(prefixInput, prefixOutput); + + AStoreable sub = null; if (prefixOutput.actual.isArray()) { sub = new PSubArrayLength(location, PainlessLookupUtility.typeToCanonicalTypeName(prefixOutput.actual), value); @@ -99,7 +102,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { location, value, PainlessLookupUtility.typeToCanonicalTypeName(prefixOutput.actual), getter, setter); } else { EConstant index = new EConstant(location, value); - index.analyze(scriptRoot, scope, new Input()); + index.analyze(classNode, scriptRoot, scope, new Input()); if (Map.class.isAssignableFrom(prefixOutput.actual)) { sub = new PSubMapShortcut(location, prefixOutput.actual, index); @@ -119,6 +122,8 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { } } + isDefOptimized = sub.isDefOptimized(); + if (nullSafe) { sub = new PSubNullSafeField(location, sub); } @@ -128,34 +133,25 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { subInput.read = input.read; subInput.expected = input.expected; subInput.explicit = input.explicit; - Output subOutput = sub.analyze(scriptRoot, scope, subInput); + Output subOutput = sub.analyze(classNode, scriptRoot, scope, subInput); output.actual = subOutput.actual; - return output; - } - - @Override - DotNode write(ClassNode classNode) { DotNode dotNode = new DotNode(); - dotNode.setLeftNode(prefix.cast(prefix.write(classNode))); - dotNode.setRightNode(sub.write(classNode)); + dotNode.setLeftNode(prefix.cast(prefixOutput)); + dotNode.setRightNode(subOutput.expressionNode); dotNode.setLocation(location); dotNode.setExpressionType(output.actual); - return dotNode; - } + output.expressionNode = dotNode; - @Override - boolean isDefOptimized() { - return sub.isDefOptimized(); + return output; } @Override - void updateActual(Class actual) { - sub.updateActual(actual); - this.output.actual = actual; + boolean isDefOptimized() { + return isDefOptimized; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubArrayLength.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubArrayLength.java index 43cdba046bd54..aaefa0717d3ba 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubArrayLength.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubArrayLength.java @@ -30,10 +30,10 @@ /** * Represents an array length field load. */ -final class PSubArrayLength extends AStoreable { +public class PSubArrayLength extends AStoreable { - private final String type; - private final String value; + protected final String type; + protected final String value; PSubArrayLength(Location location, String type, String value) { super(location); @@ -43,9 +43,8 @@ final class PSubArrayLength extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); if ("length".equals(value)) { if (input.write) { @@ -57,27 +56,19 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { throw createError(new IllegalArgumentException("Field [" + value + "] does not exist for type [" + type + "].")); } - return output; - } - - @Override - DotSubArrayLengthNode write(ClassNode classNode) { DotSubArrayLengthNode dotSubArrayLengthNode = new DotSubArrayLengthNode(); dotSubArrayLengthNode.setLocation(location); dotSubArrayLengthNode.setExpressionType(output.actual); - return dotSubArrayLengthNode; - } + output.expressionNode = dotSubArrayLengthNode; - @Override - boolean isDefOptimized() { - throw new IllegalStateException("Illegal tree structure."); + return output; } @Override - void updateActual(Class actual) { - throw new IllegalStateException("Illegal tree structure."); + boolean isDefOptimized() { + return false; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java index 0303ce051f7f5..2a285d49acbe2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubBrace.java @@ -30,10 +30,10 @@ /** * Represents an array load/store. */ -final class PSubBrace extends AStoreable { +public class PSubBrace extends AStoreable { - private final Class clazz; - private AExpression index; + protected final Class clazz; + protected final AExpression index; PSubBrace(Location location, Class clazz, AExpression index) { super(location); @@ -43,29 +43,26 @@ final class PSubBrace extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); Input indexInput = new Input(); indexInput.expected = int.class; - index.analyze(scriptRoot, scope, indexInput); - index.cast(); + Output indexOutput = index.analyze(classNode, scriptRoot, scope, indexInput); + index.cast(indexInput, indexOutput); output.actual = clazz.getComponentType(); - return output; - } - - BraceSubNode write(ClassNode classNode) { BraceSubNode braceSubNode = new BraceSubNode(); - braceSubNode.setChildNode(index.cast(index.write(classNode))); + braceSubNode.setChildNode(index.cast(indexOutput)); braceSubNode.setLocation(location); braceSubNode.setExpressionType(output.actual); - return braceSubNode; + output.expressionNode = braceSubNode; + + return output; } @Override @@ -73,11 +70,6 @@ boolean isDefOptimized() { return false; } - @Override - void updateActual(Class actual) { - throw createError(new IllegalStateException("Illegal tree structure.")); - } - @Override public String toString() { return singleLineToString(prefix, index); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java index 7e509682f7d47..721e76cd3e500 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java @@ -26,30 +26,33 @@ import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; /** * Represents a method call. */ -final class PSubCallInvoke extends AExpression { +public class PSubCallInvoke extends AExpression { - private final PainlessMethod method; - private final Class box; - private final List arguments; + protected final PainlessMethod method; + protected final Class box; + protected final List arguments; PSubCallInvoke(Location location, PainlessMethod method, Class box, List arguments) { super(location); this.method = Objects.requireNonNull(method); this.box = box; - this.arguments = Objects.requireNonNull(arguments); + this.arguments = Collections.unmodifiableList(Objects.requireNonNull(arguments)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + List argumentOutputs = new ArrayList<>(); for (int argument = 0; argument < arguments.size(); ++argument) { AExpression expression = arguments.get(argument); @@ -57,22 +60,18 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { Input expressionInput = new Input(); expressionInput.expected = method.typeParameters.get(argument); expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); + argumentOutputs.add(expressionOutput); } output.statement = true; output.actual = method.returnType; - return output; - } - - @Override - CallSubNode write(ClassNode classNode) { CallSubNode callSubNode = new CallSubNode(); - for (AExpression argument : arguments) { - callSubNode.addArgumentNode(argument.cast(argument.write(classNode))); + for (int argument = 0; argument < arguments.size(); ++ argument) { + callSubNode.addArgumentNode(arguments.get(argument).cast(argumentOutputs.get(argument))); } callSubNode.setLocation(location); @@ -80,7 +79,9 @@ CallSubNode write(ClassNode classNode) { callSubNode.setMethod(method); callSubNode .setBox(box); - return callSubNode; + output.expressionNode = callSubNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java index 060102ae8593f..00f09a493c0e9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefArray.java @@ -32,8 +32,9 @@ /** * Represents an array load/store or shortcut on a def type. (Internal only.) */ -final class PSubDefArray extends AStoreable { - private AExpression index; +public class PSubDefArray extends AStoreable { + + protected AExpression index; PSubDefArray(Location location, AExpression index) { super(location); @@ -42,30 +43,27 @@ final class PSubDefArray extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); - Output indexOutput = index.analyze(scriptRoot, scope, new Input()); - index.input.expected = indexOutput.actual; - index.cast(); + Input indexInput = new Input(); + Output indexOutput = index.analyze(classNode, scriptRoot, scope, indexInput); + indexInput.expected = indexOutput.actual; + index.cast(indexInput, indexOutput); // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed output.actual = input.expected == null || input.expected == ZonedDateTime.class || input.explicit ? def.class : input.expected; - return output; - } - - @Override - BraceSubDefNode write(ClassNode classNode) { BraceSubDefNode braceSubDefNode = new BraceSubDefNode(); - braceSubDefNode.setChildNode(index.cast(index.write(classNode))); + braceSubDefNode.setChildNode(index.cast(indexOutput)); braceSubDefNode.setLocation(location); braceSubDefNode.setExpressionType(output.actual); - return braceSubDefNode; + output.expressionNode = braceSubDefNode; + + return output; } @Override @@ -73,11 +71,6 @@ boolean isDefOptimized() { return true; } - @Override - void updateActual(Class actual) { - this.output.actual = actual; - } - @Override public String toString() { return singleLineToString(prefix, index); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java index f810f1a634b6e..8339d161f2a84 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefCall.java @@ -28,51 +28,55 @@ import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; /** * Represents a method call made on a def type. (Internal only.) */ -final class PSubDefCall extends AExpression { +public class PSubDefCall extends AExpression { - private final String name; - private final List arguments; - - private final StringBuilder recipe = new StringBuilder(); - private final List pointers = new ArrayList<>(); - private final List> parameterTypes = new ArrayList<>(); + protected final String name; + protected final List arguments; PSubDefCall(Location location, String name, List arguments) { super(location); this.name = Objects.requireNonNull(name); - this.arguments = Objects.requireNonNull(arguments); + this.arguments = Collections.unmodifiableList(Objects.requireNonNull(arguments)); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + StringBuilder recipe = new StringBuilder(); + List pointers = new ArrayList<>(); + List> parameterTypes = new ArrayList<>(); parameterTypes.add(Object.class); int totalCaptures = 0; + List argumentOutputs = new ArrayList<>(arguments.size()); + for (int argument = 0; argument < arguments.size(); ++argument) { AExpression expression = arguments.get(argument); Input expressionInput = new Input(); expressionInput.internal = true; - Output expressionOutput = expression.analyze(scriptRoot, scope, expressionInput); + Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + argumentOutputs.add(expressionOutput); if (expressionOutput.actual == void.class) { throw createError(new IllegalArgumentException("Argument(s) cannot be of [void] type when calling method [" + name + "].")); } - expression.input.expected = expressionOutput.actual; - expression.cast(); + expressionInput.expected = expressionOutput.actual; + expression.cast(expressionInput, expressionOutput); parameterTypes.add(expressionOutput.actual); + // TODO: #54015 if (expression instanceof ILambda) { ILambda lambda = (ILambda) expression; pointers.add(lambda.getPointer()); @@ -87,15 +91,10 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed output.actual = input.expected == null || input.expected == ZonedDateTime.class || input.explicit ? def.class : input.expected; - return output; - } - - @Override - CallSubDefNode write(ClassNode classNode) { CallSubDefNode callSubDefNode = new CallSubDefNode(); - for (AExpression argument : arguments) { - callSubDefNode.addArgumentNode(argument.cast(argument.write(classNode))); + for (int argument = 0; argument < arguments.size(); ++ argument) { + callSubDefNode.addArgumentNode(arguments.get(argument).cast(argumentOutputs.get(argument))); } callSubDefNode.setLocation(location); @@ -105,7 +104,9 @@ CallSubDefNode write(ClassNode classNode) { callSubDefNode.getPointers().addAll(pointers); callSubDefNode.getTypeParameters().addAll(parameterTypes); - return callSubDefNode; + output.expressionNode = callSubDefNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefField.java index 75bfa5c5755cc..1cb8b2a0119e3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubDefField.java @@ -32,9 +32,9 @@ /** * Represents a field load/store or shortcut on a def type. (Internal only.) */ -final class PSubDefField extends AStoreable { +public class PSubDefField extends AStoreable { - private final String value; + protected final String value; PSubDefField(Location location, String value) { super(location); @@ -43,25 +43,21 @@ final class PSubDefField extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed output.actual = input.expected == null || input.expected == ZonedDateTime.class || input.explicit ? def.class : input.expected; - return output; - } - - @Override - DotSubDefNode write(ClassNode classNode) { DotSubDefNode dotSubDefNode = new DotSubDefNode(); dotSubDefNode.setLocation(location); dotSubDefNode.setExpressionType(output.actual); dotSubDefNode.setValue(value); - return dotSubDefNode; + output.expressionNode = dotSubDefNode; + + return output; } @Override @@ -69,11 +65,6 @@ boolean isDefOptimized() { return true; } - @Override - void updateActual(Class actual) { - this.output.actual = actual; - } - @Override public String toString() { return singleLineToString(prefix, value); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java index c9a438571bc36..26d2858f54afb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java @@ -33,9 +33,9 @@ /** * Represents a field load/store. */ -final class PSubField extends AStoreable { +public class PSubField extends AStoreable { - private final PainlessField field; + protected final PainlessField field; PSubField(Location location, PainlessField field) { super(location); @@ -44,9 +44,8 @@ final class PSubField extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); if (input.write && Modifier.isFinal(field.javaField.getModifiers())) { throw createError(new IllegalArgumentException("Cannot write to read-only field [" + field.javaField.getName() + "] " + @@ -55,18 +54,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { output.actual = field.typeParameter; - return output; - } - - @Override - DotSubNode write(ClassNode classNode) { DotSubNode dotSubNode = new DotSubNode(); dotSubNode.setLocation(location); dotSubNode.setExpressionType(output.actual); dotSubNode.setField(field); - return dotSubNode; + output.expressionNode = dotSubNode; + + return output; } @Override @@ -74,11 +70,6 @@ boolean isDefOptimized() { return false; } - @Override - void updateActual(Class actual) { - throw new IllegalArgumentException("Illegal tree structure."); - } - @Override public String toString() { return singleLineToString(prefix, field.javaField.getName()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java index 4dd3b46ceaa10..d23039453b3f8 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java @@ -32,13 +32,10 @@ /** * Represents a list load/store shortcut. (Internal only.) */ -final class PSubListShortcut extends AStoreable { +public class PSubListShortcut extends AStoreable { - private final Class targetClass; - private AExpression index; - - private PainlessMethod getter; - private PainlessMethod setter; + protected final Class targetClass; + protected final AExpression index; PSubListShortcut(Location location, Class targetClass, AExpression index) { super(location); @@ -48,14 +45,13 @@ final class PSubListShortcut extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass); - getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1); - setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "set", 2); + PainlessMethod getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1); + PainlessMethod setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "set", 2); if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 || getter.typeParameters.get(0) != int.class)) { @@ -71,32 +67,31 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { throw createError(new IllegalArgumentException("Shortcut argument types must match.")); } + Output indexOutput = new Output(); + if ((input.read || input.write) && (input.read == false || getter != null) && (input.write == false || setter != null)) { Input indexInput = new Input(); indexInput.expected = int.class; - index.analyze(scriptRoot, scope, indexInput); - index.cast(); + indexOutput = index.analyze(classNode, scriptRoot, scope, indexInput); + index.cast(indexInput, indexOutput); output.actual = setter != null ? setter.typeParameters.get(1) : getter.returnType; } else { throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + canonicalClassName + "].")); } - return output; - } - - @Override - ListSubShortcutNode write(ClassNode classNode) { ListSubShortcutNode listSubShortcutNode = new ListSubShortcutNode(); - listSubShortcutNode.setChildNode(index.cast(index.write(classNode))); + listSubShortcutNode.setChildNode(index.cast(indexOutput)); listSubShortcutNode.setLocation(location); listSubShortcutNode.setExpressionType(output.actual); listSubShortcutNode.setGetter(getter); listSubShortcutNode.setSetter(setter); - return listSubShortcutNode; + output.expressionNode = listSubShortcutNode; + + return output; } @Override @@ -104,11 +99,6 @@ boolean isDefOptimized() { return false; } - @Override - void updateActual(Class actual) { - throw new IllegalArgumentException("Illegal tree structure."); - } - @Override public String toString() { return singleLineToString(prefix, index); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java index 707609e5a4947..4105bcec8b261 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java @@ -32,13 +32,10 @@ /** * Represents a map load/store shortcut. (Internal only.) */ -final class PSubMapShortcut extends AStoreable { +public class PSubMapShortcut extends AStoreable { - private final Class targetClass; - private AExpression index; - - private PainlessMethod getter; - private PainlessMethod setter; + protected final Class targetClass; + protected final AExpression index; PSubMapShortcut(Location location, Class targetClass, AExpression index) { super(location); @@ -48,14 +45,13 @@ final class PSubMapShortcut extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass); - getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1); - setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "put", 2); + PainlessMethod getter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1); + PainlessMethod setter = scriptRoot.getPainlessLookup().lookupPainlessMethod(targetClass, false, "put", 2); if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) { throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + canonicalClassName + "].")); @@ -70,32 +66,31 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { throw createError(new IllegalArgumentException("Shortcut argument types must match.")); } + Output indexOutput; + if ((input.read || input.write) && (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); - index.analyze(scriptRoot, scope, indexInput); - index.cast(); + indexOutput = index.analyze(classNode, scriptRoot, scope, indexInput); + index.cast(indexInput, indexOutput); output.actual = setter != null ? setter.typeParameters.get(1) : getter.returnType; } else { throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + canonicalClassName + "].")); } - return output; - } - - @Override - MapSubShortcutNode write(ClassNode classNode) { MapSubShortcutNode mapSubShortcutNode = new MapSubShortcutNode(); - mapSubShortcutNode.setChildNode(index.cast(index.write(classNode))); + mapSubShortcutNode.setChildNode(index.cast(indexOutput)); mapSubShortcutNode.setLocation(location); mapSubShortcutNode.setExpressionType(output.actual); mapSubShortcutNode.setGetter(getter); mapSubShortcutNode.setSetter(setter); - return mapSubShortcutNode; + output.expressionNode = mapSubShortcutNode; + + return output; } @Override @@ -103,11 +98,6 @@ boolean isDefOptimized() { return false; } - @Override - void updateActual(Class actual) { - throw new IllegalArgumentException("Illegal tree structure."); - } - @Override public String toString() { return singleLineToString(prefix, index); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeCallInvoke.java index 83d5fded5649d..bc9a259a2e96c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeCallInvoke.java @@ -31,10 +31,11 @@ * Implements a call who's value is null if the prefix is null rather than throwing an NPE. */ public class PSubNullSafeCallInvoke extends AExpression { + /** - * The expression gaurded by the null check. Required at construction time and replaced at analysis time. + * The expression guarded by the null check. Required at construction time and replaced at analysis time. */ - private AExpression guarded; + protected final AExpression guarded; public PSubNullSafeCallInvoke(Location location, AExpression guarded) { super(location); @@ -42,29 +43,25 @@ public PSubNullSafeCallInvoke(Location location, AExpression guarded) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); - Output guardedOutput = guarded.analyze(scriptRoot, scope, new Input()); + Output guardedOutput = guarded.analyze(classNode, scriptRoot, scope, new Input()); output.actual = guardedOutput.actual; if (output.actual.isPrimitive()) { throw new IllegalArgumentException("Result of null safe operator must be nullable"); } - return output; - } - - @Override - NullSafeSubNode write(ClassNode classNode) { NullSafeSubNode nullSafeSubNode = new NullSafeSubNode(); - nullSafeSubNode.setChildNode(guarded.write(classNode)); + nullSafeSubNode.setChildNode(guardedOutput.expressionNode); nullSafeSubNode.setLocation(location); nullSafeSubNode.setExpressionType(output.actual); - return nullSafeSubNode; + output.expressionNode = nullSafeSubNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeField.java index 536c8b15e83c6..22d4c91945273 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubNullSafeField.java @@ -29,7 +29,8 @@ * Implements a field who's value is null if the prefix is null rather than throwing an NPE. */ public class PSubNullSafeField extends AStoreable { - private AStoreable guarded; + + protected final AStoreable guarded; public PSubNullSafeField(Location location, AStoreable guarded) { super(location); @@ -37,44 +38,37 @@ public PSubNullSafeField(Location location, AStoreable guarded) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); if (input.write) { throw createError(new IllegalArgumentException("Can't write to null safe reference")); } + Input guardedInput = new Input(); guardedInput.read = input.read; - Output guardedOutput = guarded.analyze(scriptRoot, scope, guardedInput); + Output guardedOutput = guarded.analyze(classNode, scriptRoot, scope, guardedInput); output.actual = guardedOutput.actual; + if (output.actual.isPrimitive()) { throw new IllegalArgumentException("Result of null safe operator must be nullable"); } - return output; - } - - @Override - boolean isDefOptimized() { - return guarded.isDefOptimized(); - } - - @Override - void updateActual(Class actual) { - guarded.updateActual(actual); - } - - @Override - NullSafeSubNode write(ClassNode classNode) { NullSafeSubNode nullSafeSubNode = new NullSafeSubNode(); - nullSafeSubNode.setChildNode(guarded.write(classNode)); + nullSafeSubNode.setChildNode(guardedOutput.expressionNode); nullSafeSubNode.setLocation(location); nullSafeSubNode.setExpressionType(output.actual); - return nullSafeSubNode; + output.expressionNode = nullSafeSubNode; + + return output; + } + + @Override + boolean isDefOptimized() { + return false; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java index e88fbeaac0765..a55a5556bafca 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java @@ -29,12 +29,12 @@ /** * Represents a field load/store shortcut. (Internal only.) */ -final class PSubShortcut extends AStoreable { +public class PSubShortcut extends AStoreable { - private final String value; - private final String type; - private final PainlessMethod getter; - private final PainlessMethod setter; + protected final String value; + protected final String type; + protected final PainlessMethod getter; + protected final PainlessMethod setter; PSubShortcut(Location location, String value, String type, PainlessMethod getter, PainlessMethod setter) { super(location); @@ -46,9 +46,8 @@ final class PSubShortcut extends AStoreable { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { + Output output = new Output(); if (getter != null && (getter.returnType == void.class || !getter.typeParameters.isEmpty())) { throw createError(new IllegalArgumentException( @@ -70,11 +69,6 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, AStoreable.Input input) { throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + type + "].")); } - return output; - } - - @Override - DotSubShortcutNode write(ClassNode classNode) { DotSubShortcutNode dotSubShortcutNode = new DotSubShortcutNode(); dotSubShortcutNode.setLocation(location); @@ -82,7 +76,9 @@ DotSubShortcutNode write(ClassNode classNode) { dotSubShortcutNode.setGetter(getter); dotSubShortcutNode.setSetter(setter); - return dotSubShortcutNode; + output.expressionNode = dotSubShortcutNode; + + return output; } @Override @@ -90,11 +86,6 @@ boolean isDefOptimized() { return false; } - @Override - void updateActual(Class actual) { - throw new IllegalArgumentException("Illegal tree structure."); - } - @Override public String toString() { return singleLineToString(prefix, value); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java index 8e6db345e0b30..f662976c2e4b3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java @@ -25,6 +25,7 @@ import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -33,9 +34,9 @@ /** * Represents a set of statements as a branch of control-flow. */ -public final class SBlock extends AStatement { +public class SBlock extends AStatement { - final List statements; + protected final List statements; public SBlock(Location location, List statements) { super(location); @@ -44,9 +45,8 @@ public SBlock(Location location, List statements) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (statements == null || statements.isEmpty()) { throw createError(new IllegalArgumentException("A block must contain at least one statement.")); @@ -54,6 +54,8 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { AStatement last = statements.get(statements.size() - 1); + List statementOutputs = new ArrayList<>(statements.size()); + for (AStatement statement : statements) { // Note that we do not need to check after the last statement because // there is no statement that can be unreachable after the last. @@ -65,7 +67,8 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { statementInput.inLoop = input.inLoop; statementInput.lastSource = input.lastSource && statement == last; statementInput.lastLoop = (input.beginLoop || input.lastLoop) && statement == last; - Output statementOutput = statement.analyze(scriptRoot, scope, statementInput); + + Output statementOutput = statement.analyze(classNode, scriptRoot, scope, statementInput); output.methodEscape = statementOutput.methodEscape; output.loopEscape = statementOutput.loopEscape; @@ -73,24 +76,23 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.anyContinue |= statementOutput.anyContinue; output.anyBreak |= statementOutput.anyBreak; output.statementCount += statementOutput.statementCount; - } - return output; - } + statementOutputs.add(statementOutput); + } - @Override - BlockNode write(ClassNode classNode) { BlockNode blockNode = new BlockNode(); - for (AStatement statement : statements) { - blockNode.addStatementNode(statement.write(classNode)); + for (Output statementOutput : statementOutputs) { + blockNode.addStatementNode(statementOutput.statementNode); } blockNode.setLocation(location); blockNode.setAllEscape(output.allEscape); blockNode.setStatementCount(output.statementCount); - return blockNode; + output.statementNode = blockNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java index 4b036153319ea..55d22a2097a9f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java @@ -28,16 +28,15 @@ /** * Represents a break statement. */ -public final class SBreak extends AStatement { +public class SBreak extends AStatement { public SBreak(Location location) { super(location); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.inLoop == false) { throw createError(new IllegalArgumentException("Break statement outside of a loop.")); @@ -48,16 +47,12 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.anyBreak = true; output.statementCount = 1; - return output; - } - - @Override - BreakNode write(ClassNode classNode) { BreakNode breakNode = new BreakNode(); - breakNode.setLocation(location); - return breakNode; + output.statementNode = breakNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java index f00c0b18a70fc..06dc1a06514fd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java @@ -21,8 +21,10 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.CatchNode; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.DeclarationNode; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -31,11 +33,11 @@ /** * Represents a catch block as part of a try-catch block. */ -public final class SCatch extends AStatement { +public class SCatch extends AStatement { - private final DType baseException; - private final SDeclaration declaration; - private final SBlock block; + protected final DType baseException; + protected final SDeclaration declaration; + protected final SBlock block; public SCatch(Location location, DType baseException, SDeclaration declaration, SBlock block) { super(location); @@ -46,11 +48,10 @@ public SCatch(Location location, DType baseException, SDeclaration declaration, } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); - declaration.analyze(scriptRoot, scope, new Input()); + Output declarationOutput = declaration.analyze(classNode, scriptRoot, scope, new Input()); Class baseType = baseException.resolveType(scriptRoot.getPainlessLookup()).getType(); Class type = scope.getVariable(location, declaration.name).getType(); @@ -61,12 +62,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { "to [" + PainlessLookupUtility.typeToCanonicalTypeName(baseType) + "]")); } + Output blockOutput = null; + if (block != null) { Input blockInput = new Input(); blockInput.lastSource = input.lastSource; blockInput.inLoop = input.inLoop; blockInput.lastLoop = input.lastLoop; - Output blockOutput = block.analyze(scriptRoot, scope, blockInput); + blockOutput = block.analyze(classNode, scriptRoot, scope, blockInput); output.methodEscape = blockOutput.methodEscape; output.loopEscape = blockOutput.loopEscape; @@ -76,19 +79,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.statementCount = blockOutput.statementCount; } - return output; - } - - @Override - CatchNode write(ClassNode classNode) { CatchNode catchNode = new CatchNode(); - - catchNode.setDeclarationNode(declaration.write(classNode)); - catchNode.setBlockNode(block == null ? null : block.write(classNode)); - + catchNode.setDeclarationNode((DeclarationNode)declarationOutput.statementNode); + catchNode.setBlockNode(blockOutput == null ? null : (BlockNode)blockOutput.statementNode); catchNode.setLocation(location); - return catchNode; + output.statementNode = catchNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java index d946b9b1c69b5..1c03994d55d06 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java @@ -22,7 +22,6 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.ScriptClassInfo; import org.elasticsearch.painless.ir.ClassNode; -import org.elasticsearch.painless.ir.StatementNode; import org.elasticsearch.painless.symbol.FunctionTable; import org.elasticsearch.painless.symbol.ScriptRoot; import org.objectweb.asm.util.Printer; @@ -36,37 +35,25 @@ /** * The root of all Painless trees. Contains a series of statements. */ -public final class SClass extends ANode { +public class SClass extends ANode { - private final ScriptClassInfo scriptClassInfo; - private final String name; - private final Printer debugStream; - private final List functions = new ArrayList<>(); - private final List fields = new ArrayList<>(); - - private ScriptRoot scriptRoot; - private final String sourceText; + protected final ScriptClassInfo scriptClassInfo; + protected final String name; + protected final String sourceText; + protected final Printer debugStream; + protected final List functions = new ArrayList<>(); public SClass(ScriptClassInfo scriptClassInfo, String name, String sourceText, Printer debugStream, Location location, List functions) { super(location); this.scriptClassInfo = Objects.requireNonNull(scriptClassInfo); this.name = Objects.requireNonNull(name); + this.sourceText = Objects.requireNonNull(sourceText); this.debugStream = debugStream; this.functions.addAll(Objects.requireNonNull(functions)); - this.sourceText = Objects.requireNonNull(sourceText); - } - - void addFunction(SFunction function) { - functions.add(function); } - void addField(SField field) { - fields.add(field); - } - - public ScriptRoot analyze(ScriptRoot scriptRoot) { - this.scriptRoot = scriptRoot; + public ClassNode writeClass(ScriptRoot scriptRoot) { scriptRoot.addStaticConstant("$NAME", name); scriptRoot.addStaticConstant("$SOURCE", sourceText); @@ -83,31 +70,10 @@ public ScriptRoot analyze(ScriptRoot scriptRoot) { function.name, function.returnType, function.typeParameters, function.isInternal, function.isStatic); } - // copy protection is required because synthetic functions are - // added for lambdas/method references and analysis here is - // only for user-defined functions - List functions = new ArrayList<>(this.functions); - for (SFunction function : functions) { - function.analyze(scriptRoot); - } - - return scriptRoot; - } - - @Override - public StatementNode write(ClassNode classNode) { - throw new UnsupportedOperationException(); - } - - public ClassNode writeClass() { ClassNode classNode = new ClassNode(); - for (SField field : fields) { - classNode.addFieldNode(field.write(classNode)); - } - for (SFunction function : functions) { - classNode.addFunctionNode(function.write(classNode)); + classNode.addFunctionNode(function.writeFunction(classNode, scriptRoot)); } classNode.setLocation(location); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java index 1f5c752f8ccef..830455274f9e3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java @@ -28,16 +28,15 @@ /** * Represents a continue statement. */ -public final class SContinue extends AStatement { +public class SContinue extends AStatement { public SContinue(Location location) { super(location); } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (input.inLoop == false) { throw createError(new IllegalArgumentException("Continue statement outside of a loop.")); @@ -51,16 +50,12 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.anyContinue = true; output.statementCount = 1; - return output; - } - - @Override - ContinueNode write(ClassNode classNode) { ContinueNode continueNode = new ContinueNode(); - continueNode.setLocation(location); - return continueNode; + output.statementNode = continueNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java index 47bbe0d123ecb..4bbfcf6d1d505 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java @@ -23,8 +23,10 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.DeclarationBlockNode; +import org.elasticsearch.painless.ir.DeclarationNode; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -33,9 +35,9 @@ /** * Represents a series of declarations. */ -public final class SDeclBlock extends AStatement { +public class SDeclBlock extends AStatement { - private final List declarations; + protected final List declarations; public SDeclBlock(Location location, List declarations) { super(location); @@ -44,30 +46,28 @@ public SDeclBlock(Location location, List declarations) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + List declarationOutputs = new ArrayList<>(declarations.size()); for (SDeclaration declaration : declarations) { - declaration.analyze(scriptRoot, scope, new Input()); + declarationOutputs.add(declaration.analyze(classNode, scriptRoot, scope, new Input())); } output.statementCount = declarations.size(); - return output; - } - - @Override - DeclarationBlockNode write(ClassNode classNode) { DeclarationBlockNode declarationBlockNode = new DeclarationBlockNode(); - for (SDeclaration declaration : declarations) { - declarationBlockNode.addDeclarationNode(declaration.write(classNode)); + for (Output declarationOutput : declarationOutputs) { + declarationBlockNode.addDeclarationNode((DeclarationNode)declarationOutput.statementNode); } declarationBlockNode.setLocation(location); - return declarationBlockNode; + output.statementNode = declarationBlockNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java index 2a28b4d029558..22b71eb931a79 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java @@ -23,7 +23,6 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.DeclarationNode; -import org.elasticsearch.painless.node.AExpression.Input; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -31,12 +30,12 @@ /** * Represents a single variable declaration. */ -public final class SDeclaration extends AStatement { +public class SDeclaration extends AStatement { - private DType type; + protected final DType type; protected final String name; protected final boolean requiresDefault; - private AExpression expression; + protected final AExpression expression; public SDeclaration(Location location, DType type, String name, boolean requiresDefault, AExpression expression) { super(location); @@ -48,37 +47,32 @@ public SDeclaration(Location location, DType type, String name, boolean requires } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); DResolvedType resolvedType = type.resolveType(scriptRoot.getPainlessLookup()); - type = resolvedType; + + AExpression.Output expressionOutput = null; if (expression != null) { AExpression.Input expressionInput = new AExpression.Input(); expressionInput.expected = resolvedType.getType(); - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); } scope.defineVariable(location, resolvedType.getType(), name, false); - return output; - } - - @Override - DeclarationNode write(ClassNode classNode) { DeclarationNode declarationNode = new DeclarationNode(); - - declarationNode.setExpressionNode(expression == null ? null : expression.cast(expression.write(classNode))); - + declarationNode.setExpressionNode(expression == null ? null : expression.cast(expressionOutput)); declarationNode.setLocation(location); - declarationNode.setDeclarationType(((DResolvedType)type).getType()); + declarationNode.setDeclarationType(resolvedType.getType()); declarationNode.setName(name); declarationNode.setRequiresDefault(requiresDefault); - return declarationNode; + output.statementNode = declarationNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java index 8d31f325a049f..f5f4b0636a370 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.DoWhileLoopNode; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -30,12 +31,10 @@ /** * Represents a do-while loop. */ -public final class SDo extends AStatement { +public class SDo extends AStatement { - private final SBlock block; - private AExpression condition; - - private boolean continuous = false; + protected final SBlock block; + protected final AExpression condition; public SDo(Location location, SBlock block, AExpression condition) { super(location); @@ -45,10 +44,8 @@ public SDo(Location location, SBlock block, AExpression condition) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); - + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); scope = scope.newLocalScope(); if (block == null) { @@ -58,7 +55,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { Input blockInput = new Input(); blockInput.beginLoop = true; blockInput.inLoop = true; - Output blockOutput = block.analyze(scriptRoot, scope, blockInput); + Output blockOutput = block.analyze(classNode, scriptRoot, scope, blockInput); if (blockOutput.loopEscape && blockOutput.anyContinue == false) { throw createError(new IllegalArgumentException("Extraneous do while loop.")); @@ -66,8 +63,10 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { AExpression.Input conditionInput = new AExpression.Input(); conditionInput.expected = boolean.class; - condition.analyze(scriptRoot, scope, conditionInput); - condition.cast(); + AExpression.Output conditionOutput = condition.analyze(classNode, scriptRoot, scope, conditionInput); + condition.cast(conditionInput, conditionOutput); + + boolean continuous = false; if (condition instanceof EBoolean) { continuous = ((EBoolean)condition).constant; @@ -84,20 +83,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.statementCount = 1; - return output; - } - - @Override - DoWhileLoopNode write(ClassNode classNode) { DoWhileLoopNode doWhileLoopNode = new DoWhileLoopNode(); - - doWhileLoopNode.setConditionNode(condition.cast(condition.write(classNode))); - doWhileLoopNode.setBlockNode(block.write(classNode)); - + doWhileLoopNode.setConditionNode(condition.cast(conditionOutput)); + doWhileLoopNode.setBlockNode((BlockNode)blockOutput.statementNode); doWhileLoopNode.setLocation(location); doWhileLoopNode.setContinuous(continuous); - return doWhileLoopNode; + output.statementNode = doWhileLoopNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java index b5c439233b64d..ac8741cc551ec 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java @@ -36,12 +36,10 @@ */ public class SEach extends AStatement { - private final String type; - private final String name; - private AExpression expression; - private final SBlock block; - - private AStatement sub = null; + protected final String type; + protected final String name; + protected final AExpression expression; + protected final SBlock block; public SEach(Location location, String type, String name, AExpression expression, SBlock block) { super(location); @@ -53,13 +51,14 @@ public SEach(Location location, String type, String name, AExpression expression } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); - AExpression.Output expressionOutput = expression.analyze(scriptRoot, scope, new AExpression.Input()); - expression.input.expected = expressionOutput.actual; - expression.cast(); + AExpression.Input expressionInput = new AExpression.Input(); + AExpression.Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + // TODO: no need to cast here + expressionInput.expected = expressionOutput.actual; + expression.cast(expressionInput, expressionOutput); Class clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type); @@ -70,17 +69,6 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { scope = scope.newLocalScope(); Variable variable = scope.defineVariable(location, clazz, name, true); - if (expressionOutput.actual.isArray()) { - sub = new SSubEachArray(location, variable, expression, block); - } else if (expressionOutput.actual == def.class || Iterable.class.isAssignableFrom(expressionOutput.actual)) { - sub = new SSubEachIterable(location, variable, expression, block); - } else { - throw createError(new IllegalArgumentException("Illegal for each type " + - "[" + PainlessLookupUtility.typeToCanonicalTypeName(expressionOutput.actual) + "].")); - } - - sub.analyze(scriptRoot, scope, input); - if (block == null) { throw createError(new IllegalArgumentException("Extraneous for each loop.")); } @@ -88,27 +76,36 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { Input blockInput = new Input(); blockInput.beginLoop = true; blockInput.inLoop = true; - Output blockOutput = block.analyze(scriptRoot, scope, blockInput); + Output blockOutput = block.analyze(classNode, scriptRoot, scope, blockInput); blockOutput.statementCount = Math.max(1, blockOutput.statementCount); if (blockOutput.loopEscape && blockOutput.anyContinue == false) { throw createError(new IllegalArgumentException("Extraneous for loop.")); } - output.statementCount = 1; + AStatement sub; - return output; - } + if (expressionOutput.actual.isArray()) { + sub = new SSubEachArray(location, variable, expressionOutput, blockOutput); + } else if (expressionOutput.actual == def.class || Iterable.class.isAssignableFrom(expressionOutput.actual)) { + sub = new SSubEachIterable(location, variable, expressionOutput, blockOutput); + } else { + throw createError(new IllegalArgumentException("Illegal for each type " + + "[" + PainlessLookupUtility.typeToCanonicalTypeName(expressionOutput.actual) + "].")); + } - @Override - ForEachLoopNode write(ClassNode classNode) { - ForEachLoopNode forEachLoopNode = new ForEachLoopNode(); + Output subOutput = sub.analyze(classNode, scriptRoot, scope, input); - forEachLoopNode.setConditionNode((ConditionNode)sub.write(classNode)); + output.statementCount = 1; + ForEachLoopNode forEachLoopNode = new ForEachLoopNode(); + forEachLoopNode.setConditionNode((ConditionNode)subOutput.statementNode); forEachLoopNode.setLocation(location); - return forEachLoopNode; + output.statementNode = forEachLoopNode; + + return output; + } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java index f04cefa622acd..ace1be4b96736 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java @@ -25,7 +25,6 @@ import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.ir.ReturnNode; import org.elasticsearch.painless.ir.StatementExpressionNode; -import org.elasticsearch.painless.ir.StatementNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Objects; @@ -33,9 +32,9 @@ /** * Represents the top-level node for an expression as a statement. */ -public final class SExpression extends AStatement { +public class SExpression extends AStatement { - private AExpression expression; + protected final AExpression expression; public SExpression(Location location, AExpression expression) { super(location); @@ -44,15 +43,13 @@ public SExpression(Location location, AExpression expression) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { Class rtnType = scope.getReturnType(); boolean isVoid = rtnType == void.class; AExpression.Input expressionInput = new AExpression.Input(); expressionInput.read = input.lastSource && !isVoid; - AExpression.Output expressionOutput = expression.analyze(scriptRoot, scope, expressionInput); + AExpression.Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); if ((input.lastSource == false || isVoid) && expressionOutput.statement == false) { throw createError(new IllegalArgumentException("Not a statement.")); @@ -60,40 +57,33 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { boolean rtn = input.lastSource && isVoid == false && expressionOutput.actual != void.class; - expression.input.expected = rtn ? rtnType : expressionOutput.actual; - expression.input.internal = rtn; - expression.cast(); + expressionInput.expected = rtn ? rtnType : expressionOutput.actual; + expressionInput.internal = rtn; + expression.cast(expressionInput, expressionOutput); - output = new Output(); + Output output = new Output(); output.methodEscape = rtn; output.loopEscape = rtn; output.allEscape = rtn; output.statementCount = 1; - return output; - } - - @Override - StatementNode write(ClassNode classNode) { - ExpressionNode expressionNode = expression.cast(expression.write(classNode)); + ExpressionNode expressionNode = expression.cast(expressionOutput); if (output.methodEscape) { ReturnNode returnNode = new ReturnNode(); - returnNode.setExpressionNode(expressionNode); - returnNode.setLocation(location); - return returnNode; + output.statementNode = returnNode; } else { StatementExpressionNode statementExpressionNode = new StatementExpressionNode(); - statementExpressionNode.setExpressionNode(expressionNode); - statementExpressionNode.setLocation(location); - return statementExpressionNode; + output.statementNode = statementExpressionNode; } + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java deleted file mode 100644 index 944802fb7ac39..0000000000000 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.painless.node; - -import org.elasticsearch.painless.Location; -import org.elasticsearch.painless.ir.ClassNode; -import org.elasticsearch.painless.ir.FieldNode; - -/** - * Represents a member field for its parent class (internal only). - */ -public class SField extends ANode { - - private final int modifiers; - private final String name; - private final Class type; - - /** - * Standard constructor. - * @param location original location in the source - * @param modifiers java modifiers for the field - * @param name name of the field - * @param type type of the field - */ - public SField(Location location, int modifiers, String name, Class type) { - super(location); - - this.modifiers = modifiers; - this.name = name; - this.type = type; - } - - public String getName() { - return name; - } - - @Override - FieldNode write(ClassNode classNode) { - FieldNode fieldNode = new FieldNode(); - - fieldNode.setLocation(location); - fieldNode.setModifiers(modifiers); - fieldNode.setName(name); - fieldNode.setFieldType(type); - - return fieldNode; - } - - @Override - public String toString() { - return singleLineToString(name, type); - } -} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java index 241c50b8fe2b9..ce43e9a434f1a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java @@ -21,8 +21,8 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; -import org.elasticsearch.painless.ir.ExpressionNode; import org.elasticsearch.painless.ir.ForLoopNode; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -33,14 +33,12 @@ /** * Represents a for loop. */ -public final class SFor extends AStatement { +public class SFor extends AStatement { - private ANode initializer; - private AExpression condition; - private AExpression afterthought; - private final SBlock block; - - private boolean continuous = false; + protected final ANode initializer; + protected final AExpression condition; + protected final AExpression afterthought; + protected final SBlock block; public SFor(Location location, ANode initializer, AExpression condition, AExpression afterthought, SBlock block) { super(location); @@ -52,37 +50,42 @@ public SFor(Location location, ANode initializer, AExpression condition, AExpres } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { scope = scope.newLocalScope(); + Output initializerStatementOutput = null; + AExpression.Output initializerExpressionOutput = null; + if (initializer != null) { if (initializer instanceof SDeclBlock) { - ((SDeclBlock)initializer).analyze(scriptRoot, scope, new Input()); + initializerStatementOutput = ((SDeclBlock)initializer).analyze(classNode, scriptRoot, scope, new Input()); } else if (initializer instanceof AExpression) { AExpression initializer = (AExpression)this.initializer; AExpression.Input initializerInput = new AExpression.Input(); initializerInput.read = false; - AExpression.Output initializerOutput = initializer.analyze(scriptRoot, scope, initializerInput); + initializerExpressionOutput = initializer.analyze(classNode, scriptRoot, scope, initializerInput); - if (initializerOutput.statement == false) { + if (initializerExpressionOutput.statement == false) { throw createError(new IllegalArgumentException("Not a statement.")); } - initializer.input.expected = initializerOutput.actual; - initializer.cast(); + initializerInput.expected = initializerExpressionOutput.actual; + initializer.cast(initializerInput, initializerExpressionOutput); } else { throw createError(new IllegalStateException("Illegal tree structure.")); } } + boolean continuous = false; + + AExpression.Output conditionOutput = null; + if (condition != null) { AExpression.Input conditionInput = new AExpression.Input(); conditionInput.expected = boolean.class; - condition.analyze(scriptRoot, scope, conditionInput); - condition.cast(); + conditionOutput = condition.analyze(classNode, scriptRoot, scope, conditionInput); + condition.cast(conditionInput, conditionOutput); if (condition instanceof EBoolean) { continuous = ((EBoolean)condition).constant; @@ -99,27 +102,30 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { continuous = true; } + AExpression.Output afterthoughtOutput = null; + if (afterthought != null) { AExpression.Input afterthoughtInput = new AExpression.Input(); afterthoughtInput.read = false; - AExpression.Output afterthoughtOutput = afterthought.analyze(scriptRoot, scope, afterthoughtInput); + afterthoughtOutput = afterthought.analyze(classNode, scriptRoot, scope, afterthoughtInput); if (afterthoughtOutput.statement == false) { throw createError(new IllegalArgumentException("Not a statement.")); } - afterthought.input.expected = afterthoughtOutput.actual; - afterthought.cast(); + afterthoughtInput.expected = afterthoughtOutput.actual; + afterthought.cast(afterthoughtInput, afterthoughtOutput); } - output = new Output(); + Output output = new Output(); + Output blockOutput = null; if (block != null) { Input blockInput = new Input(); blockInput.beginLoop = true; blockInput.inLoop = true; - Output blockOutput = block.analyze(scriptRoot, scope, blockInput); + blockOutput = block.analyze(classNode, scriptRoot, scope, blockInput); if (blockOutput.loopEscape && blockOutput.anyContinue == false) { throw createError(new IllegalArgumentException("Extraneous for loop.")); @@ -135,24 +141,19 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.statementCount = 1; - return output; - } - - @Override - ForLoopNode write(ClassNode classNode) { ForLoopNode forLoopNode = new ForLoopNode(); - forLoopNode.setInitialzerNode(initializer == null ? null : initializer instanceof AExpression ? - ((AExpression)initializer).cast((ExpressionNode)initializer.write(classNode)) : - initializer.write(classNode)); - forLoopNode.setConditionNode(condition == null ? null : condition.cast(condition.write(classNode))); - forLoopNode.setAfterthoughtNode(afterthought == null ? null : afterthought.cast(afterthought.write(classNode))); - forLoopNode.setBlockNode(block == null ? null : block.write(classNode)); - + ((AExpression)initializer).cast(initializerExpressionOutput) : + initializerStatementOutput.statementNode); + forLoopNode.setConditionNode(condition == null ? null : condition.cast(conditionOutput)); + forLoopNode.setAfterthoughtNode(afterthought == null ? null : afterthought.cast(afterthoughtOutput)); + forLoopNode.setBlockNode(blockOutput == null ? null : (BlockNode)blockOutput.statementNode); forLoopNode.setLocation(location); forLoopNode.setContinuous(continuous); - return forLoopNode; + output.statementNode = forLoopNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index 96bf040099a17..fad71982b4772 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -46,32 +46,28 @@ /** * Represents a user-defined function. */ -public final class SFunction extends ANode { +public class SFunction extends ANode { - private final String rtnTypeStr; - public final String name; - private final List paramTypeStrs; - private final List paramNameStrs; - private final SBlock block; - public final boolean isInternal; - public final boolean isStatic; - public final boolean synthetic; + protected final String rtnTypeStr; + protected final String name; + protected final List paramTypeStrs; + protected final List paramNameStrs; + protected final SBlock block; + protected final boolean isInternal; + protected final boolean isStatic; + protected final boolean synthetic; /** * If set to {@code true} default return values are inserted if * not all paths return a value. */ - public final boolean isAutoReturnEnabled; + protected final boolean isAutoReturnEnabled; - private int maxLoopCounter; + protected Class returnType; + protected List> typeParameters; + protected MethodType methodType; - Class returnType; - List> typeParameters; - MethodType methodType; - - org.objectweb.asm.commons.Method method; - - private boolean methodEscape; + protected org.objectweb.asm.commons.Method method; public SFunction(Location location, String rtnType, String name, List paramTypes, List paramNames, @@ -89,6 +85,7 @@ public SFunction(Location location, String rtnType, String name, this.isAutoReturnEnabled = isAutoReturnEnabled; } + // TODO: do this in class on add to remove need for mutable state void generateSignature(PainlessLookup painlessLookup) { returnType = painlessLookup.canonicalTypeNameToType(rtnTypeStr); @@ -121,7 +118,7 @@ void generateSignature(PainlessLookup painlessLookup) { PainlessLookupUtility.typeToJavaType(returnType), paramClasses).toMethodDescriptorString()); } - void analyze(ScriptRoot scriptRoot) { + FunctionNode writeFunction(ClassNode classNode, ScriptRoot scriptRoot) { FunctionScope functionScope = newFunctionScope(returnType); for (int index = 0; index < typeParameters.size(); ++index) { @@ -130,7 +127,7 @@ void analyze(ScriptRoot scriptRoot) { functionScope.defineVariable(location, typeParameter, parameterName, false); } - maxLoopCounter = scriptRoot.getCompilerSettings().getMaxLoopCounter(); + int maxLoopCounter = scriptRoot.getCompilerSettings().getMaxLoopCounter(); if (block.statements.isEmpty()) { throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "].")); @@ -138,8 +135,8 @@ void analyze(ScriptRoot scriptRoot) { Input blockInput = new Input(); blockInput.lastSource = true; - Output blockOutput = block.analyze(scriptRoot, functionScope.newLocalScope(), blockInput); - methodEscape = blockOutput.methodEscape; + Output blockOutput = block.analyze(classNode, scriptRoot, functionScope.newLocalScope(), blockInput); + boolean methodEscape = blockOutput.methodEscape; if (methodEscape == false && isAutoReturnEnabled == false && returnType != void.class) { throw createError(new IllegalArgumentException("not all paths provide a return value " + @@ -152,11 +149,8 @@ void analyze(ScriptRoot scriptRoot) { scriptRoot.setUsedVariables(functionScope.getUsedVariables()); } // TODO: end - } - @Override - public FunctionNode write(ClassNode classNode) { - BlockNode blockNode = block.write(classNode); + BlockNode blockNode = (BlockNode)blockOutput.statementNode; if (methodEscape == false) { ExpressionNode expressionNode; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java index a7d738670ec6f..44b6e0d6105c4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.IfNode; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -30,10 +31,10 @@ /** * Represents an if block. */ -public final class SIf extends AStatement { +public class SIf extends AStatement { - AExpression condition; - final SBlock ifblock; + protected final AExpression condition; + protected final SBlock ifblock; public SIf(Location location, AExpression condition, SBlock ifblock) { super(location); @@ -43,14 +44,13 @@ public SIf(Location location, AExpression condition, SBlock ifblock) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); AExpression.Input conditionInput = new AExpression.Input(); conditionInput.expected = boolean.class; - condition.analyze(scriptRoot, scope, conditionInput); - condition.cast(); + AExpression.Output conditionOutput = condition.analyze(classNode, scriptRoot, scope, conditionInput); + condition.cast(conditionInput, conditionOutput); if (condition instanceof EBoolean) { throw createError(new IllegalArgumentException("Extraneous if statement.")); @@ -65,25 +65,20 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { ifblockInput.inLoop = input.inLoop; ifblockInput.lastLoop = input.lastLoop; - Output ifblockOutput = ifblock.analyze(scriptRoot, scope.newLocalScope(), ifblockInput); + Output ifblockOutput = ifblock.analyze(classNode, scriptRoot, scope.newLocalScope(), ifblockInput); output.anyContinue = ifblockOutput.anyContinue; output.anyBreak = ifblockOutput.anyBreak; output.statementCount = ifblockOutput.statementCount; - return output; - } - - @Override - IfNode write(ClassNode classNode) { IfNode ifNode = new IfNode(); - - ifNode.setConditionNode(condition.cast(condition.write(classNode))); - ifNode.setBlockNode(ifblock.write(classNode)); - + ifNode.setConditionNode(condition.cast(conditionOutput)); + ifNode.setBlockNode((BlockNode)ifblockOutput.statementNode); ifNode.setLocation(location); - return ifNode; + output.statementNode = ifNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java index f433db860ccfb..6ae366ad8428a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.IfElseNode; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -33,11 +34,11 @@ /** * Represents an if/else block. */ -public final class SIfElse extends AStatement { +public class SIfElse extends AStatement { - private AExpression condition; - private final SBlock ifblock; - private final SBlock elseblock; + protected final AExpression condition; + protected final SBlock ifblock; + protected final SBlock elseblock; public SIfElse(Location location, AExpression condition, SBlock ifblock, SBlock elseblock) { super(location); @@ -48,14 +49,13 @@ public SIfElse(Location location, AExpression condition, SBlock ifblock, SBlock } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); AExpression.Input conditionInput = new AExpression.Input(); conditionInput.expected = boolean.class; - condition.analyze(scriptRoot, scope, conditionInput); - condition.cast(); + AExpression.Output conditionOutput = condition.analyze(classNode, scriptRoot, scope, conditionInput); + condition.cast(conditionInput, conditionOutput); if (condition instanceof EBoolean) { throw createError(new IllegalArgumentException("Extraneous if statement.")); @@ -70,7 +70,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { ifblockInput.inLoop = input.inLoop; ifblockInput.lastLoop = input.lastLoop; - Output ifblockOutput = ifblock.analyze(scriptRoot, scope.newLocalScope(), ifblockInput); + Output ifblockOutput = ifblock.analyze(classNode, scriptRoot, scope.newLocalScope(), ifblockInput); output.anyContinue = ifblockOutput.anyContinue; output.anyBreak = ifblockOutput.anyBreak; @@ -85,7 +85,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { elseblockInput.inLoop = input.inLoop; elseblockInput.lastLoop = input.lastLoop; - Output elseblockOutput = elseblock.analyze(scriptRoot, scope.newLocalScope(), elseblockInput); + Output elseblockOutput = elseblock.analyze(classNode, scriptRoot, scope.newLocalScope(), elseblockInput); output.methodEscape = ifblockOutput.methodEscape && elseblockOutput.methodEscape; output.loopEscape = ifblockOutput.loopEscape && elseblockOutput.loopEscape; @@ -94,20 +94,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.anyBreak |= elseblockOutput.anyBreak; output.statementCount = Math.max(ifblockOutput.statementCount, elseblockOutput.statementCount); - return output; - } - - @Override - IfElseNode write(ClassNode classNode) { IfElseNode ifElseNode = new IfElseNode(); - - ifElseNode.setConditionNode(condition.cast(condition.write(classNode))); - ifElseNode.setBlockNode(ifblock.write(classNode)); - ifElseNode.setElseBlockNode(elseblock.write(classNode)); - + ifElseNode.setConditionNode(condition.cast(conditionOutput)); + ifElseNode.setBlockNode((BlockNode)ifblockOutput.statementNode); + ifElseNode.setElseBlockNode((BlockNode)elseblockOutput.statementNode); ifElseNode.setLocation(location); - return ifElseNode; + output.statementNode = ifElseNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java index 23a5d4183edbc..6ab6c6590e350 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java @@ -29,9 +29,9 @@ /** * Represents a return statement. */ -public final class SReturn extends AStatement { +public class SReturn extends AStatement { - private AExpression expression; + protected final AExpression expression; public SReturn(Location location, AExpression expression) { super(location); @@ -40,9 +40,10 @@ public SReturn(Location location, AExpression expression) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); + + AExpression.Output expressionOutput = null; if (expression == null) { if (scope.getReturnType() != void.class) { @@ -54,8 +55,8 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { AExpression.Input expressionInput = new AExpression.Input(); expressionInput.expected = scope.getReturnType(); expressionInput.internal = true; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); } output.methodEscape = true; @@ -64,18 +65,13 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.statementCount = 1; - return output; - } - - @Override - ReturnNode write(ClassNode classNode) { ReturnNode returnNode = new ReturnNode(); - - returnNode.setExpressionNode(expression == null ? null : expression.cast(expression.write(classNode))); - + returnNode.setExpressionNode(expression == null ? null : expression.cast(expressionOutput)); returnNode.setLocation(location); - return returnNode; + output.statementNode = returnNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java index 5e1846457d515..fef81dfed6968 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java @@ -23,6 +23,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.Scope.Variable; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.ForEachSubArrayNode; import org.elasticsearch.painless.lookup.PainlessCast; @@ -33,46 +34,34 @@ /** * Represents a for-each loop for arrays. */ -final class SSubEachArray extends AStatement { - private final Variable variable; - private AExpression expression; - private final SBlock block; +public class SSubEachArray extends AStatement { - private PainlessCast cast = null; - private Variable array = null; - private Variable index = null; - private Class indexed = null; + protected final Variable variable; + protected final AExpression.Output expressionOutput; + protected final Output blockOutput; - SSubEachArray(Location location, Variable variable, AExpression expression, SBlock block) { + SSubEachArray(Location location, Variable variable, AExpression.Output expressionOutput, Output blockOutput) { super(location); this.variable = Objects.requireNonNull(variable); - this.expression = Objects.requireNonNull(expression); - this.block = block; + this.expressionOutput = Objects.requireNonNull(expressionOutput); + this.blockOutput = blockOutput; } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); // We must store the array and index as variables for securing slots on the stack, and // also add the location offset to make the names unique in case of nested for each loops. - array = scope.defineInternalVariable(location, expression.output.actual, "array" + location.getOffset(), true); - index = scope.defineInternalVariable(location, int.class, "index" + location.getOffset(), true); - indexed = expression.output.actual.getComponentType(); - cast = AnalyzerCaster.getLegalCast(location, indexed, variable.getType(), true, true); + Variable array = scope.defineVariable(location, expressionOutput.actual, "#array" + location.getOffset(), true); + Variable index = scope.defineVariable(location, int.class, "#index" + location.getOffset(), true); + Class indexed = expressionOutput.actual.getComponentType(); + PainlessCast cast = AnalyzerCaster.getLegalCast(location, indexed, variable.getType(), true, true); - return output; - } - - @Override - ForEachSubArrayNode write(ClassNode classNode) { ForEachSubArrayNode forEachSubArrayNode = new ForEachSubArrayNode(); - - forEachSubArrayNode.setConditionNode(expression.write(classNode)); - forEachSubArrayNode.setBlockNode(block.write(classNode)); - + forEachSubArrayNode.setConditionNode(expressionOutput.expressionNode); + forEachSubArrayNode.setBlockNode((BlockNode)blockOutput.statementNode); forEachSubArrayNode.setLocation(location); forEachSubArrayNode.setVariableType(variable.getType()); forEachSubArrayNode.setVariableName(variable.getName()); @@ -84,11 +73,14 @@ ForEachSubArrayNode write(ClassNode classNode) { forEachSubArrayNode.setIndexedType(indexed); forEachSubArrayNode.setContinuous(false); - return forEachSubArrayNode; + output.statementNode = forEachSubArrayNode; + + return output; } @Override public String toString() { - return singleLineToString(variable.getCanonicalTypeName(), variable.getName(), expression, block); + //return singleLineToString(variable.getCanonicalTypeName(), variable.getName(), expression, block); + return null; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java index 4198452ff8e79..83561431db1b1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java @@ -23,6 +23,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.Scope.Variable; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.ForEachSubIterableNode; import org.elasticsearch.painless.lookup.PainlessCast; @@ -38,56 +39,46 @@ /** * Represents a for-each loop for iterables. */ -final class SSubEachIterable extends AStatement { +public class SSubEachIterable extends AStatement { - private AExpression expression; - private final SBlock block; - private final Variable variable; + protected final Variable variable; + protected final AExpression.Output expressionOutput; + protected final Output blockOutput; - private PainlessCast cast = null; - private Variable iterator = null; - private PainlessMethod method = null; - - SSubEachIterable(Location location, Variable variable, AExpression expression, SBlock block) { + SSubEachIterable(Location location, Variable variable, AExpression.Output expressionOutput, Output blockOutput) { super(location); this.variable = Objects.requireNonNull(variable); - this.expression = Objects.requireNonNull(expression); - this.block = block; + this.expressionOutput = Objects.requireNonNull(expressionOutput); + this.blockOutput = blockOutput; } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); // We must store the iterator as a variable for securing a slot on the stack, and // also add the location offset to make the name unique in case of nested for each loops. - iterator = scope.defineInternalVariable(location, Iterator.class, "itr" + location.getOffset(), true); + Variable iterator = scope.defineVariable(location, Iterator.class, "#itr" + location.getOffset(), true); + + PainlessMethod method; - if (expression.output.actual == def.class) { + if (expressionOutput.actual == def.class) { method = null; } else { - method = scriptRoot.getPainlessLookup().lookupPainlessMethod(expression.output.actual, false, "iterator", 0); + method = scriptRoot.getPainlessLookup().lookupPainlessMethod(expressionOutput.actual, false, "iterator", 0); if (method == null) { throw createError(new IllegalArgumentException( - "method [" + typeToCanonicalTypeName(expression.output.actual) + ", iterator/0] not found")); + "method [" + typeToCanonicalTypeName(expressionOutput.actual) + ", iterator/0] not found")); } } - cast = AnalyzerCaster.getLegalCast(location, def.class, variable.getType(), true, true); - - return output; - } + PainlessCast cast = AnalyzerCaster.getLegalCast(location, def.class, variable.getType(), true, true); - @Override - ForEachSubIterableNode write(ClassNode classNode) { ForEachSubIterableNode forEachSubIterableNode = new ForEachSubIterableNode(); - - forEachSubIterableNode.setConditionNode(expression.write(classNode)); - forEachSubIterableNode.setBlockNode(block.write(classNode)); - + forEachSubIterableNode.setConditionNode(expressionOutput.expressionNode); + forEachSubIterableNode.setBlockNode((BlockNode)blockOutput.statementNode); forEachSubIterableNode.setLocation(location); forEachSubIterableNode.setVariableType(variable.getType()); forEachSubIterableNode.setVariableName(variable.getName()); @@ -97,11 +88,14 @@ ForEachSubIterableNode write(ClassNode classNode) { forEachSubIterableNode.setMethod(method); forEachSubIterableNode.setContinuous(false); - return forEachSubIterableNode; + output.statementNode = forEachSubIterableNode; + + return output; } @Override public String toString() { - return singleLineToString(variable.getCanonicalTypeName(), variable.getName(), expression, block); + //return singleLineToString(variable.getCanonicalTypeName(), variable.getName(), expression, block); + return null; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java index 86b40fafa378b..3fff9615b12d9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java @@ -30,9 +30,9 @@ /** * Represents a throw statement. */ -public final class SThrow extends AStatement { +public class SThrow extends AStatement { - private AExpression expression; + protected final AExpression expression; public SThrow(Location location, AExpression expression) { super(location); @@ -41,32 +41,26 @@ public SThrow(Location location, AExpression expression) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); AExpression.Input expressionInput = new AExpression.Input(); expressionInput.expected = Exception.class; - expression.analyze(scriptRoot, scope, expressionInput); - expression.cast(); + AExpression.Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + expression.cast(expressionInput, expressionOutput); output.methodEscape = true; output.loopEscape = true; output.allEscape = true; output.statementCount = 1; - return output; - } - - @Override - ThrowNode write(ClassNode classNode) { ThrowNode throwNode = new ThrowNode(); - - throwNode.setExpressionNode(expression.cast(expression.write(classNode))); - + throwNode.setExpressionNode(expression.cast(expressionOutput)); throwNode.setLocation(location); - return throwNode; + output.statementNode = throwNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java index 7798fd13b13a2..b9fd59f6b3fc1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java @@ -21,10 +21,13 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; +import org.elasticsearch.painless.ir.CatchNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.TryNode; import org.elasticsearch.painless.symbol.ScriptRoot; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -33,10 +36,10 @@ /** * Represents the try block as part of a try-catch block. */ -public final class STry extends AStatement { +public class STry extends AStatement { - private final SBlock block; - private final List catches; + protected final SBlock block; + protected final List catches; public STry(Location location, SBlock block, List catches) { super(location); @@ -46,9 +49,8 @@ public STry(Location location, SBlock block, List catches) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); if (block == null) { throw createError(new IllegalArgumentException("Extraneous try statement.")); @@ -59,7 +61,7 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { blockInput.inLoop = input.inLoop; blockInput.lastLoop = input.lastLoop; - Output blockOutput = block.analyze(scriptRoot, scope.newLocalScope(), blockInput); + Output blockOutput = block.analyze(classNode, scriptRoot, scope.newLocalScope(), blockInput); output.methodEscape = blockOutput.methodEscape; output.loopEscape = blockOutput.loopEscape; @@ -69,13 +71,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { int statementCount = 0; + List catchOutputs = new ArrayList<>(); + for (SCatch catc : catches) { Input catchInput = new Input(); catchInput.lastSource = input.lastSource; catchInput.inLoop = input.inLoop; catchInput.lastLoop = input.lastLoop; - Output catchOutput = catc.analyze(scriptRoot, scope.newLocalScope(), catchInput); + Output catchOutput = catc.analyze(classNode, scriptRoot, scope.newLocalScope(), catchInput); output.methodEscape &= catchOutput.methodEscape; output.loopEscape &= catchOutput.loopEscape; @@ -83,27 +87,25 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.anyContinue |= catchOutput.anyContinue; output.anyBreak |= catchOutput.anyBreak; + catchOutputs.add(catchOutput); + statementCount = Math.max(statementCount, catchOutput.statementCount); } output.statementCount = blockOutput.statementCount + statementCount; - return output; - } - - @Override - TryNode write(ClassNode classNode) { TryNode tryNode = new TryNode(); - for (SCatch catc : catches) { - tryNode.addCatchNode(catc.write(classNode)); + for (Output catchOutput : catchOutputs) { + tryNode.addCatchNode((CatchNode)catchOutput.statementNode); } - tryNode.setBlockNode(block.write(classNode)); - + tryNode.setBlockNode((BlockNode)blockOutput.statementNode); tryNode.setLocation(location); - return tryNode; + output.statementNode = tryNode; + + return output; } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java index eead51786e2d3..9fa3139b87eed 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java @@ -21,6 +21,7 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; +import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.WhileNode; import org.elasticsearch.painless.symbol.ScriptRoot; @@ -30,12 +31,10 @@ /** * Represents a while loop. */ -public final class SWhile extends AStatement { +public class SWhile extends AStatement { - private AExpression condition; - private final SBlock block; - - private boolean continuous = false; + protected final AExpression condition; + protected final SBlock block; public SWhile(Location location, AExpression condition, SBlock block) { super(location); @@ -45,16 +44,16 @@ public SWhile(Location location, AExpression condition, SBlock block) { } @Override - Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { - this.input = input; - output = new Output(); - + Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input input) { + Output output = new Output(); scope = scope.newLocalScope(); AExpression.Input conditionInput = new AExpression.Input(); conditionInput.expected = boolean.class; - condition.analyze(scriptRoot, scope, conditionInput); - condition.cast(); + AExpression.Output conditionOutput = condition.analyze(classNode, scriptRoot, scope, conditionInput); + condition.cast(conditionInput, conditionOutput); + + boolean continuous = false; if (condition instanceof EBoolean) { continuous = ((EBoolean)condition).constant; @@ -68,12 +67,14 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { } } + Output blockOutput = null; + if (block != null) { Input blockInput = new Input(); blockInput.beginLoop = true; blockInput.inLoop = true; - Output blockOutput = block.analyze(scriptRoot, scope, blockInput); + blockOutput = block.analyze(classNode, scriptRoot, scope, blockInput); if (blockOutput.loopEscape && blockOutput.anyContinue == false) { throw createError(new IllegalArgumentException("Extraneous while loop.")); @@ -89,20 +90,15 @@ Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) { output.statementCount = 1; - return output; - } - - @Override - WhileNode write(ClassNode classNode) { WhileNode whileNode = new WhileNode(); - - whileNode.setConditionNode(condition.cast(condition.write(classNode))); - whileNode.setBlockNode(block == null ? null : block.write(classNode)); - + whileNode.setConditionNode(condition.cast(conditionOutput)); + whileNode.setBlockNode(blockOutput == null ? null : (BlockNode)blockOutput.statementNode); whileNode.setLocation(location); whileNode.setContinuous(continuous); - return whileNode; + output.statementNode = whileNode; + + return output; } @Override diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java index 0a7c0cdcd0d7e..0cee95adef5fd 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless; public class DefOptimizationTests extends ScriptTestCase { + public void testIntBraceArrayOptiLoad() { final String script = "int x = 0; def y = new int[1]; y[0] = 5; x = y[0]; return x;"; assertBytecodeExists(script, "INVOKEDYNAMIC arrayLoad(Ljava/lang/Object;I)I"); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java index bdc683125eb68..70e754a4168f5 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java @@ -24,7 +24,6 @@ import org.elasticsearch.painless.FeatureTestObject; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Operation; -import org.elasticsearch.painless.Scope.Variable; import org.elasticsearch.painless.ScriptClassInfo; import org.elasticsearch.painless.action.PainlessExecuteAction; import org.elasticsearch.painless.antlr.Walker; @@ -749,30 +748,6 @@ public void testSIfElse() { + "}"); } - public void testSSubEachArray() { - Location l = new Location(getTestName(), 0); - Variable v = new Variable(int.class, "test", false); - AExpression e = new ENewArray(l, "int", Arrays.asList(new EConstant(l, 1), new EConstant(l, 2), new EConstant(l, 3)), true); - SBlock b = new SBlock(l, singletonList(new SReturn(l, new EConstant(l, 5)))); - SSubEachArray node = new SSubEachArray(l, v, e, b); - assertEquals( - "(SSubEachArray int test (ENewArray int init (Args (EConstant Integer 1) (EConstant Integer 2) (EConstant Integer 3))) " - + "(SBlock (SReturn (EConstant Integer 5))))", - node.toString()); - } - - public void testSSubEachIterable() { - Location l = new Location(getTestName(), 0); - Variable v = new Variable(int.class, "test", false); - AExpression e = new EListInit(l, Arrays.asList(new EConstant(l, 1), new EConstant(l, 2), new EConstant(l, 3))); - SBlock b = new SBlock(l, singletonList(new SReturn(l, new EConstant(l, 5)))); - SSubEachIterable node = new SSubEachIterable(l, v, e, b); - assertEquals( - "(SSubEachIterable int test (EListInit (EConstant Integer 1) (EConstant Integer 2) (EConstant Integer 3)) (SBlock " - + "(SReturn (EConstant Integer 5))))", - node.toString()); - } - public void testSThrow() { assertToString("(SClass (SThrow (ENewObj RuntimeException)))", "throw new RuntimeException()"); }