8294942: Compiler implementation for Record Patterns (Second Preview)

8294945: Compiler implementation for Pattern Matching for switch (Fourth Preview)

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:
Jan Lahoda 2022-12-01 08:02:40 +00:00
parent c612512014
commit 756dd5bb0d
38 changed files with 1260 additions and 363 deletions

@ -48,11 +48,5 @@ public interface DeconstructionPatternTree extends PatternTree {
*/
List<? extends PatternTree> getNestedPatterns();
/**
* Returns the binding variable.
* @return the binding variable
*/
VariableTree getVariable();
}

@ -25,6 +25,8 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A tree node for an {@code instanceof} expression.
*
@ -40,6 +42,23 @@ package com.sun.source.tree;
* @since 1.6
*/
public interface InstanceOfTree extends ExpressionTree {
/**
* Two possible variants of instanceof expressions:
* <ul>
* <li> testing types, and
* <li> performing pattern matching
* </ul>
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public enum TestKind {
/** instanceof only testing a type */
TYPE,
/** instanceof doing a pattern matching */
PATTERN
}
/**
* Returns the expression to be tested.
* @return the expression
@ -73,4 +92,13 @@ public interface InstanceOfTree extends ExpressionTree {
* @since 16
*/
PatternTree getPattern();
/**
* Returns the kind of this instanceof expression.
*
* @return the kind of this instanceof expression
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
TestKind getTestKind();
}

@ -839,7 +839,6 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
public R visitDeconstructionPattern(DeconstructionPatternTree node, P p) {
R r = scan(node.getDeconstructor(), p);
r = scanAndReduce(node.getNestedPatterns(), p, r);
r = scanAndReduce(node.getVariable(), p, r);
return r;
}

@ -2042,6 +2042,11 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons
this.kind = Kind.THROWS;
}
public void setNormal() {
Assert.check(this.kind == Kind.CAPTURED);
this.kind = Kind.NORMAL;
}
/**
* Returns a new copy of this undet var.
*/

@ -1665,7 +1665,8 @@ public class Attr extends JCTree.Visitor {
} else {
patternSwitch = cases.stream()
.flatMap(c -> c.labels.stream())
.anyMatch(l -> l.hasTag(PATTERNCASELABEL));
.anyMatch(l -> l.hasTag(PATTERNCASELABEL) ||
TreeInfo.isNullCaseLabel(l));
}
// Attribute all cases and
@ -1677,7 +1678,6 @@ public class Attr extends JCTree.Visitor {
boolean hasNullPattern = false; // Is there a null pattern?
CaseTree.CaseKind caseKind = null;
boolean wasError = false;
MatchBindings prevBindings = null;
for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
if (caseKind == null) {
@ -1687,7 +1687,7 @@ public class Attr extends JCTree.Visitor {
Errors.SwitchMixingCaseTypes);
wasError = true;
}
MatchBindings currentBindings = prevBindings;
MatchBindings currentBindings = null;
boolean wasUnconditionalPattern = hasUnconditionalPattern;
for (JCCaseLabel label : c.labels) {
if (label instanceof JCConstantCaseLabel constLabel) {
@ -1751,7 +1751,7 @@ public class Attr extends JCTree.Visitor {
} else if (label instanceof JCPatternCaseLabel patternlabel) {
//pattern
JCPattern pat = patternlabel.pat;
attribExpr(pat, switchEnv);
attribExpr(pat, switchEnv, seltype);
Type primaryType = TreeInfo.primaryPatternType(pat);
if (!primaryType.hasTag(TYPEVAR)) {
primaryType = chk.checkClassOrArrayType(pat.pos(), primaryType);
@ -1805,9 +1805,6 @@ public class Attr extends JCTree.Visitor {
preFlow(c);
c.completesNormally = flow.aliveAfter(caseEnv, c, make);
prevBindings = c.caseKind == CaseTree.CaseKind.STATEMENT && c.completesNormally ? currentBindings
: null;
}
if (patternSwitch) {
chk.checkSwitchCaseStructure(cases);
@ -4078,7 +4075,7 @@ public class Attr extends JCTree.Visitor {
if (tree.pattern.getTag() == BINDINGPATTERN ||
tree.pattern.getTag() == PARENTHESIZEDPATTERN ||
tree.pattern.getTag() == RECORDPATTERN) {
attribTree(tree.pattern, env, unknownExprInfo);
attribExpr(tree.pattern, env, exprtype);
clazztype = tree.pattern.type;
if (types.isSubtype(exprtype, clazztype) &&
!exprtype.isErroneous() && !clazztype.isErroneous() &&
@ -4146,8 +4143,7 @@ public class Attr extends JCTree.Visitor {
public void visitBindingPattern(JCBindingPattern tree) {
Type type;
if (tree.var.vartype != null) {
ResultInfo varInfo = new ResultInfo(KindSelector.TYP, resultInfo.pt, resultInfo.checkContext);
type = attribTree(tree.var.vartype, env, varInfo);
type = attribType(tree.var.vartype, env);
} else {
type = resultInfo.pt;
}
@ -4170,14 +4166,20 @@ public class Attr extends JCTree.Visitor {
@Override
public void visitRecordPattern(JCRecordPattern tree) {
tree.type = attribType(tree.deconstructor, env);
Type type = attribType(tree.deconstructor, env);
if (type.isRaw() && type.tsym.getTypeParameters().nonEmpty()) {
Type inferred = infer.instantiatePatternType(resultInfo.pt, type.tsym);
if (inferred == null) {
log.error(tree.pos(), Errors.PatternTypeCannotInfer);
} else {
type = inferred;
}
}
tree.type = tree.deconstructor.type = type;
Type site = types.removeWildcards(tree.type);
List<Type> expectedRecordTypes;
if (site.tsym.kind == Kind.TYP && ((ClassSymbol) site.tsym).isRecord()) {
ClassSymbol record = (ClassSymbol) site.tsym;
if (record.type.getTypeArguments().nonEmpty() && tree.type.isRaw()) {
log.error(tree.pos(),Errors.RawDeconstructionPattern);
}
expectedRecordTypes = record.getRecordComponents()
.stream()
.map(rc -> types.memberType(site, rc)).collect(List.collector());
@ -4195,10 +4197,7 @@ public class Attr extends JCTree.Visitor {
Env<AttrContext> localEnv = env.dup(tree, env.info.dup(env.info.scope.dup()));
try {
while (recordTypes.nonEmpty() && nestedPatterns.nonEmpty()) {
boolean nestedIsVarPattern = false;
nestedIsVarPattern |= nestedPatterns.head.hasTag(BINDINGPATTERN) &&
((JCBindingPattern) nestedPatterns.head).var.vartype == null;
attribExpr(nestedPatterns.head, localEnv, nestedIsVarPattern ? recordTypes.head : Type.noType);
attribExpr(nestedPatterns.head, localEnv, recordTypes.head);
checkCastablePattern(nestedPatterns.head.pos(), recordTypes.head, nestedPatterns.head.type);
outBindings.addAll(matchBindings.bindingsWhenTrue);
matchBindings.bindingsWhenTrue.forEach(localEnv.info.scope::enter);
@ -4216,21 +4215,6 @@ public class Attr extends JCTree.Visitor {
Errors.IncorrectNumberOfNestedPatterns(expectedRecordTypes,
nestedTypes));
}
if (tree.var != null) {
BindingSymbol v = new BindingSymbol(tree.var.mods.flags, tree.var.name, tree.type,
localEnv.info.scope.owner);
v.pos = tree.pos;
tree.var.sym = v;
if (chk.checkUnique(tree.var.pos(), v, localEnv.info.scope)) {
chk.checkTransparentVar(tree.var.pos(), v, localEnv.info.scope);
}
if (tree.var.vartype != null) {
annotate.annotateLater(tree.var.mods.annotations, localEnv, v, tree.pos());
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, localEnv, v, tree.var.pos());
annotate.flush();
}
outBindings.add(v);
}
} finally {
localEnv.info.scope.leave();
}

@ -4346,74 +4346,107 @@ public class Check {
* @param cases the cases that should be checked.
*/
void checkSwitchCaseStructure(List<JCCase> cases) {
boolean wasConstant = false; // Seen a constant in the same case label
boolean wasDefault = false; // Seen a default in the same case label
boolean wasNullPattern = false; // Seen a null pattern in the same case label,
//or fall through from a null pattern
boolean wasPattern = false; // Seen a pattern in the same case label
//or fall through from a pattern
boolean wasTypePattern = false; // Seen a pattern in the same case label
//or fall through from a type pattern
boolean wasNonEmptyFallThrough = false;
for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
for (JCCaseLabel label : c.labels) {
if (label.hasTag(CONSTANTCASELABEL)) {
JCExpression expr = ((JCConstantCaseLabel) label).expr;
if (TreeInfo.isNull(expr)) {
if (wasPattern && !wasTypePattern && !wasNonEmptyFallThrough) {
log.error(label.pos(), Errors.FlowsThroughFromPattern);
if (c.labels.head instanceof JCConstantCaseLabel constLabel) {
if (TreeInfo.isNull(constLabel.expr)) {
if (c.labels.tail.nonEmpty()) {
if (c.labels.tail.head instanceof JCDefaultCaseLabel defLabel) {
if (c.labels.tail.tail.nonEmpty()) {
log.error(c.labels.tail.tail.head.pos(), Errors.InvalidCaseLabelCombination);
}
} else {
log.error(c.labels.tail.head.pos(), Errors.InvalidCaseLabelCombination);
}
wasNullPattern = true;
} else {
if (wasPattern && !wasNonEmptyFallThrough) {
log.error(label.pos(), Errors.FlowsThroughFromPattern);
}
wasConstant = true;
}
} else if (label.hasTag(DEFAULTCASELABEL)) {
if (wasPattern && !wasNonEmptyFallThrough) {
log.error(label.pos(), Errors.FlowsThroughFromPattern);
}
wasDefault = true;
} else {
JCPattern pat = ((JCPatternCaseLabel) label).pat;
while (pat instanceof JCParenthesizedPattern parenthesized) {
pat = parenthesized.pattern;
for (JCCaseLabel label : c.labels.tail) {
if (!(label instanceof JCConstantCaseLabel) || TreeInfo.isNullCaseLabel(label)) {
log.error(label.pos(), Errors.InvalidCaseLabelCombination);
break;
}
}
boolean isTypePattern = pat.hasTag(BINDINGPATTERN);
if (wasPattern || wasConstant || wasDefault ||
(wasNullPattern && (!isTypePattern || wasNonEmptyFallThrough))) {
log.error(label.pos(), Errors.FlowsThroughToPattern);
}
wasPattern = true;
wasTypePattern = isTypePattern;
}
} else {
if (c.labels.tail.nonEmpty()) {
log.error(c.labels.tail.head.pos(), Errors.FlowsThroughFromPattern);
}
}
}
boolean completesNormally = c.caseKind == CaseTree.CaseKind.STATEMENT ? c.completesNormally
: false;
boolean isCaseStatementGroup = cases.nonEmpty() &&
cases.head.caseKind == CaseTree.CaseKind.STATEMENT;
if (c.stats.nonEmpty()) {
wasConstant = false;
wasDefault = false;
wasNullPattern &= completesNormally;
wasPattern &= completesNormally;
wasTypePattern &= completesNormally;
if (isCaseStatementGroup) {
boolean previousCompletessNormally = false;
for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
if (previousCompletessNormally &&
c.stats.nonEmpty() &&
c.labels.head instanceof JCPatternCaseLabel patternLabel &&
hasBindings(patternLabel.pat)) {
log.error(c.labels.head.pos(), Errors.FlowsThroughToPattern);
} else if (c.stats.isEmpty() &&
c.labels.head instanceof JCPatternCaseLabel patternLabel &&
hasBindings(patternLabel.pat) &&
hasStatements(l.tail)) {
log.error(c.labels.head.pos(), Errors.FlowsThroughFromPattern);
}
previousCompletessNormally = c.completesNormally;
}
wasNonEmptyFallThrough = c.stats.nonEmpty() && completesNormally;
}
}
boolean hasBindings(JCPattern p) {
boolean[] bindings = new boolean[1];
new TreeScanner() {
@Override
public void visitBindingPattern(JCBindingPattern tree) {
bindings[0] = true;
super.visitBindingPattern(tree);
}
}.scan(p);
return bindings[0];
}
boolean hasStatements(List<JCCase> cases) {
for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
if (l.head.stats.nonEmpty()) {
return true;
}
}
return false;
}
void checkSwitchCaseLabelDominated(List<JCCase> cases) {
List<JCCaseLabel> caseLabels = List.nil();
boolean seenDefault = false;
boolean seenDefaultLabel = false;
boolean warnDominatedByDefault = false;
for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
for (JCCaseLabel label : c.labels) {
if (label.hasTag(DEFAULTCASELABEL) || TreeInfo.isNullCaseLabel(label)) {
if (label.hasTag(DEFAULTCASELABEL)) {
seenDefault = true;
seenDefaultLabel |=
TreeInfo.isNullCaseLabel(c.labels.head);
continue;
}
if (TreeInfo.isNullCaseLabel(label)) {
if (seenDefault) {
log.error(label.pos(), Errors.PatternDominated);
}
continue;
}
if (seenDefault && !warnDominatedByDefault) {
if (label.hasTag(PATTERNCASELABEL) ||
(label instanceof JCConstantCaseLabel && seenDefaultLabel)) {
log.error(label.pos(), Errors.PatternDominated);
warnDominatedByDefault = true;
}
}
Type currentType = labelType(label);
for (JCCaseLabel testCaseLabel : caseLabels) {
Type testType = labelType(testCaseLabel);

@ -57,6 +57,7 @@ import com.sun.tools.javac.code.Type.TypeVar;
import static com.sun.tools.javac.code.TypeTag.BOOLEAN;
import static com.sun.tools.javac.code.TypeTag.NONE;
import static com.sun.tools.javac.code.TypeTag.VOID;
import com.sun.tools.javac.code.Types.UniqueType;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;
@ -688,7 +689,7 @@ public class Flow {
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases);
if (exhaustiveSwitch) {
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.selector, tree.cases);
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.cases);
tree.isExhaustive |= isExhaustive(tree.selector.pos(), tree.selector.type, coveredSymbols);
if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustiveStatement);
@ -723,7 +724,7 @@ public class Flow {
}
}
}
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.selector, tree.cases);
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.cases);
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) ||
isExhaustive(tree.selector.pos(), tree.selector.type, coveredSymbols);
@ -735,7 +736,7 @@ public class Flow {
}
private Set<Symbol> coveredSymbolsForCases(DiagnosticPosition pos,
JCExpression selector, List<JCCase> cases) {
List<JCCase> cases) {
HashSet<JCTree> labelValues = cases.stream()
.flatMap(c -> c.labels.stream())
.filter(TreeInfo::unguardedCaseLabel)
@ -743,13 +744,13 @@ public class Flow {
.map(l -> l.hasTag(CONSTANTCASELABEL) ? ((JCConstantCaseLabel) l).expr
: ((JCPatternCaseLabel) l).pat)
.collect(Collectors.toCollection(HashSet::new));
return coveredSymbols(pos, selector.type, labelValues);
return coveredSymbols(pos, labelValues);
}
private Set<Symbol> coveredSymbols(DiagnosticPosition pos, Type targetType,
private Set<Symbol> coveredSymbols(DiagnosticPosition pos,
Iterable<? extends JCTree> labels) {
Set<Symbol> coveredSymbols = new HashSet<>();
Map<Symbol, List<JCRecordPattern>> deconstructionPatternsBySymbol = new HashMap<>();
Map<UniqueType, List<JCRecordPattern>> deconstructionPatternsByType = new HashMap<>();
for (JCTree labelValue : labels) {
switch (labelValue.getTag()) {
@ -761,12 +762,12 @@ public class Flow {
}
case RECORDPATTERN -> {
JCRecordPattern dpat = (JCRecordPattern) labelValue;
Symbol type = dpat.record;
UniqueType type = new UniqueType(dpat.type, types);
List<JCRecordPattern> augmentedPatterns =
deconstructionPatternsBySymbol.getOrDefault(type, List.nil())
deconstructionPatternsByType.getOrDefault(type, List.nil())
.prepend(dpat);
deconstructionPatternsBySymbol.put(type, augmentedPatterns);
deconstructionPatternsByType.put(type, augmentedPatterns);
}
default -> {
@ -777,19 +778,18 @@ public class Flow {
}
}
}
for (Entry<Symbol, List<JCRecordPattern>> e : deconstructionPatternsBySymbol.entrySet()) {
for (Entry<UniqueType, List<JCRecordPattern>> e : deconstructionPatternsByType.entrySet()) {
if (e.getValue().stream().anyMatch(r -> r.nested.size() != r.record.getRecordComponents().size())) {
coveredSymbols.add(syms.errSymbol);
}
else if (coversDeconstructionFromComponent(pos, targetType, e.getValue(), 0)) {
coveredSymbols.add(e.getKey());
} else if (coversDeconstructionFromComponent(pos, e.getKey().type, e.getValue(), 0)) {
coveredSymbols.add(e.getKey().type.tsym);
}
}
return coveredSymbols;
}
private boolean coversDeconstructionFromComponent(DiagnosticPosition pos,
Type targetType,
Type recordType,
List<JCRecordPattern> deconstructionPatterns,
int component) {
//Given a set of record patterns for the same record, and a starting component,
@ -812,9 +812,9 @@ public class Flow {
}
//for the first tested component, gather symbols covered by the nested patterns:
Type instantiatedComponentType = types.memberType(targetType, components.get(component));
Type instantiatedComponentType = types.memberType(recordType, components.get(component));
List<JCPattern> nestedComponentPatterns = deconstructionPatterns.map(d -> d.nested.get(component));
Set<Symbol> coveredSymbolsForComponent = coveredSymbols(pos, instantiatedComponentType,
Set<Symbol> coveredSymbolsForComponent = coveredSymbols(pos,
nestedComponentPatterns);
//for each of the symbols covered by the starting component, find all deconstruction patterns
@ -854,7 +854,7 @@ public class Flow {
Set<Symbol> covered = new HashSet<>();
for (Entry<Symbol, List<JCRecordPattern>> e : coveredSymbol2Patterns.entrySet()) {
if (coversDeconstructionFromComponent(pos, targetType, e.getValue(), component + 1)) {
if (coversDeconstructionFromComponent(pos, recordType, e.getValue(), component + 1)) {
covered.add(e.getKey());
}
}
@ -935,7 +935,7 @@ public class Flow {
}
case TYPEVAR -> isExhaustive(pos, ((TypeVar) seltype).getUpperBound(), covered);
default -> {
yield covered.contains(seltype.tsym);
yield covered.contains(types.erasure(seltype).tsym);
}
};
}
@ -2970,14 +2970,6 @@ public class Flow {
scan(tree.guard);
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
super.visitRecordPattern(tree);
if (tree.var != null) {
initParam(tree.var);
}
}
void referenced(Symbol sym) {
unrefdResources.remove(sym);
}
@ -3152,7 +3144,6 @@ public class Flow {
public void visitRecordPattern(JCRecordPattern tree) {
scan(tree.deconstructor);
scan(tree.nested);
scan(tree.var);
}
@Override

@ -69,6 +69,7 @@ import java.util.function.BiPredicate;
import java.util.function.Predicate;
import static com.sun.tools.javac.code.TypeTag.*;
import java.util.Comparator;
/** Helper class for type parameter inference, used by the attribution phase.
*
@ -507,8 +508,11 @@ public class Infer {
}
}
//step 2 - replace fresh tvars in their bounds
List<Type> formals = vars;
for (Type t : todo) {
replaceTypeVarsInBounds(todo.toList(), inferenceContext);
}
private void replaceTypeVarsInBounds(List<Type> vars, InferenceContext inferenceContext) {
for (Type t : vars) {
UndetVar uv = (UndetVar)t;
TypeVar ct = (TypeVar)uv.getInst();
ct.setUpperBound( types.glb(inferenceContext.asInstTypes(types.getBounds(ct))) );
@ -516,7 +520,6 @@ public class Infer {
//report inference error if glb fails
reportBoundError(uv, InferenceBound.UPPER);
}
formals = formals.tail;
}
}
@ -651,6 +654,122 @@ public class Infer {
return owntype;
}
}
/**
* Infer record type for pattern matching. Given an expression type
* (@code expressionType}), and a given record ({@code patternTypeSymbol}),
* a parameterized type of {@code patternTypeSymbol} is inferred
* according to JLS 18.5.5.
*
* @param expressionType
* @param patternTypeSymbol
* @return
*/
public Type instantiatePatternType(Type expressionType, TypeSymbol patternTypeSymbol) {
if (expressionType.tsym == patternTypeSymbol)
return expressionType;
//step 1:
List<Type> expressionTypes = List.nil();
List<Type> params = patternTypeSymbol.type.allparams();
List<Type> capturedWildcards = List.nil();
List<Type> todo = List.of(expressionType);
while (todo.nonEmpty()) {
Type current = todo.head;
todo = todo.tail;
switch (current.getTag()) {
case CLASS -> {
if (current.isCompound()) {
todo = todo.prependList(types.directSupertypes(current));
} else {
Type captured = types.capture(current);
for (Type ta : captured.getTypeArguments()) {
if (ta.hasTag(TYPEVAR) && ((TypeVar) ta).isCaptured()) {
params = params.prepend((TypeVar) ta);
capturedWildcards = capturedWildcards.prepend(ta);
}
}
expressionTypes = expressionTypes.prepend(captured);
}
}
case TYPEVAR -> {
todo = todo.prepend(types.skipTypeVars(current, false));
}
default -> expressionTypes = expressionTypes.prepend(current);
}
}
//add synthetic captured ivars
InferenceContext c = new InferenceContext(this, params);
Type patternType = c.asUndetVar(patternTypeSymbol.type);
List<Type> exprTypes = expressionTypes.map(t -> c.asUndetVar(t));
capturedWildcards.forEach(s -> ((UndetVar) c.asUndetVar(s)).setNormal());
try {
//step 2:
for (Type exprType : exprTypes) {
if (exprType.isParameterized()) {
Type patternAsExpression =
types.asSuper(patternType, exprType.tsym);
if (patternAsExpression == null ||
!types.isSameType(patternAsExpression, exprType)) {
return null;
}
}
}
doIncorporation(c, types.noWarnings);
//step 3:
List<Type> freshVars = instantiatePatternVars(params, c);
Type substituted = c.asInstType(patternTypeSymbol.type);
//step 4:
return types.upward(substituted, freshVars);
} catch (Infer.InferenceException ex) {
return null;
}
}
private List<Type> instantiatePatternVars(List<Type> vars, InferenceContext c) {
ListBuffer<Type> freshVars = new ListBuffer<>();
ListBuffer<Type> todo = new ListBuffer<>();
//step 1 - create fresh tvars
for (Type t : vars) {
UndetVar undet = (UndetVar) c.asUndetVar(t);
List<Type> bounds = InferenceStep.EQ.filterBounds(undet, c);
if (bounds.nonEmpty()) {
undet.setInst(bounds.head);
} else {
List<Type> upperBounds = undet.getBounds(InferenceBound.UPPER);
Type upper;
boolean recursive = Type.containsAny(upperBounds, vars);
if (recursive) {
upper = types.makeIntersectionType(upperBounds);
todo.append(undet);
} else if (upperBounds.nonEmpty()) {
upper = types.glb(upperBounds);
} else {
upper = syms.objectType;
}
List<Type> lowerBounds = undet.getBounds(InferenceBound.LOWER);
Type lower = lowerBounds.isEmpty() ? syms.botType
: lowerBounds.tail.isEmpty() ? lowerBounds.head
: types.lub(lowerBounds);
TypeVar vt = new TypeVar(syms.noSymbol, upper, lower);
freshVars.add(vt);
undet.setInst(vt);
}
}
//step 2 - replace fresh tvars in their bounds
replaceTypeVarsInBounds(todo.toList(), c);
return freshVars.toList();
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Incorporation">

@ -323,23 +323,6 @@ public class TransPatterns extends TreeTranslator {
nestedPatterns = nestedPatterns.tail;
}
if (tree.var != null) {
BindingSymbol binding = (BindingSymbol) tree.var.sym;
Type castTargetType = principalType(tree);
VarSymbol bindingVar = bindingContext.bindingDeclared(binding);
JCAssign fakeInit =
(JCAssign) make.at(TreeInfo.getStartPos(tree))
.Assign(make.Ident(bindingVar),
convert(make.Ident(currentValue), castTargetType))
.setType(bindingVar.erasure(types));
LetExpr nestedLE = make.LetExpr(List.of(make.Exec(fakeInit)),
make.Literal(true));
nestedLE.needsCond = true;
nestedLE.setType(syms.booleanType);
test = test != null ? makeBinary(Tag.AND, test, nestedLE) : nestedLE;
}
Assert.check(components.isEmpty() == nestedPatterns.isEmpty());
Assert.check(components.isEmpty() == nestedFullComponentTypes.isEmpty());
result = test != null ? test : makeLit(syms.booleanType, 1);
@ -441,15 +424,6 @@ public class TransPatterns extends TreeTranslator {
// return -1 when the input is null
//
//note the selector is evaluated only once and stored in a temporary variable
ListBuffer<JCCase> newCases = new ListBuffer<>();
for (List<JCCase> c = cases; c.nonEmpty(); c = c.tail) {
if (c.head.stats.isEmpty() && c.tail.nonEmpty()) {
c.tail.head.labels = c.tail.head.labels.prependList(c.head.labels);
} else {
newCases.add(c.head);
}
}
cases = newCases.toList();
ListBuffer<JCStatement> statements = new ListBuffer<>();
VarSymbol temp = new VarSymbol(Flags.SYNTHETIC,
names.fromString("selector" + tree.pos + target.syntheticNameChar() + "temp"),
@ -757,7 +731,7 @@ public class TransPatterns extends TreeTranslator {
if (bindingVar == null) {
super.visitIdent(tree);
} else {
result = make.at(tree.pos).Ident(bindingVar);
result = make.at(tree.pos).Ident(bindingVar).setType(bindingVar.erasure(types));
}
}

@ -61,6 +61,7 @@ import static com.sun.tools.javac.tree.JCTree.Tag.*;
import static com.sun.tools.javac.resources.CompilerProperties.Fragments.ImplicitAndExplicitNotAllowed;
import static com.sun.tools.javac.resources.CompilerProperties.Fragments.VarAndExplicitNotAllowed;
import static com.sun.tools.javac.resources.CompilerProperties.Fragments.VarAndImplicitNotAllowed;
import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
import java.util.function.BiFunction;
/**
@ -796,18 +797,7 @@ public class JavacParser implements Parser {
nextToken();
}
accept(RPAREN);
JCVariableDecl var;
if (token.kind == IDENTIFIER) {
if (!checkGuard || token.name() != names.when) {
var = to(F.at(token.pos).VarDef(F.Modifiers(0), token.name(), e, null));
nextToken();
} else {
var = null;
}
} else {
var = null;
}
pattern = toP(F.at(pos).RecordPattern(e, nested.toList(), var));
pattern = toP(F.at(pos).RecordPattern(e, nested.toList()));
} else {
//type test pattern:
JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), e, null));
@ -1536,12 +1526,14 @@ public class JavacParser implements Parser {
pats.append(toP(F.at(casePos).DefaultCaseLabel()));
} else {
accept(CASE);
boolean allowDefault = false;
while (true) {
JCCaseLabel label = parseCaseLabel();
JCCaseLabel label = parseCaseLabel(allowDefault);
pats.append(label);
if (token.kind != COMMA) break;
checkSourceLevel(Feature.SWITCH_MULTIPLE_CASE_LABELS);
nextToken();
allowDefault = TreeInfo.isNullCaseLabel(label);
};
}
List<JCStatement> stats = null;
@ -3034,11 +3026,14 @@ public class JavacParser implements Parser {
case CASE: {
nextToken();
ListBuffer<JCCaseLabel> pats = new ListBuffer<>();
boolean allowDefault = false;
while (true) {
pats.append(parseCaseLabel());
JCCaseLabel label = parseCaseLabel(allowDefault);
pats.append(label);
if (token.kind != COMMA) break;
nextToken();
checkSourceLevel(Feature.SWITCH_MULTIPLE_CASE_LABELS);
allowDefault = TreeInfo.isNullCaseLabel(label);
};
CaseTree.CaseKind caseKind;
JCTree body = null;
@ -3091,12 +3086,16 @@ public class JavacParser implements Parser {
throw new AssertionError("should not reach here");
}
private JCCaseLabel parseCaseLabel() {
private JCCaseLabel parseCaseLabel(boolean allowDefault) {
int patternPos = token.pos;
JCCaseLabel label;
if (token.kind == DEFAULT) {
checkSourceLevel(token.pos, Feature.PATTERN_SWITCH);
if (!allowDefault) {
reportSyntaxError(new SimpleDiagnosticPosition(token.pos),
Errors.DefaultLabelNotAllowed);
}
nextToken();
label = toP(F.at(patternPos).DefaultCaseLabel());
} else {

@ -525,6 +525,15 @@ compiler.err.flows.through.to.pattern=\
compiler.err.flows.through.from.pattern=\
illegal fall-through from a pattern
compiler.err.invalid.case.label.combination=\
invalid case label combination
compiler.err.default.label.not.allowed=\
default label not allowed here
compiler.err.pattern.type.cannot.infer=\
cannot infer pattern type
compiler.err.else.without.if=\
''else'' without ''if''
@ -3910,9 +3919,6 @@ compiler.err.incorrect.number.of.nested.patterns=\
required: {0}\n\
found: {1}
compiler.err.raw.deconstruction.pattern=\
raw deconstruction patterns are not allowed
# 0: kind name, 1: symbol
compiler.warn.declared.using.preview=\
{0} {1} is declared using a preview feature, which may be removed in a future release.

@ -2227,6 +2227,10 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
@DefinedBy(Api.COMPILER_TREE)
public JCExpression getExpression() { return expr; }
@DefinedBy(Api.COMPILER_TREE)
public TestKind getTestKind() {
return pattern instanceof JCPatternCaseLabel ? TestKind.PATTERN : TestKind.TYPE;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public <R,D> R accept(TreeVisitor<R,D> v, D d) {
return v.visitInstanceOf(this, d);
@ -2428,15 +2432,12 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
implements DeconstructionPatternTree {
public JCExpression deconstructor;
public List<JCPattern> nested;
public JCVariableDecl var;
public ClassSymbol record;
public List<Type> fullComponentTypes;
protected JCRecordPattern(JCExpression deconstructor, List<JCPattern> nested,
JCVariableDecl var) {
protected JCRecordPattern(JCExpression deconstructor, List<JCPattern> nested) {
this.deconstructor = deconstructor;
this.nested = nested;
this.var = var;
}
@DefinedBy(Api.COMPILER_TREE)
@ -2475,11 +2476,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
return RECORDPATTERN;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public VariableTree getVariable() {
return var;
}
}
/**

@ -945,10 +945,6 @@ public class Pretty extends JCTree.Visitor {
print("(");
printExprs(tree.nested);
print(")");
if (tree.var != null) {
print(" ");
print(tree.var.name);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}

@ -530,8 +530,7 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
JCRecordPattern t = (JCRecordPattern) node;
JCExpression deconstructor = copy(t.deconstructor, p);
List<JCPattern> nested = copy(t.nested, p);
JCVariableDecl var = copy(t.var, p);
return M.at(t.pos).RecordPattern(deconstructor, nested, var);
return M.at(t.pos).RecordPattern(deconstructor, nested);
}
@DefinedBy(Api.COMPILER_TREE)

@ -512,9 +512,8 @@ public class TreeMaker implements JCTree.Factory {
return tree;
}
public JCRecordPattern RecordPattern(JCExpression deconstructor, List<JCPattern> nested,
JCVariableDecl var) {
JCRecordPattern tree = new JCRecordPattern(deconstructor, nested, var);
public JCRecordPattern RecordPattern(JCExpression deconstructor, List<JCPattern> nested) {
JCRecordPattern tree = new JCRecordPattern(deconstructor, nested);
tree.pos = pos;
return tree;
}

@ -331,9 +331,6 @@ public class TreeScanner extends Visitor {
public void visitRecordPattern(JCRecordPattern that) {
scan(that.deconstructor);
scan(that.nested);
if (that.var != null) {
scan(that.var);
}
}
public void visitIndexed(JCArrayAccess tree) {

@ -21,15 +21,15 @@
* questions.
*/
// key: compiler.err.raw.deconstruction.pattern
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: --enable-preview -source ${jdk.version}
// key: compiler.err.default.label.not.allowed
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class RawDeconstructionPattern {
boolean test(Object o) {
return o instanceof R(String s);
class DefaultLabelNotAllowed {
private void doSwitch(Object o) {
switch (o) {
case default: break;
}
}
record R<T>(T t) {}
}

@ -24,12 +24,14 @@
// key: compiler.err.flows.through.to.pattern
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.case.null
// key: compiler.warn.preview.feature.use
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class FlowsThroughToPattern {
private void doSwitch(Object o) {
switch (o) {
case String str:
case null:
case Object obj: break;
}
}

@ -0,0 +1,36 @@
/*
* 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.err.invalid.case.label.combination
// key: compiler.misc.feature.case.null
// key: compiler.warn.preview.feature.use
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class InvalidCaseLabelCombination {
private void doSwitch(Integer i) {
switch (i) {
case null, 1: break;
default: break;
}
}
}

@ -0,0 +1,36 @@
/*
* 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.err.pattern.type.cannot.infer
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.deconstruction.patterns
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class PatternTypeCannotInfer {
interface A<T> {}
record R<T extends Number>() implements A<T> {}
void test(A<String> i) {
if (i instanceof R()) {
}
}
}

@ -1,37 +0,0 @@
/*
* @test /nodynamiccopyright/
* @bug 8262891
* @summary Check null handling for non-pattern switches.
* @compile/fail/ref=CaseDefault.out --release 16 -XDrawDiagnostics CaseDefault.java
* @compile --enable-preview -source ${jdk.version} CaseDefault.java
* @run main/othervm --enable-preview CaseDefault
*/
public class CaseDefault {
public static void main(String[] args) {
new CaseDefault().run();
}
void run() {
String str = "other";
switch (str) {
case "a": throw new AssertionError("Wrong branch.");
case default: break; //OK
}
switch (str) {
case "a" -> throw new AssertionError("Wrong branch.");
case default -> {} //OK
}
int i;
i = switch (str) {
case "a": throw new AssertionError("Wrong branch.");
case default: yield 0; //OK
};
i = switch (str) {
case "a" -> throw new AssertionError("Wrong branch.");
case default -> 0; //OK
};
}
}

@ -1,2 +0,0 @@
CaseDefault.java:20:18: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.pattern.switch)
1 error

@ -95,7 +95,6 @@ public class CaseStructureTest extends ComboInstance<CaseStructureTest> {
task.generate(result -> {
boolean shouldPass = true;
long patternCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.TYPE_PATTERN || l == CaseLabel.PARENTHESIZED_PATTERN).count();
long typePatternCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.TYPE_PATTERN || l == CaseLabel.PARENTHESIZED_PATTERN).count();
long constantCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.CONSTANT).count();
long nullCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.NULL).count();
long defaultCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.DEFAULT).count();
@ -105,14 +104,22 @@ public class CaseStructureTest extends ComboInstance<CaseStructureTest> {
if (constantCases > 0) {
shouldPass &= patternCases == 0;
}
if (defaultCases > 0) {
shouldPass &= asCaseLabelElements && nullCases > 0;
}
if (defaultCases > 1) {
shouldPass &= false;
}
if (nullCases > 1) {
shouldPass &= false;
}
if (nullCases > 0 && patternCases > 0) {
shouldPass &= patternCases == typePatternCases;
if (nullCases > 0) {
shouldPass &= patternCases == 0 && (constantCases == 0 || !asCaseLabelElements);
if (defaultCases > 0 && asCaseLabelElements) {
int nullIndex = Arrays.asList(caseLabels).indexOf(CaseLabel.NULL);
int defaultIndex = Arrays.asList(caseLabels).indexOf(CaseLabel.DEFAULT);
shouldPass &= nullIndex < defaultIndex;
}
}
if (patternCases > 1) {
shouldPass &= false;

@ -26,8 +26,8 @@ public class DeconstructionPatternErrors {
if (p instanceof GenRecord<String>(var v)); //incorrect generic type
if (p instanceof P4(GenRecord<String>(var v))); //incorrect generic type
if (p instanceof GenRecord<String>(Integer v)); //inconsistency in types
if (p instanceof P2(var v, var v) v); //duplicated variables
if (p instanceof P6(P2(var v1, var v2) v1, P2(var v1, var v2) v2) v1); //duplicated variables
if (p instanceof P2(var v, var v)); //duplicated variables
if (p instanceof P6(P2(var v1, var v2), P2(var v1, var v2))); //duplicated variables
if (p instanceof P7(byte b)); //incorrect pattern type
if (p instanceof P7(long l)); //incorrect pattern type
switch (p) {
@ -44,6 +44,7 @@ public class DeconstructionPatternErrors {
switch (r1) {
case GenRecord<>(String s) -> {}
}
boolean b = p instanceof P(int i) p; //introducing a variable for the record pattern
}
public record P(int i) {

@ -2,6 +2,8 @@ DeconstructionPatternErrors.java:15:28: compiler.err.underscore.as.identifier
DeconstructionPatternErrors.java:15:29: compiler.err.expected: token.identifier
DeconstructionPatternErrors.java:43:37: compiler.err.illegal.start.of.type
DeconstructionPatternErrors.java:45:28: compiler.err.illegal.start.of.type
DeconstructionPatternErrors.java:47:42: compiler.err.expected: ';'
DeconstructionPatternErrors.java:47:43: compiler.err.not.stmt
DeconstructionPatternErrors.java:16:29: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.util.List<java.lang.String>, java.util.ArrayList<java.lang.Integer>)
DeconstructionPatternErrors.java:17:29: compiler.err.instanceof.reifiable.not.safe: java.lang.Object, java.util.ArrayList<java.lang.Integer>
DeconstructionPatternErrors.java:18:29: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, int)
@ -18,18 +20,13 @@ DeconstructionPatternErrors.java:27:29: compiler.err.instanceof.reifiable.not.sa
DeconstructionPatternErrors.java:28:44: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Integer)
DeconstructionPatternErrors.java:28:13: compiler.err.instanceof.reifiable.not.safe: java.lang.Object, DeconstructionPatternErrors.GenRecord<java.lang.String>
DeconstructionPatternErrors.java:29:40: compiler.err.match.binding.exists
DeconstructionPatternErrors.java:29:43: compiler.err.match.binding.exists
DeconstructionPatternErrors.java:30:48: compiler.err.match.binding.exists
DeconstructionPatternErrors.java:30:59: compiler.err.already.defined: kindname.variable, v1, kindname.method, main(java.lang.String...)
DeconstructionPatternErrors.java:30:67: compiler.err.already.defined: kindname.variable, v2, kindname.method, main(java.lang.String...)
DeconstructionPatternErrors.java:30:71: compiler.err.match.binding.exists
DeconstructionPatternErrors.java:30:75: compiler.err.match.binding.exists
DeconstructionPatternErrors.java:30:56: compiler.err.already.defined: kindname.variable, v1, kindname.method, main(java.lang.String...)
DeconstructionPatternErrors.java:30:64: compiler.err.already.defined: kindname.variable, v2, kindname.method, main(java.lang.String...)
DeconstructionPatternErrors.java:31:29: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, byte)
DeconstructionPatternErrors.java:32:29: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, long)
DeconstructionPatternErrors.java:34:21: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, byte)
DeconstructionPatternErrors.java:35:21: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, long)
DeconstructionPatternErrors.java:39:27: compiler.err.raw.deconstruction.pattern
DeconstructionPatternErrors.java:41:18: compiler.err.raw.deconstruction.pattern
DeconstructionPatternErrors.java:44:9: compiler.err.not.exhaustive.statement
- compiler.note.preview.filename: DeconstructionPatternErrors.java, DEFAULT
- compiler.note.preview.recompile
32 errors
29 errors

@ -1170,6 +1170,29 @@ public class Exhaustiveness extends TestRunner {
"1 error");
}
@Test
public void testInferenceExhaustive(Path base) throws Exception {
doTest(base,
new String[0],
"""
package test;
public class Test {
sealed interface Opt<T> {}
record Some<T>(T t) implements Opt<T> {}
final class None<T> implements Opt<T> {}
void test(Opt<String> optValue) {
switch (optValue) {
case Some<String>(String s) ->
System.out.printf("got string: %s%n", s);
case None<String> none ->
System.out.println("got none");
}
}
}
""");
}
private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException {
Path current = base.resolve(".");
Path libClasses = current.resolve("libClasses");

@ -39,6 +39,11 @@ public class GenericRecordDeconstructionPattern {
runTest(this::runIf);
runTest(this::runSwitch);
runTest(this::runSwitchExpression);
runTest(this::runSwitchInference1);
runTest(this::runSwitchInference2);
runTest(this::runSwitchInference3);
runTest(this::runSwitchInference4);
testInference3();
}
void runTest(Function<Box<String>, Integer> test) {
@ -65,7 +70,49 @@ public class GenericRecordDeconstructionPattern {
};
}
record Box<V>(V v) {
int runSwitchInference1(I<String> b) {
switch (b) {
case Box(String s): return s == null ? 1 : s.length();
default: return -1;
}
}
int runSwitchInference2(I<String> b) {
switch (b) {
case Box(var s): return s == null ? 1 : s.length();
default: return -1;
}
}
int runSwitchInference3(I<String> b) {
return b instanceof Box(var s) ? s == null ? 1 : s.length()
: -1;
}
<Z extends I<String>> int runSwitchInference4(Z b) {
return b instanceof Box(var s) ? s == null ? 1 : s.length()
: -1;
}
<B extends CharSequence & Runnable, Z extends I<B>> int runSwitchInference5(Z b) {
return b instanceof Box(var s) ? s == null ? 1 : s.length()
: -1;
}
void testInference3() {
I<I<String>> b = new Box<>(new Box<>(null));
assertEquals(1, runSwitchInferenceNested(b));
}
int runSwitchInferenceNested(I<I<String>> b) {
switch (b) {
case Box(Box(var s)): return s == null ? 1 : s.length();
default: return -1;
}
}
sealed interface I<T> {}
record Box<V>(V v) implements I<V> {
}
void assertEquals(Object expected, Object actual) {

@ -48,7 +48,6 @@ public class Guards {
runIfTrue(this::typeGuardAfterParenthesizedTrueSwitchStatement);
runIfTrue(this::typeGuardAfterParenthesizedTrueSwitchExpression);
runIfTrue(this::typeGuardAfterParenthesizedTrueIfStatement);
testGuardNPE();
}
void run(Function<Object, String> convert) {
@ -162,34 +161,6 @@ public class Guards {
return null;
}
void testGuardNPE() {
doTestGuardNPE(this::guardNPE1);
doTestGuardNPE(this::guardNPE2);
}
void doTestGuardNPE(Function<Object, String> test) {
assertEquals("empty", test.apply(""));
assertEquals("A", test.apply("A"));
assertEquals("other", test.apply(1));
assertEquals("empty", test.apply(null));
}
String guardNPE1(Object o) {
return switch (o) {
case null, String s when s.isEmpty() -> "empty";
case String s -> s;
case Object x -> "other";
};
}
String guardNPE2(Object o) {
return switch (o) {
case null, ((((String s)))) when s.isEmpty() -> "empty";
case ((((String s)))) -> s;
case Object x -> "other";
};
}
record Box(Object o) {}
void assertEquals(String expected, String actual) {

@ -0,0 +1,210 @@
/*
* 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.
*/
/*
* @test
* @summary Verify Infer.instantiatePatternType provides correct results
* @library /tools/lib/types
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.code
* jdk.compiler/com.sun.tools.javac.comp
* jdk.compiler/com.sun.tools.javac.model
* jdk.compiler/com.sun.tools.javac.parser
* jdk.compiler/com.sun.tools.javac.tree
* jdk.compiler/com.sun.tools.javac.util
* @run main InferenceUnitTest
*/
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.comp.Infer;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
public class InferenceUnitTest {
Context context;
Infer infer;
Types types;
public static void main(String... args) throws Exception {
new InferenceUnitTest().runAll();
}
void runAll() throws URISyntaxException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, null, null, null, null, List.of(new SimpleJavaFileObject(new URI("mem://Test.java"), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return """
interface A<T> {}
interface B<T> extends A<T> {}
interface C<X,Y> extends A<X> {}
interface D<X,Y> extends A<Y> {}
interface E<T> extends C<T,T> {}
interface F<T> extends A<B<T>> {}
interface G<T extends Number> extends A<T> {}
interface H extends A<String> {}
interface I<T> extends H {}
class Test<T1 extends CharSequence&Runnable, T2 extends Number> {
}
interface RecursiveTest1Interface<IB extends RecursiveTest1Interface<IB>> { }
interface RecursiveTest1Use<BB extends RecursiveTest1Use<BB>> extends RecursiveTest1Interface<BB> { }
interface RecursiveTest2Interface<X> { }
interface RecursiveTest2Use<X extends RecursiveTest2Use<X, Y>, Y> extends RecursiveTest2Interface<Y> { }
""";
}
}));
task.enter();
context = task.getContext();
infer = Infer.instance(context);
types = Types.instance(context);
checkInferedType("A<String>", "B", "B<java.lang.String>");
checkInferedType("A<String>", "C", "C<java.lang.String,?>");
checkInferedType("A<String>", "D", "D<?,java.lang.String>");
checkInferedType("A<String>", "E", "E<java.lang.String>");
checkInferedType("A<String>", "F", null);
checkInferedType("A<String>", "G", null); // doesn't check bounds
checkInferedType("A<String>", "H", "H");
checkInferedType("A<String>", "I", "I<?>");
checkInferedType("A<B<String>>", "B", "B<B<java.lang.String>>");
checkInferedType("A<B<String>>", "C", "C<B<java.lang.String>,?>");
checkInferedType("A<B<String>>", "F", "F<java.lang.String>");
checkInferedType("A<B<String>>", "H", null);
checkInferedType("A<B<String>>", "I", null);
checkInferedType("C<String, String>", "E", "E<java.lang.String>");
checkInferedType("C<String, Integer>", "E", null);
checkInferedType("C<A<?>, A<?>>", "E", "E<A<?>>");
checkInferedType("C<A<? extends Object>, A<?>>", "E", "E<A<? extends java.lang.Object>>");
if (false) {
checkInferedType("A", "B", "B");
checkInferedType("A", "C", "C");
checkInferedType("A", "D", "D");
checkInferedType("A", "E", "E");
checkInferedType("A", "F", "F");
checkInferedType("A", "G", "G");
checkInferedType("A", "H", "H");
}
checkInferedType("A", "I", "I<?>"); // always erases if input is raw
checkInferedType("A<?>", "B", "B<?>");
checkInferedType("A<?>", "C", "C<?,?>");
checkInferedType("A<?>", "D", "D<?,?>");
checkInferedType("A<?>", "E", "E<?>");
checkInferedType("A<?>", "F", "F<?>");
checkInferedType("A<?>", "G", "G<?>");
checkInferedType("A<?>", "H", "H");
checkInferedType("A<?>", "I", "I<?>");
checkInferedType("A<? extends Runnable>", "B", "B<? extends java.lang.Runnable>");
checkInferedType("A<? extends Runnable>", "C", "C<? extends java.lang.Runnable,?>");
checkInferedType("A<? extends Runnable>", "D", "D<?,? extends java.lang.Runnable>");
checkInferedType("A<? extends Runnable>", "E", "E<? extends java.lang.Runnable>");
checkInferedType("A<? extends Runnable>", "F", null);
checkInferedType("A<? extends Runnable>", "G", "G<? extends java.lang.Number&java.lang.Runnable>"); // should infer an intersection bound
checkInferedType("A<? extends Runnable>", "H", null);
checkInferedType("A<? extends Runnable>", "I", null);
checkInferedType("A<? extends B<String>>", "F", "F<java.lang.String>"); // inference doesn't recur on bounds checks
checkInferedType("A<? extends A<String>>", "F", "F<java.lang.String>"); // inference doesn't recur on bounds checks
checkInferedType("C<? extends Number, Integer>", "E", "E<java.lang.Integer>"); // doesn't know how to mix types and wildcards
checkInferedType("C<Integer, ? extends Number>", "E", "E<java.lang.Integer>"); // doesn't know how to mix types and wildcards
checkInferedType("C<?, ? extends Number>", "E", "E<? extends java.lang.Number>");
checkInferedType("C<? extends Number, ?>", "E", "E<? extends java.lang.Number>");
checkInferedType("C<? extends Number, ? extends Integer>", "E", "E<? extends java.lang.Integer>");
checkInferedType("C<? extends Integer, ? extends Number>", "E", "E<? extends java.lang.Integer>");
checkInferedType("C<? extends Runnable, ? extends Cloneable>", "E", "E<? extends java.lang.Object&java.lang.Cloneable&java.lang.Runnable>"); // should infer an intersection bound
checkInferedType("C<? extends Number, ? super Integer>", "E", "E<? extends java.lang.Number>"); // doesn't know how to mix lower/upper
checkInferedType("C<? super Integer, ? super Number>", "E", "E<? super java.lang.Number>");
checkInferedType("C<? super B<String>, ? super C<String,String>>", "E", "E<? super A<java.lang.String>>"); // doesn't do lub
checkInferedType("H", "I", "I<?>");
checkInferedType("B<String>", "C", null); // no sideways casts
checkInferedType("A<T1>", "B", "B<T1>");
checkInferedType("RecursiveTest1Interface<?>", "RecursiveTest1Use", "RecursiveTest1Use<? extends java.lang.Object&RecursiveTest1Use<?>&RecursiveTest1Interface<? extends RecursiveTest1Use<?>>>");
checkInferedType("RecursiveTest2Interface<?>", "RecursiveTest2Use", "RecursiveTest2Use<? extends RecursiveTest2Use<?,?>,?>");
}
private void checkInferedType(String base, String test, String expected) {
Type baseType = parseType(base);
TypeSymbol testType = parseType(test).tsym;
Type actualType = infer.instantiatePatternType(baseType, testType);
String actualTypeString = actualType != null ? actualType.toString() : null;
if (!Objects.equals(expected, actualTypeString)) {
error("Unexpected type, expected: " + expected + ", got: " + actualTypeString);
}
}
Type parseType(String spec) {
ParserFactory fact = ParserFactory.instance(context);
JCExpression specTypeTree = fact.newParser(spec, false, false, false).parseType();
Attr attr = Attr.instance(context);
JavacElements elementUtils = JavacElements.instance(context);
ClassSymbol testClass = elementUtils.getTypeElement("Test");
return attr.attribType(specTypeTree, testClass);
}
/** assert that 's' is the same type as 't' */
public void assertSameType(Type s, Type t) {
assertSameType(s, t, true);
}
/** assert that 's' is/is not the same type as 't' */
public void assertSameType(Type s, Type t, boolean expected) {
if (types.isSameType(s, t) != expected) {
String msg = expected ?
" is not the same type as " :
" is the same type as ";
error(s + msg + t);
}
}
private void error(String msg) {
throw new AssertionError("Unexpected result: " + msg);
}
}

@ -0,0 +1,457 @@
/*
* 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.
*/
/*
* @test
* @bug 8294942
* @summary Check compilation outcomes for various combinations of case label element.
* @library /tools/lib /tools/javac/lib
* @modules
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.util
* @build toolbox.ToolBox toolbox.JavacTask
* @compile NewCaseStructureTest.java
* @run main NewCaseStructureTest
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.TestRunner;
import toolbox.ToolBox;
public class NewCaseStructureTest extends TestRunner {
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
ToolBox tb;
public static void main(String... args) throws Exception {
new NewCaseStructureTest().runTests();
}
NewCaseStructureTest() {
super(System.err);
tb = new ToolBox();
}
public void runTests() throws Exception {
runTests(m -> new Object[] { Paths.get(m.getName()) });
}
@Test
public void testCorrectMultiLabelCaseStructure(Path base) throws Exception {
for (String pattern : new String[] {"String s",
"(String s)",
"R(int i)",
"(R(int i))",
"null, default",
"null",
"1",
"1, 2",
"1, 2, 3"}) {
for (String sep : new String[] {":", "->"}) {
doTest(base,
"""
package test;
public class Test {
private int test(${switchType} obj) {
return switch (obj) {
case ${pattern} ${sep} { yield 0; }
${default}
};
}
}
record R(int i) {}
""".replace("${switchType}", pattern.contains("1") ? "Integer" : "Object")
.replace("${pattern}", pattern)
.replace("${sep}", sep)
.replace("${default}", pattern.contains("default") ? "" : "default " + sep + " { yield 1; }"),
false);
}
}
}
@Test
public void testMalformedCaseStructure(Path base) throws Exception {
for (String pattern : new String[] {"String s, Integer i",
"String s, R(int i)",
"E1(), E2()",
"String s, null",
"String s, default",
"String s, null, default",
"null, String s",
"null, default, String s",
"default, String s",
"1, Integer i",
"1, 2, 3, Integer i",
"Integer i, 1, 2, 3",
"1, null",
"1, 2, 3, null",
"null, 1, 2, 3",
"default, null",
"default"}) {
for (String sep : new String[] {":", "->"}) {
doTest(base,
"""
package test;
public class Test {
private int test(${switchType} obj) {
return switch (obj) {
case ${pattern} ${sep} { yield 0; }
${default}
};
}
}
record R(int i) {}
record E1() {}
record E2() {}
""".replace("${switchType}", pattern.contains("1") ? "Integer" : "Object")
.replace("${pattern}", pattern)
.replace("${sep}", sep)
.replace("${default}", pattern.contains("default") ? "" : "default " + sep + " { yield 1; }"),
true);
}
}
}
@Test
public void testSwitchLabeledStatementGroups(Path base) throws Exception {
doTest(base,
"""
package test;
public class Test {
private int test(Object o) {
return switch (o) {
case null:
case Object obj: System.err.println(); yield 0;
};
}
}
""",
"Test.java:6:18: compiler.err.flows.through.to.pattern",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Object o) {
return switch (o) {
case null: System.err.println();
case Object obj: System.err.println(); yield 0;
};
}
}
""",
"Test.java:6:18: compiler.err.flows.through.to.pattern",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Object o) {
return switch (o) {
case Object obj:
case null: System.err.println(); yield 0;
};
}
}
""",
"Test.java:5:18: compiler.err.flows.through.from.pattern",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Object o) {
return switch (o) {
case Object obj: System.err.println();
case null: System.err.println();
yield 0;
};
}
}
""");
doTest(base,
"""
package test;
public class Test {
private int test(Object o) {
return switch (o) {
case String s: System.err.println();
case E1(): System.err.println(); yield 0;
default: yield 0;
};
}
}
record E1() {}
""");
doTest(base,
"""
package test;
public class Test {
private int test(Object o) {
return switch (o) {
case String s: System.err.println();
default: System.err.println(); yield 0;
};
}
}
""");
doTest(base,
"""
package test;
public class Test {
private void test(Object o) {
switch (o) {
case String s:
case Integer i:
case Object obj:
}
}
}
""");
doTest(base,
"""
package test;
public class Test {
private void test(Object o) {
switch (o) {
case Object obj: System.err.println();
case null: System.err.println(obj);
}
}
}
""",
"Test.java:6:43: compiler.err.cant.resolve.location: kindname.variable, obj, , , (compiler.misc.location: kindname.class, test.Test, null)",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
case default -> 0;
case 0 -> 0;
};
}
}
""",
"Test.java:5:18: compiler.err.default.label.not.allowed",
"1 error");
}
@Test
public void testDominance(Path base) throws Exception {
//A case label with a case pattern p (guarded or unguarded) dominates another case label with a case constant c if p dominates c, which is defined as follows:
// A type pattern that declares a pattern variable of type T dominates a constant c of a primitive type P if the wrapper class of P ([5.1.7]) is a subtype of the erasure of T.
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
case Integer i when i > 0 -> 0;
case 0 -> 0;
case Integer i -> 0;
};
}
}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
// A type pattern that declares a pattern variable of type T dominates an enum constant c of type E if E is a subtype of the erasure of the type of T.
doTest(base,
"""
package test;
public class Test {
private int test(E o) {
return switch (o) {
case E e when e == E.A -> 0;
case B -> 0;
case E e -> 0;
};
}
}
enum E {A, B;}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
//dtto for String:
doTest(base,
"""
package test;
public class Test {
private int test(String o) {
return switch (o) {
case String s when s.isEmpty() -> 0;
case "a" -> 0;
case String s -> 0;
};
}
}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
// A parenthesized pattern dominates a constant c if its contained pattern dominates c.
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
case (Integer i) when i > 0 -> 0;
case 0 -> 0;
case Integer i -> 0;
};
}
}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
// A default label dominates a case label with a case pattern, and it also dominates a case label with a null case constant.
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
default -> 0;
case (Integer i) when i > 0 -> 0;
case (Integer i) when i > 0 -> 0;
};
}
}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
case (Integer i) when i > 0 -> 0;
default -> 0;
case null -> 0;
};
}
}
""",
"Test.java:7:18: compiler.err.pattern.dominated",
"1 error");
// case label with a default dominates all other switch labels.
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
case null, default -> 0;
case (Integer i) when i > 0 -> 0;
};
}
}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
case null, default -> 0;
case 0 -> 0;
case 1 -> 0;
};
}
}
""",
"Test.java:6:18: compiler.err.pattern.dominated",
"1 error");
doTest(base,
"""
package test;
public class Test {
private int test(Integer o) {
return switch (o) {
default -> 0;
case 0 -> 0;
};
}
}
""");
}
private void doTest(Path base, String testCode, boolean expectErrors) throws IOException {
doTest(base, testCode, expectErrors, (String[]) null);
}
private void doTest(Path base, String testCode, String... output) throws IOException {
doTest(base, testCode, output != null && output.length > 0, output);
}
private void doTest(Path base, String testCode, boolean expectErrors, String... output) throws IOException {
Path current = base.resolve(".");
Path src = current.resolve("src");
tb.writeJavaFiles(src, testCode);
Path classes = current.resolve("classes");
Files.createDirectories(classes);
List<String> actual = new JavacTask(tb)
.options("--enable-preview",
"-source", JAVA_VERSION,
"-XDrawDiagnostics",
"-Xlint:-preview",
"-XDshould-stop.at=FLOW")
.outdir(classes)
.files(tb.findJavaFiles(src))
.run(expectErrors? Task.Expect.FAIL : Task.Expect.SUCCESS)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
if (output != null) {
actual.remove("- compiler.note.preview.filename: Test.java, DEFAULT");
actual.remove("- compiler.note.preview.recompile");
actual.remove("");
List<String> expected = List.of(output);
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Unexpected output: " + actual);
}
}
}
}

@ -25,9 +25,6 @@ public class NullSwitch {
assertEquals(100, matchingSwitch3(0));
assertEquals(-1, matchingSwitch3(null));
assertEquals(-2, matchingSwitch3(0.0));
assertEquals(0, matchingSwitch4(""));
assertEquals(1, matchingSwitch4(null));
assertEquals(1, matchingSwitch4(0.0));
assertEquals(0, matchingSwitch5(""));
assertEquals(1, matchingSwitch5("a"));
assertEquals(100, matchingSwitch5(0));
@ -92,7 +89,8 @@ public class NullSwitch {
private int matchingSwitch1(Object obj) {
return switch (obj) {
case String s -> s.length();
case null, Integer i -> i == null ? -1 : 100 + i;
case Integer i -> 100 + i;
case null -> -1;
default -> -2;
};
}
@ -107,23 +105,17 @@ public class NullSwitch {
private int matchingSwitch3(Object obj) {
return switch (obj) {
case String s -> s.length();
case Integer i, null -> i == null ? -1 : 100 + i;
case Integer i -> 100 + i;
case null -> -1;
default -> -2;
};
}
private int matchingSwitch4(Object obj) {
return switch (obj) {
case String s -> 0;
case default, null -> 1;
};
}
private int matchingSwitch5(Object obj) {
return switch (obj) {
case String s: yield s.length();
case null:
case Integer i: yield i == null ? -1 : 100 + i;
case null: yield -1;
case Integer i: yield 100 + i;
default: yield -2;
};
}
@ -131,7 +123,7 @@ public class NullSwitch {
private int matchingSwitch6(Object obj) {
return switch (obj) {
case String s: yield 0;
case null:
case null: yield 1;
default: yield 1;
};
}
@ -139,8 +131,8 @@ public class NullSwitch {
private int matchingSwitch7(Object obj) {
return switch (obj) {
case String s: yield s.length();
case Integer i:
case null: yield i == null ? -1 : 100 + i;
case Integer i: yield 100 + i;
case null: yield -1;
default: yield -2;
};
}
@ -148,22 +140,24 @@ public class NullSwitch {
private int matchingSwitch8(Object obj) {
return switch (obj) {
case String s: yield 0;
default:
case null: yield 1;
case null:
default: yield 1;
};
}
private int matchingSwitch9a(Object obj) {
return switch (obj) {
case String s: yield 0;
case null, Object o: yield 1;
case null: yield 1;
case Object o: yield 1;
};
}
private int matchingSwitch10a(Object obj) {
switch (obj) {
case String s: return 0;
case null, Object o: return 1;
case null: return 1;
case Object o: return 1;
}
}
@ -214,8 +208,8 @@ public class NullSwitch {
private int matchingSwitch13(Object obj) {
try {
switch (obj) {
default: return 1;
case String s: return 0;
default: return 1;
}
} catch (NullPointerException ex) {
return 2;

@ -54,7 +54,6 @@ public class PrettyTest {
" b = o instanceof R(var s);\n" +
" b = o instanceof R2(R(var s), String t);\n" +
" b = o instanceof R2(R(var s), var t);\n" +
" b = o instanceof R(String s) r;\n" +
" }\n" +
" record R(String s) {}\n" +
" record R2(R r, String s) {}\n" +
@ -71,7 +70,6 @@ public class PrettyTest {
b = o instanceof R(/*missing*/ s);
b = o instanceof R2(R(/*missing*/ s), String t);
b = o instanceof R2(R(/*missing*/ s), /*missing*/ t);
b = o instanceof R(String s) r;
}
\n\
class R {

@ -91,9 +91,6 @@ public class SimpleDeconstructionPattern {
if (!testC(new P6(new P3("")))) {
throw new IllegalStateException();
}
if (!testD(new P4("test"))) {
throw new IllegalStateException();
}
if (!testE(new P6(new P3(null)))) {
throw new IllegalStateException();
}
@ -171,10 +168,6 @@ public class SimpleDeconstructionPattern {
return o instanceof P6(P3(String s)) && s.isEmpty();
}
private static boolean testD(Object o) throws Throwable {
return o instanceof P4(String s) p && (s.isEmpty() || "test".equals(p.o()));
}
private static boolean testE(Object o) throws Throwable {
return o instanceof P6(P3(String s)) && s == null;
}

@ -1,2 +1,2 @@
SimpleDeconstructionPattern.java:121:27: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.deconstruction.patterns)
SimpleDeconstructionPattern.java:118:27: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.deconstruction.patterns)
1 error

@ -277,4 +277,27 @@ public class SwitchErrors {
default: break;
}
}
void noDiamond(Object o) {
record R<T>(T t) {}
switch (o) {
case R<> r -> {}
default -> {}
}
if (o instanceof R<> r) {}
}
void noRawInferenceNonDeconstruction() {
record R<T>(T t) {}
R<String> o = null;
switch (o) {
case R r -> System.out.println(r.t().length());
}
if (o instanceof R r) System.out.println(r.t().length());
}
void cannotInfer() {
interface A<T> {}
record R<T extends Number>() implements A<T> {}
A<String> i = null;
if (i instanceof R()) {
}
}
}

@ -1,3 +1,15 @@
SwitchErrors.java:66:18: compiler.err.default.label.not.allowed
SwitchErrors.java:72:18: compiler.err.default.label.not.allowed
SwitchErrors.java:72:27: compiler.err.default.label.not.allowed
SwitchErrors.java:138:28: compiler.err.default.label.not.allowed
SwitchErrors.java:144:18: compiler.err.default.label.not.allowed
SwitchErrors.java:149:18: compiler.err.default.label.not.allowed
SwitchErrors.java:154:18: compiler.err.default.label.not.allowed
SwitchErrors.java:213:29: compiler.err.default.label.not.allowed
SwitchErrors.java:220:47: compiler.err.default.label.not.allowed
SwitchErrors.java:227:47: compiler.err.default.label.not.allowed
SwitchErrors.java:283:20: compiler.err.illegal.start.of.type
SwitchErrors.java:286:28: compiler.err.illegal.start.of.type
SwitchErrors.java:11:18: compiler.err.constant.label.not.compatible: java.lang.String, java.lang.Object
SwitchErrors.java:17:18: compiler.err.constant.label.not.compatible: int, java.lang.Object
SwitchErrors.java:23:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Integer)
@ -11,10 +23,10 @@ SwitchErrors.java:49:18: compiler.err.unconditional.pattern.and.default
SwitchErrors.java:55:18: compiler.err.duplicate.unconditional.pattern
SwitchErrors.java:61:13: compiler.err.duplicate.default.label
SwitchErrors.java:67:13: compiler.err.duplicate.default.label
SwitchErrors.java:72:27: compiler.err.duplicate.default.label
SwitchErrors.java:78:18: compiler.err.duplicate.case.label
SwitchErrors.java:83:24: compiler.err.duplicate.case.label
SwitchErrors.java:88:28: compiler.err.flows.through.to.pattern
SwitchErrors.java:88:28: compiler.err.flows.through.from.pattern
SwitchErrors.java:93:18: compiler.err.flows.through.from.pattern
SwitchErrors.java:94:18: compiler.err.flows.through.to.pattern
SwitchErrors.java:101:18: compiler.err.flows.through.to.pattern
SwitchErrors.java:108:18: compiler.err.flows.through.to.pattern
@ -22,25 +34,27 @@ SwitchErrors.java:113:18: compiler.err.prob.found.req: (compiler.misc.inconverti
SwitchErrors.java:119:18: compiler.err.instanceof.reifiable.not.safe: java.util.List, java.util.List<java.lang.Integer>
SwitchErrors.java:125:18: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.class, SwitchErrors, null)
SwitchErrors.java:132:18: compiler.err.type.found.req: int, (compiler.misc.type.req.class.array)
SwitchErrors.java:138:28: compiler.err.flows.through.from.pattern
SwitchErrors.java:144:18: compiler.err.flows.through.from.pattern
SwitchErrors.java:149:27: compiler.err.flows.through.to.pattern
SwitchErrors.java:143:18: compiler.err.flows.through.from.pattern
SwitchErrors.java:149:27: compiler.err.flows.through.from.pattern
SwitchErrors.java:155:18: compiler.err.flows.through.to.pattern
SwitchErrors.java:167:18: compiler.err.pattern.expected
SwitchErrors.java:173:78: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null)
SwitchErrors.java:179:73: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null)
SwitchErrors.java:186:21: compiler.err.flows.through.to.pattern
SwitchErrors.java:186:21: compiler.err.invalid.case.label.combination
SwitchErrors.java:195:44: compiler.err.flows.through.from.pattern
SwitchErrors.java:213:29: compiler.err.unconditional.pattern.and.default
SwitchErrors.java:220:21: compiler.err.flows.through.to.pattern
SwitchErrors.java:220:47: compiler.err.flows.through.from.pattern
SwitchErrors.java:204:24: compiler.err.invalid.case.label.combination
SwitchErrors.java:220:21: compiler.err.invalid.case.label.combination
SwitchErrors.java:227:44: compiler.err.flows.through.from.pattern
SwitchErrors.java:227:47: compiler.err.flows.through.from.pattern
SwitchErrors.java:239:18: compiler.err.duplicate.unconditional.pattern
SwitchErrors.java:244:18: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, java.lang.Integer)
SwitchErrors.java:249:18: compiler.err.type.found.req: int, (compiler.misc.type.req.class.array)
SwitchErrors.java:262:24: compiler.err.flows.through.to.pattern
SwitchErrors.java:276:37: compiler.err.flows.through.from.pattern
SwitchErrors.java:255:24: compiler.err.invalid.case.label.combination
SwitchErrors.java:262:24: compiler.err.invalid.case.label.combination
SwitchErrors.java:269:18: compiler.err.flows.through.from.pattern
SwitchErrors.java:276:18: compiler.err.flows.through.from.pattern
SwitchErrors.java:292:49: compiler.err.cant.resolve.location.args: kindname.method, length, , , (compiler.misc.location: kindname.class, java.lang.Object, null)
SwitchErrors.java:294:55: compiler.err.cant.resolve.location.args: kindname.method, length, , , (compiler.misc.location: kindname.class, java.lang.Object, null)
SwitchErrors.java:300:26: compiler.err.pattern.type.cannot.infer
SwitchErrors.java:10:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:16:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:22:9: compiler.err.not.exhaustive.statement
@ -55,4 +69,4 @@ SwitchErrors.java:159:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:232:9: compiler.err.not.exhaustive.statement
- compiler.note.preview.filename: SwitchErrors.java, DEFAULT
- compiler.note.preview.recompile
55 errors
69 errors

@ -47,8 +47,6 @@ public class Switches {
assertTrue(testNullSwitch(""));
runArrayTypeTest(this::testArrayTypeStatement);
runArrayTypeTest(this::testArrayTypeExpression);
runDefaultTest(this::testDefaultDoesNotDominateStatement);
runDefaultTest(this::testDefaultDoesNotDominateExpression);
runEnumTest(this::testEnumExpression1);
runEnumTest(this::testEnumExpression2);
runEnumTest(this::testEnumWithGuards1);
@ -98,6 +96,9 @@ public class Switches {
assertEquals("OK", totalPatternAndNull(null));
assertEquals("1", nullAfterTotal(Integer.valueOf(42)));
assertEquals("OK", nullAfterTotal(null));
emptyFallThrough(1);
emptyFallThrough("");
emptyFallThrough(1.0);
}
void run(Function<Object, Integer> mapper) {
@ -226,22 +227,6 @@ public class Switches {
};
}
String testDefaultDoesNotDominateStatement(Object o) {
String res;
switch (o) {
default -> res = "default";
case String str -> res = "str" + str.length();
}
return res;
}
String testDefaultDoesNotDominateExpression(Object o) {
return switch (o) {
case default -> "default";
case String str -> "str" + str.length();
};
}
int testStringWithConstant(String str) {
switch (str) {
case "A": return 1;
@ -262,7 +247,8 @@ public class Switches {
return switch (e) {
case A -> "a";
case B -> "b";
case null, E x -> String.valueOf(x);
case E x -> String.valueOf(x);
case null -> "null";
};
}
@ -270,7 +256,8 @@ public class Switches {
return switch (e) {
case A -> "a";
case B -> "b";
case E x, null -> String.valueOf(x);
case E x -> String.valueOf(x);
case null -> "null";
};
}
@ -280,7 +267,8 @@ public class Switches {
case B: return "b";
case C: return String.valueOf(e);
case E x when "A".equals(x.name()): return "broken";
case null, E x: return String.valueOf(x);
case E x: return String.valueOf(x);
case null: return "null";
}
}
@ -290,7 +278,8 @@ public class Switches {
case B -> "b";
case C -> String.valueOf(e);
case E x when "A".equals(x.name()) -> "broken";
case null, E x -> String.valueOf(x);
case E x -> String.valueOf(x);
case null -> "null";
};
}
@ -299,7 +288,8 @@ public class Switches {
case A: return "a";
case B: return "b";
case E x when "C".equals(x.name()): return "C";
case null, E x: return e == E.C ? "broken" : String.valueOf(x);
case E x: return e == E.C ? "broken" : String.valueOf(x);
case null: return "null";
}
}
@ -308,7 +298,8 @@ public class Switches {
case A -> "a";
case B -> "b";
case E x when "C".equals(x.name()) -> "C";
case null, E x -> e == E.C ? "broken" : String.valueOf(x);
case E x -> e == E.C ? "broken" : String.valueOf(x);
case null -> "null";
};
}
@ -317,7 +308,8 @@ public class Switches {
case A: return "a";
case B: return "b";
case Object x when "C".equals(x.toString()): return "C";
case null, E x: return e == E.C ? "broken" : String.valueOf(x);
case E x: return e == E.C ? "broken" : String.valueOf(x);
case null: return "null";
}
}
@ -326,7 +318,8 @@ public class Switches {
case A -> "a";
case B -> "b";
case Object x when "C".equals(x.toString()) -> "C";
case null, E x -> e == E.C ? "broken" : String.valueOf(x);
case E x -> e == E.C ? "broken" : String.valueOf(x);
case null -> "null";
};
}
@ -335,7 +328,8 @@ public class Switches {
case A: return "a";
case B: return "b";
case Runnable x when "C".equals(x.toString()): return "C";
case null, E x: return e == E.C ? "broken" : String.valueOf(x);
case E x: return e == E.C ? "broken" : String.valueOf(x);
case null: return "null";
}
}
@ -344,7 +338,8 @@ public class Switches {
case A -> "a";
case B -> "b";
case Runnable x when "C".equals(x.toString()) -> "C";
case null, E x -> e == E.C ? "broken" : String.valueOf(x);
case E x -> e == E.C ? "broken" : String.valueOf(x);
case null -> "null";
};
}
@ -353,7 +348,8 @@ public class Switches {
case "A": return "a";
case Switches.ConstantClassClash: return "b";
case String x when "C".equals(x): return "C";
case null, String x: return "C".equals(x) ? "broken" : String.valueOf(x);
case String x: return "C".equals(x) ? "broken" : String.valueOf(x);
case null: return "null";
}
}
@ -362,7 +358,8 @@ public class Switches {
case "A" -> "a";
case ConstantClassClash -> "b";
case String x when "C".equals(x) -> "C";
case null, String x -> e == E.C ? "broken" : String.valueOf(x);
case String x -> e == E.C ? "broken" : String.valueOf(x);
case null -> "null";
};
}
@ -371,7 +368,8 @@ public class Switches {
case 0: return "a";
case 1: return "b";
case Integer x when x.equals(2): return "C";
case null, Integer x: return Objects.equals(x, 2) ? "broken" : String.valueOf(x);
case Integer x: return Objects.equals(x, 2) ? "broken" : String.valueOf(x);
case null: return "null";
}
}
@ -380,7 +378,8 @@ public class Switches {
case 0 -> "a";
case 1 -> "b";
case Integer x when x.equals(2) -> "C";
case null, Integer x -> Objects.equals(x, 2) ? "broken" : String.valueOf(x);
case Integer x -> Objects.equals(x, 2) ? "broken" : String.valueOf(x);
case null -> "null";
};
}
@ -462,21 +461,21 @@ public class Switches {
}
}
switch (o) {
case null, Object obj:; //no break intentionally - should not fall through to any possible default
}
switch (o) {
case Object obj, null:; //no break intentionally - should not fall through to any possible default
case Object obj: int i;
case null:; //no break intentionally - should not fall through to any possible default
}
}
void exhaustiveStatementSane2(I i) {
switch (i) {
case A a: break;
case null, B b:; //no break intentionally - should not fall through to any possible default
case B b:; //no break intentionally - should not fall through to any possible default
case null:;
}
switch (i) {
case A a -> {}
case null, B b -> {}
case B b -> {}
case null -> {}
}
}
@ -624,7 +623,7 @@ public class Switches {
String deconstructStatement(Object o) {
switch (o) {
case R(String s) -> {return s;}
case R(Integer i) r -> {return r.o().toString();}
case R(Integer i) -> {return i.toString();}
case Object x -> {return "other";}
}
}
@ -632,7 +631,7 @@ public class Switches {
String deconstructExpression(Object o) {
return switch (o) {
case R(String s) -> s;
case R(Integer i) r -> r.o().toString();
case R(Integer i) -> i.toString();
case Object x -> "other";
};
}
@ -640,7 +639,8 @@ public class Switches {
String totalPatternAndNull(Integer in) {
return switch (in) {
case -1: { yield "";}
case Integer i: case null: { yield "OK";}
case null: { yield "OK";}
case Integer i: { yield "OK";}
};
}
@ -651,6 +651,14 @@ public class Switches {
};
}
void emptyFallThrough(Object o) {
switch (o) {
case Integer i:
case String s:
case Object obj:
}
}
//verify that for cases like:
//case ConstantClassClash ->
//ConstantClassClash is interpreted as a field, not as a class

@ -97,8 +97,8 @@ public class SwitchNull {
private int switchEnumWithDefault(E e) {
switch (e) {
case A: return 0;
default: return 1;
case null: return -1;
default: return 1;
}
}