From 6035a2979239a407525b005af2a761a4963e958d Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Tue, 21 Apr 2020 10:30:28 -0700 Subject: [PATCH] Remove ILambda user tree node from Painless (#55190) The ILambda user tree node is no longer necessary with the addition of the ir tree as it was only supporting writing the ASM, but had no bearing on semantic checking. This moves the code necessary to build the def lambda recipes into the ir tree since we know that they are correct for compile time and the information necessary to do this is already known by the correct ir nodes. This change also splits up the ir reference nodes into DefReferenceInterfaceNode, TypedInterfaceReferenceNode, and TypedCaptureReferenceNode which covers all possible cases of known information at compile time for method references. Each of these nodes is built by the user tree nodes as necessary. Relates #53702 Closes #54015 --- .../painless/ir/CallSubDefNode.java | 80 +++++++++------- .../painless/ir/CapturingFuncRefNode.java | 93 ------------------- ...de.java => DefInterfaceReferenceNode.java} | 31 ++++--- .../elasticsearch/painless/ir/LambdaNode.java | 80 ---------------- .../ILambda.java => ir/ReferenceNode.java} | 31 +++---- .../ir/TypedCaptureReferenceNode.java | 55 +++++++++++ ....java => TypedInterfaceReferenceNode.java} | 28 +++--- .../painless/node/ECapturingFunctionRef.java | 77 ++++++++------- .../painless/node/EFunctionRef.java | 49 ++++------ .../elasticsearch/painless/node/ELambda.java | 53 +++++------ .../painless/node/ENewArrayFunctionRef.java | 55 +++++------ .../painless/node/PSubDefCall.java | 33 +------ .../painless/node/package-info.java | 1 - 13 files changed, 252 insertions(+), 414 deletions(-) delete mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java rename modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/{NewArrayFuncRefNode.java => DefInterfaceReferenceNode.java} (58%) delete mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java rename modules/lang-painless/src/main/java/org/elasticsearch/painless/{node/ILambda.java => ir/ReferenceNode.java} (53%) create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java rename modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/{FuncRefNode.java => TypedInterfaceReferenceNode.java} (64%) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java index a12988c13a79b..d98c7c2a6fba3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CallSubDefNode.java @@ -28,14 +28,13 @@ import java.util.ArrayList; import java.util.List; +import static org.elasticsearch.painless.symbol.ScopeTable.Variable; + public class CallSubDefNode extends ArgumentsNode { /* ---- begin node data ---- */ private String name; - private String recipe; - private final List pointers = new ArrayList<>(); - private final List> typeParameters = new ArrayList<>(); public void setName(String name) { this.name = name; @@ -45,50 +44,65 @@ public String getName() { return name; } - public void setRecipe(String recipe) { - this.recipe = recipe; - } - - public String getRecipe() { - return recipe; - } - - public void addPointer(String pointer) { - pointers.add(pointer); - } - - public List getPointers() { - return pointers; - } - - public void addTypeParameter(Class typeParameter) { - typeParameters.add(typeParameter); - } - - public List> getTypeParameters() { - return typeParameters; - } - /* ---- end node data ---- */ + /** + * Writes an invokedynamic instruction for a call with an unknown receiver type. + */ @Override protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) { methodWriter.writeDebugInfo(location); - for (ExpressionNode argumentNode : getArgumentNodes()) { + // its possible to have unknown functional interfaces + // as arguments that require captures; the set of + // captures with call arguments is ambiguous so + // additional information is encoded to indicate + // which are values are arguments and which are captures + StringBuilder defCallRecipe = new StringBuilder(); + List boostrapArguments = new ArrayList<>(); + List> typeParameters = new ArrayList<>(); + int capturedCount = 0; + + // add an Object class as a placeholder type for the receiver + typeParameters.add(Object.class); + + for (int i = 0; i < getArgumentNodes().size(); ++i) { + ExpressionNode argumentNode = getArgumentNodes().get(i); argumentNode.write(classWriter, methodWriter, scopeTable); + + typeParameters.add(argumentNode.getExpressionType()); + + // handle the case for unknown functional interface + // to hint at which values are the call's arguments + // versus which values are captures + if (argumentNode instanceof DefInterfaceReferenceNode) { + DefInterfaceReferenceNode defInterfaceReferenceNode = (DefInterfaceReferenceNode)argumentNode; + boostrapArguments.add(defInterfaceReferenceNode.getDefReferenceEncoding()); + + // the encoding uses a char to indicate the number of captures + // where the value is the number of current arguments plus the + // total number of captures for easier capture count tracking + // when resolved at runtime + char encoding = (char)(i + capturedCount); + defCallRecipe.append(encoding); + capturedCount += defInterfaceReferenceNode.getCaptures().size(); + + for (String capturedName : defInterfaceReferenceNode.getCaptures()) { + Variable capturedVariable = scopeTable.getVariable(capturedName); + typeParameters.add(capturedVariable.getType()); + } + } } - // create method type from return value and arguments Type[] asmParameterTypes = new Type[typeParameters.size()]; + for (int index = 0; index < asmParameterTypes.length; ++index) { asmParameterTypes[index] = MethodWriter.getType(typeParameters.get(index)); } + Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), asmParameterTypes); - List args = new ArrayList<>(); - args.add(recipe); - args.addAll(pointers); - methodWriter.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, args.toArray()); + boostrapArguments.add(0, defCallRecipe.toString()); + methodWriter.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, boostrapArguments.toArray()); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java deleted file mode 100644 index 1c4701fea59f9..0000000000000 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/CapturingFuncRefNode.java +++ /dev/null @@ -1,93 +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.ir; - -import org.elasticsearch.painless.ClassWriter; -import org.elasticsearch.painless.DefBootstrap; -import org.elasticsearch.painless.FunctionRef; -import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.symbol.ScopeTable; -import org.elasticsearch.painless.symbol.ScopeTable.Variable; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -public class CapturingFuncRefNode extends ExpressionNode { - - /* ---- begin node data ---- */ - - private String name; - private String capturedName; - private FunctionRef funcRef; - private String pointer; - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setCapturedName(String capturedName) { - this.capturedName = capturedName; - } - - public String getCapturedName() { - return capturedName; - } - - public void setFuncRef(FunctionRef funcRef) { - this.funcRef = funcRef; - } - - public FunctionRef getFuncRef() { - return funcRef; - } - - public void setPointer(String pointer) { - this.pointer = pointer; - } - - public String getPointer() { - return pointer; - } - - /* ---- end node data ---- */ - - @Override - protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) { - methodWriter.writeDebugInfo(location); - Variable captured = scopeTable.getVariable(capturedName); - if (pointer != null) { - // dynamic interface: placeholder for run-time lookup - methodWriter.push((String)null); - methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot()); - } else if (funcRef == null) { - // typed interface, dynamic implementation - methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot()); - Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), captured.getAsmType()); - methodWriter.invokeDefCall(name, methodType, DefBootstrap.REFERENCE, getExpressionCanonicalTypeName()); - } else { - // typed interface, typed implementation - methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot()); - methodWriter.invokeLambdaCall(funcRef); - } - } -} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DefInterfaceReferenceNode.java similarity index 58% rename from modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java rename to modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DefInterfaceReferenceNode.java index 6afb9e68cf52f..20cd6c76a27a0 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/NewArrayFuncRefNode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/DefInterfaceReferenceNode.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -20,34 +20,37 @@ package org.elasticsearch.painless.ir; import org.elasticsearch.painless.ClassWriter; -import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.symbol.ScopeTable; +import org.objectweb.asm.Opcodes; -public class NewArrayFuncRefNode extends ExpressionNode { +public class DefInterfaceReferenceNode extends ReferenceNode { /* ---- begin node data ---- */ - private FunctionRef funcRef; + private String defReferenceEncoding; - public void setFuncRef(FunctionRef funcRef) { - this.funcRef = funcRef; + public void setDefReferenceEncoding(String defReferenceEncoding) { + this.defReferenceEncoding = defReferenceEncoding; } - public FunctionRef getFuncRef() { - return funcRef; + public String getDefReferenceEncoding() { + return defReferenceEncoding; } /* ---- end node data ---- */ @Override protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) { - if (funcRef != null) { - methodWriter.writeDebugInfo(location); - methodWriter.invokeLambdaCall(funcRef); - } else { - // push a null instruction as a placeholder for future lambda instructions - methodWriter.push((String)null); + methodWriter.writeDebugInfo(location); + + // place holder for functional interface receiver + // which is resolved and replace at runtime + methodWriter.push((String)null); + + for (String capture : getCaptures()) { + ScopeTable.Variable variable = scopeTable.getVariable(capture); + methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot()); } } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java deleted file mode 100644 index 5cf60d6ed157a..0000000000000 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/LambdaNode.java +++ /dev/null @@ -1,80 +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.ir; - -import org.elasticsearch.painless.ClassWriter; -import org.elasticsearch.painless.FunctionRef; -import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.symbol.ScopeTable; -import org.elasticsearch.painless.symbol.ScopeTable.Variable; -import org.objectweb.asm.Opcodes; - -import java.util.ArrayList; -import java.util.List; - -public class LambdaNode extends ExpressionNode { - - /* ---- begin node data ---- */ - - private final List captures = new ArrayList<>(); - private FunctionRef funcRef; - - public void addCapture(String capture) { - captures.add(capture); - } - - public List getCaptures() { - return captures; - } - - public void setFuncRef(FunctionRef funcRef) { - this.funcRef = funcRef; - } - - public FunctionRef getFuncRef() { - return funcRef; - } - - /* ---- end node data ---- */ - - @Override - protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) { - methodWriter.writeDebugInfo(location); - - if (funcRef != null) { - methodWriter.writeDebugInfo(location); - // load captures - for (String capture : captures) { - Variable variable = scopeTable.getVariable(capture); - methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot()); - } - - methodWriter.invokeLambdaCall(funcRef); - } else { - // placeholder - methodWriter.push((String)null); - // load captures - for (String capture : captures) { - Variable variable = scopeTable.getVariable(capture); - methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot()); - } - } - } -} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ILambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReferenceNode.java similarity index 53% rename from modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ILambda.java rename to modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReferenceNode.java index 884d31cd9d546..931d6328998cb 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ILambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ReferenceNode.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -17,26 +17,25 @@ * under the License. */ -package org.elasticsearch.painless.node; +package org.elasticsearch.painless.ir; +import java.util.ArrayList; import java.util.List; -/** - * Interface for lambda/method reference nodes. They need special handling by LDefCall. - *

- * This is because they know nothing about the target interface, and can only push - * all their captures onto the stack and defer everything until link-time. - */ -interface ILambda { +public abstract class ReferenceNode extends ExpressionNode { + + /* ---- begin node data ---- */ - /** Returns reference to resolve at link-time */ - String getPointer(); + private final List captures = new ArrayList<>(); - /** Returns the types of captured parameters. Can be empty */ - List> getCaptures(); + public void addCapture(String capture) { + captures.add(capture); + } - /** Returns the number of captured parameters */ - default int getCaptureCount() { - return getCaptures().size(); + public List getCaptures() { + return captures; } + + /* ---- end node data ---- */ + } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java new file mode 100644 index 0000000000000..b4bb5874456c7 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedCaptureReferenceNode.java @@ -0,0 +1,55 @@ +/* + * 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.ir; + +import org.elasticsearch.painless.ClassWriter; +import org.elasticsearch.painless.DefBootstrap; +import org.elasticsearch.painless.MethodWriter; +import org.elasticsearch.painless.symbol.ScopeTable; +import org.elasticsearch.painless.symbol.ScopeTable.Variable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class TypedCaptureReferenceNode extends ReferenceNode { + + /* ---- begin node data ---- */ + + private String methodName; + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String getMethodName() { + return methodName; + } + + /* ---- end node data ---- */ + + @Override + protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) { + methodWriter.writeDebugInfo(location); + Variable captured = scopeTable.getVariable(getCaptures().get(0)); + + methodWriter.visitVarInsn(captured.getAsmType().getOpcode(Opcodes.ILOAD), captured.getSlot()); + Type methodType = Type.getMethodType(MethodWriter.getType(getExpressionType()), captured.getAsmType()); + methodWriter.invokeDefCall(methodName, methodType, DefBootstrap.REFERENCE, getExpressionCanonicalTypeName()); + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedInterfaceReferenceNode.java similarity index 64% rename from modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java rename to modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedInterfaceReferenceNode.java index 32161967b765c..0240f13b6f793 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FuncRefNode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/TypedInterfaceReferenceNode.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -23,31 +23,33 @@ import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.symbol.ScopeTable; +import org.objectweb.asm.Opcodes; -public class FuncRefNode extends ExpressionNode { +public class TypedInterfaceReferenceNode extends ReferenceNode { /* ---- begin node data ---- */ - private FunctionRef funcRef; + private FunctionRef reference; - public void setFuncRef(FunctionRef funcRef) { - this.funcRef = funcRef; + public void setReference(FunctionRef reference) { + this.reference = reference; } - public FunctionRef getFuncRef() { - return funcRef; + public FunctionRef getReference() { + return reference; } /* ---- end node data ---- */ @Override protected void write(ClassWriter classWriter, MethodWriter methodWriter, ScopeTable scopeTable) { - if (funcRef != null) { - methodWriter.writeDebugInfo(location); - methodWriter.invokeLambdaCall(funcRef); - } else { - // dynamic interface: placeholder for run-time lookup - methodWriter.push((String)null); + methodWriter.writeDebugInfo(location); + + for (String capture : getCaptures()) { + ScopeTable.Variable variable = scopeTable.getVariable(capture); + methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ILOAD), variable.getSlot()); } + + methodWriter.invokeLambdaCall(reference); } } 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 2611aa031225b..933d5394ff0dc 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 @@ -23,27 +23,23 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.Scope.Variable; -import org.elasticsearch.painless.ir.CapturingFuncRefNode; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.DefInterfaceReferenceNode; +import org.elasticsearch.painless.ir.TypedCaptureReferenceNode; +import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode; import org.elasticsearch.painless.lookup.def; import org.elasticsearch.painless.symbol.ScriptRoot; -import java.util.Collections; -import java.util.List; import java.util.Objects; /** * Represents a capturing function reference. For member functions that require a this reference, ie not static. */ -public class ECapturingFunctionRef extends AExpression implements ILambda { +public class ECapturingFunctionRef extends AExpression { protected final String variable; protected final String call; - // TODO: #54015 - private Variable captured; - private String defPointer; - public ECapturingFunctionRef(Location location, String variable, String call) { super(location); @@ -63,51 +59,54 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in "not a statement: capturing function reference [" + variable + ":" + call + "] not used")); } - FunctionRef ref = null; - Output output = new Output(); - captured = scope.getVariable(location, variable); + Variable captured = scope.getVariable(location, variable); if (input.expected == null) { + String defReferenceEncoding; if (captured.getType() == def.class) { - // dynamic implementation - defPointer = "D" + variable + "." + call + ",1"; + // unknown functional interface + defReferenceEncoding = "D" + variable + "." + call + ",1"; } else { - // typed implementation - defPointer = "S" + captured.getCanonicalTypeName() + "." + call + ",1"; + // known functional interface + defReferenceEncoding = "S" + captured.getCanonicalTypeName() + "." + call + ",1"; } output.actual = String.class; + + DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode(); + + defInterfaceReferenceNode.setLocation(location); + defInterfaceReferenceNode.setExpressionType(output.actual); + defInterfaceReferenceNode.addCapture(captured.getName()); + defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding); + + output.expressionNode = defInterfaceReferenceNode; } else { - defPointer = null; - // static case + output.actual = input.expected; + // known functional interface if (captured.getType() != def.class) { - ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location, + FunctionRef ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location, input.expected, captured.getCanonicalTypeName(), call, 1); - } - output.actual = input.expected; - } - CapturingFuncRefNode capturingFuncRefNode = new CapturingFuncRefNode(); + TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode(); + typedInterfaceReferenceNode.setLocation(location); + typedInterfaceReferenceNode.setExpressionType(output.actual); + typedInterfaceReferenceNode.addCapture(captured.getName()); + typedInterfaceReferenceNode.setReference(ref); - capturingFuncRefNode.setLocation(location); - capturingFuncRefNode.setExpressionType(output.actual); - capturingFuncRefNode.setCapturedName(captured.getName()); - capturingFuncRefNode.setName(call); - capturingFuncRefNode.setPointer(defPointer); - capturingFuncRefNode.setFuncRef(ref); + output.expressionNode = typedInterfaceReferenceNode; + // known functional interface, unknown receiver type + } else { + TypedCaptureReferenceNode typedCaptureReferenceNode = new TypedCaptureReferenceNode(); + typedCaptureReferenceNode.setLocation(location); + typedCaptureReferenceNode.setExpressionType(output.actual); + typedCaptureReferenceNode.addCapture(captured.getName()); + typedCaptureReferenceNode.setMethodName(call); - output.expressionNode = capturingFuncRefNode; + output.expressionNode = typedCaptureReferenceNode; + } + } return output; } - - @Override - public String getPointer() { - return defPointer; - } - - @Override - public List> getCaptures() { - return Collections.singletonList(captured.getType()); - } } 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 be9d3cc15d562..01ba449193cc0 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 @@ -23,24 +23,20 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.ClassNode; -import org.elasticsearch.painless.ir.FuncRefNode; +import org.elasticsearch.painless.ir.DefInterfaceReferenceNode; +import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode; import org.elasticsearch.painless.symbol.ScriptRoot; -import java.util.Collections; -import java.util.List; import java.util.Objects; /** * Represents a function reference. */ -public class EFunctionRef extends AExpression implements ILambda { +public class EFunctionRef extends AExpression { protected final String type; protected final String call; - // TODO: #54015 - private String defPointer; - public EFunctionRef(Location location, String type, String call) { super(location); @@ -60,39 +56,32 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in "not a statement: function reference [" + type + ":" + call + "] not used")); } - FunctionRef ref; - Output output = new Output(); if (input.expected == null) { - ref = null; output.actual = String.class; - defPointer = "S" + type + "." + call + ",0"; + String defReferenceEncoding = "S" + type + "." + call + ",0"; + + DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode(); + + defInterfaceReferenceNode.setLocation(location); + defInterfaceReferenceNode.setExpressionType(output.actual); + defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding); + + output.expressionNode = defInterfaceReferenceNode; } else { - defPointer = null; - ref = FunctionRef.create( + FunctionRef ref = FunctionRef.create( scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location, input.expected, type, call, 0); output.actual = input.expected; - } - - FuncRefNode funcRefNode = new FuncRefNode(); - funcRefNode.setLocation(location); - funcRefNode.setExpressionType(output.actual); - funcRefNode.setFuncRef(ref); + TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode(); + typedInterfaceReferenceNode.setLocation(location); + typedInterfaceReferenceNode.setExpressionType(output.actual); + typedInterfaceReferenceNode.setReference(ref); - output.expressionNode = funcRefNode; + output.expressionNode = typedInterfaceReferenceNode; + } return output; } - - @Override - public String getPointer() { - return defPointer; - } - - @Override - public List> getCaptures() { - return Collections.emptyList(); - } } 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 ad44bbf29e340..745845c75ca4e 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 @@ -26,8 +26,10 @@ import org.elasticsearch.painless.Scope.Variable; import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.DefInterfaceReferenceNode; import org.elasticsearch.painless.ir.FunctionNode; -import org.elasticsearch.painless.ir.LambdaNode; +import org.elasticsearch.painless.ir.ReferenceNode; +import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.lookup.def; @@ -60,16 +62,12 @@ *
* {@code sort(list, lambda$0(capture))} */ -public class ELambda extends AExpression implements ILambda { +public class ELambda extends AExpression { protected final List paramTypeStrs; protected final List paramNameStrs; protected final List statements; - // TODO: #54015 - private List captures; - private String defPointer; - public ELambda(Location location, List paramTypes, List paramNames, List statements) { @@ -96,7 +94,6 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in List parameterNames; SBlock block; int maxLoopCounter; - FunctionRef ref; Output output = new Output(); @@ -181,7 +178,7 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in maxLoopCounter = scriptRoot.getCompilerSettings().getMaxLoopCounter(); // prepend capture list to lambda's arguments - captures = new ArrayList<>(lambdaScope.getCaptures()); + List captures = new ArrayList<>(lambdaScope.getCaptures()); typeParametersWithCaptures = new ArrayList<>(captures.size() + typeParameters.size()); parameterNames = new ArrayList<>(captures.size() + paramNameStrs.size()); for (Variable var : captures) { @@ -195,16 +192,24 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in name = scriptRoot.getNextSyntheticName("lambda"); scriptRoot.getFunctionTable().addFunction(name, returnType, typeParametersWithCaptures, true, true); + ReferenceNode referenceNode; + // setup method reference to synthetic method if (input.expected == null) { - ref = null; output.actual = String.class; - defPointer = "Sthis." + name + "," + captures.size(); + String defReferenceEncoding = "Sthis." + name + "," + captures.size(); + + DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode(); + defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding); + referenceNode = defInterfaceReferenceNode; } else { - defPointer = null; - ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), + FunctionRef ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location, input.expected, "this", name, captures.size()); output.actual = input.expected; + + TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode(); + typedInterfaceReferenceNode.setReference(ref); + referenceNode = typedInterfaceReferenceNode; } FunctionNode functionNode = new FunctionNode(); @@ -221,31 +226,15 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in classNode.addFunctionNode(functionNode); - LambdaNode lambdaNode = new LambdaNode(); - lambdaNode.setLocation(location); - lambdaNode.setExpressionType(output.actual); - lambdaNode.setFuncRef(ref); + referenceNode.setLocation(location); + referenceNode.setExpressionType(output.actual); for (Variable capture : captures) { - lambdaNode.addCapture(capture.getName()); + referenceNode.addCapture(capture.getName()); } - output.expressionNode = lambdaNode; + output.expressionNode = referenceNode; return output; } - - @Override - public String getPointer() { - return defPointer; - } - - @Override - public List> getCaptures() { - List> types = new ArrayList<>(); - for (Variable capture : captures) { - types.add(capture.getType()); - } - return types; - } } 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 df067baf04b33..e3b37e258406a 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 @@ -24,27 +24,24 @@ import org.elasticsearch.painless.Scope; import org.elasticsearch.painless.ir.BlockNode; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.DefInterfaceReferenceNode; import org.elasticsearch.painless.ir.FunctionNode; -import org.elasticsearch.painless.ir.NewArrayFuncRefNode; import org.elasticsearch.painless.ir.NewArrayNode; import org.elasticsearch.painless.ir.ReturnNode; +import org.elasticsearch.painless.ir.TypedInterfaceReferenceNode; import org.elasticsearch.painless.ir.VariableNode; import org.elasticsearch.painless.symbol.ScriptRoot; import java.util.Collections; -import java.util.List; import java.util.Objects; /** * Represents a function reference. */ -public class ENewArrayFunctionRef extends AExpression implements ILambda { +public class ENewArrayFunctionRef extends AExpression { protected final String type; - // TODO: #54015 - private String defPointer; - public ENewArrayFunctionRef(Location location, String type) { super(location); @@ -65,10 +62,6 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in Output output = new Output(); - if (input.read == false) { - throw createError(new IllegalArgumentException("A newly created array must be read from.")); - } - Class clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type); if (clazz == null) { @@ -78,17 +71,29 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in String name = scriptRoot.getNextSyntheticName("newarray"); scriptRoot.getFunctionTable().addFunction(name, clazz, Collections.singletonList(int.class), true, true); - FunctionRef ref; - if (input.expected == null) { - ref = null; output.actual = String.class; - defPointer = "Sthis." + name + ",0"; + String defReferenceEncoding = "Sthis." + name + ",0"; + + DefInterfaceReferenceNode defInterfaceReferenceNode = new DefInterfaceReferenceNode(); + + defInterfaceReferenceNode.setLocation(location); + defInterfaceReferenceNode.setExpressionType(output.actual); + defInterfaceReferenceNode.setDefReferenceEncoding(defReferenceEncoding); + + output.expressionNode = defInterfaceReferenceNode; } else { - defPointer = null; - ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), + FunctionRef ref = FunctionRef.create(scriptRoot.getPainlessLookup(), scriptRoot.getFunctionTable(), location, input.expected, "this", name, 0); output.actual = input.expected; + + TypedInterfaceReferenceNode typedInterfaceReferenceNode = new TypedInterfaceReferenceNode(); + + typedInterfaceReferenceNode.setLocation(location); + typedInterfaceReferenceNode.setExpressionType(output.actual); + typedInterfaceReferenceNode.setReference(ref); + + output.expressionNode = typedInterfaceReferenceNode; } VariableNode variableNode = new VariableNode(); @@ -125,24 +130,6 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in classNode.addFunctionNode(functionNode); - NewArrayFuncRefNode newArrayFuncRefNode = new NewArrayFuncRefNode(); - - newArrayFuncRefNode.setLocation(location); - newArrayFuncRefNode.setExpressionType(output.actual); - newArrayFuncRefNode.setFuncRef(ref); - - output.expressionNode = newArrayFuncRefNode; - return output; } - - @Override - public String getPointer() { - return defPointer; - } - - @Override - public List> getCaptures() { - return Collections.emptyList(); - } } 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 b0b3f5cc0cde5..4302e7f4b7564 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 @@ -49,23 +49,12 @@ public class PSubDefCall extends AExpression { @Override 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); - + for (AExpression argument : arguments) { Input expressionInput = new Input(); expressionInput.internal = true; - Output expressionOutput = expression.analyze(classNode, scriptRoot, scope, expressionInput); + Output expressionOutput = argument.analyze(classNode, scriptRoot, scope, expressionInput); argumentOutputs.add(expressionOutput); if (expressionOutput.actual == void.class) { @@ -73,21 +62,10 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in } 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()); - // encode this parameter as a deferred reference - char ch = (char) (argument + totalCaptures); - recipe.append(ch); - totalCaptures += lambda.getCaptureCount(); - parameterTypes.addAll(lambda.getCaptures()); - } + argument.cast(expressionInput, expressionOutput); } + 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; @@ -100,9 +78,6 @@ Output analyze(ClassNode classNode, ScriptRoot scriptRoot, Scope scope, Input in callSubDefNode.setLocation(location); callSubDefNode.setExpressionType(output.actual); callSubDefNode.setName(name); - callSubDefNode.setRecipe(recipe.toString()); - callSubDefNode.getPointers().addAll(pointers); - callSubDefNode.getTypeParameters().addAll(parameterTypes); output.expressionNode = callSubDefNode; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java index 005fe979ea117..207a21fda2ee7 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java @@ -58,7 +58,6 @@ * {@link org.elasticsearch.painless.node.EString} - Represents a string constant. * {@link org.elasticsearch.painless.node.EUnary} - Represents a unary math expression. * {@link org.elasticsearch.painless.node.EVariable} - Represents a variable load/store. - * {@link org.elasticsearch.painless.node.ILambda} - Represents a marker to signify this node is a lambda function. * {@link org.elasticsearch.painless.node.PBrace} - Represents an array load/store and defers to a child subnode. * {@link org.elasticsearch.painless.node.PCallInvoke} - Represents a method call and defers to a child subnode. * {@link org.elasticsearch.painless.node.PField} - Represents a field load/store and defers to a child subnode.