7115052: Add parser support for method references
authormcimadamore
Mon, 28 Nov 2011 16:05:46 +0000
changeset 11144 8a4ae514eedf
parent 11143 9dbe313bfb74
child 11145 893729a0ff19
child 11146 ceac617c1081
7115052: Add parser support for method references Summary: Add support for parsing method references to JavacParser Reviewed-by: jjg
langtools/src/share/classes/com/sun/tools/javac/code/Source.java
langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java
langtools/src/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java
langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java
langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java
langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties
langtools/test/tools/javac/diags/examples/IllegalChar.java
langtools/test/tools/javac/diags/examples/MethodReferencesNotSupported.java
langtools/test/tools/javac/lambda/MethodReferenceParserTest.java
langtools/test/tools/javac/quid/T6999438.out
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Source.java	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Source.java	Mon Nov 28 16:05:46 2011 +0000
@@ -197,6 +197,9 @@
     public boolean allowLambda() {
         return compareTo(JDK1_8) >= 0;
     }
+    public boolean allowMethodReferences() {
+        return compareTo(JDK1_8) >= 0;
+    }
     public static SourceVersion toSourceVersion(Source source) {
         switch(source) {
         case JDK1_2:
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Nov 28 16:05:46 2011 +0000
@@ -1980,6 +1980,11 @@
         throw new UnsupportedOperationException("Lambda expression not supported yet");
     }
 
+    @Override
+    public void visitReference(JCMemberReference that) {
+        throw new UnsupportedOperationException("Member references not supported yet");
+    }
+
     public void visitParens(JCParens tree) {
         Type owntype = attribTree(tree.expr, env, pkind, pt);
         result = check(tree, owntype, pkind, pkind, pt);
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java	Mon Nov 28 16:05:46 2011 +0000
@@ -637,6 +637,10 @@
                         lexError(pos, "unclosed.str.lit");
                     }
                     break loop;
+                case '#':
+                    reader.scanChar();
+                    tk = TokenKind.HASH;
+                    break loop;
                 default:
                     if (isSpecial(reader.ch)) {
                         scanOperator();
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java	Mon Nov 28 16:05:46 2011 +0000
@@ -27,6 +27,8 @@
 
 import java.util.*;
 
+import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
+
 import com.sun.tools.javac.code.*;
 import com.sun.tools.javac.parser.Tokens.*;
 import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
@@ -112,6 +114,8 @@
         this.allowStringFolding = fac.options.getBoolean("allowStringFolding", true);
         this.allowLambda = source.allowLambda() &&
                 fac.options.isSet("allowLambda");
+        this.allowMethodReferences = source.allowMethodReferences() &&
+                fac.options.isSet("allowMethodReferences");
         this.keepDocComments = keepDocComments;
         docComments = keepDocComments ? new HashMap<JCTree,String>() : null;
         this.keepLineMap = keepLineMap;
@@ -172,6 +176,10 @@
      */
     boolean allowLambda;
 
+    /** Switch: should we allow method/constructor references?
+     */
+    boolean allowMethodReferences;
+
     /** Switch: should we keep docComments?
      */
     boolean keepDocComments;
@@ -779,7 +787,7 @@
             top++;
             topOp = token;
             nextToken();
-            odStack[top] = (topOp.kind == INSTANCEOF) ? parseType() : term3();
+            odStack[top] = (topOp.kind == INSTANCEOF) ? parseType() : term3NoParams();
             while (top > 0 && prec(topOp.kind) >= prec(token.kind)) {
                 odStack[top-1] = makeOp(topOp.pos, topOp.kind, odStack[top-1],
                                         odStack[top]);
@@ -882,6 +890,7 @@
      *                 | "(" Arguments ")" "->" ( Expression | Block )
      *                 | Ident "->" ( Expression | Block )
      *                 | Ident { "." Ident }
+     *                 | Expression3 MemberReferenceSuffix
      *                   [ "[" ( "]" BracketsOpt "." CLASS | Expression "]" )
      *                   | Arguments
      *                   | "." ( CLASS | THIS | [TypeArguments] SUPER Arguments | NEW [TypeArguments] InnerCreator )
@@ -922,7 +931,7 @@
                     mode = EXPR;
                     t = literal(names.hyphen, pos);
                 } else {
-                    t = term3();
+                    t = term3NoParams();
                     return F.at(pos).Unary(unoptag(tk), t);
                 }
             } else return illegal();
@@ -938,8 +947,8 @@
                     break;
                 } else {
                     nextToken();
-                    mode = EXPR | TYPE | NOPARAMS;
-                    t = term3();
+                    mode = EXPR | TYPE;
+                    t = term3NoParams();
                     if ((mode & TYPE) != 0 && token.kind == LT) {
                         // Could be a cast to a parameterized type
                         JCTree.Tag op = JCTree.Tag.LT;
@@ -1002,7 +1011,7 @@
                 lastmode = mode;
                 mode = EXPR;
                 if ((lastmode & EXPR) == 0) {
-                    JCExpression t1 = term3();
+                    JCExpression t1 = term3NoParams();
                     return F.at(pos).TypeCast(t, t1);
                 } else if ((lastmode & TYPE) != 0) {
                     switch (token.kind) {
@@ -1015,7 +1024,7 @@
                         case NEW: case IDENTIFIER: case ASSERT: case ENUM:
                     case BYTE: case SHORT: case CHAR: case INT:
                     case LONG: case FLOAT: case DOUBLE: case BOOLEAN: case VOID:
-                        JCExpression t1 = term3();
+                        JCExpression t1 = term3NoParams();
                         return F.at(pos).TypeCast(t, t1);
                     }
                 }
@@ -1134,6 +1143,49 @@
                         // typeArgs saved for next loop iteration.
                         t = toP(F.at(pos).Select(t, ident()));
                         break;
+                    case LT:
+                        if ((mode & (TYPE | NOPARAMS)) == 0) {
+                            //could be an unbound method reference whose qualifier
+                            //is a generic type i.e. A<S>#m
+                            mode = EXPR | TYPE;
+                            JCTree.Tag op = JCTree.Tag.LT;
+                            int pos1 = token.pos;
+                            nextToken();
+                            mode |= EXPR | TYPE | TYPEARG;
+                            JCExpression t1 = term3();
+                            if ((mode & TYPE) != 0 &&
+                                (token.kind == COMMA || token.kind == GT)) {
+                                mode = TYPE;
+                                ListBuffer<JCExpression> args = new ListBuffer<JCExpression>();
+                                args.append(t1);
+                                while (token.kind == COMMA) {
+                                    nextToken();
+                                    args.append(typeArgument());
+                                }
+                                accept(GT);
+                                t = toP(F.at(pos1).TypeApply(t, args.toList()));
+                                checkGenerics();
+                                while (token.kind == DOT) {
+                                    nextToken();
+                                    mode = TYPE;
+                                    t = toP(F.at(token.pos).Select(t, ident()));
+                                    t = typeArgumentsOpt(t);
+                                }
+                                if (token.kind != HASH) {
+                                    //method reference expected here
+                                    t = illegal();
+                                }
+                                mode = EXPR;
+                                break;
+                            } else if ((mode & EXPR) != 0) {
+                                //rollback - it was a binary expression
+                                mode = EXPR;
+                                JCExpression e = term2Rest(t1, TreeInfo.shiftPrec);
+                                t = F.at(pos1).Binary(op, t, e);
+                                t = termRest(term1Rest(term2Rest(t, TreeInfo.orPrec)));
+                            }
+                        }
+                        break loop;
                     default:
                         break loop;
                     }
@@ -1173,6 +1225,15 @@
         return term3Rest(t, typeArgs);
     }
 
+    JCExpression term3NoParams() {
+        try {
+            mode |= NOPARAMS;
+            return term3();
+        } finally {
+            mode &= ~NOPARAMS;
+        }
+    }
+
     JCExpression term3Rest(JCExpression t, List<JCExpression> typeArgs) {
         if (typeArgs != null) illegal();
         while (true) {
@@ -1218,6 +1279,11 @@
                     t = argumentsOpt(typeArgs, typeArgumentsOpt(t));
                     typeArgs = null;
                 }
+            } else if ((mode & EXPR) != 0 && token.kind == HASH) {
+                mode = EXPR;
+                if (typeArgs != null) return illegal();
+                accept(HASH);
+                t = memberReferenceSuffix(pos1, t);
             } else {
                 break;
             }
@@ -1281,6 +1347,9 @@
         nextToken();
         if (token.kind == LPAREN || typeArgs != null) {
             t = arguments(typeArgs, t);
+        } else if (token.kind == HASH) {
+            if (typeArgs != null) return illegal();
+            t = memberReferenceSuffix(t);
         } else {
             int pos = token.pos;
             accept(DOT);
@@ -1490,6 +1559,36 @@
         return t;
     }
 
+    /**
+     * MemberReferenceSuffix = "#" [TypeArguments] Ident
+     *                       | "#" [TypeArguments] "new"
+     */
+    JCExpression memberReferenceSuffix(JCExpression t) {
+        int pos1 = token.pos;
+        accept(HASH);
+        return memberReferenceSuffix(pos1, t);
+    }
+
+    JCExpression memberReferenceSuffix(int pos1, JCExpression t) {
+        checkMethodReferences();
+        mode = EXPR;
+        List<JCExpression> typeArgs = null;
+        if (token.kind == LT) {
+            typeArgs = typeArguments(false);
+        }
+        Name refName = null;
+        ReferenceMode refMode = null;
+        if (token.kind == NEW) {
+            refMode = ReferenceMode.NEW;
+            refName = names.init;
+            nextToken();
+        } else {
+            refMode = ReferenceMode.INVOKE;
+            refName = ident();
+        }
+        return toP(F.at(t.getStartPosition()).Reference(refMode, refName, t, typeArgs));
+    }
+
     /** Creator = Qualident [TypeArguments] ( ArrayCreatorRest | ClassCreatorRest )
      */
     JCExpression creator(int newpos, List<JCExpression> typeArgs) {
@@ -3166,6 +3265,12 @@
             allowLambda = true;
         }
     }
+    void checkMethodReferences() {
+        if (!allowMethodReferences) {
+            log.error(token.pos, "method.references.not.supported.in.source", source.name);
+            allowMethodReferences = true;
+        }
+    }
 
     /*
      * a functional source tree and end position mappings
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java	Mon Nov 28 16:05:46 2011 +0000
@@ -177,6 +177,7 @@
         FALSE("false", Tag.NAMED),
         NULL("null", Tag.NAMED),
         ARROW("->"),
+        HASH("#"),
         LPAREN("("),
         RPAREN(")"),
         LBRACE("{"),
--- a/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Mon Nov 28 16:05:46 2011 +0000
@@ -1950,6 +1950,11 @@
     lambda expressions are not supported in -source {0}\n\
     (use -source 8 or higher to enable lambda expressions)
 
+# 0: string
+compiler.err.method.references.not.supported.in.source=\
+    method references are not supported in -source {0}\n\
+    (use -source 8 or higher to enable method references)
+
 ########################################
 # Diagnostics for verbose resolution
 # used by Resolve (debug only)
--- a/langtools/test/tools/javac/diags/examples/IllegalChar.java	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/test/tools/javac/diags/examples/IllegalChar.java	Mon Nov 28 16:05:46 2011 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2011, 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
@@ -24,5 +24,5 @@
 // key: compiler.err.illegal.char
 
 class IllegalChar {
-    int i = #;
+    int i = `;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/diags/examples/MethodReferencesNotSupported.java	Mon Nov 28 16:05:46 2011 +0000
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011, 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.
+ */
+
+// key: compiler.err.method.references.not.supported.in.source
+// options: -source 7 -Xlint:-options
+
+class MethodReferencesNotSupported {
+    S s = A#foo;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/lambda/MethodReferenceParserTest.java	Mon Nov 28 16:05:46 2011 +0000
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2011, 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
+ * @bug 7115052
+ * @summary Add parser support for method references
+ */
+
+import com.sun.source.util.JavacTask;
+import java.net.URI;
+import java.util.Arrays;
+import javax.tools.Diagnostic;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+public class MethodReferenceParserTest {
+
+    static int checkCount = 0;
+
+    enum ReferenceKind {
+        METHOD_REF("#Q##Gm"),
+        CONSTRUCTOR_REF("#Q##Gnew"),
+        ERR_SUPER("#Q##Gsuper"),
+        ERR_METH0("#Q##Gm()"),
+        ERR_METH1("#Q##Gm(X)"),
+        ERR_CONSTR0("#Q##Gnew()"),
+        ERR_CONSTR1("#Q##Gnew(X)");
+
+        String referenceTemplate;
+
+        ReferenceKind(String referenceTemplate) {
+            this.referenceTemplate = referenceTemplate;
+        }
+
+        String getReferenceString(QualifierKind qk, GenericKind gk) {
+            return referenceTemplate
+                    .replaceAll("#Q", qk.qualifier)
+                    .replaceAll("#G", gk.typeParameters);
+        }
+
+        boolean erroneous() {
+            switch (this) {
+                case ERR_SUPER:
+                case ERR_METH0:
+                case ERR_METH1:
+                case ERR_CONSTR0:
+                case ERR_CONSTR1:
+                    return true;
+                default: return false;
+            }
+        }
+    }
+
+    enum GenericKind {
+        NONE(""),
+        ONE("<X>"),
+        TWO("<X,Y>");
+
+        String typeParameters;
+
+        GenericKind(String typeParameters) {
+            this.typeParameters = typeParameters;
+        }
+    }
+
+    enum QualifierKind {
+        THIS("this"),
+        SUPER("super"),
+        NEW("new Foo()"),
+        METHOD("m()"),
+        FIELD("a.f"),
+        UBOUND_SIMPLE("A"),
+        UNBOUND_GENERIC1("A<X>"),
+        UNBOUND_GENERIC2("A<X, Y>"),
+        UNBOUND_GENERIC3("A<? extends X, ? super Y>");
+
+        String qualifier;
+
+        QualifierKind(String qualifier) {
+            this.qualifier = qualifier;
+        }
+    }
+
+    enum ExprKind {
+        NONE("#R#S"),
+        SINGLE_PAREN1("(#R#S)"),
+        SINGLE_PAREN2("(#R)#S"),
+        DOUBLE_PAREN1("((#R#S))"),
+        DOUBLE_PAREN2("((#R)#S)"),
+        DOUBLE_PAREN3("((#R))#S");
+
+        String expressionTemplate;
+
+        ExprKind(String expressionTemplate) {
+            this.expressionTemplate = expressionTemplate;
+        }
+
+        String expressionString(ReferenceKind rk, QualifierKind qk, GenericKind gk, SubExprKind sk) {
+            return expressionTemplate
+                    .replaceAll("#R", rk.getReferenceString(qk, gk))
+                    .replaceAll("#S", sk.subExpression);
+        }
+    }
+
+    enum SubExprKind {
+        NONE(""),
+        SELECT_FIELD(".f"),
+        SELECT_METHOD(".f()"),
+        SELECT_NEW(".new Foo()"),
+        POSTINC("++"),
+        POSTDEC("--");
+
+        String subExpression;
+
+        SubExprKind(String subExpression) {
+            this.subExpression = subExpression;
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+
+        //create default shared JavaCompiler - reused across multiple compilations
+        JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
+        StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
+
+        for (ReferenceKind rk : ReferenceKind.values()) {
+            for (QualifierKind qk : QualifierKind.values()) {
+                for (GenericKind gk : GenericKind.values()) {
+                    for (SubExprKind sk : SubExprKind.values()) {
+                        for (ExprKind ek : ExprKind.values()) {
+                            new MethodReferenceParserTest(rk, qk, gk, sk, ek).run(comp, fm);
+                        }
+                    }
+                }
+            }
+        }
+        System.out.println("Total check executed: " + checkCount);
+    }
+
+    ReferenceKind rk;
+    QualifierKind qk;
+    GenericKind gk;
+    SubExprKind sk;
+    ExprKind ek;
+    JavaSource source;
+    DiagnosticChecker diagChecker;
+
+    MethodReferenceParserTest(ReferenceKind rk, QualifierKind qk, GenericKind gk, SubExprKind sk, ExprKind ek) {
+        this.rk = rk;
+        this.qk = qk;
+        this.gk = gk;
+        this.sk = sk;
+        this.ek = ek;
+        this.source = new JavaSource();
+        this.diagChecker = new DiagnosticChecker();
+    }
+
+    class JavaSource extends SimpleJavaFileObject {
+
+        String template = "class Test {\n" +
+                          "   SAM s = #E;\n" +
+                          "}";
+
+        String source;
+
+        public JavaSource() {
+            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+            source = template.replaceAll("#E", ek.expressionString(rk, qk, gk, sk));
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            return source;
+        }
+    }
+
+    void run(JavaCompiler tool, StandardJavaFileManager fm) throws Exception {
+        JavacTask ct = (JavacTask)tool.getTask(null, fm, diagChecker,
+                Arrays.asList("-XDallowMethodReferences"), null, Arrays.asList(source));
+        try {
+            ct.parse();
+        } catch (Throwable ex) {
+            throw new AssertionError("Error thrown when parsing the following source:\n" + source.getCharContent(true));
+        }
+        check();
+    }
+
+    void check() {
+        checkCount++;
+
+        if (diagChecker.errorFound != rk.erroneous()) {
+            throw new Error("invalid diagnostics for source:\n" +
+                source.getCharContent(true) +
+                "\nFound error: " + diagChecker.errorFound +
+                "\nExpected error: " + rk.erroneous());
+        }
+    }
+
+    static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
+
+        boolean errorFound;
+
+        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
+                errorFound = true;
+            }
+        }
+    }
+}
--- a/langtools/test/tools/javac/quid/T6999438.out	Mon Nov 28 15:56:42 2011 +0000
+++ b/langtools/test/tools/javac/quid/T6999438.out	Mon Nov 28 16:05:46 2011 +0000
@@ -1,4 +1,4 @@
-T6999438.java:8:9: compiler.err.illegal.char: 35
+T6999438.java:8:8: compiler.err.expected: token.identifier
 T6999438.java:8:10: compiler.err.illegal.start.of.type
 T6999438.java:8:25: compiler.err.expected: token.identifier
 T6999438.java:8:26: compiler.err.expected: ';'