Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8294942: Compiler implementation for Record Patterns (Second Preview) #10814

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -48,11 +48,5 @@ public interface DeconstructionPatternTree extends PatternTree {
*/
List<? extends PatternTree> getNestedPatterns();

/**
* Returns the binding variable.
* @return the binding variable
*/
VariableTree getVariable();

}

Expand Up @@ -25,6 +25,8 @@

package com.sun.source.tree;

import jdk.internal.javac.PreviewFeature;

/**
* A tree node for an {@code instanceof} expression.
*
Expand All @@ -40,6 +42,23 @@
* @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
Expand Down Expand Up @@ -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();
}
Expand Up @@ -839,7 +839,6 @@ public R visitPatternCaseLabel(PatternCaseLabelTree node, P 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;
}

Expand Down
Expand Up @@ -2042,6 +2042,11 @@ public void setThrow() {
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.
*/
Expand Down
50 changes: 17 additions & 33 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
Expand Up @@ -1665,7 +1665,8 @@ private void handleSwitch(JCTree switchTree,
} 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
Expand All @@ -1677,7 +1678,6 @@ private void handleSwitch(JCTree switchTree,
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) {
Expand All @@ -1687,7 +1687,7 @@ private void handleSwitch(JCTree switchTree,
Errors.SwitchMixingCaseTypes);
wasError = true;
}
MatchBindings currentBindings = prevBindings;
MatchBindings currentBindings = null;
boolean wasUnconditionalPattern = hasUnconditionalPattern;
for (JCCaseLabel label : c.labels) {
if (label instanceof JCConstantCaseLabel constLabel) {
Expand Down Expand Up @@ -1751,7 +1751,7 @@ private void handleSwitch(JCTree switchTree,
} 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);
Expand Down Expand Up @@ -1805,9 +1805,6 @@ private void handleSwitch(JCTree switchTree,

preFlow(c);
c.completesNormally = flow.aliveAfter(caseEnv, c, make);

prevBindings = c.caseKind == CaseTree.CaseKind.STATEMENT && c.completesNormally ? currentBindings
: null;
}
if (patternSwitch) {
chk.checkSwitchCaseStructure(cases);
Expand Down Expand Up @@ -4078,7 +4075,7 @@ public void visitTypeTest(JCInstanceOf tree) {
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() &&
Expand Down Expand Up @@ -4146,8 +4143,7 @@ private boolean checkCastablePattern(DiagnosticPosition pos,
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;
}
Expand All @@ -4170,14 +4166,20 @@ public void visitBindingPattern(JCBindingPattern tree) {

@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());
Expand All @@ -4195,10 +4197,7 @@ public void visitRecordPattern(JCRecordPattern tree) {
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);
Expand All @@ -4216,21 +4215,6 @@ public void visitRecordPattern(JCRecordPattern tree) {
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();
}
Expand Down
127 changes: 80 additions & 47 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
Expand Up @@ -4346,74 +4346,107 @@ void checkModuleRequires(final DiagnosticPosition pos, final RequiresDirective r
* @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);
}
wasNullPattern = true;
} else {
if (wasPattern && !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);
}
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;
}
boolean isTypePattern = pat.hasTag(BINDINGPATTERN);
if (wasPattern || wasConstant || wasDefault ||
(wasNullPattern && (!isTypePattern || wasNonEmptyFallThrough))) {
log.error(label.pos(), Errors.FlowsThroughToPattern);
for (JCCaseLabel label : c.labels.tail) {
if (!(label instanceof JCConstantCaseLabel) || TreeInfo.isNullCaseLabel(label)) {
log.error(label.pos(), Errors.InvalidCaseLabelCombination);
break;
}
}
wasPattern = true;
wasTypePattern = isTypePattern;
}
} else {
if (c.labels.tail.nonEmpty()) {
log.error(c.labels.tail.head.pos(), Errors.FlowsThroughFromPattern);
}
}
}

boolean isCaseStatementGroup = cases.nonEmpty() &&
cases.head.caseKind == CaseTree.CaseKind.STATEMENT;

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;
}
}
}

boolean completesNormally = c.caseKind == CaseTree.CaseKind.STATEMENT ? c.completesNormally
: false;
boolean hasBindings(JCPattern p) {
boolean[] bindings = new boolean[1];

if (c.stats.nonEmpty()) {
wasConstant = false;
wasDefault = false;
wasNullPattern &= completesNormally;
wasPattern &= completesNormally;
wasTypePattern &= completesNormally;
new TreeScanner() {
@Override
public void visitBindingPattern(JCBindingPattern tree) {
bindings[0] = true;
super.visitBindingPattern(tree);
}
}.scan(p);

wasNonEmptyFallThrough = c.stats.nonEmpty() && completesNormally;
}
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);
Expand Down