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:
parent
c612512014
commit
756dd5bb0d
src/jdk.compiler/share/classes/com/sun
source
tools/javac
test/langtools/tools/javac
diags/examples
DefaultLabelNotAllowed.javaFlowsThroughToPattern.javaInvalidCaseLabelCombination.javaPatternTypeCannotInfer.java
patterns
CaseDefault.javaCaseDefault.outCaseStructureTest.javaDeconstructionPatternErrors.javaDeconstructionPatternErrors.outExhaustiveness.javaGenericRecordDeconstructionPattern.javaGuards.javaInferenceUnitTest.javaNewCaseStructureTest.javaNullSwitch.javaPrettyTest.javaSimpleDeconstructionPattern.javaSimpleDeconstructionPatternNoPreview.outSwitchErrors.javaSwitchErrors.outSwitches.java
switchnull
@ -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) {
|
||||
|
210
test/langtools/tools/javac/patterns/InferenceUnitTest.java
Normal file
210
test/langtools/tools/javac/patterns/InferenceUnitTest.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
457
test/langtools/tools/javac/patterns/NewCaseStructureTest.java
Normal file
457
test/langtools/tools/javac/patterns/NewCaseStructureTest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user