All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.painless.node.ELambda Maven / Gradle / Ivy

/*
 * 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.Definition.Type;
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 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 final class ELambda extends AExpression implements ILambda { private final String name; private final FunctionReserved reserved; private final List paramTypeStrs; private final List paramNameStrs; private final List statements; // desugared synthetic method (lambda body) private SFunction desugared; // captured variables private List captures; // static parent, static lambda private FunctionRef ref; // dynamic parent, deferred until link time private String defPointer; public ELambda(String name, FunctionReserved reserved, Location location, List paramTypes, List paramNames, List statements) { super(location); 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) { final Type returnType; final List actualParamTypeStrs; Method interfaceMethod; // inspect the target first, set interface method if we know it. if (expected == null) { interfaceMethod = null; // we don't know anything: treat as def returnType = Definition.DEF_TYPE; // don't infer any types actualParamTypeStrs = paramTypeStrs; } else { // we know the method statically, infer return type and any unknown/def types interfaceMethod = expected.struct.getFunctionalMethod(); if (interfaceMethod == null) { throw createError(new IllegalArgumentException("Cannot pass lambda to [" + expected.name + "], not a functional interface")); } // check arity before we manipulate parameters if (interfaceMethod.arguments.size() != paramTypeStrs.size()) throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name + "] in [" + expected.clazz + "]"); // for method invocation, its allowed to ignore the return value if (interfaceMethod.rtn == Definition.VOID_TYPE) { returnType = Definition.DEF_TYPE; } else { returnType = interfaceMethod.rtn; } // replace any def types with the actual type (which could still be def) actualParamTypeStrs = new ArrayList(); for (int i = 0; i < paramTypeStrs.size(); i++) { String paramType = paramTypeStrs.get(i); if (paramType.equals(Definition.DEF_TYPE.name)) { actualParamTypeStrs.add(interfaceMethod.arguments.get(i).name); } else { actualParamTypeStrs.add(paramType); } } } // 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(actualParamTypeStrs); paramNames.addAll(paramNameStrs); // desugar lambda body into a synthetic method desugared = new SFunction(reserved, location, returnType.name, name, paramTypes, paramNames, statements, true); desugared.generateSignature(); desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters, captures.size(), reserved.getMaxLoopCounter())); // setup method reference to synthetic method if (expected == null) { ref = null; actual = Definition.getType("String"); defPointer = "Sthis." + name + "," + captures.size(); } else { defPointer = null; try { ref = new FunctionRef(expected, interfaceMethod, desugared.method, captures.size()); } catch (IllegalArgumentException e) { throw createError(e); } actual = expected; } } @Override void write(MethodWriter writer, Globals 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(); org.objectweb.asm.Type samMethodType = org.objectweb.asm.Type.getMethodType(ref.samMethodType.toMethodDescriptorString()); org.objectweb.asm.Type interfaceType = org.objectweb.asm.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 defPointer; } @Override public org.objectweb.asm.Type[] getCaptures() { org.objectweb.asm.Type[] types = new org.objectweb.asm.Type[captures.size()]; for (int i = 0; i < types.length; i++) { types[i] = captures.get(i).type.type; } return types; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy