captures) {
- return new LambdaLocals(currentScope, parameters, captures);
+ /**
+ * Creates a new lambda scope inside the current scope
+ *
+ * This is just like {@link #newFunctionScope}, except the captured parameters are made read-only.
+ */
+ public static Locals newLambdaScope(Locals programScope, List parameters, int captureCount, int maxLoopCounter) {
+ Locals locals = new Locals(programScope, Definition.DEF_TYPE);
+ for (int i = 0; i < parameters.size(); i++) {
+ Parameter parameter = parameters.get(i);
+ boolean isCapture = i < captureCount;
+ locals.addVariable(parameter.location, parameter.type, parameter.name, isCapture);
+ }
+ // Loop counter to catch infinite loops. Internal use only.
+ if (maxLoopCounter > 0) {
+ locals.defineVariable(null, Definition.INT_TYPE, LOOP, true);
+ }
+ return locals;
}
/** Creates a new function scope inside the current scope */
public static Locals newFunctionScope(Locals programScope, Type returnType, List parameters, int maxLoopCounter) {
Locals locals = new Locals(programScope, returnType);
for (Parameter parameter : parameters) {
- locals.defineVariable(parameter.location, parameter.type, parameter.name, false);
+ locals.addVariable(parameter.location, parameter.type, parameter.name, false);
}
// Loop counter to catch infinite loops. Internal use only.
if (maxLoopCounter > 0) {
@@ -129,7 +143,7 @@ public class Locals {
}
/** Checks if a variable exists or not, in this scope or any parents. */
- public final boolean hasVariable(String name) {
+ public boolean hasVariable(String name) {
Variable variable = lookupVariable(null, name);
if (variable != null) {
return true;
@@ -153,7 +167,7 @@ public class Locals {
}
/** Looks up a method. Returns null if the method does not exist. */
- public final Method getMethod(MethodKey key) {
+ public Method getMethod(MethodKey key) {
Method method = lookupMethod(key);
if (method != null) {
return method;
@@ -165,7 +179,7 @@ public class Locals {
}
/** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */
- public final Variable addVariable(Location location, Type type, String name, boolean readonly) {
+ public Variable addVariable(Location location, Type type, String name, boolean readonly) {
if (hasVariable(name)) {
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
}
@@ -196,23 +210,23 @@ public class Locals {
// return type of this scope
private final Type returnType;
// next slot number to assign
- int nextSlotNumber;
+ private int nextSlotNumber;
// variable name -> variable
- Map variables;
+ private Map variables;
// method name+arity -> methods
- Map methods;
+ private Map methods;
/**
* Create a new Locals
*/
- Locals(Locals parent) {
+ private Locals(Locals parent) {
this(parent, parent.getReturnType());
}
/**
* Create a new Locals with specified return type
*/
- Locals(Locals parent, Type returnType) {
+ private Locals(Locals parent, Type returnType) {
this.parent = parent;
this.returnType = returnType;
if (parent == null) {
@@ -223,12 +237,12 @@ public class Locals {
}
/** Returns the parent scope */
- Locals getParent() {
+ private Locals getParent() {
return parent;
}
/** Looks up a variable at this scope only. Returns null if the variable does not exist. */
- Variable lookupVariable(Location location, String name) {
+ private Variable lookupVariable(Location location, String name) {
if (variables == null) {
return null;
}
@@ -236,7 +250,7 @@ public class Locals {
}
/** Looks up a method at this scope only. Returns null if the method does not exist. */
- Method lookupMethod(MethodKey key) {
+ private Method lookupMethod(MethodKey key) {
if (methods == null) {
return null;
}
@@ -245,19 +259,17 @@ public class Locals {
/** Defines a variable at this scope internally. */
- Variable defineVariable(Location location, Type type, String name, boolean readonly) {
+ private Variable defineVariable(Location location, Type type, String name, boolean readonly) {
if (variables == null) {
variables = new HashMap<>();
}
- Variable variable = new Variable(location, name, type, readonly);
- variable.slot = getNextSlot();
+ Variable variable = new Variable(location, name, type, getNextSlot(), readonly);
variables.put(name, variable); // TODO: check result
nextSlotNumber += type.type.getSize();
return variable;
}
- // TODO: make private, thats bogus
- public void addMethod(Method method) {
+ private void addMethod(Method method) {
if (methods == null) {
methods = new HashMap<>();
}
@@ -266,7 +278,7 @@ public class Locals {
}
- int getNextSlot() {
+ private int getNextSlot() {
return nextSlotNumber;
}
@@ -274,13 +286,14 @@ public class Locals {
public final Location location;
public final String name;
public final Type type;
- int slot = -1;
public final boolean readonly;
+ private final int slot;
- public Variable(Location location, String name, Type type, boolean readonly) {
+ public Variable(Location location, String name, Type type, int slot, boolean readonly) {
this.location = location;
this.name = name;
this.type = type;
+ this.slot = slot;
this.readonly = readonly;
}
@@ -289,7 +302,7 @@ public class Locals {
}
}
- public static class Parameter {
+ public static final class Parameter {
public final Location location;
public final String name;
public final Type type;
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java
index 365060fe004..31c4e22dd0c 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java
@@ -208,15 +208,6 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme
};
}
- /**
- * Action taken when a script is removed from the cache.
- * @param script The removed script.
- */
- @Override
- public void scriptRemoved(final CompiledScript script) {
- // Nothing to do.
- }
-
/**
* Action taken when the engine is closed.
*/
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 08092a9e116..55d62108cba 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
@@ -21,6 +21,9 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Location;
+import java.util.Objects;
+import java.util.Set;
+
/**
* The superclass for all other nodes.
*/
@@ -31,9 +34,17 @@ public abstract class ANode {
final Location location;
ANode(Location location) {
- this.location = location;
+ this.location = Objects.requireNonNull(location);
}
+ /**
+ * Adds all variable names referenced to the variable set.
+ *
+ * This can be called at any time, e.g. to support lambda capture.
+ * @param variables set of variables referenced (any scope)
+ */
+ abstract void extractVariables(Set variables);
+
public RuntimeException createError(RuntimeException exception) {
return location.createError(exception);
}
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 87b12284789..ca6332cd65e 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
@@ -25,6 +25,10 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation;
@@ -48,9 +52,15 @@ public final class EBinary extends AExpression {
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
- this.operation = operation;
- this.left = left;
- this.right = right;
+ this.operation = Objects.requireNonNull(operation);
+ this.left = Objects.requireNonNull(left);
+ this.right = Objects.requireNonNull(right);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ left.extractVariables(variables);
+ right.extractVariables(variables);
}
@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 9e39ca712f5..d707cc811f9 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
@@ -25,6 +25,10 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.MethodWriter;
/**
@@ -39,9 +43,15 @@ public final class EBool extends AExpression {
public EBool(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
- this.operation = operation;
- this.left = left;
- this.right = right;
+ this.operation = Objects.requireNonNull(operation);
+ this.left = Objects.requireNonNull(left);
+ this.right = Objects.requireNonNull(right);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ left.extractVariables(variables);
+ right.extractVariables(variables);
}
@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 a152422fac4..dc25bb0ed44 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
@@ -25,6 +25,8 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
+import java.util.Set;
+
/**
* Represents a boolean constant.
*/
@@ -35,6 +37,9 @@ public final class EBoolean extends AExpression {
this.constant = constant;
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
void analyze(Locals locals) {
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 62ecf63b9b3..aa7f807aff1 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
@@ -33,32 +33,39 @@ import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import java.lang.invoke.LambdaMetafactory;
+import java.util.Objects;
+import java.util.Set;
/**
* Represents a capturing function reference.
*/
public class ECapturingFunctionRef extends AExpression implements ILambda {
- public final String type;
+ public final String variable;
public final String call;
private FunctionRef ref;
Variable captured;
String defPointer;
- public ECapturingFunctionRef(Location location, String type, String call) {
+ public ECapturingFunctionRef(Location location, String variable, String call) {
super(location);
- this.type = type;
- this.call = call;
+ this.variable = Objects.requireNonNull(variable);
+ this.call = Objects.requireNonNull(call);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ variables.add(variable);
}
@Override
void analyze(Locals variables) {
- captured = variables.getVariable(location, type);
+ captured = variables.getVariable(location, variable);
if (expected == null) {
if (captured.type.sort == Definition.Sort.DEF) {
// dynamic implementation
- defPointer = "D" + type + "." + call + ",1";
+ defPointer = "D" + variable + "." + call + ",1";
} else {
// typed implementation
defPointer = "S" + captured.type.name + "." + call + ",1";
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java
index 1073125c637..c7dda568ff5 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java
@@ -20,6 +20,10 @@
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Cast;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
@@ -40,9 +44,13 @@ final class ECast extends AExpression {
super(location);
this.type = null;
- this.child = child;
-
- this.cast = cast;
+ this.child = Objects.requireNonNull(child);
+ this.cast = Objects.requireNonNull(cast);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ child.extractVariables(variables);
}
@Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java
index aa5bbcaab9d..44fe019990f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java
@@ -33,6 +33,8 @@ import org.elasticsearch.painless.MethodWriter;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
/**
* Represents the entirety of a variable/method chain for read/write operations.
@@ -60,12 +62,22 @@ public final class EChain extends AExpression {
boolean pre, boolean post, Operation operation, AExpression expression) {
super(location);
- this.links = links;
+ this.links = Objects.requireNonNull(links);
this.pre = pre;
this.post = post;
this.operation = operation;
this.expression = expression;
}
+
+ @Override
+ void extractVariables(Set variables) {
+ for (ALink link : links) {
+ link.extractVariables(variables);
+ }
+ if (expression != null) {
+ expression.extractVariables(variables);
+ }
+ }
@Override
void analyze(Locals locals) {
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 31050f9f6ce..d76fbbe9065 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
@@ -29,6 +29,10 @@ import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE;
@@ -47,9 +51,15 @@ public final class EComp extends AExpression {
public EComp(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
- this.operation = operation;
- this.left = left;
- this.right = right;
+ this.operation = Objects.requireNonNull(operation);
+ this.left = Objects.requireNonNull(left);
+ this.right = Objects.requireNonNull(right);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ left.extractVariables(variables);
+ right.extractVariables(variables);
}
@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 600626348dd..e05419e1c52 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
@@ -26,6 +26,10 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.MethodWriter;
/**
@@ -40,9 +44,16 @@ public final class EConditional extends AExpression {
public EConditional(Location location, AExpression condition, AExpression left, AExpression right) {
super(location);
- this.condition = condition;
- this.left = left;
- this.right = right;
+ this.condition = Objects.requireNonNull(condition);
+ this.left = Objects.requireNonNull(left);
+ this.right = Objects.requireNonNull(right);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ condition.extractVariables(variables);
+ left.extractVariables(variables);
+ right.extractVariables(variables);
}
@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 b037d25c2d6..c5e10a340d6 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
@@ -22,6 +22,9 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort;
+
+import java.util.Set;
+
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
@@ -37,6 +40,9 @@ final class EConstant extends AExpression {
this.constant = constant;
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
void analyze(Locals locals) {
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 efa453605cb..e93b63c3bcd 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
@@ -25,8 +25,11 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
+import java.util.Objects;
+import java.util.Set;
+
/**
- * Respresents a decimal constant.
+ * Represents a decimal constant.
*/
public final class EDecimal extends AExpression {
@@ -35,8 +38,11 @@ public final class EDecimal extends AExpression {
public EDecimal(Location location, String value) {
super(location);
- this.value = value;
+ this.value = Objects.requireNonNull(value);
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
void analyze(Locals locals) {
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 1c186faaeeb..71ad952baff 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
@@ -25,6 +25,9 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
+import java.util.Objects;
+import java.util.Set;
+
/**
* Represents an explicit cast.
*/
@@ -36,8 +39,13 @@ public final class EExplicit extends AExpression {
public EExplicit(Location location, String type, AExpression child) {
super(location);
- this.type = type;
- this.child = child;
+ this.type = Objects.requireNonNull(type);
+ this.child = Objects.requireNonNull(child);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ child.extractVariables(variables);
}
@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 856a876550c..298b84ffe29 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
@@ -32,6 +32,8 @@ import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import java.lang.invoke.LambdaMetafactory;
+import java.util.Objects;
+import java.util.Set;
/**
* Represents a function reference.
@@ -46,9 +48,12 @@ public class EFunctionRef extends AExpression implements ILambda {
public EFunctionRef(Location location, String type, String call) {
super(location);
- this.type = type;
- this.call = call;
+ this.type = Objects.requireNonNull(type);
+ this.call = Objects.requireNonNull(call);
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
void analyze(Locals locals) {
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 d0b531c74d3..1d1498a51c4 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
@@ -23,72 +23,194 @@ import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
+import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.node.SFunction.FunctionReserved;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
+import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
+import java.lang.invoke.LambdaMetafactory;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
+
+/**
+ * Lambda expression node.
+ *
+ * This can currently only be the direct argument of a call (method/constructor).
+ * When the argument is of a known type, it uses
+ *
+ * Java's lambda translation. However, if its a def call, then we don't have
+ * enough information, and have to defer this until link time. In that case a placeholder
+ * and all captures are pushed onto the stack and folded into the signature of the parent call.
+ *
+ * For example:
+ *
+ * {@code def list = new ArrayList(); int capture = 0; list.sort((x,y) -> x - y + capture)}
+ *
+ * is converted into a call (pseudocode) such as:
+ *
+ * {@code sort(list, lambda$0, capture)}
+ *
+ * At link time, when we know the interface type, this is decomposed with MethodHandle
+ * combinators back into (pseudocode):
+ *
+ * {@code sort(list, lambda$0(capture))}
+ */
public class ELambda extends AExpression implements ILambda {
final String name;
final FunctionReserved reserved;
final List paramTypeStrs;
final List paramNameStrs;
final List statements;
+
// desugared synthetic method (lambda body)
SFunction desugared;
- // method ref (impl detail)
- ILambda impl;
+ // captured variables
+ List captures;
+ // static parent, static lambda
+ FunctionRef ref;
+ // dynamic parent, deferred until link time
+ String defPointer;
public ELambda(String name, FunctionReserved reserved,
Location location, List paramTypes, List paramNames,
List statements) {
super(location);
- this.name = name;
- this.reserved = reserved;
+ this.name = Objects.requireNonNull(name);
+ this.reserved = Objects.requireNonNull(reserved);
this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
this.paramNameStrs = Collections.unmodifiableList(paramNames);
this.statements = Collections.unmodifiableList(statements);
}
+
+ @Override
+ void extractVariables(Set variables) {
+ for (AStatement statement : statements) {
+ statement.extractVariables(variables);
+ }
+ }
@Override
void analyze(Locals locals) {
+ // gather any variables used by the lambda body first.
+ Set variables = new HashSet<>();
+ for (AStatement statement : statements) {
+ statement.extractVariables(variables);
+ }
+ // any of those variables defined in our scope need to be captured
+ captures = new ArrayList<>();
+ for (String variable : variables) {
+ if (locals.hasVariable(variable)) {
+ captures.add(locals.getVariable(location, variable));
+ }
+ }
+ // prepend capture list to lambda's arguments
+ List paramTypes = new ArrayList<>();
+ List paramNames = new ArrayList<>();
+ for (Variable var : captures) {
+ paramTypes.add(var.type.name);
+ paramNames.add(var.name);
+ }
+ paramTypes.addAll(paramTypeStrs);
+ paramNames.addAll(paramNameStrs);
+
// desugar lambda body into a synthetic method
desugared = new SFunction(reserved, location, "def", name,
- paramTypeStrs, paramNameStrs, statements, true);
+ paramTypes, paramNames, statements, true);
desugared.generate();
- List captures = new ArrayList<>();
- desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters, captures));
+ desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters,
+ captures.size(), reserved.getMaxLoopCounter()));
- // setup reference
- EFunctionRef ref = new EFunctionRef(location, "this", name);
- ref.expected = expected;
- // hack, create a new scope, with our method, so the ref can see it (impl detail)
- locals = Locals.newLocalScope(locals);
- locals.addMethod(desugared.method);
- ref.analyze(locals);
- actual = ref.actual;
- impl = ref;
+ // setup method reference to synthetic method
+ if (expected == null) {
+ ref = null;
+ actual = Definition.getType("String");
+ defPointer = "Sthis." + name + "," + captures.size();
+ } else {
+ defPointer = null;
+ try {
+ Method interfaceMethod = expected.struct.getFunctionalMethod();
+ if (interfaceMethod == null) {
+ throw new IllegalArgumentException("Cannot pass lambda to [" + expected.name +
+ "], not a functional interface");
+ }
+ Class> captureClasses[] = new Class>[captures.size()];
+ for (int i = 0; i < captures.size(); i++) {
+ captureClasses[i] = captures.get(i).type.clazz;
+ }
+ ref = new FunctionRef(expected, interfaceMethod, desugared.method, captureClasses);
+ } catch (IllegalArgumentException e) {
+ throw createError(e);
+ }
+ actual = expected;
+ }
}
@Override
void write(MethodWriter writer, Globals globals) {
- AExpression expr = (AExpression) impl;
- expr.write(writer, globals);
+ writer.writeDebugInfo(location);
+
+ if (ref != null) {
+ writer.writeDebugInfo(location);
+ // load captures
+ for (Variable capture : captures) {
+ writer.visitVarInsn(capture.type.type.getOpcode(Opcodes.ILOAD), capture.getSlot());
+ }
+ // convert MethodTypes to asm Type for the constant pool.
+ String invokedType = ref.invokedType.toMethodDescriptorString();
+ Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
+ Type interfaceType = Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
+ if (ref.needsBridges()) {
+ writer.invokeDynamic(ref.invokedName,
+ invokedType,
+ LAMBDA_BOOTSTRAP_HANDLE,
+ samMethodType,
+ ref.implMethodASM,
+ samMethodType,
+ LambdaMetafactory.FLAG_BRIDGES,
+ 1,
+ interfaceType);
+ } else {
+ writer.invokeDynamic(ref.invokedName,
+ invokedType,
+ LAMBDA_BOOTSTRAP_HANDLE,
+ samMethodType,
+ ref.implMethodASM,
+ samMethodType,
+ 0);
+ }
+ } else {
+ // placeholder
+ writer.push((String)null);
+ // load captures
+ for (Variable capture : captures) {
+ writer.visitVarInsn(capture.type.type.getOpcode(Opcodes.ILOAD), capture.getSlot());
+ }
+ }
+
// add synthetic method to the queue to be written
globals.addSyntheticMethod(desugared);
}
@Override
public String getPointer() {
- return impl.getPointer();
+ return defPointer;
}
@Override
public Type[] getCaptures() {
- return impl.getCaptures();
+ Type[] types = new Type[captures.size()];
+ for (int i = 0; i < types.length; i++) {
+ types[i] = captures.get(i).type.type;
+ }
+ return types;
}
-
}
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 0a1242e6507..ad6562c0e14 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
@@ -24,6 +24,9 @@ import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
import org.elasticsearch.painless.MethodWriter;
/**
@@ -34,6 +37,9 @@ public final class ENull extends AExpression {
public ENull(Location location) {
super(location);
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
void analyze(Locals locals) {
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 e2314880448..e9a28a1e06a 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,6 +23,10 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
@@ -37,9 +41,12 @@ public final class ENumeric extends AExpression {
public ENumeric(Location location, String value, int radix) {
super(location);
- this.value = value;
+ this.value = Objects.requireNonNull(value);
this.radix = radix;
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
void analyze(Locals locals) {
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 40776811b67..a635ec811a3 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
@@ -29,6 +29,10 @@ import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.MethodWriter;
/**
@@ -44,8 +48,13 @@ public final class EUnary extends AExpression {
public EUnary(Location location, Operation operation, AExpression child) {
super(location);
- this.operation = operation;
- this.child = child;
+ this.operation = Objects.requireNonNull(operation);
+ this.child = Objects.requireNonNull(child);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ child.extractVariables(variables);
}
@Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java
index 1a240747d73..a2970ca2e53 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java
@@ -25,6 +25,9 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
+import java.util.Objects;
+import java.util.Set;
+
/**
* Represents an array length field load.
*/
@@ -35,8 +38,11 @@ public final class LArrayLength extends ALink {
LArrayLength(Location location, String value) {
super(location, -1);
- this.value = value;
+ this.value = Objects.requireNonNull(value);
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
ALink analyze(Locals locals) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java
index e8c23bf3857..16b9b8c5d44 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java
@@ -28,6 +28,8 @@ import org.elasticsearch.painless.MethodWriter;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
/**
* Represents an array load/store or defers to possible shortcuts.
@@ -39,7 +41,12 @@ public final class LBrace extends ALink {
public LBrace(Location location, AExpression index) {
super(location, 2);
- this.index = index;
+ this.index = Objects.requireNonNull(index);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ index.extractVariables(variables);
}
@Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java
index 34ad0343aa9..124755780cb 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java
@@ -29,9 +29,11 @@ import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
/**
- * Represents a method call or deferes to a def call.
+ * Represents a method call or defers to a def call.
*/
public final class LCallInvoke extends ALink {
@@ -43,8 +45,15 @@ public final class LCallInvoke extends ALink {
public LCallInvoke(Location location, String name, List arguments) {
super(location, -1);
- this.name = name;
- this.arguments = arguments;
+ this.name = Objects.requireNonNull(name);
+ this.arguments = Objects.requireNonNull(arguments);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ for (AExpression argument : arguments) {
+ argument.extractVariables(variables);
+ }
}
@Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java
index 4503692b48d..7aa46f004d6 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallLocal.java
@@ -27,6 +27,8 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
@@ -43,8 +45,15 @@ public class LCallLocal extends ALink {
public LCallLocal(Location location, String name, List arguments) {
super(location, -1);
- this.name = name;
- this.arguments = arguments;
+ this.name = Objects.requireNonNull(name);
+ this.arguments = Objects.requireNonNull(arguments);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ for (AExpression argument : arguments) {
+ argument.extractVariables(variables);
+ }
}
@Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java
index bf900cedafc..86dbdda24c4 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java
@@ -23,6 +23,9 @@ import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Cast;
+
+import java.util.Set;
+
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter;
@@ -41,6 +44,9 @@ public final class LCast extends ALink {
this.type = type;
}
+
+ @Override
+ void extractVariables(Set variables) {}
@Override
ALink analyze(Locals locals) {
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java
index 93fd21f00b0..828e0a1cd8a 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java
@@ -25,6 +25,10 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Type;
+
+import java.util.Objects;
+import java.util.Set;
+
import org.elasticsearch.painless.MethodWriter;
/**
@@ -37,7 +41,12 @@ final class LDefArray extends ALink implements IDefLink {
LDefArray(Location location, AExpression index) {
super(location, 2);
- this.index = index;
+ this.index = Objects.requireNonNull(index);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ index.extractVariables(variables);
}
@Override
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java
index b104ce732ae..dfdaef2369f 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java
@@ -29,6 +29,8 @@ import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
/**
* Represents a method call made on a def type. (Internal only.)
@@ -37,26 +39,26 @@ final class LDefCall extends ALink implements IDefLink {
final String name;
final List arguments;
- long recipe;
+ StringBuilder recipe;
List pointers = new ArrayList<>();
LDefCall(Location location, String name, List arguments) {
super(location, -1);
- this.name = name;
- this.arguments = arguments;
+ this.name = Objects.requireNonNull(name);
+ this.arguments = Objects.requireNonNull(arguments);
+ }
+
+ @Override
+ void extractVariables(Set variables) {
+ for (AExpression argument : arguments) {
+ argument.extractVariables(variables);
+ }
}
@Override
ALink analyze(Locals locals) {
- if (arguments.size() > 63) {
- // technically, the limitation is just methods with > 63 params, containing method references.
- // this is because we are lazy and use a long as a bitset. we can always change to a "string" if need be.
- // but NEED NOT BE. nothing with this many parameters is in the whitelist and we do not support varargs.
- throw new UnsupportedOperationException("methods with > 63 arguments are currently not supported");
- }
-
- recipe = 0;
+ recipe = new StringBuilder();
int totalCaptures = 0;
for (int argument = 0; argument < arguments.size(); ++argument) {
AExpression expression = arguments.get(argument);
@@ -67,7 +69,9 @@ final class LDefCall extends ALink implements IDefLink {
if (expression instanceof ILambda) {
ILambda lambda = (ILambda) expression;
pointers.add(lambda.getPointer());
- recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference
+ // encode this parameter as a deferred reference
+ char ch = (char) (argument + totalCaptures);
+ recipe.append(ch);
totalCaptures += lambda.getCaptureCount();
}
@@ -111,7 +115,7 @@ final class LDefCall extends ALink implements IDefLink {
Type methodType = Type.getMethodType(after.type, parameterTypes.toArray(new Type[0]));
List