8005852: Treatment of '_' as identifier
authormcimadamore
Thu, 17 Jan 2013 18:15:20 +0000
changeset 15367 31b57f2b8d0b
parent 15366 545307ec740c
child 15368 2577ddb7e710
8005852: Treatment of '_' as identifier Summary: warn when '_' is found in an identifier position Reviewed-by: jjg
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/lambda/LambdaParserTest.java
--- 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) +