8294943: Implement record patterns in enhanced for

8296802: Parse errors when deconstructing a record using the enhanced for loop of JEP 432

Co-authored-by: Jan Lahoda <jlahoda@openjdk.org>
Co-authored-by: Aggelos Biboudis <abimpoudis@openjdk.org>
Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org>
Reviewed-by: mcimadamore, vromero
This commit is contained in:
Aggelos Biboudis 2022-12-01 12:40:09 +00:00 committed by Jan Lahoda
parent fc9d419b4f
commit 2cb64a7557
27 changed files with 838 additions and 71 deletions

View File

@ -25,6 +25,8 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A tree node for an "enhanced" {@code for} loop statement.
*
@ -41,12 +43,37 @@ package com.sun.source.tree;
* @since 1.6
*/
public interface EnhancedForLoopTree extends StatementTree {
/**
* "Enhanced" {@code for} declarations come in two forms:
* <ul>
* <li> local variable declarations and
* <li> record patterns
* </ul>
*
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public enum DeclarationKind {
/** enum constant for local variable declarations */
VARIABLE,
/** enum constant for record pattern declarations */
PATTERN
}
/**
* Returns the control variable for the loop.
* @return the control variable
* @return the control variable, or {@code null} if this "enhanced" {@code for} uses a pattern
*/
VariableTree getVariable();
/**
* Returns the control variable or pattern for the loop.
* @return the control variable or pattern
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
Tree getVariableOrRecordPattern();
/**
* Returns the expression yielding the values for the control variable.
* @return the expression
@ -58,4 +85,12 @@ public interface EnhancedForLoopTree extends StatementTree {
* @return the body of the loop
*/
StatementTree getStatement();
/**
* Returns the kind of the declaration of the "enhanced" {@code for}.
* @return the kind of the declaration
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
DeclarationKind getDeclarationKind();
}

View File

@ -333,7 +333,7 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
*/
@Override
public R visitEnhancedForLoop(EnhancedForLoopTree node, P p) {
R r = scan(node.getVariable(), p);
R r = scan(node.getVariableOrRecordPattern(), p);
r = scanAndReduce(node.getExpression(), p, r);
r = scanAndReduce(node.getStatement(), p, r);
return r;

View File

@ -190,6 +190,7 @@ public class Symtab {
public final Type incompatibleClassChangeErrorType;
public final Type cloneNotSupportedExceptionType;
public final Type matchExceptionType;
public final Type nullPointerExceptionType;
public final Type annotationType;
public final TypeSymbol enumSym;
public final Type listType;
@ -555,6 +556,7 @@ public class Symtab {
incompatibleClassChangeErrorType = enterClass("java.lang.IncompatibleClassChangeError");
cloneNotSupportedExceptionType = enterClass("java.lang.CloneNotSupportedException");
matchExceptionType = enterClass("java.lang.MatchException");
nullPointerExceptionType = enterClass("java.lang.NullPointerException");
annotationType = enterClass("java.lang.annotation.Annotation");
classLoaderType = enterClass("java.lang.ClassLoader");
enumSym = enterClass(java_base, names.java_lang_Enum);

View File

@ -33,6 +33,7 @@ import java.util.Map;
import java.util.Queue;
import java.util.stream.Collectors;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.VariableTree;
@ -424,18 +425,24 @@ public class Analyzer {
@Override
boolean match(JCEnhancedForLoop tree){
return !isImplicitlyTyped(tree.var);
return tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE &&
!isImplicitlyTyped((JCVariableDecl) tree.varOrRecordPattern);
}
@Override
List<JCEnhancedForLoop> rewrite(JCEnhancedForLoop oldTree) {
Assert.check(oldTree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
JCEnhancedForLoop newTree = copier.copy(oldTree);
newTree.var = rewriteVarType(oldTree.var);
newTree.varOrRecordPattern = rewriteVarType((JCVariableDecl) oldTree.varOrRecordPattern);
newTree.body = make.at(oldTree.body).Block(0, List.nil());
return List.of(newTree);
}
@Override
void process(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree, boolean hasErrors){
processVar(oldTree.var, newTree.var, hasErrors);
Assert.check(oldTree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
processVar((JCVariableDecl) oldTree.varOrRecordPattern,
(JCVariableDecl) newTree.varOrRecordPattern, hasErrors);
}
}

View File

@ -34,6 +34,7 @@ import javax.lang.model.element.ElementKind;
import javax.tools.JavaFileObject;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.source.tree.MemberSelectTree;
@ -1513,24 +1514,25 @@ public class Attr extends JCTree.Visitor {
public void visitForeachLoop(JCEnhancedForLoop tree) {
Env<AttrContext> loopEnv =
env.dup(env.tree, env.info.dup(env.info.scope.dup()));
try {
//the Formal Parameter of a for-each loop is not in the scope when
//attributing the for-each expression; we mimic this by attributing
//the for-each expression first (against original scope).
Type exprType = types.cvarUpperBound(attribExpr(tree.expr, loopEnv));
chk.checkNonVoid(tree.pos(), exprType);
Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
if (elemtype == null) {
tree.elementType = types.elemtype(exprType); // perhaps expr is an array?
if (tree.elementType == null) {
// or perhaps expr implements Iterable<T>?
Type base = types.asSuper(exprType, syms.iterableType.tsym);
if (base == null) {
log.error(tree.expr.pos(),
Errors.ForeachNotApplicableToType(exprType,
Fragments.TypeReqArrayOrIterable));
elemtype = types.createErrorType(exprType);
tree.elementType = types.createErrorType(exprType);
} else {
List<Type> iterableParams = base.allparams();
elemtype = iterableParams.isEmpty()
tree.elementType = iterableParams.isEmpty()
? syms.objectType
: types.wildUpperBound(iterableParams.head);
@ -1544,14 +1546,40 @@ public class Attr extends JCTree.Visitor {
}
}
}
if (tree.var.isImplicitlyTyped()) {
Type inferredType = chk.checkLocalVarType(tree.var, elemtype, tree.var.name);
setSyntheticVariableType(tree.var, inferredType);
if (tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
if (jcVariableDecl.isImplicitlyTyped()) {
Type inferredType = chk.checkLocalVarType(jcVariableDecl, tree.elementType, jcVariableDecl.name);
setSyntheticVariableType(jcVariableDecl, inferredType);
}
attribStat(jcVariableDecl, loopEnv);
chk.checkType(tree.expr.pos(), tree.elementType, jcVariableDecl.sym.type);
loopEnv.tree = tree; // before, we were not in loop!
attribStat(tree.body, loopEnv);
} else {
Assert.check(tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.PATTERN);
JCRecordPattern jcRecordPattern = (JCRecordPattern) tree.varOrRecordPattern;
attribExpr(jcRecordPattern, loopEnv, tree.elementType);
// for(<pattern> x : xs) { y }
// we include x's bindings when true in y
// we don't do anything with x's bindings when false
MatchBindings forWithRecordPatternBindings = matchBindings;
Env<AttrContext> recordPatternEnv = bindingEnv(loopEnv, forWithRecordPatternBindings.bindingsWhenTrue);
Type clazztype = jcRecordPattern.type;
checkCastablePattern(tree.expr.pos(), tree.elementType, clazztype);
recordPatternEnv.tree = tree; // before, we were not in loop!
try {
attribStat(tree.body, recordPatternEnv);
} finally {
recordPatternEnv.info.scope.leave();
}
}
attribStat(tree.var, loopEnv);
chk.checkType(tree.expr.pos(), elemtype, tree.var.sym.type);
loopEnv.tree = tree; // before, we were not in loop!
attribStat(tree.body, loopEnv);
result = null;
}
finally {

View File

@ -645,7 +645,21 @@ public class Flow {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
visitVarDef(tree.var);
if(tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
visitVarDef(jcVariableDecl);
} else if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
visitRecordPattern(jcRecordPattern);
Set<Symbol> coveredSymbols =
coveredSymbols(jcRecordPattern.pos(), List.of(jcRecordPattern));
boolean isExhaustive =
isExhaustive(jcRecordPattern.pos(), tree.elementType, coveredSymbols);
if (!isExhaustive) {
log.error(tree, Errors.ForeachNotExhaustiveOnType(jcRecordPattern.type, tree.elementType));
}
}
ListBuffer<PendingExit> prevPendingExits = pendingExits;
scan(tree.expr);
pendingExits = new ListBuffer<>();
@ -1358,7 +1372,11 @@ public class Flow {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
visitVarDef(tree.var);
if(tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
visitVarDef(jcVariableDecl);
} else if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
visitRecordPattern(jcRecordPattern);
}
ListBuffer<PendingExit> prevPendingExits = pendingExits;
scan(tree.expr);
pendingExits = new ListBuffer<>();
@ -2506,8 +2524,6 @@ public class Flow {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
visitVarDef(tree.var);
ListBuffer<PendingExit> prevPendingExits = pendingExits;
FlowKind prevFlowKind = flowKind;
flowKind = FlowKind.NORMAL;
@ -2516,7 +2532,13 @@ public class Flow {
final Bits initsStart = new Bits(inits);
final Bits uninitsStart = new Bits(uninits);
letInit(tree.pos(), tree.var.sym);
if(tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
visitVarDef(jcVariableDecl);
letInit(tree.pos(), jcVariableDecl.sym);
} else if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
visitRecordPattern(jcRecordPattern);
}
pendingExits = new ListBuffer<>();
int prevErrors = log.nerrors;
do {

View File

@ -28,6 +28,7 @@ package com.sun.tools.javac.comp;
import java.util.*;
import java.util.stream.Collectors;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Kinds.KindSelector;
import com.sun.tools.javac.code.Scope.WriteableScope;
@ -3467,13 +3468,18 @@ public class Lower extends TreeTranslator {
Type elemtype = types.elemtype(tree.expr.type);
JCExpression loopvarinit = make.Indexed(make.Ident(arraycache),
make.Ident(index)).setType(elemtype);
JCVariableDecl loopvardef = (JCVariableDecl)make.VarDef(tree.var.mods,
tree.var.name,
tree.var.vartype,
loopvarinit).setType(tree.var.type);
loopvardef.sym = tree.var.sym;
Assert.check(tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
JCVariableDecl jcVariableDecl = (JCVariableDecl) tree.varOrRecordPattern;
JCVariableDecl loopvardef = (JCVariableDecl)make.VarDef(jcVariableDecl.mods,
jcVariableDecl.name,
jcVariableDecl.vartype,
loopvarinit).setType(jcVariableDecl.type);
loopvardef.sym = jcVariableDecl.sym;
JCBlock body = make.
Block(0, List.of(loopvardef, tree.body));
Block(0, List.of(loopvardef, tree.body));
result = translate(make.
ForLoop(loopinit,
@ -3552,22 +3558,26 @@ public class Lower extends TreeTranslator {
itvar.type,
List.nil());
JCExpression vardefinit = make.App(make.Select(make.Ident(itvar), next));
if (tree.var.type.isPrimitive())
Assert.check(tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
JCVariableDecl var = (JCVariableDecl) tree.varOrRecordPattern;
if (var.type.isPrimitive())
vardefinit = make.TypeCast(types.cvarUpperBound(iteratorTarget), vardefinit);
else
vardefinit = make.TypeCast(tree.var.type, vardefinit);
JCVariableDecl indexDef = (JCVariableDecl)make.VarDef(tree.var.mods,
tree.var.name,
tree.var.vartype,
vardefinit).setType(tree.var.type);
indexDef.sym = tree.var.sym;
vardefinit = make.TypeCast(var.type, vardefinit);
JCVariableDecl indexDef = (JCVariableDecl) make.VarDef(var.mods,
var.name,
var.vartype,
vardefinit).setType(var.type);
indexDef.sym = var.sym;
JCBlock body = make.Block(0, List.of(indexDef, tree.body));
body.endpos = TreeInfo.endPos(tree.body);
result = translate(make.
ForLoop(List.of(init),
cond,
List.nil(),
body));
ForLoop(List.of(init),
cond,
List.nil(),
body));
patchTargets(body, tree, result);
}

View File

@ -26,6 +26,7 @@
package com.sun.tools.javac.comp;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Kinds;
@ -55,6 +56,7 @@ import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCBindingPattern;
import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
@ -689,6 +691,77 @@ public class TransPatterns extends TreeTranslator {
}
}
@Override
public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
bindingContext = new BasicBindingContext();
VarSymbol prevCurrentValue = currentValue;
try {
if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
/**
* A statement of the form
*
* <pre>
* for (<pattern> : coll ) stmt ;
* </pre>
*
* (where coll implements {@code Iterable<R>}) gets translated to
*
* <pre>{@code
* for (<type-of-coll-item> N$temp : coll) {
* switch (N$temp) {
* case <pattern>: stmt;
* case null: throw new MatchException();
* }
* }</pre>
*
*/
Type selectorType = types.classBound(tree.elementType);
currentValue = new VarSymbol(Flags.FINAL | Flags.SYNTHETIC,
names.fromString("patt" + tree.pos + target.syntheticNameChar() + "temp"),
selectorType,
currentMethodSym);
JCStatement newForVariableDeclaration =
make.at(tree.pos).VarDef(currentValue, null).setType(selectorType);
List<JCExpression> nestedNPEParams = List.of(makeNull());
JCNewClass nestedNPE = makeNewClass(syms.nullPointerExceptionType, nestedNPEParams);
List<JCExpression> matchExParams = List.of(makeNull(), nestedNPE);
JCThrow thr = make.Throw(makeNewClass(syms.matchExceptionType, matchExParams));
JCCase caseNull = make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(makeNull())), List.of(thr), null);
JCCase casePattern = make.Case(CaseTree.CaseKind.STATEMENT,
List.of(make.PatternCaseLabel(jcRecordPattern, null)),
List.of(translate(tree.body)),
null);
JCSwitch switchBody =
make.Switch(make.Ident(currentValue).setType(selectorType),
List.of(caseNull, casePattern));
switchBody.patternSwitch = true;
// re-using the same node to eliminate the need to re-patch targets (break/continue)
tree.varOrRecordPattern = newForVariableDeclaration.setType(selectorType);
tree.expr = translate(tree.expr);
tree.body = translate(switchBody);
JCTree.JCEnhancedForLoop newForEach = tree;
result = bindingContext.decorateStatement(newForEach);
} else {
super.visitForeachLoop(tree);
result = bindingContext.decorateStatement(tree);
}
} finally {
currentValue = prevCurrentValue;
bindingContext.pop();
}
}
@Override
public void visitWhileLoop(JCWhileLoop tree) {
bindingContext = new BasicBindingContext();

View File

@ -511,7 +511,7 @@ public class TransTypes extends TreeTranslator {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
tree.var = translate(tree.var, null);
tree.varOrRecordPattern = translate(tree.varOrRecordPattern, null);
Type iterableType = tree.expr.type;
tree.expr = translate(tree.expr, erasure(tree.expr.type));
if (types.elemtype(tree.expr.type) == null)

View File

@ -373,7 +373,7 @@ public class TreeDiffer extends TreeScanner {
public void visitForeachLoop(JCEnhancedForLoop tree) {
JCEnhancedForLoop that = (JCEnhancedForLoop) parameter;
result =
scan(tree.var, that.var)
scan(tree.varOrRecordPattern, that.varOrRecordPattern)
&& scan(tree.expr, that.expr)
&& scan(tree.body, that.body);
}

View File

@ -293,7 +293,7 @@ implements CRTFlags {
public void visitForeachLoop(JCEnhancedForLoop tree) {
SourceRange sr = new SourceRange(startPos(tree), endPos(tree));
sr.mergeWith(csp(tree.var));
sr.mergeWith(csp(tree.varOrRecordPattern));
sr.mergeWith(csp(tree.expr));
sr.mergeWith(csp(tree.body));
result = sr;

View File

@ -760,7 +760,6 @@ public class JavacParser implements Parser {
/** parses patterns.
*/
public JCPattern parsePattern(int pos, JCModifiers mods, JCExpression parsedType,
boolean allowVar, boolean checkGuard) {
JCPattern pattern;
@ -2818,25 +2817,47 @@ public class JavacParser implements Parser {
case FOR: {
nextToken();
accept(LPAREN);
List<JCStatement> inits = token.kind == SEMI ? List.nil() : forInit();
if (inits.length() == 1 &&
inits.head.hasTag(VARDEF) &&
((JCVariableDecl) inits.head).init == null &&
token.kind == COLON) {
JCVariableDecl var = (JCVariableDecl)inits.head;
JCTree pattern;
ForInitResult initResult = analyzeForInit();
if (initResult == ForInitResult.RecordPattern) {
int patternPos = token.pos;
JCModifiers mods = optFinal(0);
int typePos = token.pos;
JCExpression type = unannotatedType(false);
pattern = parsePattern(patternPos, mods, type, false, false);
if (pattern != null) {
checkSourceLevel(token.pos, Feature.PATTERN_SWITCH);
}
accept(COLON);
JCExpression expr = parseExpression();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForeachLoop(var, expr, body);
return F.at(pos).ForeachLoop(pattern, expr, body);
} else {
accept(SEMI);
JCExpression cond = token.kind == SEMI ? null : parseExpression();
accept(SEMI);
List<JCExpressionStatement> steps = token.kind == RPAREN ? List.nil() : forUpdate();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForLoop(inits, cond, steps, body);
List<JCStatement> inits = token.kind == SEMI ? List.nil() : forInit();
if (inits.length() == 1 &&
inits.head.hasTag(VARDEF) &&
((JCVariableDecl) inits.head).init == null &&
token.kind == COLON) {
JCVariableDecl var = (JCVariableDecl) inits.head;
accept(COLON);
JCExpression expr = parseExpression();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForeachLoop(var, expr, body);
} else {
accept(SEMI);
JCExpression cond = token.kind == SEMI ? null : parseExpression();
accept(SEMI);
List<JCExpressionStatement> steps = token.kind == RPAREN ? List.nil() : forUpdate();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForLoop(inits, cond, steps, body);
}
}
}
case WHILE: {
@ -2953,6 +2974,91 @@ public class JavacParser implements Parser {
}
}
private enum ForInitResult {
LocalVarDecl,
RecordPattern
}
@SuppressWarnings("fallthrough")
ForInitResult analyzeForInit() {
boolean inType = false;
boolean inSelectionAndParenthesis = false;
int typeParameterPossibleStart = -1;
outer: for (int lookahead = 0; ; lookahead++) {
TokenKind tk = S.token(lookahead).kind;
switch (tk) {
case DOT:
if (inType) break; // in qualified type
case COMMA:
typeParameterPossibleStart = lookahead;
break;
case QUES:
// "?" only allowed in a type parameter position - otherwise it's an expression
if (typeParameterPossibleStart == lookahead - 1) break;
else return ForInitResult.LocalVarDecl;
case EXTENDS: case SUPER: case AMP:
case GTGTGT: case GTGT: case GT:
case FINAL: case ELLIPSIS:
break;
case BYTE: case SHORT: case INT: case LONG: case FLOAT:
case DOUBLE: case BOOLEAN: case CHAR: case VOID:
if (peekToken(lookahead, IDENTIFIER)) {
return inSelectionAndParenthesis ? ForInitResult.RecordPattern
: ForInitResult.LocalVarDecl;
}
break;
case LPAREN:
if (lookahead != 0 && inType) {
inSelectionAndParenthesis = true;
inType = false;
}
break;
case RPAREN:
// a method call in the init part or a record pattern?
if (inSelectionAndParenthesis) {
if (peekToken(lookahead, DOT) ||
peekToken(lookahead, SEMI) ||
peekToken(lookahead, ARROW)) {
return ForInitResult.LocalVarDecl;
}
else if(peekToken(lookahead, COLON)) {
return ForInitResult.RecordPattern;
}
break;
}
case UNDERSCORE:
case ASSERT:
case ENUM:
case IDENTIFIER:
if (lookahead == 0) {
inType = true;
}
break;
case MONKEYS_AT: {
int prevLookahead = lookahead;
lookahead = skipAnnotation(lookahead);
if (typeParameterPossibleStart == prevLookahead - 1) {
// move possible start of type param after the anno
typeParameterPossibleStart = lookahead;
}
break;
}
case LBRACKET:
if (peekToken(lookahead, RBRACKET)) {
return inSelectionAndParenthesis ? ForInitResult.RecordPattern
: ForInitResult.LocalVarDecl;
}
return ForInitResult.LocalVarDecl;
case LT:
typeParameterPossibleStart = lookahead;
break;
default:
//this includes EOF
return ForInitResult.LocalVarDecl;
}
}
}
@Override
public JCStatement parseStatement() {
return parseStatementAsBlock();

View File

@ -603,6 +603,10 @@ compiler.err.foreach.not.applicable.to.type=\
required: {1}\n\
found: {0}
# 0: type, 1: type
compiler.err.foreach.not.exhaustive.on.type=\
Pattern {0} is not exhaustive on {1}
compiler.err.fp.number.too.large=\
floating-point number too large

View File

@ -1210,11 +1210,13 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
* The enhanced for loop.
*/
public static class JCEnhancedForLoop extends JCStatement implements EnhancedForLoopTree {
public JCVariableDecl var;
public JCTree varOrRecordPattern;
public JCExpression expr;
public JCStatement body;
protected JCEnhancedForLoop(JCVariableDecl var, JCExpression expr, JCStatement body) {
this.var = var;
public Type elementType;
protected JCEnhancedForLoop(JCTree varOrRecordPattern, JCExpression expr, JCStatement body) {
this.varOrRecordPattern = varOrRecordPattern;
this.expr = expr;
this.body = body;
}
@ -1224,7 +1226,11 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
@DefinedBy(Api.COMPILER_TREE)
public Kind getKind() { return Kind.ENHANCED_FOR_LOOP; }
@DefinedBy(Api.COMPILER_TREE)
public JCVariableDecl getVariable() { return var; }
public JCVariableDecl getVariable() {
return varOrRecordPattern instanceof JCVariableDecl var ? var : null;
}
@DefinedBy(Api.COMPILER_TREE)
public JCTree getVariableOrRecordPattern() { return varOrRecordPattern; }
@DefinedBy(Api.COMPILER_TREE)
public JCExpression getExpression() { return expr; }
@DefinedBy(Api.COMPILER_TREE)
@ -1237,6 +1243,10 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public Tag getTag() {
return FOREACHLOOP;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public EnhancedForLoopTree.DeclarationKind getDeclarationKind() {
return varOrRecordPattern.hasTag(VARDEF) ? DeclarationKind.VARIABLE : DeclarationKind.PATTERN;
}
}
/**
@ -3410,7 +3420,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
JCExpression cond,
List<JCExpressionStatement> step,
JCStatement body);
JCEnhancedForLoop ForeachLoop(JCVariableDecl var, JCExpression expr, JCStatement body);
JCEnhancedForLoop ForeachLoop(JCTree var, JCExpression expr, JCStatement body);
JCLabeledStatement Labelled(Name label, JCStatement body);
JCSwitch Switch(JCExpression selector, List<JCCase> cases);
JCSwitchExpression SwitchExpression(JCExpression selector, List<JCCase> cases);

View File

@ -801,7 +801,7 @@ public class Pretty extends JCTree.Visitor {
public void visitForeachLoop(JCEnhancedForLoop tree) {
try {
print("for (");
printExpr(tree.var);
printExpr(tree.varOrRecordPattern);
print(" : ");
printExpr(tree.expr);
print(") ");

View File

@ -223,10 +223,10 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
@DefinedBy(Api.COMPILER_TREE)
public JCTree visitEnhancedForLoop(EnhancedForLoopTree node, P p) {
JCEnhancedForLoop t = (JCEnhancedForLoop) node;
JCVariableDecl var = copy(t.var, p);
JCTree varOrRecordPattern = copy(t.varOrRecordPattern, p);
JCExpression expr = copy(t.expr, p);
JCStatement body = copy(t.body, p);
return M.at(t.pos).ForeachLoop(var, expr, body);
return M.at(t.pos).ForeachLoop(varOrRecordPattern, expr, body);
}
@DefinedBy(Api.COMPILER_TREE)

View File

@ -273,8 +273,8 @@ public class TreeMaker implements JCTree.Factory {
return tree;
}
public JCEnhancedForLoop ForeachLoop(JCVariableDecl var, JCExpression expr, JCStatement body) {
JCEnhancedForLoop tree = new JCEnhancedForLoop(var, expr, body);
public JCEnhancedForLoop ForeachLoop(JCTree varOrRecordPattern, JCExpression expr, JCStatement body) {
JCEnhancedForLoop tree = new JCEnhancedForLoop(varOrRecordPattern, expr, body);
tree.pos = pos;
return tree;
}

View File

@ -162,7 +162,7 @@ public class TreeScanner extends Visitor {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
scan(tree.var);
scan(tree.varOrRecordPattern);
scan(tree.expr);
scan(tree.body);
}

View File

@ -189,7 +189,7 @@ public class TreeTranslator extends JCTree.Visitor {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
tree.var = translate(tree.var);
tree.varOrRecordPattern = translate(tree.varOrRecordPattern);
tree.expr = translate(tree.expr);
tree.body = translate(tree.body);
result = tree;

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2022, 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.misc.feature.deconstruction.patterns
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// key: compiler.err.foreach.not.exhaustive.on.type
// options: --enable-preview -source ${jdk.version} -Xlint:preview
import java.util.List;
class ForeachNotExhaustive {
void m(List<Object> points) {
for (Point(var x, var y): points) {
System.out.println();
}
}
record Point(Integer x, Integer y) { }
}

View File

@ -720,7 +720,7 @@ public class DPrinter {
@Override
public void visitForeachLoop(JCEnhancedForLoop tree) {
printTree("var", tree.var);
printTree("var", tree.varOrRecordPattern);
printTree("expr", tree.expr);
printTree("body", tree.body);
}

View File

@ -35,16 +35,30 @@ import com.sun.source.tree.CaseLabelTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ConstantCaseLabelTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.PatternCaseLabelTree;
import com.sun.source.tree.PatternTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.parser.JavacParser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
public class DisambiguatePatterns {
@ -110,14 +124,44 @@ public class DisambiguatePatterns {
ExpressionType.EXPRESSION);
test.disambiguationTest("a & b",
ExpressionType.EXPRESSION);
test.forDisambiguationTest("T[] a", ForType.ENHANCED_FOR);
test.forDisambiguationTest("T[].class.getName()", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("T[].class", ForType.TRADITIONAL_FOR, "compiler.err.not.stmt");
test.forDisambiguationTest("R(T[] a)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("Point(Integer a, Integer b)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("ForEachPatterns.Point(Integer a, Integer b)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<Integer>(Integer a, Integer b)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("@Annot(field = \"test\") Point p", ForType.ENHANCED_FOR);
test.forDisambiguationTest("GPoint<Point>(Point(Integer a, Integer b), Point c)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<Point>(Point(var a, Integer b), Point c)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<VoidPoint>(VoidPoint(), VoidPoint())", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("RecordOfLists(List<Integer> lr)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("RecordOfLists2(List<List<Integer>> lr)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<@Annot(field = \"\") ? extends Point>(var x, var y)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("method()", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("method(), method()", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("method2((Integer a) -> 42)", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("m(cond ? b() : i)", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("m((GPoint<?>)null, cond ? b() : i)", ForType.TRADITIONAL_FOR);
}
private final ParserFactory factory;
private final List<String> errors = new ArrayList<>();
public DisambiguatePatterns() {
public DisambiguatePatterns() throws URISyntaxException {
Context context = new Context();
context.put(DiagnosticListener.class, d -> {
if (d.getKind() == Diagnostic.Kind.ERROR) {
errors.add(d.getCode());
}
});
JavacFileManager jfm = new JavacFileManager(context, true, Charset.defaultCharset());
Options.instance(context).put(Option.PREVIEW, "");
SimpleJavaFileObject source =
new SimpleJavaFileObject(new URI("mem://Test.java"), JavaFileObject.Kind.SOURCE) {};
Log.instance(context).useSource(source);
factory = ParserFactory.instance(context);
}
@ -148,9 +192,66 @@ public class DisambiguatePatterns {
}
}
void forDisambiguationTest(String snippet, ForType forType, String... expectedErrors) {
errors.clear();
String codeTemplate = switch (forType) {
case TRADITIONAL_FOR ->
"""
public class Test {
private void test() {
for (SNIPPET; ;) {
}
}
}
""";
case ENHANCED_FOR, ENHANCED_FOR_WITH_PATTERNS ->
"""
public class Test {
private void test() {
for (SNIPPET : collection) {
}
}
}
""";
};
String code = codeTemplate.replace("SNIPPET", snippet);
JavacParser parser = factory.newParser(code, false, false, false);
CompilationUnitTree result = parser.parseCompilationUnit();
if (!Arrays.asList(expectedErrors).equals(errors)) {
throw new AssertionError("Expected errors: " + Arrays.asList(expectedErrors) +
", actual: " + errors +
", for: " + code);
}
ClassTree clazz = (ClassTree) result.getTypeDecls().get(0);
MethodTree method = (MethodTree) clazz.getMembers().get(0);
StatementTree st = method.getBody().getStatements().get(0);
if (forType == ForType.TRADITIONAL_FOR) {
if (st.getKind() != Kind.FOR_LOOP) {
throw new AssertionError("Unpected statement: " + st);
}
} else {
EnhancedForLoopTree ef = (EnhancedForLoopTree) st;
ForType actualType = switch (ef.getVariableOrRecordPattern()) {
case PatternTree pattern -> ForType.ENHANCED_FOR_WITH_PATTERNS;
default -> ForType.ENHANCED_FOR;
};
if (forType != actualType) {
throw new AssertionError("Expected: " + forType + ", actual: " + actualType +
", for: " + code + ", parsed: " + result);
}
}
}
enum ExpressionType {
PATTERN,
EXPRESSION;
}
enum ForType {
TRADITIONAL_FOR,
ENHANCED_FOR,
ENHANCED_FOR_WITH_PATTERNS;
}
}

View File

@ -0,0 +1,254 @@
/*
* @test /nodynamiccopyright/
* @summary
* @enablePreview
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
public class ForEachPatterns {
public static void main(String[] args) {
List<Point> in = List.of(new Point(1, 2), new Point(2, 3));
List<IPoint> in_iface = List.of(new Point(1, 2), new Point(2, 3));
List inRaw = List.of(new Point(1, 2), new Point(2, 3), new Frog(3, 4));
List<PointEx> inWithPointEx = List.of(new PointEx(1, 2));
byte[] inBytes = { (byte) 127, (byte) 127 };
List<Point> inWithNullComponent = List.of(new Point(1, null), new Point(2, 3));
Point[] inArray = in.toArray(Point[]::new);
List<WithPrimitives> inWithPrimitives = List.of(new WithPrimitives(1, 2), new WithPrimitives(2, 3));
IParent recs [] = { new Rec(1) };
List<Point> inWithNull = new ArrayList<>();
{
inWithNull.add(new Point(2, 3));
inWithNull.add(null);
}
assertEquals(8, iteratorEnhancedFor(in));
assertEquals(8, arrayEnhancedFor(inArray));
assertEquals(8, simpleDecostructionPatternWithAccesses(in));
assertEx(ForEachPatterns::simpleDecostructionPatternWithAccesses, null, NullPointerException.class);
assertMatchExceptionWithNested(ForEachPatterns::simpleDecostructionPatternWithAccesses, inWithNull, NullPointerException.class);
assertEx(ForEachPatterns::simpleDecostructionPatternWithAccesses, inWithNullComponent, NullPointerException.class);
assertMatchExceptionWithNested(ForEachPatterns::simpleDecostructionPatternException, inWithPointEx, TestPatternFailed.class);
assertEx(ForEachPatterns::simpleDecostructionPatternWithAccesses, (List<Point>) inRaw, ClassCastException.class);
assertEquals(2, simpleDecostructionPatternNoComponentAccess(in));
assertMatchExceptionWithNested(ForEachPatterns::simpleDecostructionPatternNoComponentAccess, inWithNull, NullPointerException.class);
assertEquals(2, simpleDecostructionPatternNoComponentAccess(inWithNullComponent));
assertEquals(8, varAndConcrete(in));
assertEquals(3, returnFromEnhancedFor(in));
assertEquals(0, breakFromEnhancedFor(in));
assertEquals(254, primitiveWidening(inBytes));
assertEquals(8, sealedRecordPassBaseType(in_iface));
assertEquals(8, withPrimitives(inWithPrimitives));
assertEquals(List.of(Color.RED), JEPExample());
assertEquals(1, arrayWithSealed(recs));
}
static int iteratorEnhancedFor(List<Point> points) {
int result = 0;
for (Point(Integer a, Integer b) : points) {
result += a + b;
}
return result;
}
static int arrayEnhancedFor(Point[] points) {
int result = 0;
for (Point(Integer a, Integer b) : points) {
result += a + b;
}
return result;
}
static int simpleDecostructionPatternWithAccesses(List<Point> points) {
int result = 0;
for (Point(var a, var b): points) {
result += a + b;
}
return result;
}
static int simpleDecostructionPatternException(List<PointEx> points) {
int result = 0;
for (PointEx(var a, var b): points) {
result += a + b;
}
return result;
}
static int simpleDecostructionPatternNoComponentAccess(List<Point> points) {
int result = 0;
for (Point(var a, var b): points) {
result += 1;
}
return result;
}
static int varAndConcrete(List<Point> points) {
int result = 0;
for (Point(Integer a, var b): points) {
result += a + b;
}
return result;
}
static int returnFromEnhancedFor(List<Point> points) {
for (Point(var a, var b): points) {
return a + b;
}
return -1;
}
static int breakFromEnhancedFor(List<Point> points) {
int i = 1;
int result = 0;
for (Point(var a, var b): points) {
if (i == 1) break;
else result += a + b;
}
return result;
}
static int sealedRecordPassBaseType(List<IPoint> points) {
int result = 0;
for(Point(var x, var y) : points) {
result += (x + y);
}
return result;
}
static int withPrimitives(List<WithPrimitives> points) {
int result = 0;
for (WithPrimitives(int a, double b): points) {
result += a + (int) b;
}
return result;
}
// Simpler pos tests with local variable declarations
// Should pass now and in the future if local variable
// declaration is subsumed by patterns (not just record patterns)
static int primitiveWidening(byte[] inBytes) {
int acc = 0;
for (int i: inBytes) {
acc += i;
}
return acc;
}
static int applicability1(List<Point> points) {
for (IPoint p: points) {
System.out.println(p);
}
return -1;
}
static int applicability2(List<Object> points) {
for (Object p: points) {
System.out.println(p);
}
return -1;
}
static List<Color> JEPExample() {
Rectangle rect = new Rectangle(
new ColoredPoint(new Point(1,2), Color.RED),
new ColoredPoint(new Point(3,4), Color.GREEN)
);
Rectangle[] rArr = {rect};
return printUpperLeftColors(rArr);
}
//where
static List<Color> printUpperLeftColors(Rectangle[] r) {
List<Color> ret = new ArrayList<>();
for (Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr): r) {
ret.add(c);
}
return ret;
}
static int arrayWithSealed(IParent[] recs){
for (Rec(int a) : recs) {
return a;
}
return -1;
}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
sealed interface IParent permits Rec {}
record Rec(int a) implements IParent {}
sealed interface IPoint permits Point {}
record Point(Integer x, Integer y) implements IPoint { }
record GPoint<T>(T x, T y) { }
record VoidPoint() { }
record RecordOfLists(List<Integer> o) {}
record RecordOfLists2(List<List<Integer>> o) {}
@Target({ElementType.TYPE_USE, ElementType.LOCAL_VARIABLE})
@interface Annot {
String field();
}
record Frog(Integer x, Integer y) { }
record PointEx(Integer x, Integer y) {
@Override
public Integer x() {
throw new TestPatternFailed(EXCEPTION_MESSAGE);
}
}
record WithPrimitives(int x, double y) { }
static final String EXCEPTION_MESSAGE = "exception-message";
public static class TestPatternFailed extends AssertionError {
public TestPatternFailed(String message) {
super(message);
}
}
// error handling
static void fail(String message) {
throw new AssertionError(message);
}
static void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + "," +
"got: " + actual);
}
}
static <T> void assertMatchExceptionWithNested(Function<List<T>, Integer> f, List<T> points, Class<?> nestedExceptionClass) {
try {
f.apply(points);
fail("Expected an exception, but none happened!");
}
catch(Exception ex) {
assertEquals(MatchException.class, ex.getClass());
MatchException me = (MatchException) ex;
assertEquals(nestedExceptionClass, me.getCause().getClass());
}
}
static <T> void assertEx(Function<List<T>, Integer> f, List<T> points, Class<?> exceptionClass) {
try {
f.apply(points);
fail("Expected an exception, but none happened!");
}
catch(Exception ex) {
assertEquals(exceptionClass, ex.getClass());
}
}
}

View File

@ -0,0 +1,46 @@
/*
* @test /nodynamiccopyright/
* @summary
* @enablePreview
* @compile/fail/ref=ForEachPatternsErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW ForEachPatternsErrors.java
*/
import java.util.List;
public class ForEachPatternsErrors {
static void exhaustivity_error1(List<Object> points) {
for (Point(var x, var y): points) {
System.out.println();
}
}
static void exhaustivity_error2(List points) {
for (Point(var x, var y): points) {
System.out.println();
}
}
static void exhaustivity_error3(List<OPoint> opoints) {
for (OPoint(String s, String t) : opoints) {
System.out.println(s);
}
}
static void exhaustivity_error4(List<?> f) {
for (Rec(var x): f){
}
}
static void applicability_error(List<Object> points) {
for (Interface p: points) {
System.out.println(p);
}
}
record Rec(String x) { }
interface Interface {}
sealed interface IPoint permits Point {}
record Point(Integer x, Integer y) implements IPoint { }
record OPoint(Object x, Object y) { }
}

View File

@ -0,0 +1,8 @@
ForEachPatternsErrors.java:36:27: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Object, ForEachPatternsErrors.Interface)
ForEachPatternsErrors.java:13:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.Point, java.lang.Object
ForEachPatternsErrors.java:19:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.Point, java.lang.Object
ForEachPatternsErrors.java:25:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.OPoint, ForEachPatternsErrors.OPoint
ForEachPatternsErrors.java:31:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.Rec, compiler.misc.type.captureof: 1, ?
- compiler.note.preview.filename: ForEachPatternsErrors.java, DEFAULT
- compiler.note.preview.recompile
5 errors

View File

@ -0,0 +1,12 @@
/*
* @test /nodynamiccopyright/
* @summary
* @enablePreview
* @compile -XDfind=all ForEachTestAllAnalyzers.java
*/
public class ForEachTestAllAnalyzers {
private void test(Iterable<? extends R> l) {
for (R(Object a) : l) { }
}
record R(Object a) {}
}

View File

@ -25,7 +25,7 @@
* @test
* @enablePreview
*/
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
@ -44,6 +44,8 @@ public class GenericRecordDeconstructionPattern {
runTest(this::runSwitchInference3);
runTest(this::runSwitchInference4);
testInference3();
assertEquals(0, forEachInference(List.of(new Box(""))));
assertEquals(1, forEachInference(List.of(new Box(null))));
}
void runTest(Function<Box<String>, Integer> test) {
@ -99,6 +101,13 @@ public class GenericRecordDeconstructionPattern {
: -1;
}
int forEachInference(Iterable<I<String>> b) {
for (Box(var s) : b) {
return s == null ? 1 : s.length();
}
return -1;
}
void testInference3() {
I<I<String>> b = new Box<>(new Box<>(null));
assertEquals(1, runSwitchInferenceNested(b));