mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-28 11:53:09 +00:00
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:
parent
fc9d419b4f
commit
2cb64a7557
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(") ");
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) { }
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
254
test/langtools/tools/javac/patterns/ForEachPatterns.java
Normal file
254
test/langtools/tools/javac/patterns/ForEachPatterns.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) { }
|
||||
}
|
||||
@ -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
|
||||
@ -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) {}
|
||||
}
|
||||
@ -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));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user