Improved support for lambda conversion.
authormcimadamore
Mon Jun 21 13:43:37 2010 +0100 (2 years ago)
changeset 58102b8de982628
parent 5809f79be8946c6
child 5821cbf9ca0c589
Improved support for lambda conversion.

Now the prototype supports lambda conversion where the target type is an abstract class. This is (temporarily) done by generating a synthetic anonymous inner class implementing the SAM method. Also added some checks involving lambda conversion described in the strawman:

*) abstract class targeted by a lambda conversion must have default constructor
*) target method of lambda conversion cannot be generic
src/share/classes/com/sun/tools/javac/code/Types.java
src/share/classes/com/sun/tools/javac/comp/Lower.java
src/share/classes/com/sun/tools/javac/resources/compiler.properties
test/tools/javac/lambda/BadConv01.java
test/tools/javac/lambda/BadConv01.out
test/tools/javac/lambda/BadConv02.java
test/tools/javac/lambda/BadConv02.out
test/tools/javac/lambda/LambdaConv07.java
--- a/src/share/classes/com/sun/tools/javac/code/Types.java Fri Jun 18 13:02:01 2010 +0100
+++ b/src/share/classes/com/sun/tools/javac/code/Types.java Mon Jun 21 13:43:37 2010 +0100
@@ -298,19 +298,25 @@ public class Types {
}
public Type findSAM(Type t) {
- if (t.isInterface()) {
+ if (t.tsym.kind == Kinds.TYP &&
+ (t.tsym.flags() & ABSTRACT) != 0 &&
+ (t.isInterface() || hasDefaultConstructor((ClassSymbol)t.tsym))) {
ListBuffer<Symbol> abstracts = ListBuffer.lb();
findSAM(t, abstracts);
if (abstracts.size() == 0) {
return null;
} else if (abstracts.size() == 1) {
- return memberType(t, abstracts.first());
+ Symbol sym = abstracts.first();
+ return sym.type.tag == FORALL ?
+ null :
+ memberType(t, sym);
} else {
Type resType = Type.noType;
List<Type> thrownTypes = List.nil();
List<Type> argtypes =
memberType(t, abstracts.first()).getParameterTypes();
for (Symbol msym : abstracts.toList()) {
+ if (msym.type.tag == FORALL) return null;
Type mtype = memberType(t, msym);
resType = mtype.getReturnType() == syms.voidType ?
syms.voidType :
@@ -326,7 +332,7 @@ public class Types {
return null;
}
- private void findSAM(Type t, ListBuffer<Symbol> buf) {
+ public void findSAM(Type t, ListBuffer<Symbol> buf) {
if (t == Type.noType) return;
for (Scope.Entry e = t.tsym.members().elems ; e != null ; e = e.sibling) {
if (e.sym != null &&
@@ -346,6 +352,16 @@ public class Types {
private boolean overridesObjectMethod(Symbol msym, TypeSymbol tsym) {
for (Scope.Entry e = syms.objectType.tsym.members().lookup(msym.name) ; e.scope != null ; e = e.next()) {
if (msym.overrides(e.sym, tsym, this, true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ //where
+ private boolean hasDefaultConstructor(ClassSymbol csym) {
+ for (Scope.Entry e = csym.members().lookup(names.init) ; e.scope != null ; e = e.next()) {
+ if (e.sym.kind == Kinds.MTH &&
+ e.sym.type.getParameterTypes().isEmpty()) {
return true;
}
}
--- a/src/share/classes/com/sun/tools/javac/comp/Lower.java Fri Jun 18 13:02:01 2010 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Lower.java Mon Jun 21 13:43:37 2010 +0100
@@ -71,6 +71,7 @@ public class Lower extends TreeTranslato
private Resolve rs;
private Check chk;
private Attr attr;
+ private MemberEnter memberEnter;
private TreeMaker make;
private DiagnosticPosition make_pos;
private ClassWriter writer;
@@ -93,6 +94,7 @@ public class Lower extends TreeTranslato
rs = Resolve.instance(context);
chk = Check.instance(context);
attr = Attr.instance(context);
+ memberEnter = MemberEnter.instance(context);
make = TreeMaker.instance(context);
writer = ClassWriter.instance(context);
reader = ClassReader.instance(context);
@@ -682,13 +684,19 @@ public class Lower extends TreeTranslato
/** Look up a method in a given scope.
*/
private MethodSymbol lookupMethod(DiagnosticPosition pos, Name name, Type qual, List<Type> args) {
- return rs.resolveInternalMethod(pos, attrEnv, qual, name, args, null);
+ return rs.resolveInternalMethod(pos, attrEnv, qual, name, args, List.<Type>nil());
}
/** Look up a constructor.
*/
private MethodSymbol lookupConstructor(DiagnosticPosition pos, Type qual, List<Type> args) {
return rs.resolveInternalConstructor(pos, attrEnv, qual, args, null);
+ }
+
+ /** Look up an implicit method in a given scope (such as MethodHandle.invoke()).
+ */
+ private MethodSymbol lookupImplicitMethod(DiagnosticPosition pos, Name name, Type qual, List<Type> args, Type restype) {
+ return rs.resolveInternalMethod(pos, attrEnv, qual, name, args, List.of(restype));
}
/** Look up a field.
@@ -1725,13 +1733,59 @@ public class Lower extends TreeTranslato
return make.Block(0, List.of(tryCatch));
}
// where
- /** Create an attributed tree of the form left.name(). */
- private JCMethodInvocation makeCall(JCExpression left, Name name, List<JCExpression> args) {
- assert left.type != null;
- Symbol funcsym = lookupMethod(make_pos, name, left.type,
- TreeInfo.types(args));
- return make.App(make.Select(left, funcsym), args);
- }
+
+ /** Create an attributed tree of the form left.name(). */
+ private JCMethodInvocation makeCall(JCExpression left, Name name, List<JCExpression> args) {
+ assert left.type != null;
+ Symbol funcsym = lookupMethod(make_pos, name, left.type,
+ TreeInfo.types(args));
+ return make.App(make.Select(left, funcsym), args);
+ }
+
+ private JCMethodInvocation makeImplicitCall(JCExpression left, Name name, List<JCExpression> args, JCExpression restype) {
+ assert left.type != null;
+ Symbol funcsym = lookupImplicitMethod(make_pos, name, left.type,
+ TreeInfo.types(args), restype.type);
+ return make.App(make.Select(left, funcsym), args);
+ }
+
+ private JCMethodDecl makeDefaultConstructor(final DiagnosticPosition pos, final ClassSymbol owner) {
+
+ final MethodSymbol defaultConstrSym = new MethodSymbol(0, names.init, null, owner);
+ defaultConstrSym.type = new MethodType(List.<Type>nil(), syms.voidType, List.<Type>nil(), syms.methodClass);
+
+ JCMethodDecl defaultConstr = (JCMethodDecl)memberEnter.DefaultConstructor(
+ make,
+ owner,
+ List.<Type>nil(),
+ List.<Type>nil(),
+ List.<Type>nil(),
+ defaultConstrSym.flags(),
+ false);
+ defaultConstr.sym = defaultConstrSym;
+ defaultConstr.type = defaultConstrSym.type;
+
+ class DefaultConstructorPatcher extends TreeScanner {
+ @Override
+ public void visitApply(JCMethodInvocation tree) {
+ super.visitApply(tree);
+ tree.type = syms.voidType; //super constructor call has void type
+ }
+ @Override
+ public void visitIdent(JCIdent tree) {
+ if (tree.name == names._super) {
+ //set super constructor symbol and type
+ tree.sym = lookupConstructor(pos,
+ types.supertype(owner.type),
+ defaultConstrSym.type.getParameterTypes());
+ tree.type = tree.sym.type;
+ }
+ }
+ }
+
+ new DefaultConstructorPatcher().scan(defaultConstr);
+ return defaultConstr;
+ }
private JCNewArray makeArray(Type elemType, List<JCExpression> elems) {
Type arrType = new ArrayType(elemType, syms.arrayClass);
@@ -2819,13 +2873,117 @@ public class Lower extends TreeTranslato
<T extends JCTree> T unlambdaIfNeeded(T tree, Type type) {
if (types.isSameType(tree.type, syms.methodHandleType) &&
types.findSAM(type) != null) {
- List<JCExpression> args = List.of((JCExpression)tree, classOfType(type, tree.pos()));
- JCMethodInvocation proxyCall = makeCall(make.QualIdent(syms.proxyHelper.tsym), names.makeProxy, args).setType(syms.objectType);
- return (T)convert(proxyCall, type);
+ return (T)(type.isInterface() ?
+ lambdaToInterface((JCExpression)tree, type) :
+ lambdaToClass((JCExpression)tree, type));
}
else {
return tree;
}
+ }
+
+ JCExpression lambdaToInterface(JCExpression tree, Type type) {
+ List<JCExpression> args = List.of(tree, classOfType(type, tree.pos()));
+ JCMethodInvocation proxyCall = makeCall(make.QualIdent(syms.proxyHelper.tsym), names.makeProxy, args).setType(syms.objectType);
+ return (JCExpression)convert(proxyCall, type);
+ }
+
+ JCExpression lambdaToClass(JCExpression tree, Type type) {
+ Symbol currentOwner = currentMethodSym != null
+ ? currentMethodSym
+ : currentClass;
+
+ boolean isStatic = outerThisStack.isEmpty();
+
+ ListBuffer<Symbol> samMethods = ListBuffer.lb();
+ types.findSAM(type, samMethods);
+ Symbol targetMethodSym = samMethods.first();
+ Type targetMethodType = types.findSAM(type);
+
+ //assign translated method handle to final variable $mh
+ // MethodHandle $mh = <trans-closure>
+
+ VarSymbol handleSym = new VarSymbol(SYNTHETIC | FINAL,
+ names.fromString("mh" + target.syntheticNameChar()),
+ syms.methodHandleType,
+ currentOwner);
+ JCVariableDecl handleDecl = make.VarDef(handleSym, tree);
+
+ //create new anon class definition implementing SAM method
+ // class <anon> extends SAMClass { ... }
+
+ ClassSymbol samClassSym = new ClassSymbol((isStatic ? STATIC : 0) | SYNTHETIC, names.empty, currentOwner);
+ samClassSym.members_field = new Scope(samClassSym);
+ samClassSym.type = new ClassType(isStatic ? Type.noType : currentClass.type, List.<Type>nil(), samClassSym);
+ ((ClassType) samClassSym.type).supertype_field = type;
+ samClassSym.flatname = chk.localClassName(samClassSym);
+
+ JCClassDecl samClassDecl = make.ClassDef(
+ make.Modifiers(samClassSym.flags_field),
+ names.empty,
+ List.<JCTypeParameter>nil(),
+ make.QualIdent(type.tsym).setType(type),
+ List.<JCExpression>nil(),
+ null);
+
+ samClassDecl.sym = samClassSym;
+ samClassDecl.type = samClassSym.type;
+ classdefs.put(samClassSym, samClassDecl);
+
+ JCMethodDecl samConstrDecl = makeDefaultConstructor(tree.pos(), samClassSym);
+
+ //create SAM method
+ // R m(A1, A2 ... An) throws T1, T2 ... Tn {
+ // [return] $mh.invoke(a1, a2, ... an);
+ // }
+
+ MethodSymbol samMethSym = new MethodSymbol(targetMethodSym.flags() & ~ABSTRACT,
+ targetMethodSym.name,
+ null,
+ samClassSym);
+ samMethSym.type = new MethodType(targetMethodType.getParameterTypes(),
+ targetMethodType.getReturnType(),
+ targetMethodType.getThrownTypes(),
+ syms.methodClass);
+
+ JCMethodDecl samMethodDecl = make.MethodDef(samMethSym, null);
+
+ ListBuffer<JCExpression> args = ListBuffer.lb();
+
+ for (JCVariableDecl param : samMethodDecl.getParameters()) {
+ args.append(make.Ident(param.sym));
+ }
+
+ JCMethodInvocation invokeCall = makeImplicitCall(make.Ident(handleDecl.sym),
+ names.invoke,
+ args.toList(),
+ samMethodDecl.restype);
+
+ JCBlock body = make.Block(0,
+ List.of(targetMethodType.getReturnType().tag == VOID
+ ? make.Exec(invokeCall)
+ : make.Return(invokeCall)));
+
+ samMethodDecl.body = body;
+ samClassDecl.defs = List.<JCTree>of(samConstrDecl, samMethodDecl);
+
+ samClassSym.members().enter(samConstrDecl.sym);
+ samClassSym.members().enter(samMethSym);
+
+ //create anon inner class creation expression
+ // let MethodHandle $mh = <trans-lambda> in
+ // new <SAMClass>() { ... };
+
+ JCNewClass newClass = make.NewClass(null,
+ List.<JCExpression>nil(),
+ make.QualIdent(type.tsym),
+ List.<JCExpression>nil(),
+ samClassDecl);
+ newClass.constructor = samConstrDecl.sym;
+ newClass.setType(samClassSym.type);
+
+ JCExpression res = make.LetExpr(handleDecl, newClass).setType(samClassSym.type);
+ return translate(res); // lower result
}
/** Visitor method for parenthesized expressions.
--- a/src/share/classes/com/sun/tools/javac/resources/compiler.properties Fri Jun 18 13:02:01 2010 +0100
+++ b/src/share/classes/com/sun/tools/javac/resources/compiler.properties Mon Jun 21 13:43:37 2010 +0100
@@ -1289,7 +1289,7 @@ compiler.err.lambda.not.supported.in.sou
(use -source 7 or higher to enable strings in switch)
compiler.err.func.types.not.supported.in.source=\
- lambda expressions are not supported in -source {0}\n\
+ function types are not supported in -source {0}\n\
(use -source 7 or higher to enable strings in switch and -XDallowFunctionTypes)
########################################
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/BadConv01.java Mon Jun 21 13:43:37 2010 +0100
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary check that target of SAM conversion can't be a generic method
+ * @author Alex Buckley
+ * @compile/fail/ref=BadConv01.out -XDallowFunctionTypes -XDrawDiagnostics BadConv01.java
+ */
+
+class BadConv01<T> {
+ #int(T) p = #(T x) ( 10 );
+ interface Bar { <X> int m(X x); }
+
+ Bar b = p; // Illegal. Bar has an infinite number of members called m.
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/BadConv01.out Mon Jun 21 13:43:37 2010 +0100
@@ -0,0 +1,2 @@
+BadConv01.java:35:13: compiler.err.prob.found.req: (compiler.misc.incompatible.types), #int(T), BadConv01.Bar
+1 error
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/BadConv02.java Mon Jun 21 13:43:37 2010 +0100
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary check that target of SAM conversion can't be an abstract class with non-default constructor
+ * @author Alex Buckley
+ * @compile/fail/ref=BadConv02.out -XDallowFunctionTypes -XDrawDiagnostics BadConv02.java
+ */
+
+class BadConv02 {
+
+ static abstract class Foo {
+ Foo(int x) {}
+ int m() { return 0; }
+ }
+
+ static class FooImpl extends Foo {
+ FooImpl(int x) { super(x); }
+ int m() { return 1; }
+ }
+
+ #int() x = #()(42);
+ Foo f_1 = new FooImpl(); // Illegal, of course.
+ Foo f_2 = x; // x has no argument for Foo's constructor, so must be illegal like the FooImpl() line.
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/BadConv02.out Mon Jun 21 13:43:37 2010 +0100
@@ -0,0 +1,3 @@
+BadConv02.java:44:15: compiler.err.cant.apply.symbol: kindname.constructor, FooImpl, int, compiler.misc.no.args, kindname.class, BadConv02.FooImpl, null
+BadConv02.java:45:15: compiler.err.prob.found.req: (compiler.misc.incompatible.types), #int(), BadConv02.Foo
+2 errors
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/LambdaConv07.java Mon Jun 21 13:43:37 2010 +0100
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary basic test for lambda conversion targetting an abstract class
+ * @author Maurizio Cimadamore
+ * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableMethodHandles LambdaConv07
+ */
+
+public class LambdaConv07 {
+
+ static int assertionCount = 0;
+
+ static void assertTrue(boolean cond) {
+ assertionCount++;
+ if (!cond)
+ throw new AssertionError();
+ }
+
+ static abstract class SAM {
+ abstract void foo(int i);
+ }
+
+
+ static {
+ SAM s = #(int i)(assertTrue(i == 1));
+ s.foo(1);
+ }
+
+ {
+ SAM s = #(int i)(assertTrue(i == 2));
+ s.foo(2);
+ }
+
+ static void test1() {
+ SAM s = #(int i)(assertTrue(i == 3));
+ s.foo(3);
+ }
+
+ void test2() {
+ SAM s = #(int i)(assertTrue(i == 4));
+ s.foo(4);
+ }
+
+ public static void main(String[] args) {
+ test1();
+ new LambdaConv07().test2();
+ assertTrue(assertionCount == 4);
+ }
+}