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
This commit is contained in:
Jan Lahoda 2022-05-25 11:56:24 +00:00
parent ebfa27b9f0
commit e9bddc18ab
50 changed files with 2243 additions and 86 deletions

View File

@ -30,19 +30,41 @@ import jdk.internal.javac.PreviewFeature;
/**
* 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 {

View File

@ -61,6 +61,7 @@ public @interface PreviewFeature {
public enum Feature {
SWITCH_PATTERN_MATCHING,
RECORD_PATTERNS,
VIRTUAL_THREADS,
FOREIGN,
/**

View File

@ -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();
}

View File

@ -244,6 +244,14 @@ public interface Tree {
@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}.
*/

View File

@ -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

View File

@ -667,6 +667,20 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,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) {

View File

@ -800,6 +800,24 @@ public class TreeScanner<R,P> implements TreeVisitor<R,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) {

View File

@ -187,6 +187,7 @@ public class Preview {
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'

View File

@ -240,6 +240,7 @@ public enum Source {
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 {

View File

@ -28,6 +28,7 @@ package com.sun.tools.javac.comp;
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;
@ -1803,7 +1804,7 @@ public class Attr extends JCTree.Visitor {
log.error(guard.pos(), Errors.GuardHasConstantExpressionFalse);
}
}
boolean unguarded = TreeInfo.unguardedCaseLabel(pat);
boolean unguarded = TreeInfo.unguardedCaseLabel(pat) && !pat.hasTag(RECORDPATTERN);
boolean unconditional =
unguarded &&
!patternType.isErroneous() &&
@ -4118,18 +4119,20 @@ public class Attr extends JCTree.Visitor {
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;
@ -4163,7 +4166,10 @@ public class Attr extends JCTree.Visitor {
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;
@ -4177,23 +4183,100 @@ public class Attr extends JCTree.Visitor {
}
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;

View File

@ -27,9 +27,12 @@
package com.sun.tools.javac.comp;
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.sun.source.tree.LambdaExpressionTree.BodyKind;
@ -52,6 +55,7 @@ import static com.sun.tools.javac.code.Flags.BLOCK;
import static com.sun.tools.javac.code.Kinds.Kind.*;
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.resources.CompilerProperties.Fragments;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
@ -664,15 +668,11 @@ public class Flow {
pendingExits = new ListBuffer<>();
scan(tree.selector);
boolean exhaustiveSwitch = TreeInfo.expectedExhaustive(tree);
Set<Symbol> constants = exhaustiveSwitch ? new HashSet<>() : null;
for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
alive = Liveness.ALIVE;
JCCase c = l.head;
for (JCCaseLabel pat : c.labels) {
scan(pat);
if (TreeInfo.unguardedCaseLabel(pat)) {
handleConstantCaseLabel(constants, pat);
}
}
scanStats(c.stats);
if (alive != Liveness.DEAD && c.caseKind == JCCase.RULE) {
@ -690,7 +690,8 @@ public class Flow {
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases);
if (exhaustiveSwitch) {
tree.isExhaustive |= isExhaustive(tree.selector.pos(), tree.selector.type, constants);
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.selector, tree.cases);
tree.isExhaustive |= isExhaustive(tree.selector.pos(), tree.selector.type, coveredSymbols);
if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustiveStatement);
}
@ -706,16 +707,12 @@ public class Flow {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scan(tree.selector);
Set<Symbol> constants = new HashSet<>();
Liveness prevAlive = alive;
for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
alive = Liveness.ALIVE;
JCCase c = l.head;
for (JCCaseLabel pat : c.labels) {
scan(pat);
if (TreeInfo.unguardedCaseLabel(pat)) {
handleConstantCaseLabel(constants, pat);
}
}
scanStats(c.stats);
if (alive == Liveness.ALIVE) {
@ -728,9 +725,10 @@ public class Flow {
}
}
}
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.selector, tree.cases);
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) ||
isExhaustive(tree.selector.pos(), tree.selector.type, constants);
isExhaustive(tree.selector.pos(), tree.selector.type, coveredSymbols);
if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustive);
}
@ -738,18 +736,132 @@ public class Flow {
alive = alive.or(resolveYields(tree, prevPendingExits));
}
private void handleConstantCaseLabel(Set<Symbol> constants, JCCaseLabel pat) {
if (constants != null) {
if (pat.isExpression()) {
JCExpression expr = (JCExpression) pat;
if (expr.hasTag(IDENT) && ((JCIdent) expr).sym.isEnum())
constants.add(((JCIdent) expr).sym);
} else if (pat.isPattern()) {
Type primaryType = TreeInfo.primaryPatternType(pat);
private Set<Symbol> coveredSymbolsForCases(DiagnosticPosition pos,
JCExpression selector, List<JCCase> cases) {
HashSet<JCCaseLabel> labels = cases.stream()
.flatMap(c -> c.labels.stream())
.filter(TreeInfo::unguardedCaseLabel)
.collect(Collectors.toCollection(HashSet::new));
return coveredSymbols(pos, selector.type, labels);
}
constants.add(primaryType.tsym);
private Set<Symbol> coveredSymbols(DiagnosticPosition pos, Type targetType,
Iterable<? extends JCCaseLabel> labels) {
Set<Symbol> coveredSymbols = new HashSet<>();
Map<Symbol, List<JCRecordPattern>> deconstructionPatternsBySymbol = new HashMap<>();
for (JCCaseLabel label : labels) {
switch (label.getTag()) {
case BINDINGPATTERN, PARENTHESIZEDPATTERN -> {
Type primaryPatternType = TreeInfo.primaryPatternType((JCPattern) label);
if (!primaryPatternType.hasTag(NONE)) {
coveredSymbols.add(primaryPatternType.tsym);
}
}
case RECORDPATTERN -> {
JCRecordPattern dpat = (JCRecordPattern) label;
Symbol type = dpat.record;
List<JCRecordPattern> augmentedPatterns =
deconstructionPatternsBySymbol.getOrDefault(type, List.nil())
.prepend(dpat);
deconstructionPatternsBySymbol.put(type, augmentedPatterns);
}
case DEFAULTCASELABEL -> {}
default -> {
if (label.isExpression()) {
JCExpression expr = (JCExpression) label;
if (expr.hasTag(IDENT) && ((JCIdent) expr).sym.isEnum())
coveredSymbols.add(((JCIdent) expr).sym);
} else {
throw new AssertionError(label.getTag());
}
}
}
}
for (Entry<Symbol, List<JCRecordPattern>> e : deconstructionPatternsBySymbol.entrySet()) {
if (coversDeconstructionFromComponent(pos, targetType, e.getValue(), 0)) {
coveredSymbols.add(e.getKey());
}
}
return coveredSymbols;
}
private boolean coversDeconstructionFromComponent(DiagnosticPosition pos,
Type targetType,
List<JCRecordPattern> deconstructionPatterns,
int component) {
//Given a set of record patterns for the same record, and a starting component,
//this method checks, whether the nested patterns for the components are exhaustive,
//i.e. represent all possible combinations.
//This is done by categorizing the patterns based on the type covered by the given
//starting component.
//For each such category, it is then checked if the nested patterns starting at the next
//component are exhaustive, by recursivelly invoking this method. If these nested patterns
//are exhaustive, the given covered type is accepted.
//All such covered types are then checked whether they cover the declared type of
//the starting component's declaration. If yes, the given set of patterns starting at
//the given component cover the given record exhaustivelly, and true is returned.
List<? extends RecordComponent> components =
deconstructionPatterns.head.record.getRecordComponents();
if (components.size() == component) {
//no components remain to be checked:
return true;
}
//for the first tested component, gather symbols covered by the nested patterns:
Type instantiatedComponentType = types.memberType(targetType, components.get(component));
List<JCPattern> nestedComponentPatterns = deconstructionPatterns.map(d -> d.nested.get(component));
Set<Symbol> coveredSymbolsForComponent = coveredSymbols(pos, instantiatedComponentType,
nestedComponentPatterns);
//for each of the symbols covered by the starting component, find all deconstruction patterns
//that have the given type, or its supertype, as a type of the starting nested pattern:
Map<Symbol, List<JCRecordPattern>> coveredSymbol2Patterns = new HashMap<>();
for (JCRecordPattern deconstructionPattern : deconstructionPatterns) {
JCPattern nestedPattern = deconstructionPattern.nested.get(component);
Symbol componentPatternType;
switch (nestedPattern.getTag()) {
case BINDINGPATTERN, PARENTHESIZEDPATTERN -> {
Type primaryPatternType =
TreeInfo.primaryPatternType(nestedPattern);
componentPatternType = primaryPatternType.tsym;
}
case RECORDPATTERN -> {
componentPatternType = ((JCRecordPattern) nestedPattern).record;
}
default -> {
throw Assert.error("Unexpected tree kind: " + nestedPattern.getTag());
}
}
for (Symbol currentType : coveredSymbolsForComponent) {
if (types.isSubtype(types.erasure(currentType.type),
types.erasure(componentPatternType.type))) {
coveredSymbol2Patterns.put(currentType,
coveredSymbol2Patterns.getOrDefault(currentType,
List.nil())
.prepend(deconstructionPattern));
}
}
}
//Check the components following the starting component, for each of the covered symbol,
//if they are exhaustive. If yes, the given covered symbol should be part of the following
//exhaustiveness check:
Set<Symbol> covered = new HashSet<>();
for (Entry<Symbol, List<JCRecordPattern>> e : coveredSymbol2Patterns.entrySet()) {
if (coversDeconstructionFromComponent(pos, targetType, e.getValue(), component + 1)) {
covered.add(e.getKey());
}
}
//verify whether the filtered symbols cover the given record's declared type:
return isExhaustive(pos, instantiatedComponentType, covered);
}
private void transitiveCovers(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
@ -817,10 +929,15 @@ public class Flow {
}
yield false;
}
yield covered.contains(seltype.tsym);
yield covered.stream()
.filter(coveredSym -> coveredSym.kind == TYP)
.anyMatch(coveredSym -> types.isSubtype(types.erasure(seltype),
types.erasure(coveredSym.type)));
}
case TYPEVAR -> isExhaustive(pos, ((TypeVar) seltype).getUpperBound(), covered);
default -> false;
default -> {
yield covered.contains(seltype.tsym);
}
};
}
@ -2857,6 +2974,14 @@ 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);
}
@ -3044,6 +3169,19 @@ public class Flow {
}
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
scan(tree.deconstructor);
scan(tree.nested);
JCTree prevTree = currentTree;
try {
currentTree = tree;
scan(tree.guard);
} finally {
currentTree = prevTree;
}
}
@Override
public void visitIdent(JCIdent tree) {
if (tree.sym.kind == VAR) {

View File

@ -142,8 +142,8 @@ public class MatchBindingsComputer extends TreeScanner {
public MatchBindings finishBindings(JCTree tree, MatchBindings matchBindings) {
switch (tree.getTag()) {
case NOT: case AND: case OR: case BINDINGPATTERN:
case PARENTHESIZEDPATTERN:
case PARENS: case TYPETEST:
case PARENTHESIZEDPATTERN: case TYPETEST:
case PARENS: case RECORDPATTERN:
case CONDEXPR: //error recovery:
return matchBindings;
default:

View File

@ -35,10 +35,11 @@ import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.BindingSymbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol;
import com.sun.tools.javac.code.Symbol.DynamicVarSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.Type.MethodType;
import com.sun.tools.javac.code.Type.WildcardType;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBinary;
@ -63,16 +64,14 @@ import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.Options;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.LinkedHashMap;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.Type.MethodType;
import com.sun.tools.javac.code.Type.WildcardType;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Symbol.RecordComponent;
import com.sun.tools.javac.code.Type;
import static com.sun.tools.javac.code.TypeTag.BOT;
import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
import com.sun.tools.javac.jvm.Target;
@ -86,15 +85,17 @@ import com.sun.tools.javac.tree.JCTree.JCContinue;
import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCParenthesizedPattern;
import com.sun.tools.javac.tree.JCTree.JCPattern;
import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
import com.sun.tools.javac.tree.JCTree.LetExpr;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.List;
import java.util.Iterator;
/**
* This pass translates pattern-matching constructs, such as instanceof <pattern>.
@ -164,8 +165,11 @@ public class TransPatterns extends TreeTranslator {
boolean debugTransPatterns;
private ClassSymbol currentClass = null;
private JCClassDecl currentClassTree = null;
private ListBuffer<JCTree> pendingMethods = null;
private MethodSymbol currentMethodSym = null;
private VarSymbol currentValue = null;
private Map<RecordComponent, MethodSymbol> component2Proxy = null;
protected TransPatterns(Context context) {
context.put(transPatternsKey, this);
@ -254,6 +258,131 @@ public class TransPatterns extends TreeTranslator {
result = translate(tree.pattern);
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
//type test already done, finish handling of deconstruction patterns ("T(PATT1, PATT2, ...)")
//=>
//<PATT1-handling> && <PATT2-handling> && ...
List<? extends RecordComponent> components = tree.record.getRecordComponents();
List<? extends Type> nestedFullComponentTypes = tree.fullComponentTypes;
List<? extends JCPattern> nestedPatterns = tree.nested;
JCExpression test = null;
while (components.nonEmpty() && nestedFullComponentTypes.nonEmpty() && nestedPatterns.nonEmpty()) {
//PATTn for record component COMPn of type Tn;
//PATTn is a type test pattern or a deconstruction pattern:
//=>
//(let Tn $c$COMPn = ((T) N$temp).COMPn(); <PATTn extractor>)
//or
//(let Tn $c$COMPn = ((T) N$temp).COMPn(); $c$COMPn != null && <PATTn extractor>)
//or
//(let Tn $c$COMPn = ((T) N$temp).COMPn(); $c$COMPn instanceof T' && <PATTn extractor>)
RecordComponent component = components.head;
JCPattern nested = nestedPatterns.head;
VarSymbol nestedTemp = new VarSymbol(Flags.SYNTHETIC,
names.fromString(target.syntheticNameChar() + "c" + target.syntheticNameChar() + component.name),
component.erasure(types),
currentMethodSym);
Symbol accessor = getAccessor(component);
JCVariableDecl nestedTempVar =
make.VarDef(nestedTemp,
make.App(make.QualIdent(accessor),
List.of(convert(make.Ident(currentValue), tree.type))));
JCExpression extracted;
VarSymbol prevCurrentValue = currentValue;
try {
currentValue = nestedTemp;
extracted = (JCExpression) this.<JCTree>translate(nested);
} finally {
currentValue = prevCurrentValue;
}
JCExpression extraTest = null;
if (!types.isAssignable(nestedTemp.type, nested.type)) {
if (!types.isAssignable(nestedFullComponentTypes.head, nested.type)) {
extraTest = makeTypeTest(make.Ident(nestedTemp),
make.Type(nested.type));
}
} else if (nested.type.isReference() && nested.hasTag(Tag.RECORDPATTERN)) {
extraTest = makeBinary(Tag.NE, make.Ident(nestedTemp), makeNull());
}
if (extraTest != null) {
extracted = makeBinary(Tag.AND, extraTest, extracted);
}
LetExpr getAndRun = make.LetExpr(nestedTempVar, extracted);
getAndRun.needsCond = true;
getAndRun.setType(syms.booleanType);
if (test == null) {
test = getAndRun;
} else {
test = makeBinary(Tag.AND, test, getAndRun);
}
components = components.tail;
nestedFullComponentTypes = nestedFullComponentTypes.tail;
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);
}
private MethodSymbol getAccessor(RecordComponent component) {
return component2Proxy.computeIfAbsent(component, c -> {
MethodSymbol realAccessor = (MethodSymbol) component.owner
.members()
.findFirst(component.name, s -> s.kind == Kind.MTH &&
((MethodSymbol) s).params.isEmpty());
MethodType type = new MethodType(List.of(component.owner.erasure(types)),
types.erasure(component.type),
List.nil(),
syms.methodClass);
MethodSymbol proxy = new MethodSymbol(Flags.STATIC | Flags.SYNTHETIC,
names.fromString("$proxy$" + component.name),
type,
currentClass);
JCStatement accessorStatement =
make.Return(make.App(make.Select(make.Ident(proxy.params().head), realAccessor)));
VarSymbol ctch = new VarSymbol(Flags.SYNTHETIC,
names.fromString("catch" + currentClassTree.pos + target.syntheticNameChar()),
syms.throwableType,
currentMethodSym);
JCNewClass newException = makeNewClass(syms.matchExceptionType,
List.of(makeApply(make.Ident(ctch),
names.toString,
List.nil()),
make.Ident(ctch)));
JCTree.JCCatch catchClause = make.Catch(make.VarDef(ctch, null),
make.Block(0, List.of(make.Throw(newException))));
JCStatement tryCatchAll = make.Try(make.Block(0, List.of(accessorStatement)),
List.of(catchClause),
null);
JCMethodDecl md = make.MethodDef(proxy,
proxy.externalType(types),
make.Block(0, List.of(tryCatchAll)));
pendingMethods.append(md);
currentClass.members().enter(proxy);
return proxy;
});
}
@Override
public void visitSwitch(JCSwitch tree) {
handleSwitch(tree, tree.selector, tree.cases,
@ -480,6 +609,25 @@ public class TransPatterns extends TreeTranslator {
}
}
JCMethodInvocation makeApply(JCExpression selector, Name name, List<JCExpression> args) {
MethodSymbol method = rs.resolveInternalMethod(
currentClassTree.pos(), env,
selector.type, name,
TreeInfo.types(args), List.nil());
JCMethodInvocation tree = make.App( make.Select(selector, method), args)
.setType(types.erasure(method.getReturnType()));
return tree;
}
JCNewClass makeNewClass(Type ctype, List<JCExpression> args) {
JCNewClass tree = make.NewClass(null,
null, make.QualIdent(ctype.tsym), args, null);
tree.constructor = rs.resolveConstructor(
currentClassTree.pos(), this.env, ctype, TreeInfo.types(args), List.nil());
tree.type = ctype;
return tree;
}
private Type principalType(JCTree p) {
return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p)));
}
@ -659,14 +807,24 @@ public class TransPatterns extends TreeTranslator {
@Override
public void visitClassDef(JCClassDecl tree) {
ClassSymbol prevCurrentClass = currentClass;
JCClassDecl prevCurrentClassTree = currentClassTree;
ListBuffer<JCTree> prevPendingMethods = pendingMethods;
MethodSymbol prevMethodSym = currentMethodSym;
Map<RecordComponent, MethodSymbol> prevAccessor2Proxy = component2Proxy;
try {
currentClass = tree.sym;
currentClassTree = tree;
pendingMethods = new ListBuffer<>();
currentMethodSym = null;
component2Proxy = new HashMap<>();
super.visitClassDef(tree);
tree.defs = tree.defs.prependList(pendingMethods.toList());
} finally {
currentClass = prevCurrentClass;
currentClassTree = prevCurrentClassTree;
pendingMethods = prevPendingMethods;
currentMethodSym = prevMethodSym;
component2Proxy = prevAccessor2Proxy;
}
}
@ -862,4 +1020,10 @@ public class TransPatterns extends TreeTranslator {
JCExpression makeLit(Type type, Object value) {
return make.Literal(type.getTag(), value).setType(type.constType(value));
}
/** Make an attributed tree representing null.
*/
JCExpression makeNull() {
return makeLit(syms.botType, null);
}
}

View File

@ -587,6 +587,14 @@ public class TransTypes extends TreeTranslator {
result = tree;
}
public void visitRecordPattern(JCRecordPattern tree) {
tree.fullComponentTypes = tree.record.getRecordComponents()
.map(rc -> types.memberType(tree.type, rc));
tree.deconstructor = translate(tree.deconstructor, null);
tree.nested = translate(tree.nested, null);
result = tree;
}
public void visitSynchronized(JCSynchronized tree) {
tree.lock = translate(tree.lock, erasure(tree.lock.type));
tree.body = translate(tree.body);

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2018, Google LLC. All rights reserved.
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 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
@ -71,6 +71,7 @@ import com.sun.tools.javac.tree.JCTree.JCOpens;
import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCProvides;
import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
import com.sun.tools.javac.tree.JCTree.JCRequires;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
@ -264,6 +265,14 @@ public class TreeDiffer extends TreeScanner {
}
}
@Override
public void visitRecordPattern(JCTree.JCRecordPattern tree) {
JCRecordPattern that = (JCRecordPattern) parameter;
result =
scan(tree.deconstructor, that.deconstructor)
&& scan(tree.nested, that.nested);
}
@Override
public void visitBlock(JCBlock tree) {
JCBlock that = (JCBlock) parameter;

View File

@ -759,27 +759,67 @@ public class JavacParser implements Parser {
return term(EXPR);
}
/** parses patterns.
*/
public JCPattern parsePattern(int pos, JCModifiers mods, JCExpression parsedType) {
public JCPattern parsePattern(int pos, JCModifiers mods, JCExpression parsedType,
boolean allowVar, boolean checkGuard) {
JCPattern pattern;
if (token.kind == LPAREN && parsedType == null) {
//parenthesized pattern:
int startPos = token.pos;
accept(LPAREN);
JCPattern p = parsePattern(token.pos, null, null);
JCPattern p = parsePattern(token.pos, null, null, true, false);
accept(RPAREN);
pattern = toP(F.at(startPos).ParenthesizedPattern(p));
} else {
mods = mods != null ? mods : optFinal(0);
JCExpression e = parsedType == null ? term(TYPE | NOLAMBDA) : parsedType;
JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), e, null));
pattern = toP(F.at(pos).BindingPattern(var));
JCExpression e;
if (parsedType == null) {
boolean var = token.kind == IDENTIFIER && token.name() == names.var;
e = unannotatedType(allowVar, TYPE | NOLAMBDA);
if (var) {
e = null;
}
} else {
e = parsedType;
}
if (token.kind == LPAREN) {
//deconstruction pattern:
checkSourceLevel(Feature.RECORD_PATTERNS);
ListBuffer<JCPattern> nested = new ListBuffer<>();
if (!peekToken(RPAREN)) {
do {
nextToken();
JCPattern nestedPattern = parsePattern(token.pos, null, null, true, false);
nested.append(nestedPattern);
} while (token.kind == COMMA);
} else {
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));
} else {
//type test pattern:
JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), e, null));
pattern = toP(F.at(pos).BindingPattern(var));
}
}
return pattern;
}
/**
* parses (optional) type annotations followed by a type. If the
* annotations are present before the type and are not consumed during array
@ -812,7 +852,11 @@ public class JavacParser implements Parser {
}
public JCExpression unannotatedType(boolean allowVar) {
JCExpression result = term(TYPE);
return unannotatedType(allowVar, TYPE);
}
public JCExpression unannotatedType(boolean allowVar, int newmode) {
JCExpression result = term(newmode);
Name restrictedTypeName = restrictedTypeName(result, !allowVar);
if (restrictedTypeName != null && (!allowVar || restrictedTypeName != names.var)) {
@ -961,7 +1005,7 @@ public class JavacParser implements Parser {
JCTree pattern;
if (token.kind == LPAREN) {
checkSourceLevel(token.pos, Feature.PATTERN_SWITCH);
pattern = parsePattern(token.pos, null, null);
pattern = parsePattern(token.pos, null, null, false, false);
} else {
int patternPos = token.pos;
JCModifiers mods = optFinal(0);
@ -969,7 +1013,9 @@ public class JavacParser implements Parser {
JCExpression type = unannotatedType(false);
if (token.kind == IDENTIFIER) {
checkSourceLevel(token.pos, Feature.PATTERN_MATCHING_IN_INSTANCEOF);
pattern = parsePattern(patternPos, mods, type);
pattern = parsePattern(patternPos, mods, type, false, false);
} else if (token.kind == LPAREN) {
pattern = parsePattern(patternPos, mods, type, false, false);
} else {
checkNoMods(typePos, mods.flags & ~Flags.DEPRECATED);
if (mods.annotations.nonEmpty()) {
@ -3070,7 +3116,7 @@ public class JavacParser implements Parser {
analyzePattern(lookahead) == PatternResult.PATTERN;
if (pattern) {
checkSourceLevel(token.pos, Feature.PATTERN_SWITCH);
JCPattern p = parsePattern(patternPos, mods, null);
JCPattern p = parsePattern(patternPos, mods, null, false, true);
if (token.kind == IDENTIFIER && token.name() == names.when) {
nextToken();
p.guard = term(EXPR | NOLAMBDA);
@ -3086,25 +3132,34 @@ public class JavacParser implements Parser {
@SuppressWarnings("fallthrough")
PatternResult analyzePattern(int lookahead) {
int depth = 0;
int typeDepth = 0;
int parenDepth = 0;
PatternResult pendingResult = PatternResult.EXPRESSION;
while (true) {
TokenKind token = S.token(lookahead).kind;
switch (token) {
case BYTE: case SHORT: case INT: case LONG: case FLOAT:
case DOUBLE: case BOOLEAN: case CHAR: case VOID:
case ASSERT, ENUM, IDENTIFIER, UNDERSCORE:
if (depth == 0 && peekToken(lookahead, LAX_IDENTIFIER)) return PatternResult.PATTERN;
if (typeDepth == 0 && peekToken(lookahead, LAX_IDENTIFIER)) {
if (parenDepth == 0) {
return PatternResult.PATTERN;
} else {
pendingResult = PatternResult.PATTERN;
}
}
break;
case DOT, QUES, EXTENDS, SUPER, COMMA: break;
case LT: depth++; break;
case GTGTGT: depth--;
case GTGT: depth--;
case LT: typeDepth++; break;
case GTGTGT: typeDepth--;
case GTGT: typeDepth--;
case GT:
depth--;
if (depth == 0) {
return peekToken(lookahead, LAX_IDENTIFIER) ? PatternResult.PATTERN
: PatternResult.EXPRESSION;
} else if (depth < 0) return PatternResult.EXPRESSION;
typeDepth--;
if (typeDepth == 0) {
return peekToken(lookahead, LAX_IDENTIFIER) ||
peekToken(lookahead, tk -> tk == LPAREN) ? PatternResult.PATTERN
: PatternResult.EXPRESSION;
} else if (typeDepth < 0) return PatternResult.EXPRESSION;
break;
case MONKEYS_AT:
lookahead = skipAnnotation(lookahead);
@ -3118,7 +3173,17 @@ public class JavacParser implements Parser {
} else {
return PatternResult.EXPRESSION;
}
default: return PatternResult.EXPRESSION;
case LPAREN:
if (S.token(lookahead + 1).kind == RPAREN) {
return parenDepth != 0 && S.token(lookahead + 2).kind == ARROW
? PatternResult.EXPRESSION
: PatternResult.PATTERN;
}
parenDepth++; break;
case RPAREN: parenDepth--; break;
case ARROW: return parenDepth > 0 ? PatternResult.EXPRESSION
: pendingResult;
default: return pendingResult;
}
lookahead++;
}

View File

@ -3080,6 +3080,9 @@ compiler.misc.feature.pattern.matching.instanceof=\
compiler.misc.feature.reifiable.types.instanceof=\
reifiable types in instanceof
compiler.misc.feature.deconstruction.patterns=\
deconstruction patterns
compiler.misc.feature.records=\
records
@ -3919,6 +3922,19 @@ compiler.err.preview.not.latest=\
compiler.err.preview.without.source.or.release=\
--enable-preview must be used with either -source or --release
# 0: symbol
compiler.err.deconstruction.pattern.only.records=\
deconstruction patterns can only be applied to records, {0} is not a record
# 0: list of type, 1: list of type
compiler.err.incorrect.number.of.nested.patterns=\
incorrect number of nested patterns\n\
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.

View File

@ -243,6 +243,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
DEFAULTCASELABEL,
PARENTHESIZEDPATTERN,
RECORDPATTERN,
/** Indexed array expressions, of type Indexed.
*/
INDEXED,
@ -2368,6 +2370,64 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
}
}
public static class JCRecordPattern extends JCPattern
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) {
this.deconstructor = deconstructor;
this.nested = nested;
this.var = var;
}
@DefinedBy(Api.COMPILER_TREE)
public Name getBinding() {
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public ExpressionTree getDeconstructor() {
return deconstructor;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public List<? extends JCPattern> getNestedPatterns() {
return nested;
}
@Override
public void accept(Visitor v) {
v.visitRecordPattern(this);
}
@DefinedBy(Api.COMPILER_TREE)
public Kind getKind() {
return Kind.DECONSTRUCTION_PATTERN;
}
@Override
@DefinedBy(Api.COMPILER_TREE)
public <R, D> R accept(TreeVisitor<R, D> v, D d) {
return v.visitDeconstructionPattern(this, d);
}
@Override
public Tag getTag() {
return RECORDPATTERN;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public VariableTree getVariable() {
return var;
}
}
/**
* An array selection
*/
@ -3410,6 +3470,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public void visitBindingPattern(JCBindingPattern that) { visitTree(that); }
public void visitDefaultCaseLabel(JCDefaultCaseLabel that) { visitTree(that); }
public void visitParenthesizedPattern(JCParenthesizedPattern that) { visitTree(that); }
public void visitRecordPattern(JCRecordPattern that) { visitTree(that); }
public void visitIndexed(JCArrayAccess that) { visitTree(that); }
public void visitSelect(JCFieldAccess that) { visitTree(that); }
public void visitReference(JCMemberReference that) { visitTree(that); }

View File

@ -924,6 +924,26 @@ public class Pretty extends JCTree.Visitor {
}
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
try {
printExpr(tree.deconstructor);
print("(");
printExprs(tree.nested);
print(")");
if (tree.var != null) {
print(" ");
print(tree.var.name);
}
if (tree.guard != null) {
print(" when ");
printExpr(tree.guard);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void visitSynchronized(JCSynchronized tree) {
try {
print("synchronized ");

View File

@ -516,6 +516,15 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
return M.at(t.pos).DefaultCaseLabel();
}
@DefinedBy(Api.COMPILER_TREE)
public JCTree visitDeconstructionPattern(DeconstructionPatternTree node, P 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);
}
@DefinedBy(Api.COMPILER_TREE)
public JCTree visitUnary(UnaryTree node, P p) {
JCUnary t = (JCUnary) node;

View File

@ -1310,14 +1310,16 @@ public class TreeInfo {
return switch (pat.getTag()) {
case BINDINGPATTERN -> pat.type;
case PARENTHESIZEDPATTERN -> primaryPatternType(((JCParenthesizedPattern) pat).pattern);
case RECORDPATTERN -> ((JCRecordPattern) pat).type;
default -> throw new AssertionError();
};
}
public static JCBindingPattern primaryPatternTree(JCTree pat) {
public static JCTree primaryPatternTypeTree(JCTree pat) {
return switch (pat.getTag()) {
case BINDINGPATTERN -> (JCBindingPattern) pat;
case PARENTHESIZEDPATTERN -> primaryPatternTree(((JCParenthesizedPattern) pat).pattern);
case BINDINGPATTERN -> ((JCBindingPattern) pat).var.vartype;
case PARENTHESIZEDPATTERN -> primaryPatternTypeTree(((JCParenthesizedPattern) pat).pattern);
case RECORDPATTERN -> ((JCRecordPattern) pat).deconstructor;
default -> throw new AssertionError();
};
}

View File

@ -500,6 +500,13 @@ 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);
tree.pos = pos;
return tree;
}
public JCArrayAccess Indexed(JCExpression indexed, JCExpression index) {
JCArrayAccess tree = new JCArrayAccess(indexed, index);
tree.pos = pos;

View File

@ -318,6 +318,16 @@ public class TreeScanner extends Visitor {
scan(tree.guard);
}
@Override
public void visitRecordPattern(JCRecordPattern that) {
scan(that.deconstructor);
scan(that.nested);
if (that.var != null) {
scan(that.var);
}
scan(that.guard);
}
public void visitIndexed(JCArrayAccess tree) {
scan(tree.indexed);
scan(tree.index);

View File

@ -470,6 +470,13 @@ public class TreeTranslator extends JCTree.Visitor {
result = tree;
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
tree.deconstructor = translate(tree.deconstructor);
tree.nested = translate(tree.nested);
result = tree;
}
public void visitTree(JCTree tree) {
throw new AssertionError(tree);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 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
@ -151,14 +151,14 @@ public class Assert {
/** Equivalent to
* assert false;
*/
public static void error() {
public static Error error() {
throw new AssertionError();
}
/** Equivalent to
* assert false : msg;
*/
public static void error(String msg) {
public static Error error(String msg) {
throw new AssertionError(msg);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 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
@ -28,10 +28,12 @@
* @modules java.compiler
* jdk.jdeps/com.sun.tools.javap
* @build toolbox.JavapTask
* @run main Patterns
* @compile --enable-preview -source ${jdk.version} Patterns.java
* @run main/othervm --enable-preview Patterns
*/
import java.lang.annotation.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -45,10 +47,11 @@ public class Patterns {
private ToolBox tb = new ToolBox();
public static void main(String[] args) throws Exception {
new Patterns().run();
new Patterns().runBinding();
new Patterns().runDeconstruction();
}
public void run() throws Exception {
public void runBinding() throws Exception {
String out = new JavapTask(tb)
.options("-private",
"-verbose")
@ -161,6 +164,132 @@ public class Patterns {
}
}
public void runDeconstruction() throws Exception {
List<String> outLines = new JavapTask(tb)
.options("-private",
"-verbose")
.classpath(System.getProperty("test.classes"))
.classes("Patterns$DeconstructionPattern")
.run()
.getOutputLines(Task.OutputKind.DIRECT);
String out = clearCodeAttribute(outLines);
String constantPool = out.substring(0, out.indexOf('{'));
out = out.substring(out.indexOf('{'));
out = out.substring(0, out.lastIndexOf('}') + 1);
String A = snipCPNumber(constantPool, "LPatterns$DeconstructionPattern$A;");
String CA = snipCPNumber(constantPool, "LPatterns$DeconstructionPattern$CA;");
String value = snipCPNumber(constantPool, "value");
String expected = """
{
private static final java.lang.Object o;
descriptor: Ljava/lang/Object;
flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
private static final boolean B1s;
descriptor: Z
flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
private static final boolean B1m;
descriptor: Z
flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
private final boolean B2s;
descriptor: Z
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
private final boolean B2m;
descriptor: Z
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
public Patterns$DeconstructionPattern();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
RuntimeInvisibleTypeAnnotations:
0: #_A_(): LOCAL_VARIABLE, {start_pc=251, length=11, index=2}
Patterns$DeconstructionPattern$A
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=290, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
2: #_A_(): LOCAL_VARIABLE, {start_pc=26, length=11, index=1}
Patterns$DeconstructionPattern$A
3: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=63, length=11, index=1}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
4: #_A_(): LOCAL_VARIABLE, {start_pc=101, length=11, index=2}
Patterns$DeconstructionPattern$A
5: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=140, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
6: #_A_(): LOCAL_VARIABLE, {start_pc=176, length=11, index=2}
Patterns$DeconstructionPattern$A
7: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=215, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
void testPatterns();
descriptor: ()V
flags: (0x0000)
RuntimeInvisibleTypeAnnotations:
0: #_A_(): LOCAL_VARIABLE, {start_pc=23, length=11, index=2}
Patterns$DeconstructionPattern$A
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=62, length=11, index=3}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
static java.lang.String $proxy$s(Patterns$DeconstructionPattern$R);
descriptor: (LPatterns$DeconstructionPattern$R;)Ljava/lang/String;
flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
RuntimeInvisibleTypeAnnotations:
0: #_A_(): LOCAL_VARIABLE, {start_pc=26, length=11, index=0}
Patterns$DeconstructionPattern$A
1: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=62, length=11, index=0}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
2: #_A_(): LOCAL_VARIABLE, {start_pc=98, length=11, index=1}
Patterns$DeconstructionPattern$A
3: #_CA_(#_value_=[@#_A_(),@#_A_()]): LOCAL_VARIABLE, {start_pc=134, length=11, index=2}
Patterns$DeconstructionPattern$CA(
value=[@Patterns$DeconstructionPattern$A,@Patterns$DeconstructionPattern$A]
)
}""".replace("_A_", A).replace("_CA_", CA).replace("_value_", value);
if (!expected.equals(out)) {
throw new AssertionError("Unexpected output:\n" + out + "\nexpected:\n" + expected);
}
}
private String clearCodeAttribute(List<String> out) {
StringBuilder result = new StringBuilder();
boolean codeSeen = false;
for (String line : out) {
if (line.contains(" Code:")) {
codeSeen = true;
} else if (codeSeen && line.startsWith(" ") &&
!line.contains("RuntimeInvisibleTypeAnnotations")) {
//ignore
} else {
result.append(line).append("\n");
codeSeen = false;
}
}
return result.toString();
}
private String snipCPNumber(String constantPool, String expectedConstant) {
Matcher m = Pattern.compile("#([0-9]+).*" + Pattern.quote(expectedConstant))
.matcher(constantPool);
@ -218,4 +347,47 @@ public class Patterns {
boolean B8sx = o instanceof String && (s = (String) o) == s && s.isEmpty();
}
}
static class DeconstructionPattern {
@Target(ElementType.TYPE_USE)
@Repeatable(CA.class)
@interface A {}
@Target(ElementType.TYPE_USE)
@interface CA {
public A[] value();
}
private static final Object o = "";
private static final boolean B1s = o instanceof R(@A String s) && s.isEmpty();
private static final boolean B1m = o instanceof R(@A @A String s) && s.isEmpty();
private final boolean B2s = o instanceof R(@A String s) && s.isEmpty();
private final boolean B2m = o instanceof R(@A @A String s) && s.isEmpty();
static {
boolean B3s = o instanceof R(@A String s) && s.isEmpty();
boolean B3m = o instanceof R(@A @A String s) && s.isEmpty();
}
{
boolean B4s = o instanceof R(@A String s) && s.isEmpty();
boolean B4m = o instanceof R(@A @A String s) && s.isEmpty();
}
{
boolean B5s = o instanceof R(@A String s) && s.isEmpty();
boolean B5m = o instanceof R(@A @A String s) && s.isEmpty();
}
public DeconstructionPattern() {
boolean B6s = o instanceof R(@A String s) && s.isEmpty();
boolean B6m = o instanceof R(@A @A String s) && s.isEmpty();
}
void testPatterns() {
boolean B7s = o instanceof R(@A String s) && s.isEmpty();
boolean B7m = o instanceof R(@A @A String s) && s.isEmpty();
}
record R(String s) {}
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020, 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.deconstruction.pattern.only.records
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.deconstruction.patterns
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class DeconstructionpatternOnlyRecords {
public boolean deconstruction(Object o) {
return o instanceof String(var content);
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.incorrect.number.of.nested.patterns
// key: compiler.misc.feature.deconstruction.patterns
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class IncorrectNumberOfNestedPatterns {
private boolean t(Object o) {
return o instanceof R(var i);
}
record R(int i, int j) {}
}

View File

@ -0,0 +1,35 @@
/*
* 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.raw.deconstruction.pattern
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: --enable-preview -source ${jdk.version}
class RawDeconstructionPattern {
boolean test(Object o) {
return o instanceof R(String s);
}
record R<T>(T t) {}
}

View File

@ -166,6 +166,12 @@ public class Deduplication {
group((Function<Object, Integer>) x -> x instanceof Integer i ? i : -1,
(Function<Object, Integer>) x -> x instanceof Integer i ? i : -1);
group((Function<Object, Integer>) x -> x instanceof R(var i1, var i2) ? i1 : -1,
(Function<Object, Integer>) x -> x instanceof R(var i1, var i2) ? i1 : -1 );
group((Function<Object, Integer>) x -> x instanceof R(Integer i1, int i2) ? i2 : -1,
(Function<Object, Integer>) x -> x instanceof R(Integer i1, int i2) ? i2 : -1 );
}
void f() {}
@ -174,4 +180,6 @@ public class Deduplication {
int i;
int j;
record R(Integer i1, int i2) {}
}

View File

@ -95,7 +95,9 @@ public class DeduplicationTest {
".",
"-g:none",
"-XDdebug.dumpLambdaToMethodDeduplication",
"-XDdebug.dumpLambdaToMethodStats"),
"-XDdebug.dumpLambdaToMethodStats",
"--enable-preview",
"-source", System.getProperty("java.specification.version")),
null,
fileManager.getJavaFileObjects(file));
Map<JCLambda, JCLambda> dedupedLambdas = new LinkedHashMap<>();
@ -134,6 +136,9 @@ public class DeduplicationTest {
Set<String> bootstrapMethodNames = new TreeSet<>();
for (JavaFileObject output : generated) {
ClassFile cf = ClassFile.read(output.openInputStream());
if (cf.getName().equals("com/sun/tools/javac/comp/Deduplication$R")) {
continue;
}
BootstrapMethods_attribute bsm =
(BootstrapMethods_attribute) cf.getAttribute(Attribute.BootstrapMethods);
for (BootstrapMethodSpecifier b : bsm.bootstrap_method_specifiers) {

View File

@ -0,0 +1,60 @@
/**
* @test /nodynamiccopyright/
* @summary Verify error reports for erroneous deconstruction patterns are sensible
* @compile/fail/ref=DeconstructionPatternErrors.out --enable-preview -source ${jdk.version} -XDrawDiagnostics -XDshould-stop.at=FLOW -XDdev DeconstructionPatternErrors.java
*/
import java.util.ArrayList;
import java.util.List;
public class DeconstructionPatternErrors {
public static void main(String... args) throws Throwable {
Object p;
p = new P(42);
if (p instanceof P(_));
if (p instanceof P3(ArrayList<Integer> l));
if (p instanceof P4(ArrayList<Integer> l));
if (p instanceof P5(int i));
if (p instanceof P(String s));
if (p instanceof P5(P(var v)));
if (p instanceof P2(var v1)); //too few nested patterns
if (p instanceof P2(Runnable v1)); //too few nested patterns
if (p instanceof P(var v1, var v2)); //too many nested patterns
if (p instanceof P(int v1, int v2)); //too many nested patterns
if (p instanceof P(int v1, Unresolvable v2)); //too many nested patterns
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 P7(byte b)); //incorrect pattern type
if (p instanceof P7(long l)); //incorrect pattern type
switch (p) {
case P7(byte b) -> {} //incorrect pattern type - no exception should occur
case P7(long l) -> {} //incorrect pattern type - no exception should occur
default -> {}
}
GenRecord<String> r1 = null;
if (r1 instanceof GenRecord(String s)) {}
switch (r1) {
case GenRecord(String s) -> {}
}
if (r1 instanceof GenRecord<>(String s)) {}
switch (r1) {
case GenRecord<>(String s) -> {}
}
}
public record P(int i) {
}
public record P2(Runnable r1, Runnable r2) {}
public record P3(List<String> l) {}
public record P4(Object o) {}
public record P5(String s) {}
public record P6(Object o1, Object o2) {}
public record P7(int i) {}
public record GenRecord<T>(T s) {}
}

View File

@ -0,0 +1,35 @@
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: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)
DeconstructionPatternErrors.java:19:28: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.String)
DeconstructionPatternErrors.java:20:29: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, DeconstructionPatternErrors.P)
DeconstructionPatternErrors.java:21:26: compiler.err.incorrect.number.of.nested.patterns: java.lang.Runnable,java.lang.Runnable, java.lang.Runnable
DeconstructionPatternErrors.java:22:26: compiler.err.incorrect.number.of.nested.patterns: java.lang.Runnable,java.lang.Runnable, java.lang.Runnable
DeconstructionPatternErrors.java:23:26: compiler.err.incorrect.number.of.nested.patterns: int, int,compiler.misc.type.none
DeconstructionPatternErrors.java:24:26: compiler.err.incorrect.number.of.nested.patterns: int, int,int
DeconstructionPatternErrors.java:25:36: compiler.err.cant.resolve.location: kindname.class, Unresolvable, , , (compiler.misc.location: kindname.class, DeconstructionPatternErrors, null)
DeconstructionPatternErrors.java:25:26: compiler.err.incorrect.number.of.nested.patterns: int, int,Unresolvable
DeconstructionPatternErrors.java:26:13: compiler.err.instanceof.reifiable.not.safe: java.lang.Object, DeconstructionPatternErrors.GenRecord<java.lang.String>
DeconstructionPatternErrors.java:27:29: compiler.err.instanceof.reifiable.not.safe: java.lang.Object, DeconstructionPatternErrors.GenRecord<java.lang.String>
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: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
- compiler.note.preview.filename: DeconstructionPatternErrors.java, DEFAULT
- compiler.note.preview.recompile
32 errors

View File

@ -0,0 +1,48 @@
/*
* 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
* @compile --enable-preview -source ${jdk.version} EmptyRecordClass.java
*/
public class EmptyRecordClass {
record X() {}
void test(X w) {
switch (w) {
case X(): break;
}
}
sealed interface W permits W.X1 {
record X1() implements W {}
}
public int test2(W w) {
return switch (w) {
case W.X1() -> 1;
};
}
}

View File

@ -681,6 +681,105 @@ public class Exhaustiveness extends TestRunner {
}
@Test
public void testX(Path base) throws Exception {
doTest(base,
new String[]{"""
package lib;
public sealed interface S permits A, B {}
""",
"""
package lib;
public final class A implements S {}
""",
"""
package lib;
public final class B implements S {}
""",
"""
package lib;
public record R(S a, S b) {}
"""},
"""
package test;
import lib.*;
public class Test {
private int test(R r) {
return switch (r) {
case R(A a, A b) -> 0;
case R(A a, B b) -> 0;
case R(B a, A b) -> 0;
case R(B a, B b) -> 0;
};
}
}
""");
doTest(base,
new String[]{"""
package lib;
public sealed interface S permits A, B {}
""",
"""
package lib;
public final class A implements S {}
""",
"""
package lib;
public record B(Object o) implements S {}
""",
"""
package lib;
public record R(S a, S b) {}
"""},
"""
package test;
import lib.*;
public class Test {
private int test(R r) {
return switch (r) {
case R(A a, A b) -> 0;
case R(A a, B b) -> 0;
case R(B a, A b) -> 0;
case R(B a, B(String s)) -> 0;
};
}
}
""",
"Test.java:5:16: compiler.err.not.exhaustive",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error");
doTest(base,
new String[]{"""
package lib;
public sealed interface S permits A, B {}
""",
"""
package lib;
public final class A implements S {}
""",
"""
package lib;
public record B(Object o) implements S {}
""",
"""
package lib;
public record R(S a, S b) {}
"""},
"""
package test;
import lib.*;
public class Test {
private int test(R r) {
return switch (r) {
case R(A a, A b) -> 0;
case R(A a, B b) -> 0;
case R(B a, A b) -> 0;
case R(B a, B(var o)) -> 0;
};
}
}
""");
}
public void testTransitiveSealed(Path base) throws Exception {
doTest(base,
new String[0],
@ -869,6 +968,48 @@ public class Exhaustiveness extends TestRunner {
}
@Test
public void testSuperTypesInPattern(Path base) throws Exception {
doTest(base,
new String[]{"""
package lib;
public sealed interface S permits A, B {}
""",
"""
package lib;
public final class A implements S {}
""",
"""
package lib;
public final class B implements S {}
""",
"""
package lib;
public record R(S a, S b) {}
"""},
"""
package test;
import lib.*;
public class Test {
private void testStatement(R obj) {
switch (obj) {
case R(A a, A b): break;
case R(A a, B b): break;
case R(B a, A b): break;
case R(B a, B b): break;
}
switch (obj) {
case R(S a, A b): break;
case R(S a, B b): break;
}
switch (obj) {
case R(Object a, A b): break;
case R(Object a, B b): break;
}
}
}
""");
}
public void testNonPrimitiveBooleanGuard(Path base) throws Exception {
doTest(base,
new String[0],

View File

@ -0,0 +1,91 @@
/*
* 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
* @compile --enable-preview -source ${jdk.version} GenericRecordDeconstructionPattern.java
* @run main/othervm --enable-preview GenericRecordDeconstructionPattern
*/
import java.util.Objects;
import java.util.function.Function;
public class GenericRecordDeconstructionPattern {
public static void main(String... args) throws Throwable {
new GenericRecordDeconstructionPattern().run();
}
void run() {
runTest(this::runIf);
runTest(this::runSwitch);
runTest(this::runSwitchExpression);
}
void runTest(Function<Box<String>, Integer> test) {
Box<String> b = new Box<>(null);
assertEquals(1, test.apply(b));
}
int runIf(Box<String> b) {
if (b instanceof Box<String>(String s)) return 1;
return -1;
}
int runSwitch(Box<String> b) {
switch (b) {
case Box<String>(String s): return 1;
default: return -1;
}
}
int runSwitchExpression(Box<String> b) {
return switch (b) {
case Box<String>(String s) -> 1;
default -> -1;
};
}
record Box<V>(V v) {
}
void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + "," +
"got: " + actual);
}
}
void fail(String message) {
throw new AssertionError(message);
}
public static class TestPatternFailed extends AssertionError {
public TestPatternFailed(String message) {
super(message);
}
}
}

View File

@ -56,6 +56,7 @@ public class Guards {
assertEquals("zero", convert.apply(0));
assertEquals("one", convert.apply(1));
assertEquals("other", convert.apply(-1));
assertEquals("box with empty", convert.apply(new Box("")));
assertEquals("any", convert.apply(""));
}
@ -70,6 +71,7 @@ public class Guards {
case Integer i when i == 0: return "zero";
case Integer i when i == 1: return "one";
case Integer i: return "other";
case Box(String s) when s.isEmpty(): return "box with empty";
case Object x: return "any";
}
}
@ -79,6 +81,7 @@ public class Guards {
case Integer i when i == 0 -> "zero";
case Integer i when i == 1 -> { yield "one"; }
case Integer i -> "other";
case Box(String s) when s.isEmpty() -> "box with empty";
case Object x -> "any";
};
}
@ -89,6 +92,7 @@ public class Guards {
case Integer i when i == 0 -> (x = "zero") != null;
case Integer i when i == 1 -> { x = "one"; yield true; }
case Integer i -> { x = "other"; yield true; }
case Box(String s) when s.isEmpty() -> {x = "box with empty"; yield true; }
case Object other -> (x = "any") != null;
}) {
return x;
@ -179,6 +183,8 @@ public class Guards {
};
}
record Box(Object o) {}
void assertEquals(String expected, String actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + ", but got: " + actual);

View File

@ -0,0 +1,131 @@
/*
* 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
* @compile --enable-preview -source ${jdk.version} NestedDeconstructionPattern.java
* @run main/othervm --enable-preview NestedDeconstructionPattern
*/
import java.util.Objects;
public class NestedDeconstructionPattern {
public static void main(String... args) throws Throwable {
new NestedDeconstructionPattern().doTestR();
new NestedDeconstructionPattern().doTestP();
}
void doTestR() {
assertEquals("AA", switchR1(new R(new A(), new A())));
assertEquals("AB", switchR1(new R(new A(), new B())));
assertEquals("BA", switchR1(new R(new B(), new A())));
assertEquals("BB", switchR1(new R(new B(), new B())));
try {
switchR1(null);
throw new AssertionError("Didn't get a NPE.");
} catch (NullPointerException ex) {
//OK
}
assertEquals("AA", switchR2(new R(new A(), new A())));
assertEquals("AB", switchR2(new R(new A(), new B())));
assertEquals("BA", switchR2(new R(new B(), new A())));
assertEquals("BB", switchR2(new R(new B(), new B())));
assertEquals("other", switchR2(""));
try {
switchR2(null);
throw new AssertionError("Didn't get a NPE.");
} catch (NullPointerException ex) {
//OK
}
}
String switchR1(R r) {
return switch (r) {
case R(A a, A b) -> a.name() + b.name();
case R(A a, B b) -> a.name() + b.name();
case R(B a, A b) -> a.name() + b.name();
case R(B a, B b) -> a.name() + b.name();
};
}
String switchR2(Object o) {
return switch (o) {
case R(A a, A b) -> a.name() + b.name();
case R(A a, B b) -> a.name() + b.name();
case R(B a, A b) -> a.name() + b.name();
case R(B a, B b) -> a.name() + b.name();
default -> "other";
};
}
void doTestP() {
assertEquals("AAAA", switchP1(new P(new R(new A(), new A()), new R(new A(), new A()))));
}
String switchP1(P p) {
return switch (p) {
case P(R(A a1, A b1), R(A a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, A b1), R(A a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, A b1), R(B a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, A b1), R(B a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, B b1), R(A a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, B b1), R(A a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, B b1), R(B a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(A a1, B b1), R(B a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, A b1), R(A a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, A b1), R(A a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, A b1), R(B a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, A b1), R(B a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, B b1), R(A a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, B b1), R(A a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, B b1), R(B a2, A b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(R(B a1, B b1), R(B a2, B b2)) -> a1.name() + b1.name()+a2.name() + b2.name();
case P(N a, N b) -> "other";
};
}
public sealed interface I {}
public final class A implements I {
public String name() { return "A"; }
}
public final class B implements I {
public String name() { return "B"; }
}
public record R(I a, I b) implements N {}
public sealed interface N {}
public final class C implements N {
public String name() { return "B"; }
}
public record P(N a, N b) {
}
private void assertEquals(String expected, String actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + ", but got: " + actual);
}
}
}

View File

@ -0,0 +1,64 @@
/*
* 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
* @compile --enable-preview -source ${jdk.version} NestedPrimitiveDeconstructionPattern.java
* @run main/othervm --enable-preview NestedPrimitiveDeconstructionPattern
*/
import java.util.Objects;
public class NestedPrimitiveDeconstructionPattern {
public static void main(String... args) throws Throwable {
new NestedPrimitiveDeconstructionPattern().doTestR();
}
void doTestR() {
assertEquals("OK", switchR1(new R(3, 42d)));
assertEquals("OK", switchR1_int_double(new R_i(3, 42d)));
}
record R(Integer x, Double y) {}
String switchR1(R r) {
return switch (r) {
case R(Integer x, Double y) -> "OK";
};
}
record R_i(int x, double y) {}
String switchR1_int_double(R_i r) {
return switch (r) {
case R_i(int x, double y) -> "OK";
};
}
private void assertEquals(String expected, String actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + ", but got: " + actual);
}
}
}

View File

@ -66,6 +66,28 @@ public class NullSwitch {
assertEquals(0, matchingSwitch13(""));
assertEquals(1, matchingSwitch13(0.0));
assertEquals(2, matchingSwitch13(null));
// record classes and null
assertEquals(1, matchingSwitch14(new R(null)));
assertEquals(2, matchingSwitch15(new R(null)));
}
class Super {}
class Sub extends Super {}
record R(Super s) {}
private int matchingSwitch14(R r) {
return switch(r) {
case R(Super s) -> 1;
default -> 2;
};
}
private int matchingSwitch15(R r) {
return switch(r) {
case R(Sub s) -> 1;
default -> 2;
};
}
private int matchingSwitch1(Object obj) {

View File

@ -0,0 +1,36 @@
/*
* @test /nodynamiccopyright/
* @summary Testing record patterns against the null constant (14.30.2 Pattern Matching)
* @compile --enable-preview -source ${jdk.version} NullsInDeconstructionPatterns.java
* @run main/othervm --enable-preview NullsInDeconstructionPatterns
*/
public class NullsInDeconstructionPatterns {
class Super {}
class Sub extends Super {}
record R(Super s) {}
public static void main(String[] args) {
R r = new R(null);
if (r instanceof R(Super s1)) {
System.out.println("R(Super s1) is resolved to the R(any pattern) and does match");
} else {
throw new AssertionError("broken");
}
if (r instanceof R(Object o)) {
System.out.println("R(Object) is resolved to the R(any pattern) and does match");
} else {
throw new AssertionError("broken");
}
if (r instanceof R(Sub s2)) {
throw new AssertionError("broken");
} else {
System.out.println("R(Sub s2) is resolved to the pattern R(Sub s) and does not match");
}
}
}

View File

@ -0,0 +1,3 @@
NullsInPatterns.java:12:18: compiler.err.instanceof.pattern.no.subtype: compiler.misc.type.null, java.util.List
NullsInPatterns.java:23:18: compiler.err.instanceof.pattern.no.subtype: compiler.misc.type.null, java.util.List<?>
2 errors

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2020, 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 Test behavior of Pretty
* @modules jdk.compiler
* @compile --enable-preview -source ${jdk.version} PrettyTest.java
* @run main/othervm --enable-preview PrettyTest
*/
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import javax.tools.*;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.JavacTask;
public class PrettyTest {
public static void main(String[] args) throws Exception {
new PrettyTest().run();
}
void run() throws Exception {
String code = "class Test {\n" +
" boolean t(Object o) {\n" +
" boolean b;\n" +
" b = o instanceof String s;\n" +
" b = o instanceof R(String s);\n" +
" 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" +
"}\n";
String pretty = parse(code).toString().replaceAll("\\R", "\n");
String expected = """
\n\
class Test {
\n\
boolean t(Object o) {
boolean b;
b = o instanceof String s;
b = o instanceof R(String s);
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 {
private final String s;
}
\n\
class R2 {
private final R r;
private final String s;
}
}""";
if (!expected.equals(pretty)) {
throw new AssertionError("Actual prettified source: " + pretty);
}
}
private CompilationUnitTree parse(String code) throws IOException {
final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
assert tool != null;
DiagnosticListener<JavaFileObject> noErrors = d -> {};
StringWriter out = new StringWriter();
JavacTask ct = (JavacTask) tool.getTask(out, null, noErrors,
List.of("--enable-preview", "-source", Integer.toString(Runtime.version().feature())), null,
Arrays.asList(new MyFileObject(code)));
return ct.parse().iterator().next();
}
static class MyFileObject extends SimpleJavaFileObject {
private String text;
public MyFileObject(String text) {
super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
this.text = text;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return text;
}
}
}

View File

@ -0,0 +1,213 @@
/**
* @test
* @compile/fail/ref=SimpleDeconstructionPatternNoPreview.out -XDrawDiagnostics SimpleDeconstructionPattern.java
* @compile --enable-preview -source ${jdk.version} SimpleDeconstructionPattern.java
* @run main/othervm --enable-preview SimpleDeconstructionPattern
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class SimpleDeconstructionPattern {
public static void main(String... args) throws Throwable {
if (!test2(new P(42))) {
throw new IllegalStateException();
}
if (test2(new P(41))) {
throw new IllegalStateException();
}
if (!test2a(new P(42))) {
throw new IllegalStateException();
}
if (test2a(new P(41))) {
throw new IllegalStateException();
}
if (!test4(new P2(new P(42), ""))) {
throw new IllegalStateException();
}
if (test4(new P2(new P(41), ""))) {
throw new IllegalStateException();
}
if (test4(new P2(new P(42), "a"))) {
throw new IllegalStateException();
}
if (!test5(new P(42))) {
throw new IllegalStateException();
}
if (test5(new P(41))) {
throw new IllegalStateException();
}
if (!test7(new P3(""))) {
throw new IllegalStateException();
}
if (test7(new P3("a"))) {
throw new IllegalStateException();
}
if (!test7a(new P3(""))) {
throw new IllegalStateException();
}
if (test7a(new P3("a"))) {
throw new IllegalStateException();
}
if (test8(new P4(""))) {
throw new IllegalStateException();
}
if (!test8(new P4(new P3("")))) {
throw new IllegalStateException();
}
if (!test8a(new P4(new P3("")))) {
throw new IllegalStateException();
}
if (test8(new P4(new P3("a")))) {
throw new IllegalStateException();
}
if (test8a(new P4(new P3("a")))) {
throw new IllegalStateException();
}
if (!test9(new P5(new ArrayList<String>(Arrays.asList(""))))) {
throw new IllegalStateException();
}
if (test9(new P5(new LinkedList<String>(Arrays.asList(""))))) {
throw new IllegalStateException();
}
if (testA(new P6(null))) {
throw new IllegalStateException();
}
if (!testA(new P6(new P3(null)))) {
throw new IllegalStateException();
}
if (testB(new P6(null))) {
throw new IllegalStateException();
}
if (!testB(new P6(new P3(null)))) {
throw new IllegalStateException();
}
if (testC(new P6(null))) {
throw new IllegalStateException();
}
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();
}
if (!testF(new P7(0, (short) 0))) {
throw new IllegalStateException();
}
if (testF(new P7(0, (short) 1))) {
throw new IllegalStateException();
}
if (testGen3(new GenRecord1<>(3L, ""))) {
throw new IllegalStateException();
}
if (!testGen3(new GenRecord1<>(3, ""))) {
throw new IllegalStateException();
}
if (!testGen3(new GenRecord1<>(3, ""))) {
throw new IllegalStateException();
}
}
private static void exp(Object o) throws Throwable {
if (o instanceof P(var i)) {
System.err.println("i=" + i);
}
}
private static boolean test2(Object o) throws Throwable {
return o instanceof P(var i) && i == 42;
}
private static boolean test2a(Object o) throws Throwable {
return o instanceof P(int i) && i == 42;
}
private static boolean test4(Object o) throws Throwable {
return o instanceof P2(P(var i), var s) && i == 42 && "".equals(s);
}
private static boolean test5(Object o) throws Throwable {
return o instanceof P(var i) && i == 42;
}
private static boolean test7(Object o) throws Throwable {
return o instanceof P3(var s) && "".equals(s);
}
private static boolean test7a(Object o) throws Throwable {
return o instanceof P3(String s) && "".equals(s);
}
private static boolean test8(Object o) throws Throwable {
return o instanceof P4(P3(var s)) && "".equals(s);
}
private static boolean test8a(Object o) throws Throwable {
return o instanceof P4(P3(String s)) && "".equals(s);
}
private static boolean test9(Object o) throws Throwable {
return o instanceof P5(ArrayList<String> l) && !l.isEmpty();
}
private static boolean testA(Object o) throws Throwable {
return o instanceof P6(P3(var s));
}
private static boolean testB(Object o) throws Throwable {
return o instanceof P6(P3(String s));
}
private static boolean testC(Object o) throws Throwable {
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;
}
private static boolean testF(Object o) throws Throwable {
return o instanceof P7(int i, short s) && i == s;
}
private static boolean testGen3(Object o) throws Throwable {
return o instanceof GenRecord1<?, ?>(Integer i, var s) && i.intValue() == 3 && s.length() == 0;
}
private static boolean testGen4(GenBase<Integer, String> o) throws Throwable {
return o instanceof GenRecord1<Integer, String>(var i, var s) && i.intValue() == 3 && s.length() == 0;
}
public record P(int i) {
}
public record P2(P p, String s) {
}
public record P3(String s) {
}
public record P4(Object o) {}
public record P5(List<String> l) {}
public record P6(P3 p) {}
public record P7(int i, short s) {}
public interface Base {}
public record BaseUse(Base b) {}
public record BaseSubclass(int i) implements Base {}
public interface GenBase<T1, T2 extends CharSequence> {}
public record GenRecord1<T1, T2 extends CharSequence> (T1 i, T2 s) implements GenBase<T1, T2> {}
}

View File

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

View File

@ -88,6 +88,12 @@ public class Switches {
assertEquals(5, switchOverPrimitiveInt(0));
assertEquals(7, switchOverPrimitiveInt(1));
assertEquals(9, switchOverPrimitiveInt(2));
assertEquals("a", deconstructStatement(new R("a")));
assertEquals("1", deconstructStatement(new R(1)));
assertEquals("other", deconstructStatement(""));
assertEquals("a", deconstructExpression(new R("a")));
assertEquals("1", deconstructExpression(new R(1)));
assertEquals("other", deconstructExpression(""));
}
void run(Function<Object, Integer> mapper) {
@ -611,6 +617,22 @@ 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 Object x -> {return "other";}
}
}
String deconstructExpression(Object o) {
return switch (o) {
case R(String s) -> s;
case R(Integer i) r -> r.o().toString();
case Object x -> "other";
};
}
//verify that for cases like:
//case ConstantClassClash ->
//ConstantClassClash is interpreted as a field, not as a class
@ -650,4 +672,6 @@ public class Switches {
@Override public void run() {}
}
record R(Object o) {}
}

View File

@ -0,0 +1,119 @@
/*
* 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
* @compile --enable-preview -source ${jdk.version} TypedDeconstructionPatternExc.java
* @run main/othervm --enable-preview TypedDeconstructionPatternExc
*/
import java.util.Objects;
import java.util.function.Function;
public class TypedDeconstructionPatternExc {
public static void main(String... args) throws Throwable {
new TypedDeconstructionPatternExc().run();
}
void run() {
run(this::testExpr);
run(this::testExprCond);
}
void run(Function<Pair<String, Integer>, Integer> tested) {
assertEquals(2, tested.apply(new Pair<>("1", 1)));
try {
tested.apply((Pair<String, Integer>) (Object) new Pair<Integer, Integer>(1, 1));
fail("Expected an exception, but none happened!");
} catch (ClassCastException ex) {
System.err.println("expected exception:");
ex.printStackTrace();
}
try {
tested.apply(new Pair<String, Integer>("fail", 1));
fail("Expected an exception, but none happened!");
} catch (MatchException ex) {
assertEquals(TestPatternFailed.class.getName() + ": " + EXCEPTION_MESSAGE,
ex.getMessage());
if (ex.getCause() instanceof TestPatternFailed ex2) {
System.err.println("expected exception:");
ex2.printStackTrace();
} else {
fail("Not the correct exception.");
}
}
}
int testExpr(Pair<String, Integer> p) {
return switch (p) {
case Pair<String, Integer>(String s, Integer i) -> s.length() + i;
case Object o -> -1;
};
}
int testExprCond(Pair<String, Integer> p) {
if (switch (p) {
case Pair<String, Integer>(String s, Integer i) -> true;
case Object o -> false;
}) {
return p.l().length() + p.r();
} else {
return -1;
}
}
static final String EXCEPTION_MESSAGE = "exception-message";
record Pair<L, R>(L l, R r) {
public L l() {
if ("fail".equals(l)) {
throw new TestPatternFailed(EXCEPTION_MESSAGE);
}
return l;
}
public R r() {
return r;
}
}
void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + "," +
"got: " + actual);
}
}
void fail(String message) {
throw new AssertionError(message);
}
public static class TestPatternFailed extends AssertionError {
public TestPatternFailed(String message) {
super(message);
}
}
}

View File

@ -0,0 +1,20 @@
/*
* @test /nodynamiccopyright/
* @summary Verify errors related to var patterns
* @compile/fail/ref=VarErrors.out --enable-preview -source ${jdk.version} -XDrawDiagnostics -XDshould-stop.at=FLOW -XDdev VarErrors.java
*/
public class VarErrors {
void testIf(CharSequence cs) {
if (cs instanceof var v) {}
}
void testSwitchStatement(CharSequence cs) {
switch (cs) {
case var v -> {}
}
}
void testSwitchExpression(CharSequence cs) {
int i = switch (cs) {
case var v -> 0;
};
}
}

View File

@ -0,0 +1,6 @@
VarErrors.java:8:27: compiler.err.restricted.type.not.allowed.here: var
VarErrors.java:12:18: compiler.err.restricted.type.not.allowed.here: var
VarErrors.java:17:18: compiler.err.restricted.type.not.allowed.here: var
- compiler.note.preview.filename: VarErrors.java, DEFAULT
- compiler.note.preview.recompile
3 errors

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 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
@ -34,7 +34,6 @@ import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import javax.tools.*;
@ -93,9 +92,10 @@ public class RuleParsingTest {
assert tool != null;
DiagnosticListener<JavaFileObject> noErrors = d -> { throw new AssertionError(d.getMessage(null)); };
String version = System.getProperty("java.specification.version");
StringWriter out = new StringWriter();
JavacTask ct = (JavacTask) tool.getTask(out, null, noErrors,
List.of(), null,
List.of("--enable-preview", "-source", version), null,
Arrays.asList(new MyFileObject(code.toString())));
CompilationUnitTree cut = ct.parse().iterator().next();
Trees trees = Trees.instance(ct);