Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8262889: Compiler implementation for Record Patterns
Co-authored-by: Brian Goetz <briangoetz@openjdk.org>
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org>
Co-authored-by: Aggelos Biboudis <abimpoudis@openjdk.org>
Reviewed-by: mcimadamore, vromero
  • Loading branch information
3 people committed May 25, 2022
1 parent ebfa27b commit e9bddc1
Show file tree
Hide file tree
Showing 50 changed files with 2,243 additions and 86 deletions.
36 changes: 29 additions & 7 deletions src/java.base/share/classes/java/lang/MatchException.java
Expand Up @@ -30,19 +30,41 @@
/**
* Thrown to indicate an unexpected failure in pattern matching.
*
* {@code MatchException} may be thrown when an exhaustive pattern matching language construct
* <p>{@code MatchException} may be thrown when an exhaustive pattern matching language construct
* (such as a switch expression) encounters a value that does not match any of the provided
* patterns at runtime. This can currently arise for separate compilation anomalies,
* where a sealed interface has a different set of permitted subtypes at runtime than
* it had at compilation time, an enum has a different set of constants at runtime than
* it had at compilation time, or the type hierarchy has changed in incompatible ways between
* compile time and run time.
* patterns at runtime. This can arise from a number of cases:
* <ul>
* <li>Separate compilation anomalies, where a sealed interface has a different set of permitted
* subtypes at runtime than it had at compilation time, an enum has a different set of
* constants at runtime than it had at compilation time, or the type hierarchy has changed
* in incompatible ways between compile time and run time.</li>
* <li>{@code null} values and nested patterns using sealed types. If an interface or abstract
* class {@code C} is sealed to permit {@code A} and {@code B}, then the set of record
* patterns {@code R(A a)} and {@code R(B b)} are exhaustive on a record {@code R} whose
* sole component is of type {@code C}, but neither of these patterns will match
* {@code new R(null)}.</li>
* <li>Null targets and nested record patterns. Given a record type {@code R} whose sole
* component is {@code S}, which in turn is a record whose sole component is {@code String},
* then the nested record pattern {@code R(S(String s))} will not match {@code new R(null)}.</li>
* </ul>
*
* <p>Match failures arising from unexpected inputs will generally throw {@code MatchException} only
* after all patterns have been tried; even if {@code R(S(String s))} does not match
* {@code new R(null)}, a later pattern (such as {@code R r}) may still match the target.
*
* <p>MatchException may also be thrown when operations performed as part of pattern matching throw
* an unexpected exception. For example, pattern matching may cause methods such as record component
* accessors to be implicitly invoked in order to extract pattern bindings. If these methods throw
* an exception, execution of the pattern matching construct may fail with {@code MatchException}.
* The original exception will be set as a {@link Throwable#getCause() cause} of
* the {@code MatchException}. No {@link Throwable#addSuppressed(java.lang.Throwable) suppressed}
* exceptions will be recorded.
*
* @jls 14.11.3 Execution of a switch Statement
* @jls 14.30.2 Pattern Matching
* @jls 15.28.2 Run-Time Evaluation of switch Expressions
*
* @since 19
* @since 19
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING)
public final class MatchException extends RuntimeException {
Expand Down
Expand Up @@ -61,6 +61,7 @@

public enum Feature {
SWITCH_PATTERN_MATCHING,
RECORD_PATTERNS,
VIRTUAL_THREADS,
FOREIGN,
/**
Expand Down
@@ -0,0 +1,58 @@
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/

package com.sun.source.tree;

import java.util.List;
import jdk.internal.javac.PreviewFeature;

/**
* A deconstruction pattern tree.
*
* @since 19
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public interface DeconstructionPatternTree extends PatternTree {

/**
* Returns the deconstructed type.
* @return the deconstructed type
*/
ExpressionTree getDeconstructor();

/**
* Returns the nested patterns.
* @return the nested patterns.
*/
List<? extends PatternTree> getNestedPatterns();

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

}

8 changes: 8 additions & 0 deletions src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java
Expand Up @@ -244,6 +244,14 @@ public enum Kind {
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
DEFAULT_CASE_LABEL(DefaultCaseLabelTree.class),

/**
* Used for instances of {@link DeconstructionPatternTree}.
*
* @since 19
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
DECONSTRUCTION_PATTERN(DeconstructionPatternTree.class),

/**
* Used for instances of {@link PrimitiveTypeTree}.
*/
Expand Down
Expand Up @@ -278,6 +278,16 @@ public interface TreeVisitor<R,P> {
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p);

/**
* Visits a {@code DeconstructionPatternTree} node.
* @param node the node being visited
* @param p a parameter value
* @return a result value
* @since 19
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
R visitDeconstructionPattern(DeconstructionPatternTree node, P p);

/**
* Visits a {@code MethodTree} node.
* @param node the node being visited
Expand Down
Expand Up @@ -667,6 +667,20 @@ public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
* @since 19
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public R visitDeconstructionPattern(DeconstructionPatternTree node, P p) {
return defaultAction(node, p);
}

/**
* {@inheritDoc} This implementation calls {@code defaultAction}.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
*/
@Override
public R visitArrayAccess(ArrayAccessTree node, P p) {
Expand Down
Expand Up @@ -800,6 +800,24 @@ public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 19
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
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);
r = scanAndReduce(node.getGuard(), p, r);
return r;
}

/**
* {@inheritDoc} This implementation scans the children in left to right order.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
*/
@Override
public R visitArrayAccess(ArrayAccessTree node, P p) {
Expand Down
Expand Up @@ -187,6 +187,7 @@ public boolean isPreview(Feature feature) {
case CASE_NULL -> true;
case PATTERN_SWITCH -> true;
case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> true;
case RECORD_PATTERNS -> true;

//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true'
Expand Down
Expand Up @@ -240,6 +240,7 @@ public enum Feature {
PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
REDUNDANT_STRICTFP(JDK17),
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK19, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
RECORD_PATTERNS(JDK19, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
;

enum DiagKind {
Expand Down
105 changes: 94 additions & 11 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
Expand Up @@ -28,6 +28,7 @@
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;

import javax.lang.model.element.ElementKind;
import javax.tools.JavaFileObject;
Expand Down Expand Up @@ -1803,7 +1804,7 @@ private void handleSwitch(JCTree switchTree,
log.error(guard.pos(), Errors.GuardHasConstantExpressionFalse);
}
}
boolean unguarded = TreeInfo.unguardedCaseLabel(pat);
boolean unguarded = TreeInfo.unguardedCaseLabel(pat) && !pat.hasTag(RECORDPATTERN);
boolean unconditional =
unguarded &&
!patternType.isErroneous() &&
Expand Down Expand Up @@ -4118,18 +4119,20 @@ public void visitTypeTest(JCInstanceOf tree) {
Type clazztype;
JCTree typeTree;
if (tree.pattern.getTag() == BINDINGPATTERN ||
tree.pattern.getTag() == PARENTHESIZEDPATTERN) {
tree.pattern.getTag() == PARENTHESIZEDPATTERN ||
tree.pattern.getTag() == RECORDPATTERN) {
attribTree(tree.pattern, env, unknownExprInfo);
clazztype = tree.pattern.type;
if (types.isSubtype(exprtype, clazztype) &&
!exprtype.isErroneous() && !clazztype.isErroneous()) {
!exprtype.isErroneous() && !clazztype.isErroneous() &&
tree.pattern.getTag() != RECORDPATTERN) {
if (!allowUnconditionalPatternsInstanceOf) {
log.error(tree.pos(), Errors.InstanceofPatternNoSubtype(exprtype, clazztype));
} else if (preview.isPreview(Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF)) {
preview.warnPreview(tree.pattern.pos(), Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF);
}
}
typeTree = TreeInfo.primaryPatternTree((JCPattern) tree.pattern).var.vartype;
typeTree = TreeInfo.primaryPatternTypeTree((JCPattern) tree.pattern);
} else {
clazztype = attribType(tree.pattern, env);
typeTree = tree.pattern;
Expand Down Expand Up @@ -4163,7 +4166,10 @@ private boolean checkCastablePattern(DiagnosticPosition pos,
chk.basicHandler.report(pos,
diags.fragment(Fragments.InconvertibleTypes(exprType, pattType)));
return false;
} else if (exprType.isPrimitive() ^ pattType.isPrimitive()) {
} else if ((exprType.isPrimitive() || pattType.isPrimitive()) &&
(!exprType.isPrimitive() ||
!pattType.isPrimitive() ||
!types.isSameType(exprType, pattType))) {
chk.basicHandler.report(pos,
diags.fragment(Fragments.NotApplicableTypes(exprType, pattType)));
return false;
Expand All @@ -4177,23 +4183,100 @@ private boolean checkCastablePattern(DiagnosticPosition pos,
}

public void visitBindingPattern(JCBindingPattern tree) {
ResultInfo varInfo = new ResultInfo(KindSelector.TYP, resultInfo.pt, resultInfo.checkContext);
tree.type = tree.var.type = attribTree(tree.var.vartype, env, varInfo);
BindingSymbol v = new BindingSymbol(tree.var.mods.flags, tree.var.name, tree.var.vartype.type, env.info.scope.owner);
Type type;
if (tree.var.vartype != null) {
ResultInfo varInfo = new ResultInfo(KindSelector.TYP, resultInfo.pt, resultInfo.checkContext);
type = attribTree(tree.var.vartype, env, varInfo);
} else {
type = resultInfo.pt;
}
tree.type = tree.var.type = type;
BindingSymbol v = new BindingSymbol(tree.var.mods.flags, tree.var.name, type, env.info.scope.owner);
v.pos = tree.pos;
tree.var.sym = v;
if (chk.checkUnique(tree.var.pos(), v, env.info.scope)) {
chk.checkTransparentVar(tree.var.pos(), v, env.info.scope);
}
annotate.annotateLater(tree.var.mods.annotations, env, v, tree.pos());
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, env, v, tree.var.pos());
annotate.flush();
if (tree.var.vartype != null) {
annotate.annotateLater(tree.var.mods.annotations, env, v, tree.pos());
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, env, v, tree.var.pos());
annotate.flush();
}
chk.validate(tree.var.vartype, env, true);
result = tree.type;
matchBindings = new MatchBindings(List.of(v), List.nil());
}

@Override
public void visitRecordPattern(JCRecordPattern tree) {
tree.type = attribType(tree.deconstructor, env);
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());
tree.record = record;
} else {
log.error(tree.pos(), Errors.DeconstructionPatternOnlyRecords(site.tsym));
expectedRecordTypes = Stream.generate(() -> Type.noType)
.limit(tree.nested.size())
.collect(List.collector());
}
ListBuffer<BindingSymbol> outBindings = new ListBuffer<>();
List<Type> recordTypes = expectedRecordTypes;
List<JCPattern> nestedPatterns = tree.nested;
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);
checkCastablePattern(nestedPatterns.head.pos(), recordTypes.head, nestedPatterns.head.type);
outBindings.addAll(matchBindings.bindingsWhenTrue);
matchBindings.bindingsWhenTrue.forEach(localEnv.info.scope::enter);
nestedPatterns = nestedPatterns.tail;
recordTypes = recordTypes.tail;
}
if (recordTypes.nonEmpty() || nestedPatterns.nonEmpty()) {
while (nestedPatterns.nonEmpty()) {
attribExpr(nestedPatterns.head, localEnv, Type.noType);
nestedPatterns = nestedPatterns.tail;
}
List<Type> nestedTypes =
tree.nested.stream().map(p -> p.type).collect(List.collector());
log.error(tree.pos(),
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();
}
chk.validate(tree.deconstructor, env, true);
result = tree.type;
matchBindings = new MatchBindings(outBindings.toList(), List.nil());
}

public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
attribExpr(tree.pattern, env);
result = tree.type = tree.pattern.type;
Expand Down

1 comment on commit e9bddc1

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.