Skip to content

Commit e9bddc1

Browse files
lahodajBrian Goetzbiboudis
committed
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
1 parent ebfa27b commit e9bddc1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2243
-86
lines changed

src/java.base/share/classes/java/lang/MatchException.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,41 @@
3030
/**
3131
* Thrown to indicate an unexpected failure in pattern matching.
3232
*
33-
* {@code MatchException} may be thrown when an exhaustive pattern matching language construct
33+
* <p>{@code MatchException} may be thrown when an exhaustive pattern matching language construct
3434
* (such as a switch expression) encounters a value that does not match any of the provided
35-
* patterns at runtime. This can currently arise for separate compilation anomalies,
36-
* where a sealed interface has a different set of permitted subtypes at runtime than
37-
* it had at compilation time, an enum has a different set of constants at runtime than
38-
* it had at compilation time, or the type hierarchy has changed in incompatible ways between
39-
* compile time and run time.
35+
* patterns at runtime. This can arise from a number of cases:
36+
* <ul>
37+
* <li>Separate compilation anomalies, where a sealed interface has a different set of permitted
38+
* subtypes at runtime than it had at compilation time, an enum has a different set of
39+
* constants at runtime than it had at compilation time, or the type hierarchy has changed
40+
* in incompatible ways between compile time and run time.</li>
41+
* <li>{@code null} values and nested patterns using sealed types. If an interface or abstract
42+
* class {@code C} is sealed to permit {@code A} and {@code B}, then the set of record
43+
* patterns {@code R(A a)} and {@code R(B b)} are exhaustive on a record {@code R} whose
44+
* sole component is of type {@code C}, but neither of these patterns will match
45+
* {@code new R(null)}.</li>
46+
* <li>Null targets and nested record patterns. Given a record type {@code R} whose sole
47+
* component is {@code S}, which in turn is a record whose sole component is {@code String},
48+
* then the nested record pattern {@code R(S(String s))} will not match {@code new R(null)}.</li>
49+
* </ul>
50+
*
51+
* <p>Match failures arising from unexpected inputs will generally throw {@code MatchException} only
52+
* after all patterns have been tried; even if {@code R(S(String s))} does not match
53+
* {@code new R(null)}, a later pattern (such as {@code R r}) may still match the target.
54+
*
55+
* <p>MatchException may also be thrown when operations performed as part of pattern matching throw
56+
* an unexpected exception. For example, pattern matching may cause methods such as record component
57+
* accessors to be implicitly invoked in order to extract pattern bindings. If these methods throw
58+
* an exception, execution of the pattern matching construct may fail with {@code MatchException}.
59+
* The original exception will be set as a {@link Throwable#getCause() cause} of
60+
* the {@code MatchException}. No {@link Throwable#addSuppressed(java.lang.Throwable) suppressed}
61+
* exceptions will be recorded.
4062
*
4163
* @jls 14.11.3 Execution of a switch Statement
4264
* @jls 14.30.2 Pattern Matching
4365
* @jls 15.28.2 Run-Time Evaluation of switch Expressions
4466
*
45-
* @since 19
67+
* @since 19
4668
*/
4769
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING)
4870
public final class MatchException extends RuntimeException {

src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161

6262
public enum Feature {
6363
SWITCH_PATTERN_MATCHING,
64+
RECORD_PATTERNS,
6465
VIRTUAL_THREADS,
6566
FOREIGN,
6667
/**
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.sun.source.tree;
27+
28+
import java.util.List;
29+
import jdk.internal.javac.PreviewFeature;
30+
31+
/**
32+
* A deconstruction pattern tree.
33+
*
34+
* @since 19
35+
*/
36+
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
37+
public interface DeconstructionPatternTree extends PatternTree {
38+
39+
/**
40+
* Returns the deconstructed type.
41+
* @return the deconstructed type
42+
*/
43+
ExpressionTree getDeconstructor();
44+
45+
/**
46+
* Returns the nested patterns.
47+
* @return the nested patterns.
48+
*/
49+
List<? extends PatternTree> getNestedPatterns();
50+
51+
/**
52+
* Returns the binding variable.
53+
* @return the binding variable
54+
*/
55+
VariableTree getVariable();
56+
57+
}
58+

src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@ public enum Kind {
244244
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
245245
DEFAULT_CASE_LABEL(DefaultCaseLabelTree.class),
246246

247+
/**
248+
* Used for instances of {@link DeconstructionPatternTree}.
249+
*
250+
* @since 19
251+
*/
252+
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
253+
DECONSTRUCTION_PATTERN(DeconstructionPatternTree.class),
254+
247255
/**
248256
* Used for instances of {@link PrimitiveTypeTree}.
249257
*/

src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,16 @@ public interface TreeVisitor<R,P> {
278278
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
279279
R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p);
280280

281+
/**
282+
* Visits a {@code DeconstructionPatternTree} node.
283+
* @param node the node being visited
284+
* @param p a parameter value
285+
* @return a result value
286+
* @since 19
287+
*/
288+
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
289+
R visitDeconstructionPattern(DeconstructionPatternTree node, P p);
290+
281291
/**
282292
* Visits a {@code MethodTree} node.
283293
* @param node the node being visited

src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,20 @@ public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
667667
* @param node {@inheritDoc}
668668
* @param p {@inheritDoc}
669669
* @return the result of {@code defaultAction}
670+
* @since 19
671+
*/
672+
@Override
673+
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
674+
public R visitDeconstructionPattern(DeconstructionPatternTree node, P p) {
675+
return defaultAction(node, p);
676+
}
677+
678+
/**
679+
* {@inheritDoc} This implementation calls {@code defaultAction}.
680+
*
681+
* @param node {@inheritDoc}
682+
* @param p {@inheritDoc}
683+
* @return the result of {@code defaultAction}
670684
*/
671685
@Override
672686
public R visitArrayAccess(ArrayAccessTree node, P p) {

src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,24 @@ public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
800800
* @param node {@inheritDoc}
801801
* @param p {@inheritDoc}
802802
* @return the result of scanning
803+
* @since 19
804+
*/
805+
@Override
806+
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
807+
public R visitDeconstructionPattern(DeconstructionPatternTree node, P p) {
808+
R r = scan(node.getDeconstructor(), p);
809+
r = scanAndReduce(node.getNestedPatterns(), p, r);
810+
r = scanAndReduce(node.getVariable(), p, r);
811+
r = scanAndReduce(node.getGuard(), p, r);
812+
return r;
813+
}
814+
815+
/**
816+
* {@inheritDoc} This implementation scans the children in left to right order.
817+
*
818+
* @param node {@inheritDoc}
819+
* @param p {@inheritDoc}
820+
* @return the result of scanning
803821
*/
804822
@Override
805823
public R visitArrayAccess(ArrayAccessTree node, P p) {

src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ public boolean isPreview(Feature feature) {
187187
case CASE_NULL -> true;
188188
case PATTERN_SWITCH -> true;
189189
case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> true;
190+
case RECORD_PATTERNS -> true;
190191

191192
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
192193
//When real preview features will be added, this method can be implemented to return 'true'

src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ public enum Feature {
240240
PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
241241
REDUNDANT_STRICTFP(JDK17),
242242
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK19, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
243+
RECORD_PATTERNS(JDK19, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
243244
;
244245

245246
enum DiagKind {

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.*;
2929
import java.util.function.BiConsumer;
3030
import java.util.function.Consumer;
31+
import java.util.stream.Stream;
3132

3233
import javax.lang.model.element.ElementKind;
3334
import javax.tools.JavaFileObject;
@@ -1803,7 +1804,7 @@ private void handleSwitch(JCTree switchTree,
18031804
log.error(guard.pos(), Errors.GuardHasConstantExpressionFalse);
18041805
}
18051806
}
1806-
boolean unguarded = TreeInfo.unguardedCaseLabel(pat);
1807+
boolean unguarded = TreeInfo.unguardedCaseLabel(pat) && !pat.hasTag(RECORDPATTERN);
18071808
boolean unconditional =
18081809
unguarded &&
18091810
!patternType.isErroneous() &&
@@ -4118,18 +4119,20 @@ public void visitTypeTest(JCInstanceOf tree) {
41184119
Type clazztype;
41194120
JCTree typeTree;
41204121
if (tree.pattern.getTag() == BINDINGPATTERN ||
4121-
tree.pattern.getTag() == PARENTHESIZEDPATTERN) {
4122+
tree.pattern.getTag() == PARENTHESIZEDPATTERN ||
4123+
tree.pattern.getTag() == RECORDPATTERN) {
41224124
attribTree(tree.pattern, env, unknownExprInfo);
41234125
clazztype = tree.pattern.type;
41244126
if (types.isSubtype(exprtype, clazztype) &&
4125-
!exprtype.isErroneous() && !clazztype.isErroneous()) {
4127+
!exprtype.isErroneous() && !clazztype.isErroneous() &&
4128+
tree.pattern.getTag() != RECORDPATTERN) {
41264129
if (!allowUnconditionalPatternsInstanceOf) {
41274130
log.error(tree.pos(), Errors.InstanceofPatternNoSubtype(exprtype, clazztype));
41284131
} else if (preview.isPreview(Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF)) {
41294132
preview.warnPreview(tree.pattern.pos(), Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF);
41304133
}
41314134
}
4132-
typeTree = TreeInfo.primaryPatternTree((JCPattern) tree.pattern).var.vartype;
4135+
typeTree = TreeInfo.primaryPatternTypeTree((JCPattern) tree.pattern);
41334136
} else {
41344137
clazztype = attribType(tree.pattern, env);
41354138
typeTree = tree.pattern;
@@ -4163,7 +4166,10 @@ private boolean checkCastablePattern(DiagnosticPosition pos,
41634166
chk.basicHandler.report(pos,
41644167
diags.fragment(Fragments.InconvertibleTypes(exprType, pattType)));
41654168
return false;
4166-
} else if (exprType.isPrimitive() ^ pattType.isPrimitive()) {
4169+
} else if ((exprType.isPrimitive() || pattType.isPrimitive()) &&
4170+
(!exprType.isPrimitive() ||
4171+
!pattType.isPrimitive() ||
4172+
!types.isSameType(exprType, pattType))) {
41674173
chk.basicHandler.report(pos,
41684174
diags.fragment(Fragments.NotApplicableTypes(exprType, pattType)));
41694175
return false;
@@ -4177,23 +4183,100 @@ private boolean checkCastablePattern(DiagnosticPosition pos,
41774183
}
41784184

41794185
public void visitBindingPattern(JCBindingPattern tree) {
4180-
ResultInfo varInfo = new ResultInfo(KindSelector.TYP, resultInfo.pt, resultInfo.checkContext);
4181-
tree.type = tree.var.type = attribTree(tree.var.vartype, env, varInfo);
4182-
BindingSymbol v = new BindingSymbol(tree.var.mods.flags, tree.var.name, tree.var.vartype.type, env.info.scope.owner);
4186+
Type type;
4187+
if (tree.var.vartype != null) {
4188+
ResultInfo varInfo = new ResultInfo(KindSelector.TYP, resultInfo.pt, resultInfo.checkContext);
4189+
type = attribTree(tree.var.vartype, env, varInfo);
4190+
} else {
4191+
type = resultInfo.pt;
4192+
}
4193+
tree.type = tree.var.type = type;
4194+
BindingSymbol v = new BindingSymbol(tree.var.mods.flags, tree.var.name, type, env.info.scope.owner);
41834195
v.pos = tree.pos;
41844196
tree.var.sym = v;
41854197
if (chk.checkUnique(tree.var.pos(), v, env.info.scope)) {
41864198
chk.checkTransparentVar(tree.var.pos(), v, env.info.scope);
41874199
}
4188-
annotate.annotateLater(tree.var.mods.annotations, env, v, tree.pos());
4189-
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, env, v, tree.var.pos());
4190-
annotate.flush();
4200+
if (tree.var.vartype != null) {
4201+
annotate.annotateLater(tree.var.mods.annotations, env, v, tree.pos());
4202+
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, env, v, tree.var.pos());
4203+
annotate.flush();
4204+
}
41914205
chk.validate(tree.var.vartype, env, true);
41924206
result = tree.type;
41934207
matchBindings = new MatchBindings(List.of(v), List.nil());
41944208
}
41954209

41964210
@Override
4211+
public void visitRecordPattern(JCRecordPattern tree) {
4212+
tree.type = attribType(tree.deconstructor, env);
4213+
Type site = types.removeWildcards(tree.type);
4214+
List<Type> expectedRecordTypes;
4215+
if (site.tsym.kind == Kind.TYP && ((ClassSymbol) site.tsym).isRecord()) {
4216+
ClassSymbol record = (ClassSymbol) site.tsym;
4217+
if (record.type.getTypeArguments().nonEmpty() && tree.type.isRaw()) {
4218+
log.error(tree.pos(),Errors.RawDeconstructionPattern);
4219+
}
4220+
expectedRecordTypes = record.getRecordComponents()
4221+
.stream()
4222+
.map(rc -> types.memberType(site, rc)).collect(List.collector());
4223+
tree.record = record;
4224+
} else {
4225+
log.error(tree.pos(), Errors.DeconstructionPatternOnlyRecords(site.tsym));
4226+
expectedRecordTypes = Stream.generate(() -> Type.noType)
4227+
.limit(tree.nested.size())
4228+
.collect(List.collector());
4229+
}
4230+
ListBuffer<BindingSymbol> outBindings = new ListBuffer<>();
4231+
List<Type> recordTypes = expectedRecordTypes;
4232+
List<JCPattern> nestedPatterns = tree.nested;
4233+
Env<AttrContext> localEnv = env.dup(tree, env.info.dup(env.info.scope.dup()));
4234+
try {
4235+
while (recordTypes.nonEmpty() && nestedPatterns.nonEmpty()) {
4236+
boolean nestedIsVarPattern = false;
4237+
nestedIsVarPattern |= nestedPatterns.head.hasTag(BINDINGPATTERN) &&
4238+
((JCBindingPattern) nestedPatterns.head).var.vartype == null;
4239+
attribExpr(nestedPatterns.head, localEnv, nestedIsVarPattern ? recordTypes.head : Type.noType);
4240+
checkCastablePattern(nestedPatterns.head.pos(), recordTypes.head, nestedPatterns.head.type);
4241+
outBindings.addAll(matchBindings.bindingsWhenTrue);
4242+
matchBindings.bindingsWhenTrue.forEach(localEnv.info.scope::enter);
4243+
nestedPatterns = nestedPatterns.tail;
4244+
recordTypes = recordTypes.tail;
4245+
}
4246+
if (recordTypes.nonEmpty() || nestedPatterns.nonEmpty()) {
4247+
while (nestedPatterns.nonEmpty()) {
4248+
attribExpr(nestedPatterns.head, localEnv, Type.noType);
4249+
nestedPatterns = nestedPatterns.tail;
4250+
}
4251+
List<Type> nestedTypes =
4252+
tree.nested.stream().map(p -> p.type).collect(List.collector());
4253+
log.error(tree.pos(),
4254+
Errors.IncorrectNumberOfNestedPatterns(expectedRecordTypes,
4255+
nestedTypes));
4256+
}
4257+
if (tree.var != null) {
4258+
BindingSymbol v = new BindingSymbol(tree.var.mods.flags, tree.var.name, tree.type,
4259+
localEnv.info.scope.owner);
4260+
v.pos = tree.pos;
4261+
tree.var.sym = v;
4262+
if (chk.checkUnique(tree.var.pos(), v, localEnv.info.scope)) {
4263+
chk.checkTransparentVar(tree.var.pos(), v, localEnv.info.scope);
4264+
}
4265+
if (tree.var.vartype != null) {
4266+
annotate.annotateLater(tree.var.mods.annotations, localEnv, v, tree.pos());
4267+
annotate.queueScanTreeAndTypeAnnotate(tree.var.vartype, localEnv, v, tree.var.pos());
4268+
annotate.flush();
4269+
}
4270+
outBindings.add(v);
4271+
}
4272+
} finally {
4273+
localEnv.info.scope.leave();
4274+
}
4275+
chk.validate(tree.deconstructor, env, true);
4276+
result = tree.type;
4277+
matchBindings = new MatchBindings(outBindings.toList(), List.nil());
4278+
}
4279+
41974280
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
41984281
attribExpr(tree.pattern, env);
41994282
result = tree.type = tree.pattern.type;

0 commit comments

Comments
 (0)