Merge pull request from rmuir/noncapturing_lambdas

non-capturing lambda support
This commit is contained in:
Robert Muir 2016-06-16 10:31:54 -04:00 committed by GitHub
commit ccad99fb5c
4 changed files with 107 additions and 14 deletions
modules/lang-painless/src
main/java/org/elasticsearch/painless
test/java/org/elasticsearch/painless

@ -98,7 +98,7 @@ public class FunctionRef {
implMethod = impl.handle;
// remove any prepended captured arguments for the 'natural' signature.
samMethodType = impl.getMethodType().dropParameterTypes(0, captures.length);
samMethodType = adapt(interfaceMethodType, impl.getMethodType().dropParameterTypes(0, captures.length));
}
/**
@ -119,7 +119,7 @@ public class FunctionRef {
implMethodASM = null;
// remove any prepended captured arguments for the 'natural' signature.
samMethodType = impl.type().dropParameterTypes(0, captures.length);
samMethodType = adapt(interfaceMethodType, impl.type().dropParameterTypes(0, captures.length));
}
/**
@ -171,4 +171,15 @@ public class FunctionRef {
// either way, stuff will fail if its wrong :)
return interfaceMethodType.equals(samMethodType) == false;
}
/**
* If the interface expects a primitive type to be returned, we can't return Object,
* But we can set SAM to the wrapper version, and a cast will take place
*/
private static MethodType adapt(MethodType expected, MethodType actual) {
if (expected.returnType().isPrimitive() && actual.returnType() == Object.class) {
actual = actual.changeReturnType(MethodType.methodType(expected.returnType()).wrap().returnType());
}
return actual;
}
}

@ -114,7 +114,6 @@ import org.elasticsearch.painless.node.EConditional;
import org.elasticsearch.painless.node.EDecimal;
import org.elasticsearch.painless.node.EExplicit;
import org.elasticsearch.painless.node.EFunctionRef;
import org.elasticsearch.painless.node.ELambda;
import org.elasticsearch.painless.node.ENull;
import org.elasticsearch.painless.node.ENumeric;
import org.elasticsearch.painless.node.EUnary;
@ -170,6 +169,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
private final Deque<Reserved> reserved = new ArrayDeque<>();
private final List<SFunction> synthetic = new ArrayList<>();
private int syntheticCounter = 0;
private Walker(String sourceName, String sourceText, CompilerSettings settings) {
this.settings = settings;
@ -951,7 +951,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
for (LamtypeContext lamtype : ctx.lamtype()) {
if (lamtype.decltype() == null) {
paramTypes.add(null);
paramTypes.add("def");
} else {
paramTypes.add(lamtype.decltype().getText());
}
@ -962,8 +962,12 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
for (StatementContext statement : ctx.block().statement()) {
statements.add((AStatement)visit(statement));
}
return new ELambda((FunctionReserved)reserved.pop(), location(ctx), paramTypes, paramNames, statements);
String name = nextLambda();
synthetic.add(new SFunction((FunctionReserved)reserved.pop(), location(ctx), "def", name,
paramTypes, paramNames, statements, true));
return new EFunctionRef(location(ctx), "this", name);
// TODO: use a real node for captures and shit
}
@Override
@ -1003,7 +1007,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
new LNewArray(location, arrayType, Arrays.asList(
new EChain(location,
new LVariable(location, "size"))))));
String name = "lambda$" + synthetic.size();
String name = nextLambda();
synthetic.add(new SFunction(new FunctionReserved(), location, arrayType, name,
Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true));
return new EFunctionRef(location(ctx), "this", name);
@ -1020,4 +1024,9 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
public Object visitLocalFuncref(LocalFuncrefContext ctx) {
return new EFunctionRef(location(ctx), ctx.THIS().getText(), ctx.ID().getText());
}
/** Returns name of next lambda */
private String nextLambda() {
return "lambda$" + syntheticCounter++;
}
}

@ -215,13 +215,6 @@ public class BasicStatementTests extends ScriptTestCase {
assertEquals(10, ((Map)exec("Map s = new HashMap(); s.put(\"x\", 10); return s;")).get("x"));
}
public void testLambdas() {
Exception exception = expectThrows(Exception.class, () -> {
exec("Math.max(2, p -> {p.doSomething();})");
});
assertTrue(exception.getCause().getMessage().contains("Lambda functions are not supported."));
}
public void testLastInBlockDoesntNeedSemi() {
// One statement in the block in case that is a special case
assertEquals(10, exec("def i = 1; if (i == 1) {return 10}"));

@ -0,0 +1,80 @@
/*
* 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;
public class LambdaTests extends ScriptTestCase {
public void testNoArgLambda() {
assertEquals(1, exec("Optional.empty().orElseGet(() -> { return 1; });"));
}
public void testNoArgLambdaDef() {
assertEquals(1, exec("def x = Optional.empty(); x.orElseGet(() -> { return 1; });"));
}
public void testLambdaWithArgs() {
assertEquals("short", exec("List l = new ArrayList(); l.add('looooong'); l.add('short'); "
+ "l.sort((a, b) -> { a.length() - b.length(); }); return l.get(0)"));
}
public void testLambdaWithTypedArgs() {
assertEquals("short", exec("List l = new ArrayList(); l.add('looooong'); l.add('short'); "
+ "l.sort((String a, String b) -> { (a.length() - b.length()); }); return l.get(0)"));
}
public void testPrimitiveLambdas() {
assertEquals(4, exec("List l = new ArrayList(); l.add(1); l.add(1); "
+ "return l.stream().mapToInt(x -> { x + 1; }).sum();"));
}
public void testPrimitiveLambdasWithTypedArgs() {
assertEquals(4, exec("List l = new ArrayList(); l.add(1); l.add(1); "
+ "return l.stream().mapToInt(int x -> { x + 1; }).sum();"));
}
public void testPrimitiveLambdasDef() {
assertEquals(4, exec("def l = new ArrayList(); l.add(1); l.add(1); "
+ "return l.stream().mapToInt(x -> { x + 1; }).sum();"));
}
public void testPrimitiveLambdasWithTypedArgsDef() {
assertEquals(4, exec("def l = new ArrayList(); l.add(1); l.add(1); "
+ "return l.stream().mapToInt(int x -> { x + 1; }).sum();"));
}
public void testPrimitiveLambdasConvertible() {
assertEquals(2, exec("List l = new ArrayList(); l.add(1); l.add(1); "
+ "return l.stream().mapToInt(byte x -> { return x; }).sum();"));
}
public void testPrimitiveArgs() {
assertEquals(2, exec("int applyOne(IntFunction arg) { arg.apply(1) } applyOne(x -> { x + 1; })"));
}
public void testPrimitiveArgsTyped() {
assertEquals(2, exec("int applyOne(IntFunction arg) { arg.apply(1) } applyOne(int x -> { x + 1; })"));
}
public void testPrimitiveArgsTypedOddly() {
assertEquals(2L, exec("long applyOne(IntFunction arg) { arg.apply(1) } applyOne(long x -> { x + 1; })"));
}
}