8005852: Treatment of '_' as identifier
Summary: warn when '_' is found in an identifier position
Reviewed-by: jjg
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Wed Jan 16 20:41:14 2013 -0800
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Jan 17 18:15:20 2013 +0000
@@ -245,40 +245,42 @@
token = S.token();
}
- protected boolean peekToken(TokenKind tk) {
+ protected boolean peekToken(Filter<TokenKind> tk) {
return peekToken(0, tk);
}
- protected boolean peekToken(int lookahead, TokenKind tk) {
- return S.token(lookahead + 1).kind == tk;
+ protected boolean peekToken(int lookahead, Filter<TokenKind> tk) {
+ return tk.accepts(S.token(lookahead + 1).kind);
}
- protected boolean peekToken(TokenKind tk1, TokenKind tk2) {
+ protected boolean peekToken(Filter<TokenKind> tk1, Filter<TokenKind> tk2) {
return peekToken(0, tk1, tk2);
}
- protected boolean peekToken(int lookahead, TokenKind tk1, TokenKind tk2) {
- return S.token(lookahead + 1).kind == tk1 &&
- S.token(lookahead + 2).kind == tk2;
+ protected boolean peekToken(int lookahead, Filter<TokenKind> tk1, Filter<TokenKind> tk2) {
+ return tk1.accepts(S.token(lookahead + 1).kind) &&
+ tk2.accepts(S.token(lookahead + 2).kind);
}
- protected boolean peekToken(TokenKind tk1, TokenKind tk2, TokenKind tk3) {
+ protected boolean peekToken(Filter<TokenKind> tk1, Filter<TokenKind> tk2, Filter<TokenKind> tk3) {
return peekToken(0, tk1, tk2, tk3);
}
- protected boolean peekToken(int lookahead, TokenKind tk1, TokenKind tk2, TokenKind tk3) {
- return S.token(lookahead + 1).kind == tk1 &&
- S.token(lookahead + 2).kind == tk2 &&
- S.token(lookahead + 3).kind == tk3;
+ protected boolean peekToken(int lookahead, Filter<TokenKind> tk1, Filter<TokenKind> tk2, Filter<TokenKind> tk3) {
+ return tk1.accepts(S.token(lookahead + 1).kind) &&
+ tk2.accepts(S.token(lookahead + 2).kind) &&
+ tk3.accepts(S.token(lookahead + 3).kind);
}
- protected boolean peekToken(TokenKind... kinds) {
+ @SuppressWarnings("unchecked")
+ protected boolean peekToken(Filter<TokenKind>... kinds) {
return peekToken(0, kinds);
}
- protected boolean peekToken(int lookahead, TokenKind... kinds) {
+ @SuppressWarnings("unchecked")
+ protected boolean peekToken(int lookahead, Filter<TokenKind>... kinds) {
for (; lookahead < kinds.length ; lookahead++) {
- if (S.token(lookahead + 1).kind != kinds[lookahead]) {
+ if (!kinds[lookahead].accepts(S.token(lookahead + 1).kind)) {
return false;
}
}
@@ -333,6 +335,7 @@
if (stopAtMemberDecl)
return;
break;
+ case UNDERSCORE:
case IDENTIFIER:
if (stopAtIdentifier)
return;
@@ -552,11 +555,16 @@
nextToken();
return name;
}
+ } else if (token.kind == UNDERSCORE) {
+ warning(token.pos, "underscore.as.identifier");
+ Name name = token.name();
+ nextToken();
+ return name;
} else {
accept(IDENTIFIER);
return names.error;
}
-}
+ }
/**
* Qualident = Ident { DOT Ident }
@@ -1056,7 +1064,7 @@
typeArgs = null;
} else return illegal();
break;
- case IDENTIFIER: case ASSERT: case ENUM:
+ case UNDERSCORE: case IDENTIFIER: case ASSERT: case ENUM:
if (typeArgs != null) return illegal();
if ((mode & EXPR) != 0 && peekToken(ARROW)) {
t = lambdaExpressionOrStatement(false, false, pos);
@@ -1274,7 +1282,7 @@
int pos = 0, depth = 0;
for (Token t = S.token(pos) ; ; t = S.token(++pos)) {
switch (t.kind) {
- case IDENTIFIER: case QUES: case EXTENDS: case SUPER:
+ case IDENTIFIER: case UNDERSCORE: case QUES: case EXTENDS: case SUPER:
case DOT: case RBRACKET: case LBRACKET: case COMMA:
case BYTE: case SHORT: case INT: case LONG: case FLOAT:
case DOUBLE: case BOOLEAN: case CHAR:
@@ -1323,8 +1331,8 @@
if (peekToken(lookahead, RPAREN)) {
//Type, ')' -> cast
return ParensResult.CAST;
- } else if (peekToken(lookahead, IDENTIFIER)) {
- //Type, 'Identifier -> explicit lambda
+ } else if (peekToken(lookahead, LAX_IDENTIFIER)) {
+ //Type, Identifier/'_'/'assert'/'enum' -> explicit lambda
return ParensResult.EXPLICIT_LAMBDA;
}
break;
@@ -1350,16 +1358,19 @@
case INTLITERAL: case LONGLITERAL: case FLOATLITERAL:
case DOUBLELITERAL: case CHARLITERAL: case STRINGLITERAL:
case TRUE: case FALSE: case NULL:
- case NEW: case IDENTIFIER: case ASSERT: case ENUM:
+ case NEW: case IDENTIFIER: case ASSERT: case ENUM: case UNDERSCORE:
case BYTE: case SHORT: case CHAR: case INT:
case LONG: case FLOAT: case DOUBLE: case BOOLEAN: case VOID:
return ParensResult.CAST;
default:
return ParensResult.PARENS;
}
+ case UNDERSCORE:
+ case ASSERT:
+ case ENUM:
case IDENTIFIER:
- if (peekToken(lookahead, IDENTIFIER)) {
- // Identifier, Identifier -> explicit lambda
+ if (peekToken(lookahead, LAX_IDENTIFIER)) {
+ // Identifier, Identifier/'_'/'assert'/'enum' -> explicit lambda
return ParensResult.EXPLICIT_LAMBDA;
} else if (peekToken(lookahead, RPAREN, ARROW)) {
// Identifier, ')' '->' -> implicit lambda
@@ -1372,8 +1383,8 @@
//those can only appear in explicit lambdas
return ParensResult.EXPLICIT_LAMBDA;
case LBRACKET:
- if (peekToken(lookahead, RBRACKET, IDENTIFIER)) {
- // '[', ']', Identifier -> explicit lambda
+ if (peekToken(lookahead, RBRACKET, LAX_IDENTIFIER)) {
+ // '[', ']', Identifier/'_'/'assert'/'enum' -> explicit lambda
return ParensResult.EXPLICIT_LAMBDA;
} else if (peekToken(lookahead, RBRACKET, RPAREN) ||
peekToken(lookahead, RBRACKET, AMP)) {
@@ -1402,11 +1413,11 @@
// '>', ')' -> cast
// '>', '&' -> cast
return ParensResult.CAST;
- } else if (peekToken(lookahead, IDENTIFIER, COMMA) ||
- peekToken(lookahead, IDENTIFIER, RPAREN, ARROW) ||
+ } else if (peekToken(lookahead, LAX_IDENTIFIER, COMMA) ||
+ peekToken(lookahead, LAX_IDENTIFIER, RPAREN, ARROW) ||
peekToken(lookahead, ELLIPSIS)) {
- // '>', Identifier, ',' -> explicit lambda
- // '>', Identifier, ')', '->' -> explicit lambda
+ // '>', Identifier/'_'/'assert'/'enum', ',' -> explicit lambda
+ // '>', Identifier/'_'/'assert'/'enum', ')', '->' -> explicit lambda
// '>', '...' -> explicit lambda
return ParensResult.EXPLICIT_LAMBDA;
}
@@ -1426,6 +1437,13 @@
}
}
+ /** Accepts all identifier-like tokens */
+ Filter<TokenKind> LAX_IDENTIFIER = new Filter<TokenKind>() {
+ public boolean accepts(TokenKind t) {
+ return t == IDENTIFIER || t == UNDERSCORE || t == ASSERT || t == ENUM;
+ }
+ };
+
enum ParensResult {
CAST,
EXPLICIT_LAMBDA,
@@ -1433,21 +1451,9 @@
PARENS;
}
- JCExpression lambdaExpressionOrStatement(JCVariableDecl firstParam, int pos) {
- ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>();
- params.append(firstParam);
- JCVariableDecl lastParam = firstParam;
- while ((lastParam.mods.flags & Flags.VARARGS) == 0 && token.kind == COMMA) {
- nextToken();
- params.append(lastParam = formalParameter());
- }
- accept(RPAREN);
- return lambdaExpressionOrStatementRest(params.toList(), pos);
- }
-
JCExpression lambdaExpressionOrStatement(boolean hasParens, boolean explicitParams, int pos) {
List<JCVariableDecl> params = explicitParams ?
- formalParameters() :
+ formalParameters(true) :
implicitParameters(hasParens);
return lambdaExpressionOrStatementRest(params, pos);
@@ -1628,7 +1634,7 @@
nextToken();
JCExpression bound = parseType();
return F.at(pos).Wildcard(t, bound);
- } else if (token.kind == IDENTIFIER) {
+ } else if (LAX_IDENTIFIER.accepts(token.kind)) {
//error recovery
TypeBoundKind t = F.at(Position.NOPOS).TypeBoundKind(BoundKind.UNBOUND);
JCExpression wc = toP(F.at(pos).Wildcard(t, null));
@@ -1678,7 +1684,7 @@
if (token.pos == endPosTable.errorEndPos) {
// error recovery
Name name = null;
- if (token.kind == IDENTIFIER) {
+ if (LAX_IDENTIFIER.accepts(token.kind)) {
name = token.name();
nextToken();
} else {
@@ -2026,10 +2032,7 @@
nextToken();
JCStatement stat = parseStatement();
return List.<JCStatement>of(F.at(pos).Labelled(prevToken.name(), stat));
- } else if ((lastmode & TYPE) != 0 &&
- (token.kind == IDENTIFIER ||
- token.kind == ASSERT ||
- token.kind == ENUM)) {
+ } else if ((lastmode & TYPE) != 0 && LAX_IDENTIFIER.accepts(token.kind)) {
pos = token.pos;
JCModifiers mods = F.at(Position.NOPOS).Modifiers(0);
F.at(pos);
@@ -2183,14 +2186,14 @@
}
case BREAK: {
nextToken();
- Name label = (token.kind == IDENTIFIER || token.kind == ASSERT || token.kind == ENUM) ? ident() : null;
+ Name label = LAX_IDENTIFIER.accepts(token.kind) ? ident() : null;
JCBreak t = to(F.at(pos).Break(label));
accept(SEMI);
return t;
}
case CONTINUE: {
nextToken();
- Name label = (token.kind == IDENTIFIER || token.kind == ASSERT || token.kind == ENUM) ? ident() : null;
+ Name label = LAX_IDENTIFIER.accepts(token.kind) ? ident() : null;
JCContinue t = to(F.at(pos).Continue(label));
accept(SEMI);
return t;
@@ -2351,9 +2354,7 @@
return variableDeclarators(optFinal(0), parseType(), stats).toList();
} else {
JCExpression t = term(EXPR | TYPE);
- if ((lastmode & TYPE) != 0 &&
- (token.kind == IDENTIFIER || token.kind == ASSERT ||
- token.kind == ENUM)) {
+ if ((lastmode & TYPE) != 0 && LAX_IDENTIFIER.accepts(token.kind)) {
return variableDeclarators(modifiersOpt(), t, stats).toList();
} else if ((lastmode & TYPE) != 0 && token.kind == COLON) {
error(pos, "bad.initializer", "for-loop");
@@ -2609,8 +2610,18 @@
/** VariableDeclaratorId = Ident BracketsOpt
*/
JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type) {
+ return variableDeclaratorId(mods, type, false);
+ }
+ //where
+ JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type, boolean lambdaParameter) {
int pos = token.pos;
- Name name = ident();
+ Name name;
+ if (lambdaParameter && token.kind == UNDERSCORE) {
+ syntaxError(pos, "expected", IDENTIFIER);
+ name = token.name();
+ } else {
+ name = ident();
+ }
if ((mods.flags & Flags.VARARGS) != 0 &&
token.kind == LBRACKET) {
log.error(token.pos, "varargs.and.old.array.syntax");
@@ -2770,7 +2781,7 @@
} else {
int pos = token.pos;
List<JCTree> errs;
- if (token.kind == IDENTIFIER) {
+ if (LAX_IDENTIFIER.accepts(token.kind)) {
errs = List.<JCTree>of(mods, toP(F.at(pos).Ident(ident())));
setErrorEndPos(token.pos);
} else {
@@ -2787,7 +2798,7 @@
}
int pos = token.pos;
List<JCTree> errs;
- if (token.kind == IDENTIFIER) {
+ if (LAX_IDENTIFIER.accepts(token.kind)) {
errs = List.<JCTree>of(mods, toP(F.at(pos).Ident(ident())));
setErrorEndPos(token.pos);
} else {
@@ -3182,14 +3193,17 @@
* FormalParameterListNovarargs = [ FormalParameterListNovarargs , ] FormalParameter
*/
List<JCVariableDecl> formalParameters() {
+ return formalParameters(false);
+ }
+ List<JCVariableDecl> formalParameters(boolean lambdaParameters) {
ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>();
JCVariableDecl lastParam = null;
accept(LPAREN);
if (token.kind != RPAREN) {
- params.append(lastParam = formalParameter());
+ params.append(lastParam = formalParameter(lambdaParameters));
while ((lastParam.mods.flags & Flags.VARARGS) == 0 && token.kind == COMMA) {
nextToken();
- params.append(lastParam = formalParameter());
+ params.append(lastParam = formalParameter(lambdaParameters));
}
}
accept(RPAREN);
@@ -3225,6 +3239,9 @@
* LastFormalParameter = { FINAL | '@' Annotation } Type '...' Ident | FormalParameter
*/
protected JCVariableDecl formalParameter() {
+ return formalParameter(false);
+ }
+ protected JCVariableDecl formalParameter(boolean lambdaParameter) {
JCModifiers mods = optFinal(Flags.PARAMETER);
JCExpression type = parseType();
if (token.kind == ELLIPSIS) {
@@ -3233,12 +3250,12 @@
type = to(F.at(token.pos).TypeArray(type));
nextToken();
}
- return variableDeclaratorId(mods, type);
+ return variableDeclaratorId(mods, type, lambdaParameter);
}
protected JCVariableDecl implicitParameter() {
JCModifiers mods = F.at(token.pos).Modifiers(Flags.PARAMETER);
- return variableDeclaratorId(mods, null);
+ return variableDeclaratorId(mods, null, true);
}
/* ---------- auxiliary methods -------------- */
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java Wed Jan 16 20:41:14 2013 -0800
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java Thu Jan 17 18:15:20 2013 +0000
@@ -33,6 +33,7 @@
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Filter;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;
@@ -74,7 +75,6 @@
protected Tokens(Context context) {
context.put(tokensKey, this);
names = Names.instance(context);
-
for (TokenKind t : TokenKind.values()) {
if (t.name != null)
enterKeyword(t.name, t);
@@ -113,7 +113,7 @@
* This enum defines all tokens used by the javac scanner. A token is
* optionally associated with a name.
*/
- public enum TokenKind implements Formattable {
+ public enum TokenKind implements Formattable, Filter<TokenKind> {
EOF(),
ERROR(),
IDENTIFIER(Tag.NAMED),
@@ -176,6 +176,7 @@
TRUE("true", Tag.NAMED),
FALSE("false", Tag.NAMED),
NULL("null", Tag.NAMED),
+ UNDERSCORE("_", Tag.NAMED),
ARROW("->"),
COLCOL("::"),
LPAREN("("),
@@ -283,6 +284,11 @@
public String toString(Locale locale, Messages messages) {
return name != null ? toString() : messages.getLocalizedString(locale, "compiler.misc." + toString());
}
+
+ @Override
+ public boolean accepts(TokenKind that) {
+ return this == that;
+ }
}
public interface Comment {
--- a/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties Wed Jan 16 20:41:14 2013 -0800
+++ b/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Jan 17 18:15:20 2013 +0000
@@ -2132,6 +2132,10 @@
as of release 1.4, ''assert'' is a keyword, and may not be used as an identifier\n\
(use -source 1.4 or higher to use ''assert'' as a keyword)
+compiler.warn.underscore.as.identifier=\
+ ''_'' used as an identifier\n\
+ (use of ''_'' as an identifier might not be supported in future releases)
+
compiler.err.enum.as.identifier=\
as of release 5, ''enum'' is a keyword, and may not be used as an identifier\n\
(use -source 1.4 or lower to use ''enum'' as an identifier)
--- a/langtools/test/tools/javac/lambda/LambdaParserTest.java Wed Jan 16 20:41:14 2013 -0800
+++ b/langtools/test/tools/javac/lambda/LambdaParserTest.java Thu Jan 17 18:15:20 2013 +0000
@@ -23,8 +23,7 @@
/*
* @test
- * @bug 7115050
- * @bug 8003280
+ * @bug 7115050 8003280 8005852
* @summary Add lambda tests
* Add parser support for lambda expressions
* @library ../lib
@@ -46,12 +45,12 @@
enum LambdaKind {
NILARY_EXPR("()->x"),
NILARY_STMT("()->{ return x; }"),
- ONEARY_SHORT_EXPR("x->x"),
- ONEARY_SHORT_STMT("x->{ return x; }"),
- ONEARY_EXPR("(#M1 #T1 x)->x"),
- ONEARY_STMT("(#M1 #T1 x)->{ return x; }"),
- TWOARY_EXPR("(#M1 #T1 x, #M2 #T2 y)->x"),
- TWOARY_STMT("(#M1 #T1 x, #M2 #T2 y)->{ return x; }");
+ ONEARY_SHORT_EXPR("#PN->x"),
+ ONEARY_SHORT_STMT("#PN->{ return x; }"),
+ ONEARY_EXPR("(#M1 #T1 #PN)->x"),
+ ONEARY_STMT("(#M1 #T1 #PN)->{ return x; }"),
+ TWOARY_EXPR("(#M1 #T1 #PN, #M2 #T2 y)->x"),
+ TWOARY_STMT("(#M1 #T1 #PN, #M2 #T2 y)->{ return x; }");
String lambdaTemplate;
@@ -60,11 +59,12 @@
}
String getLambdaString(LambdaParameterKind pk1, LambdaParameterKind pk2,
- ModifierKind mk1, ModifierKind mk2) {
+ ModifierKind mk1, ModifierKind mk2, LambdaParameterName pn) {
return lambdaTemplate.replaceAll("#M1", mk1.modifier)
.replaceAll("#M2", mk2.modifier)
.replaceAll("#T1", pk1.parameterType)
- .replaceAll("#T2", pk2.parameterType);
+ .replaceAll("#T2", pk2.parameterType)
+ .replaceAll("#PN", pn.nameStr);
}
int arity() {
@@ -87,6 +87,17 @@
}
}
+ enum LambdaParameterName {
+ IDENT("x"),
+ UNDERSCORE("_");
+
+ String nameStr;
+
+ LambdaParameterName(String nameStr) {
+ this.nameStr = nameStr;
+ }
+ }
+
enum LambdaParameterKind {
IMPLICIT(""),
EXPLIICT_SIMPLE("A"),
@@ -151,8 +162,8 @@
}
String expressionString(LambdaParameterKind pk1, LambdaParameterKind pk2,
- ModifierKind mk1, ModifierKind mk2, LambdaKind lk, SubExprKind sk) {
- return expressionTemplate.replaceAll("#L", lk.getLambdaString(pk1, pk2, mk1, mk2))
+ ModifierKind mk1, ModifierKind mk2, LambdaKind lk, LambdaParameterName pn, SubExprKind sk) {
+ return expressionTemplate.replaceAll("#L", lk.getLambdaString(pk1, pk2, mk1, mk2, pn))
.replaceAll("#S", sk.subExpression);
}
}
@@ -174,25 +185,27 @@
public static void main(String... args) throws Exception {
for (LambdaKind lk : LambdaKind.values()) {
- for (LambdaParameterKind pk1 : LambdaParameterKind.values()) {
- if (lk.arity() < 1 && pk1 != LambdaParameterKind.IMPLICIT)
- continue;
- for (LambdaParameterKind pk2 : LambdaParameterKind.values()) {
- if (lk.arity() < 2 && pk2 != LambdaParameterKind.IMPLICIT)
+ for (LambdaParameterName pn : LambdaParameterName.values()) {
+ for (LambdaParameterKind pk1 : LambdaParameterKind.values()) {
+ if (lk.arity() < 1 && pk1 != LambdaParameterKind.IMPLICIT)
continue;
- for (ModifierKind mk1 : ModifierKind.values()) {
- if (mk1 != ModifierKind.NONE && lk.isShort())
+ for (LambdaParameterKind pk2 : LambdaParameterKind.values()) {
+ if (lk.arity() < 2 && pk2 != LambdaParameterKind.IMPLICIT)
continue;
- if (lk.arity() < 1 && mk1 != ModifierKind.NONE)
- continue;
- for (ModifierKind mk2 : ModifierKind.values()) {
- if (lk.arity() < 2 && mk2 != ModifierKind.NONE)
+ for (ModifierKind mk1 : ModifierKind.values()) {
+ if (mk1 != ModifierKind.NONE && lk.isShort())
+ continue;
+ if (lk.arity() < 1 && mk1 != ModifierKind.NONE)
continue;
- for (SubExprKind sk : SubExprKind.values()) {
- for (ExprKind ek : ExprKind.values()) {
- pool.execute(
- new LambdaParserTest(pk1, pk2, mk1,
- mk2, lk, sk, ek));
+ for (ModifierKind mk2 : ModifierKind.values()) {
+ if (lk.arity() < 2 && mk2 != ModifierKind.NONE)
+ continue;
+ for (SubExprKind sk : SubExprKind.values()) {
+ for (ExprKind ek : ExprKind.values()) {
+ pool.execute(
+ new LambdaParserTest(pk1, pk2, mk1,
+ mk2, lk, sk, ek, pn));
+ }
}
}
}
@@ -209,6 +222,7 @@
ModifierKind mk1;
ModifierKind mk2;
LambdaKind lk;
+ LambdaParameterName pn;
SubExprKind sk;
ExprKind ek;
JavaSource source;
@@ -216,12 +230,13 @@
LambdaParserTest(LambdaParameterKind pk1, LambdaParameterKind pk2,
ModifierKind mk1, ModifierKind mk2, LambdaKind lk,
- SubExprKind sk, ExprKind ek) {
+ SubExprKind sk, ExprKind ek, LambdaParameterName pn) {
this.pk1 = pk1;
this.pk2 = pk2;
this.mk1 = mk1;
this.mk2 = mk2;
this.lk = lk;
+ this.pn = pn;
this.sk = sk;
this.ek = ek;
this.source = new JavaSource();
@@ -239,7 +254,7 @@
public JavaSource() {
super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
source = template.replaceAll("#E",
- ek.expressionString(pk1, pk2, mk1, mk2, lk, sk));
+ ek.expressionString(pk1, pk2, mk1, mk2, lk, pn, sk));
}
@Override
@@ -272,6 +287,9 @@
errorExpected = true;
}
+ errorExpected |= pn == LambdaParameterName.UNDERSCORE &&
+ lk.arity() > 0;
+
if (errorExpected != diagChecker.errorFound) {
throw new Error("invalid diagnostics for source:\n" +
source.getCharContent(true) +