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
--- 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);
+ }
+}