7115052: Add parser support for method references
Summary: Add support for parsing method references to JavacParser
Reviewed-by: jjg
--- 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: ';'