mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-23 05:10:57 +00:00
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:
parent
ebfa27b9f0
commit
e9bddc18ab
@ -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 {
|
||||
|
||||
@ -61,6 +61,7 @@ public @interface PreviewFeature {
|
||||
|
||||
public enum Feature {
|
||||
SWITCH_PATTERN_MATCHING,
|
||||
RECORD_PATTERNS,
|
||||
VIRTUAL_THREADS,
|
||||
FOREIGN,
|
||||
/**
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
|
||||
@ -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}.
|
||||
*/
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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++;
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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 ");
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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) {}
|
||||
}
|
||||
@ -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) {}
|
||||
}
|
||||
@ -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) {}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {}
|
||||
|
||||
}
|
||||
@ -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
|
||||
48
test/langtools/tools/javac/patterns/EmptyRecordClass.java
Normal file
48
test/langtools/tools/javac/patterns/EmptyRecordClass.java
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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],
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
117
test/langtools/tools/javac/patterns/PrettyTest.java
Normal file
117
test/langtools/tools/javac/patterns/PrettyTest.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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> {}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
SimpleDeconstructionPattern.java:118:27: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.deconstruction.patterns)
|
||||
1 error
|
||||
@ -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) {}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
20
test/langtools/tools/javac/patterns/VarErrors.java
Normal file
20
test/langtools/tools/javac/patterns/VarErrors.java
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
6
test/langtools/tools/javac/patterns/VarErrors.out
Normal file
6
test/langtools/tools/javac/patterns/VarErrors.out
Normal 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
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user