# HG changeset patch # User mcimadamore # Date 1322496346 0 # Node ID 3343b22e276165ac846b193d77ca0978ddc460f7 # Parent 9448fe783fd2906211b8010e3c2d18bccd23b2b7 7115052: Add parser support for method references Summary: Add support for parsing method references to JavacParser Reviewed-by: jjg diff -r 9448fe783fd2 -r 3343b22e2761 src/share/classes/com/sun/tools/javac/code/Source.java --- a/src/share/classes/com/sun/tools/javac/code/Source.java Mon Nov 28 15:56:42 2011 +0000 +++ b/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: diff -r 9448fe783fd2 -r 3343b22e2761 src/share/classes/com/sun/tools/javac/comp/Attr.java --- a/src/share/classes/com/sun/tools/javac/comp/Attr.java Mon Nov 28 15:56:42 2011 +0000 +++ b/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); diff -r 9448fe783fd2 -r 3343b22e2761 src/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java --- a/src/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java Mon Nov 28 15:56:42 2011 +0000 +++ b/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(); diff -r 9448fe783fd2 -r 3343b22e2761 src/share/classes/com/sun/tools/javac/parser/JavacParser.java --- a/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Mon Nov 28 15:56:42 2011 +0000 +++ b/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() : 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#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 args = new ListBuffer(); + 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 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 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 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 diff -r 9448fe783fd2 -r 3343b22e2761 src/share/classes/com/sun/tools/javac/parser/Tokens.java --- a/src/share/classes/com/sun/tools/javac/parser/Tokens.java Mon Nov 28 15:56:42 2011 +0000 +++ b/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("{"), diff -r 9448fe783fd2 -r 3343b22e2761 src/share/classes/com/sun/tools/javac/resources/compiler.properties --- a/src/share/classes/com/sun/tools/javac/resources/compiler.properties Mon Nov 28 15:56:42 2011 +0000 +++ b/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) diff -r 9448fe783fd2 -r 3343b22e2761 test/tools/javac/diags/examples/IllegalChar.java --- a/test/tools/javac/diags/examples/IllegalChar.java Mon Nov 28 15:56:42 2011 +0000 +++ b/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 = `; } diff -r 9448fe783fd2 -r 3343b22e2761 test/tools/javac/diags/examples/MethodReferencesNotSupported.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/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; +} diff -r 9448fe783fd2 -r 3343b22e2761 test/tools/javac/lambda/MethodReferenceParserTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/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(""), + TWO(""); + + 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"), + UNBOUND_GENERIC2("A"), + UNBOUND_GENERIC3("A"); + + 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 { + + boolean errorFound; + + public void report(Diagnostic diagnostic) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + errorFound = true; + } + } + } +} diff -r 9448fe783fd2 -r 3343b22e2761 test/tools/javac/quid/T6999438.out --- a/test/tools/javac/quid/T6999438.out Mon Nov 28 15:56:42 2011 +0000 +++ b/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: ';'