changeset 581:02b8de982628

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
author mcimadamore
date Mon, 21 Jun 2010 13:43:37 +0100
parents 9f79be8946c6
children 1cbf9ca0c589
files 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
diffstat 8 files changed, 346 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- 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 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 @@
         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 && 
@@ -351,6 +357,16 @@
         }
         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;
+            }
+        }
+        return false;
+    }
 
     /**
      * Is t a subtype of or convertiable via boxing/unboxing
--- 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 @@
     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 @@
         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,7 +684,7 @@
     /** 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.
@@ -691,6 +693,12 @@
         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.
      */
     private VarSymbol lookupField(DiagnosticPosition pos, Type qual, Name name) {
@@ -1725,14 +1733,60 @@
         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);
         return (JCNewArray)make.NewArray(make.QualIdent(elemType.tsym),
@@ -2819,14 +2873,118 @@
     <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.
      *  If the subexpression has changed, omit the parens.
--- 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 @@
 (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);
+    }
+}