8300543: Compiler Implementation for Pattern Matching for switch

8300545: Compiler Implementation for Record Patterns

Co-authored-by: Aggelos Biboudis <abimpoudis@openjdk.org>
Reviewed-by: vromero, mcimadamore
This commit is contained in:
Jan Lahoda 2023-05-22 04:24:06 +00:00
parent 5ccc962942
commit eaa80ad08c
157 changed files with 2591 additions and 2233 deletions

View File

@ -25,48 +25,57 @@
package java.lang;
import jdk.internal.javac.PreviewFeature;
/**
* Thrown to indicate an unexpected failure in pattern matching.
*
* <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 arise from a number of cases:
* <p>{@code MatchException} may be thrown when an exhaustive pattern matching
* language construct (such as a {@code switch} expression) encounters a value
* that does not match any of the specified patterns at run time, even though
* the construct has been deemed exhaustive. This is intentional and 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>
* <li>Separate compilation anomalies, where parts of the type hierarchy that
* the patterns reference have been changed, but the pattern matching
* construct has not been recompiled. For example, if a sealed interface
* has a different set of permitted subtypes at run time than it had at
* compile time, or if an enum class has a different set of enum constants
* at runtime than it had at compile time, or if the type hierarchy has
* been changed in some incompatible way between compile time and run time.</li>
*
* <li>{@code null} values and nested patterns involving sealed classes. If,
* for example, an interface {@code I} is {@code sealed} with two permitted
* subclasses {@code A} and {@code B}, and a record class {@code R} has a
* single component of type {@code I}, then the two record patterns {@code
* R(A a)} and {@code R(B b)} together are considered to be exhaustive for
* the type {@code R}, but neither of these patterns will match against the
* result of {@code new R(null)}.</li>
*
* <li>{@code null} values and nested record patterns. Given a record class
* {@code S} with a single component of type {@code T}, where {@code T} is
* another record class with a single component of type {@code String},
* then the nested record pattern {@code R(S(var s))} is considered
* exhaustive for the type {@code R} but it does not match against the
* result of {@code new R(null)} (whereas it does match against the result
* of {@code new R(new S(null))} does).</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>{@code MatchException} may also be thrown by the process of pattern matching
* a value against a pattern. For example, pattern matching involving a record
* pattern may require accessor methods to be implicitly invoked in order to
* extract the component values. If any of these accessor methods throws an
* exception, pattern matching completes abruptly and throws {@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.
*
* <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.11.3 Execution of a {@code switch} Statement
* @jls 14.30.2 Pattern Matching
* @jls 15.28.2 Run-Time Evaluation of switch Expressions
* @jls 15.28.2 Run-Time Evaluation of {@code switch} Expressions
*
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING)
public final class MatchException extends RuntimeException {
@java.io.Serial
private static final long serialVersionUID = 0L;

View File

@ -25,17 +25,18 @@
package java.lang.runtime;
import java.lang.Enum.EnumDesc;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.stream.Stream;
import jdk.internal.javac.PreviewFeature;
import static java.util.Objects.requireNonNull;
import jdk.internal.vm.annotation.Stable;
/**
* Bootstrap methods for linking {@code invokedynamic} call sites that implement
@ -43,9 +44,8 @@ import static java.util.Objects.requireNonNull;
* take additional static arguments corresponding to the {@code case} labels
* of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
*
* @since 17
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING)
public class SwitchBootstraps {
private SwitchBootstraps() {}
@ -60,7 +60,8 @@ public class SwitchBootstraps {
DO_TYPE_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doTypeSwitch",
MethodType.methodType(int.class, Object.class, int.class, Object[].class));
DO_ENUM_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doEnumSwitch",
MethodType.methodType(int.class, Enum.class, int.class, Object[].class));
MethodType.methodType(int.class, Enum.class, int.class, Object[].class,
MethodHandles.Lookup.class, Class.class, ResolvedEnumLabels.class));
}
catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
@ -71,7 +72,7 @@ public class SwitchBootstraps {
* Bootstrap method for linking an {@code invokedynamic} call site that
* implements a {@code switch} on a target of a reference type. The static
* arguments are an array of case labels which must be non-null and of type
* {@code String} or {@code Integer} or {@code Class}.
* {@code String} or {@code Integer} or {@code Class} or {@code EnumDesc}.
* <p>
* The type of the returned {@code CallSite}'s method handle will have
* a return type of {@code int}. It has two parameters: the first argument
@ -89,10 +90,16 @@ public class SwitchBootstraps {
* from the target's class; or</li>
* <li>the element is of type {@code String} or {@code Integer} and
* equals to the target.</li>
* <li>the element is of type {@code EnumDesc}, that describes a constant that is
* equals to the target.</li>
* </ul>
* <p>
* If no element in the {@code labels} array matches the target, then
* the method of the call site return the length of the {@code labels} array.
* <p>
* The value of the {@code restart} index must be between {@code 0} (inclusive) and
* the length of the {@code labels} array (inclusive),
* both or an {@link IndexOutOfBoundsException} is thrown.
*
* @param lookup Represents a lookup context with the accessibility
* privileges of the caller. When used with {@code invokedynamic},
@ -101,7 +108,7 @@ public class SwitchBootstraps {
* @param invocationType The invocation type of the {@code CallSite} with two parameters,
* a reference type, an {@code int}, and {@code int} as a return type.
* @param labels case labels - {@code String} and {@code Integer} constants
* and {@code Class} instances, in any combination
* and {@code Class} and {@code EnumDesc} instances, in any combination
* @return a {@code CallSite} returning the first matching element as described above
*
* @throws NullPointerException if any argument is {@code null}
@ -109,7 +116,7 @@ public class SwitchBootstraps {
* invocation type is not not a method type of first parameter of a reference type,
* second parameter of type {@code int} and with {@code int} as its return type,
* or if {@code labels} contains an element that is not of type {@code String},
* {@code Integer} or {@code Class}.
* {@code Integer}, {@code Class} or {@code EnumDesc}.
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
*/
@ -138,12 +145,15 @@ public class SwitchBootstraps {
Class<?> labelClass = label.getClass();
if (labelClass != Class.class &&
labelClass != String.class &&
labelClass != Integer.class) {
labelClass != Integer.class &&
labelClass != EnumDesc.class) {
throw new IllegalArgumentException("label with illegal type found: " + label.getClass());
}
}
private static int doTypeSwitch(Object target, int startIndex, Object[] labels) {
Objects.checkIndex(startIndex, labels.length + 1);
if (target == null)
return -1;
@ -160,6 +170,11 @@ public class SwitchBootstraps {
} else if (target instanceof Character input && constant.intValue() == input.charValue()) {
return i;
}
} else if (label instanceof EnumDesc<?> enumDesc) {
if (target.getClass().isEnum() &&
((Enum<?>) target).describeConstable().stream().anyMatch(d -> d.equals(enumDesc))) {
return i;
}
} else if (label.equals(target)) {
return i;
}
@ -200,6 +215,10 @@ public class SwitchBootstraps {
* <p>
* If no element in the {@code labels} array matches the target, then
* the method of the call site return the length of the {@code labels} array.
* <p>
* The value of the {@code restart} index must be between {@code 0} (inclusive) and
* the length of the {@code labels} array (inclusive),
* both or an {@link IndexOutOfBoundsException} is thrown.
*
* @param lookup Represents a lookup context with the accessibility
* privileges of the caller. When used with {@code invokedynamic},
@ -235,13 +254,28 @@ public class SwitchBootstraps {
labels = labels.clone();
Class<?> enumClass = invocationType.parameterType(0);
labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray();
Stream.of(labels).forEach(l -> validateEnumLabel(enumClass, l));
MethodHandle temporary =
MethodHandles.insertArguments(DO_ENUM_SWITCH, 2, labels, lookup, enumClass, new ResolvedEnumLabels());
temporary = temporary.asType(invocationType);
MethodHandle target =
MethodHandles.insertArguments(DO_ENUM_SWITCH, 2, (Object) labels);
target = target.asType(invocationType);
return new ConstantCallSite(temporary);
}
return new ConstantCallSite(target);
private static <E extends Enum<E>> void validateEnumLabel(Class<?> enumClassTemplate, Object label) {
if (label == null) {
throw new IllegalArgumentException("null label found");
}
Class<?> labelClass = label.getClass();
if (labelClass == Class.class) {
if (label != enumClassTemplate) {
throw new IllegalArgumentException("the Class label: " + label +
", expected the provided enum class: " + enumClassTemplate);
}
} else if (labelClass != String.class) {
throw new IllegalArgumentException("label with illegal type found: " + labelClass +
", expected label of type either String or Class");
}
}
private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Lookup lookup, Class<?> enumClassTemplate, Object label) {
@ -269,10 +303,22 @@ public class SwitchBootstraps {
}
}
private static int doEnumSwitch(Enum<?> target, int startIndex, Object[] labels) {
private static int doEnumSwitch(Enum<?> target, int startIndex, Object[] unresolvedLabels,
MethodHandles.Lookup lookup, Class<?> enumClass,
ResolvedEnumLabels resolvedLabels) {
Objects.checkIndex(startIndex, unresolvedLabels.length + 1);
if (target == null)
return -1;
if (resolvedLabels.resolvedLabels == null) {
resolvedLabels.resolvedLabels = Stream.of(unresolvedLabels)
.map(l -> convertEnumConstants(lookup, enumClass, l))
.toArray();
}
Object[] labels = resolvedLabels.resolvedLabels;
// Dumbest possible strategy
Class<?> targetClass = target.getClass();
for (int i = startIndex; i < labels.length; i++) {
@ -288,4 +334,8 @@ public class SwitchBootstraps {
return labels.length;
}
private static final class ResolvedEnumLabels {
@Stable
public Object[] resolvedLabels;
}
}

View File

@ -480,12 +480,10 @@ public abstract class CallArranger {
StorageCalculator.StructStorage[] structStorages
= storageCalculator.structStorages((GroupLayout) layout, forHFA);
for (StorageCalculator.StructStorage(
long offset, Class<?> ca, int byteWidth, VMStorage storage
) : structStorages) {
for (StorageCalculator.StructStorage structStorage : structStorages) {
bindings.dup();
bindings.vmLoad(storage, ca)
.bufferStore(offset, ca, byteWidth);
bindings.vmLoad(structStorage.storage(), structStorage.carrier())
.bufferStore(structStorage.offset(), structStorage.carrier(), structStorage.byteWidth());
}
}
case STRUCT_REFERENCE -> {

View File

@ -64,10 +64,6 @@ public @interface PreviewFeature {
* Values should be annotated with the feature's {@code JEP}.
*/
public enum Feature {
@JEP(number=433, title="Pattern Matching for switch", status="Fourth Preview")
SWITCH_PATTERN_MATCHING(),
@JEP(number=432, title="Record Patterns", status="Second Preview")
RECORD_PATTERNS,
// not used
VIRTUAL_THREADS,
@JEP(number=442, title="Foreign Function & Memory API", status="Third Preview")

View File

@ -25,12 +25,9 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A marker interface for {@code Tree}s that may be used as {@link CaseTree} labels.
*
* @since 17
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public interface CaseLabelTree extends Tree {}

View File

@ -27,8 +27,6 @@ package com.sun.source.tree;
import java.util.List;
import jdk.internal.javac.PreviewFeature;
/**
* A tree node for a {@code case} in a {@code switch} statement or expression.
*
@ -72,11 +70,18 @@ public interface CaseTree extends Tree {
* For {@code default} case return a list with a single element, {@link DefaultCaseLabelTree}.
*
* @return labels for this case
* @since 17
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
List<? extends CaseLabelTree> getLabels();
/**
* The guard for the case.
*
* @return the guard
* @since 21
*/
ExpressionTree getGuard();
/**
* For case with kind {@linkplain CaseKind#STATEMENT},
* returns the statements labeled by the case.

View File

@ -25,13 +25,10 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A case label element that refers to a constant expression
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public interface ConstantCaseLabelTree extends CaseLabelTree {
/**

View File

@ -26,14 +26,12 @@
package com.sun.source.tree;
import java.util.List;
import jdk.internal.javac.PreviewFeature;
/**
* A deconstruction pattern tree.
*
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public interface DeconstructionPatternTree extends PatternTree {
/**

View File

@ -24,12 +24,9 @@
*/
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A case label that marks {@code default} in {@code case null, default}.
*
* @since 17
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public interface DefaultCaseLabelTree extends CaseLabelTree {}

View File

@ -25,8 +25,6 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A tree node for an "enhanced" {@code for} loop statement.
*
@ -43,37 +41,12 @@ import jdk.internal.javac.PreviewFeature;
* @since 1.6
*/
public interface EnhancedForLoopTree extends StatementTree {
/**
* "Enhanced" {@code for} declarations come in two forms:
* <ul>
* <li> local variable declarations and
* <li> record patterns
* </ul>
*
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public enum DeclarationKind {
/** enum constant for local variable declarations */
VARIABLE,
/** enum constant for record pattern declarations */
PATTERN
}
/**
* Returns the control variable for the loop.
* @return the control variable, or {@code null} if this "enhanced" {@code for} uses a pattern
* @return the control variable
*/
VariableTree getVariable();
/**
* Returns the control variable or pattern for the loop.
* @return the control variable or pattern
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
Tree getVariableOrRecordPattern();
/**
* Returns the expression yielding the values for the control variable.
* @return the expression
@ -85,12 +58,4 @@ public interface EnhancedForLoopTree extends StatementTree {
* @return the body of the loop
*/
StatementTree getStatement();
/**
* Returns the kind of the declaration of the "enhanced" {@code for}.
* @return the kind of the declaration
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
DeclarationKind getDeclarationKind();
}

View File

@ -25,8 +25,6 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A tree node for an {@code instanceof} expression.
*
@ -43,22 +41,6 @@ import jdk.internal.javac.PreviewFeature;
*/
public interface InstanceOfTree extends ExpressionTree {
/**
* Two possible variants of instanceof expressions:
* <ul>
* <li> testing types, and
* <li> performing pattern matching
* </ul>
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public enum TestKind {
/** instanceof only testing a type */
TYPE,
/** instanceof doing a pattern matching */
PATTERN
}
/**
* Returns the expression to be tested.
* @return the expression
@ -93,12 +75,4 @@ public interface InstanceOfTree extends ExpressionTree {
*/
PatternTree getPattern();
/**
* Returns the kind of this instanceof expression.
*
* @return the kind of this instanceof expression
* @since 20
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
TestKind getTestKind();
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (c) 2005, 2014, 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 jdk.internal.javac.PreviewFeature;
/**
* A tree node for a parenthesized pattern.
*
* For example:
* <pre>
* ( <em>pattern</em> )
* </pre>
*
* @jls 14.30.1 Kinds of Patterns
*
* @since 17
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public interface ParenthesizedPatternTree extends PatternTree {
/**
* Returns the pattern within the parentheses.
* @return the pattern
*/
PatternTree getPattern();
}

View File

@ -25,13 +25,10 @@
package com.sun.source.tree;
import jdk.internal.javac.PreviewFeature;
/**
* A case label element that refers to an expression
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public interface PatternCaseLabelTree extends CaseLabelTree {
/**
@ -41,11 +38,4 @@ public interface PatternCaseLabelTree extends CaseLabelTree {
*/
public PatternTree getPattern();
/**
* The guard for the case.
*
* @return the guard
*/
ExpressionTree getGuard();
}

View File

@ -234,44 +234,32 @@ public interface Tree {
*/
BINDING_PATTERN(BindingPatternTree.class),
/**
* Used for instances of {@link ParenthesizedPatternTree}.
*
* @since 17
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
PARENTHESIZED_PATTERN(ParenthesizedPatternTree.class),
/**
* Used for instances of {@link DefaultCaseLabelTree}.
*
* @since 17
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
DEFAULT_CASE_LABEL(DefaultCaseLabelTree.class),
/**
* Used for instances of {@link ConstantCaseLabelTree}.
*
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
CONSTANT_CASE_LABEL(ConstantCaseLabelTree.class),
/**
* Used for instances of {@link PatternCaseLabelTree}.
*
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
PATTERN_CASE_LABEL(PatternCaseLabelTree.class),
/**
* Used for instances of {@link DeconstructionPatternTree}.
*
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
DECONSTRUCTION_PATTERN(DeconstructionPatternTree.class),
/**

View File

@ -282,9 +282,8 @@ public interface TreeVisitor<R,P> {
* @param node the node being visited
* @param p a parameter value
* @return a result value
* @since 17
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p);
/**
@ -292,9 +291,8 @@ public interface TreeVisitor<R,P> {
* @param node the node being visited
* @param p a parameter value
* @return a result value
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
R visitConstantCaseLabel(ConstantCaseLabelTree node, P p);
/**
@ -302,9 +300,8 @@ public interface TreeVisitor<R,P> {
* @param node the node being visited
* @param p a parameter value
* @return a result value
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
R visitPatternCaseLabel(PatternCaseLabelTree node, P p);
/**
@ -312,9 +309,8 @@ public interface TreeVisitor<R,P> {
* @param node the node being visited
* @param p a parameter value
* @return a result value
* @since 19
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
R visitDeconstructionPattern(DeconstructionPatternTree node, P p);
/**
@ -341,16 +337,6 @@ public interface TreeVisitor<R,P> {
*/
R visitNewArray(NewArrayTree node, P p);
/**
* Visits a {@code ParenthesizedPatternTree} node.
* @param node the node being visited
* @param p a parameter value
* @return a result value
* @since 17
*/
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
R visitParenthesizedPattern(ParenthesizedPatternTree node, P p);
/**
* Visits a {@code NewClassTree} node.
* @param node the node being visited

View File

@ -664,10 +664,9 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
* @since 17
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
return defaultAction(node, p);
}
@ -680,10 +679,9 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
* @since 19
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitConstantCaseLabel(ConstantCaseLabelTree node, P p) {
return defaultAction(node, p);
}
@ -696,10 +694,9 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
* @since 19
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.RECORD_PATTERNS, reflective=true)
public R visitDeconstructionPattern(DeconstructionPatternTree node, P p) {
return defaultAction(node, p);
}
@ -712,10 +709,9 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
* @since 19
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitPatternCaseLabel(PatternCaseLabelTree node, P p) {
return defaultAction(node, p);
}
@ -748,22 +744,6 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,P> {
return defaultAction(node, p);
}
/**
* {@inheritDoc}
*
* @implSpec This implementation calls {@code defaultAction}.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
* @since 17
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitParenthesizedPattern(ParenthesizedPatternTree node, P p) {
return defaultAction(node, p);
}
/**
* {@inheritDoc}
*

View File

@ -333,7 +333,7 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
*/
@Override
public R visitEnhancedForLoop(EnhancedForLoopTree node, P p) {
R r = scan(node.getVariableOrRecordPattern(), p);
R r = scan(node.getVariable(), p);
r = scanAndReduce(node.getExpression(), p, r);
r = scanAndReduce(node.getStatement(), p, r);
return r;
@ -397,6 +397,7 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
@Override
public R visitCase(CaseTree node, P p) {
R r = scan(node.getLabels(), p);
r = scanAndReduce(node.getGuard(), p, r);
if (node.getCaseKind() == CaseTree.CaseKind.RULE)
r = scanAndReduce(node.getBody(), p, r);
else
@ -799,10 +800,9 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 17
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
return null;
}
@ -815,10 +815,9 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 19
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitConstantCaseLabel(ConstantCaseLabelTree node, P p) {
return scan(node.getConstantExpression(), p);
}
@ -831,14 +830,11 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 19
* @since 21
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitPatternCaseLabel(PatternCaseLabelTree node, P p) {
R r = scan(node.getPattern(), p);
r = scanAndReduce(node.getGuard(), p, r);
return r;
return scan(node.getPattern(), p);
}
/**
@ -849,10 +845,9 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 19
* @since 21
*/
@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);
@ -887,22 +882,6 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
return scan(node.getExpression(), p);
}
/**
* {@inheritDoc}
*
* @implSpec This implementation scans the children in left to right order.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 17
*/
@Override
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true)
public R visitParenthesizedPattern(ParenthesizedPatternTree node, P p) {
return scan(node.getPattern(), p);
}
/**
* {@inheritDoc}
*

View File

@ -209,11 +209,7 @@ public class Preview {
*/
public boolean isPreview(Feature feature) {
return switch (feature) {
case CASE_NULL -> true;
case PATTERN_SWITCH -> true;
case STRING_TEMPLATES -> true;
case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> true;
case RECORD_PATTERNS -> true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true'

View File

@ -232,12 +232,12 @@ public enum Source {
REIFIABLE_TYPES_INSTANCEOF(JDK16, Fragments.FeatureReifiableTypesInstanceof, DiagKind.PLURAL),
RECORDS(JDK16, Fragments.FeatureRecords, DiagKind.PLURAL),
SEALED_CLASSES(JDK17, Fragments.FeatureSealedClasses, DiagKind.PLURAL),
CASE_NULL(JDK17, Fragments.FeatureCaseNull, DiagKind.NORMAL),
PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
CASE_NULL(JDK21, Fragments.FeatureCaseNull, DiagKind.NORMAL),
PATTERN_SWITCH(JDK21, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
REDUNDANT_STRICTFP(JDK17),
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK21, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
RECORD_PATTERNS(JDK21, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL),
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK19, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
RECORD_PATTERNS(JDK19, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
;

View File

@ -191,7 +191,6 @@ public class Symtab {
public final Type incompatibleClassChangeErrorType;
public final Type cloneNotSupportedExceptionType;
public final Type matchExceptionType;
public final Type nullPointerExceptionType;
public final Type annotationType;
public final TypeSymbol enumSym;
public final Type listType;
@ -224,8 +223,11 @@ public class Symtab {
public final Type typeDescriptorType;
public final Type recordType;
public final Type switchBootstrapsType;
public final Type constantBootstrapsType;
public final Type valueBasedType;
public final Type valueBasedInternalType;
public final Type classDescType;
public final Type enumDescType;
// For serialization lint checking
public final Type objectStreamFieldType;
@ -565,7 +567,6 @@ public class Symtab {
incompatibleClassChangeErrorType = enterClass("java.lang.IncompatibleClassChangeError");
cloneNotSupportedExceptionType = enterClass("java.lang.CloneNotSupportedException");
matchExceptionType = enterClass("java.lang.MatchException");
nullPointerExceptionType = enterClass("java.lang.NullPointerException");
annotationType = enterClass("java.lang.annotation.Annotation");
classLoaderType = enterClass("java.lang.ClassLoader");
enumSym = enterClass(java_base, names.java_lang_Enum);
@ -609,8 +610,11 @@ public class Symtab {
typeDescriptorType = enterClass("java.lang.invoke.TypeDescriptor");
recordType = enterClass("java.lang.Record");
switchBootstrapsType = enterClass("java.lang.runtime.SwitchBootstraps");
constantBootstrapsType = enterClass("java.lang.invoke.ConstantBootstraps");
valueBasedType = enterClass("jdk.internal.ValueBased");
valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation");
classDescType = enterClass("java.lang.constant.ClassDesc");
enumDescType = enterClass("java.lang.Enum$EnumDesc");
// For serialization lint checking
objectStreamFieldType = enterClass("java.io.ObjectStreamField");
objectInputStreamType = enterClass("java.io.ObjectInputStream");

View File

@ -33,7 +33,6 @@ import java.util.Map;
import java.util.Queue;
import java.util.stream.Collectors;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.VariableTree;
@ -426,24 +425,18 @@ public class Analyzer {
@Override
boolean match(JCEnhancedForLoop tree){
return tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE &&
!isImplicitlyTyped((JCVariableDecl) tree.varOrRecordPattern);
return !isImplicitlyTyped(tree.var);
}
@Override
List<JCEnhancedForLoop> rewrite(JCEnhancedForLoop oldTree) {
Assert.check(oldTree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
JCEnhancedForLoop newTree = copier.copy(oldTree);
newTree.varOrRecordPattern = rewriteVarType((JCVariableDecl) oldTree.varOrRecordPattern);
newTree.var = rewriteVarType(oldTree.var);
newTree.body = make.at(oldTree.body).Block(0, List.nil());
return List.of(newTree);
}
@Override
void process(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree, boolean hasErrors){
Assert.check(oldTree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
processVar((JCVariableDecl) oldTree.varOrRecordPattern,
(JCVariableDecl) newTree.varOrRecordPattern, hasErrors);
processVar(oldTree.var, newTree.var, hasErrors);
}
}

View File

@ -34,7 +34,6 @@ import javax.lang.model.element.ElementKind;
import javax.tools.JavaFileObject;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.source.tree.MemberSelectTree;
@ -171,8 +170,8 @@ public class Attr extends JCTree.Visitor {
allowRecords = Feature.RECORDS.allowedInSource(source);
allowPatternSwitch = (preview.isEnabled() || !preview.isPreview(Feature.PATTERN_SWITCH)) &&
Feature.PATTERN_SWITCH.allowedInSource(source);
allowUnconditionalPatternsInstanceOf = (preview.isEnabled() || !preview.isPreview(Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF)) &&
Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF.allowedInSource(source);
allowUnconditionalPatternsInstanceOf =
Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF.allowedInSource(source);
sourceName = source.name;
useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning");
@ -1514,25 +1513,24 @@ public class Attr extends JCTree.Visitor {
public void visitForeachLoop(JCEnhancedForLoop tree) {
Env<AttrContext> loopEnv =
env.dup(env.tree, env.info.dup(env.info.scope.dup()));
try {
//the Formal Parameter of a for-each loop is not in the scope when
//attributing the for-each expression; we mimic this by attributing
//the for-each expression first (against original scope).
Type exprType = types.cvarUpperBound(attribExpr(tree.expr, loopEnv));
chk.checkNonVoid(tree.pos(), exprType);
tree.elementType = types.elemtype(exprType); // perhaps expr is an array?
if (tree.elementType == null) {
Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
if (elemtype == null) {
// or perhaps expr implements Iterable<T>?
Type base = types.asSuper(exprType, syms.iterableType.tsym);
if (base == null) {
log.error(tree.expr.pos(),
Errors.ForeachNotApplicableToType(exprType,
Fragments.TypeReqArrayOrIterable));
tree.elementType = types.createErrorType(exprType);
elemtype = types.createErrorType(exprType);
} else {
List<Type> iterableParams = base.allparams();
tree.elementType = iterableParams.isEmpty()
elemtype = iterableParams.isEmpty()
? syms.objectType
: types.wildUpperBound(iterableParams.head);
@ -1546,40 +1544,14 @@ public class Attr extends JCTree.Visitor {
}
}
}
if (tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
if (jcVariableDecl.isImplicitlyTyped()) {
Type inferredType = chk.checkLocalVarType(jcVariableDecl, tree.elementType, jcVariableDecl.name);
setSyntheticVariableType(jcVariableDecl, inferredType);
}
attribStat(jcVariableDecl, loopEnv);
chk.checkType(tree.expr.pos(), tree.elementType, jcVariableDecl.sym.type);
loopEnv.tree = tree; // before, we were not in loop!
attribStat(tree.body, loopEnv);
} else {
Assert.check(tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.PATTERN);
JCRecordPattern jcRecordPattern = (JCRecordPattern) tree.varOrRecordPattern;
attribExpr(jcRecordPattern, loopEnv, tree.elementType);
// for(<pattern> x : xs) { y }
// we include x's bindings when true in y
// we don't do anything with x's bindings when false
MatchBindings forWithRecordPatternBindings = matchBindings;
Env<AttrContext> recordPatternEnv = bindingEnv(loopEnv, forWithRecordPatternBindings.bindingsWhenTrue);
Type clazztype = jcRecordPattern.type;
checkCastablePattern(tree.expr.pos(), tree.elementType, clazztype);
recordPatternEnv.tree = tree; // before, we were not in loop!
try {
attribStat(tree.body, recordPatternEnv);
} finally {
recordPatternEnv.info.scope.leave();
}
if (tree.var.isImplicitlyTyped()) {
Type inferredType = chk.checkLocalVarType(tree.var, elemtype, tree.var.name);
setSyntheticVariableType(tree.var, inferredType);
}
attribStat(tree.var, loopEnv);
chk.checkType(tree.expr.pos(), elemtype, tree.var.sym.type);
loopEnv.tree = tree; // before, we were not in loop!
attribStat(tree.body, loopEnv);
result = null;
}
finally {
@ -1717,7 +1689,8 @@ public class Attr extends JCTree.Visitor {
}
MatchBindings currentBindings = null;
boolean wasUnconditionalPattern = hasUnconditionalPattern;
for (JCCaseLabel label : c.labels) {
for (List<JCCaseLabel> labels = c.labels; labels.nonEmpty(); labels = labels.tail) {
JCCaseLabel label = labels.head;
if (label instanceof JCConstantCaseLabel constLabel) {
JCExpression expr = constLabel.expr;
if (TreeInfo.isNull(expr)) {
@ -1731,7 +1704,11 @@ public class Attr extends JCTree.Visitor {
} else if (enumSwitch) {
Symbol sym = enumConstant(expr, seltype);
if (sym == null) {
log.error(expr.pos(), Errors.EnumLabelMustBeUnqualifiedEnum);
if (allowPatternSwitch) {
attribTree(expr, switchEnv, caseLabelResultInfo(seltype));
} else {
log.error(expr.pos(), Errors.EnumLabelMustBeUnqualifiedEnum);
}
} else if (!constants.add(sym)) {
log.error(label.pos(), Errors.DuplicateCaseLabel);
}
@ -1747,17 +1724,14 @@ public class Attr extends JCTree.Visitor {
rs.basicLogResolveHelper = prevResolveHelper;
}
} else {
ResultInfo valTypInfo = new ResultInfo(KindSelector.VAL_TYP,
!seltype.hasTag(ERROR) ? seltype
: Type.noType);
Type pattype = attribTree(expr, switchEnv, valTypInfo);
Type pattype = attribTree(expr, switchEnv, caseLabelResultInfo(seltype));
if (!pattype.hasTag(ERROR)) {
if (pattype.constValue() == null) {
Symbol s = TreeInfo.symbol(expr);
if (s != null && s.kind == TYP && allowPatternSwitch) {
log.error(expr.pos(),
Errors.PatternExpected);
} else {
} else if ((s != null && !s.isEnum()) || !allowPatternSwitch) {
log.error(expr.pos(),
(stringSwitch ? Errors.StringConstReq : Errors.ConstExprReq));
}
@ -1786,8 +1760,8 @@ public class Attr extends JCTree.Visitor {
}
checkCastablePattern(pat.pos(), seltype, primaryType);
Type patternType = types.erasure(primaryType);
JCExpression guard = patternlabel.guard;
if (guard != null) {
JCExpression guard = c.guard;
if (labels.tail.isEmpty() && guard != null) {
MatchBindings afterPattern = matchBindings;
Env<AttrContext> bodyEnv = bindingEnv(switchEnv, matchBindings.bindingsWhenTrue);
try {
@ -1801,7 +1775,7 @@ public class Attr extends JCTree.Visitor {
log.error(guard.pos(), Errors.GuardHasConstantExpressionFalse);
}
}
boolean unguarded = TreeInfo.unguardedCaseLabel(label) && !pat.hasTag(RECORDPATTERN);
boolean unguarded = TreeInfo.unguardedCase(c) && !pat.hasTag(RECORDPATTERN);
boolean unconditional =
unguarded &&
!patternType.isErroneous() &&
@ -1854,6 +1828,11 @@ public class Attr extends JCTree.Visitor {
}
}
// where
private ResultInfo caseLabelResultInfo(Type seltype) {
return new ResultInfo(KindSelector.VAL_TYP,
!seltype.hasTag(ERROR) ? seltype
: Type.noType);
}
/** Add any variables defined in stats to the switch scope. */
private static void addVars(List<JCStatement> stats, WriteableScope switchScope) {
for (;stats.nonEmpty(); stats = stats.tail) {
@ -4101,7 +4080,6 @@ public class Attr extends JCTree.Visitor {
Type clazztype;
JCTree typeTree;
if (tree.pattern.getTag() == BINDINGPATTERN ||
tree.pattern.getTag() == PARENTHESIZEDPATTERN ||
tree.pattern.getTag() == RECORDPATTERN) {
attribExpr(tree.pattern, env, exprtype);
clazztype = tree.pattern.type;
@ -4109,9 +4087,8 @@ public class Attr extends JCTree.Visitor {
!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);
log.error(DiagnosticFlag.SOURCE_LEVEL, tree.pos(),
Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF.error(this.sourceName));
}
}
typeTree = TreeInfo.primaryPatternTypeTree((JCPattern) tree.pattern);
@ -4253,11 +4230,6 @@ public class Attr extends JCTree.Visitor {
matchBindings = new MatchBindings(outBindings.toList(), List.nil());
}
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
attribExpr(tree.pattern, env);
result = tree.type = tree.pattern.type;
}
public void visitIndexed(JCArrayAccess tree) {
Type owntype = types.createErrorType(tree.type);
Type atype = attribExpr(tree.indexed, env);

View File

@ -4626,7 +4626,7 @@ public class Check {
return false;
}
void checkSwitchCaseLabelDominated(List<JCCase> cases) {
List<JCCaseLabel> caseLabels = List.nil();
List<Pair<JCCase, JCCaseLabel>> caseLabels = List.nil();
boolean seenDefault = false;
boolean seenDefaultLabel = false;
boolean warnDominatedByDefault = false;
@ -4653,7 +4653,9 @@ public class Check {
}
}
Type currentType = labelType(label);
for (JCCaseLabel testCaseLabel : caseLabels) {
for (Pair<JCCase, JCCaseLabel> caseAndLabel : caseLabels) {
JCCase testCase = caseAndLabel.fst;
JCCaseLabel testCaseLabel = caseAndLabel.snd;
Type testType = labelType(testCaseLabel);
if (types.isSubtype(currentType, testType) &&
!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) {
@ -4663,7 +4665,7 @@ public class Check {
dominated |= !(testCaseLabel instanceof JCConstantCaseLabel);
} else if (label instanceof JCPatternCaseLabel patternCL &&
testCaseLabel instanceof JCPatternCaseLabel testPatternCaseLabel &&
TreeInfo.unguardedCaseLabel(testCaseLabel)) {
TreeInfo.unguardedCase(testCase)) {
dominated = patternDominated(testPatternCaseLabel.pat,
patternCL.pat);
}
@ -4672,7 +4674,7 @@ public class Check {
}
}
}
caseLabels = caseLabels.prepend(label);
caseLabels = caseLabels.prepend(Pair.of(c, label));
}
}
}
@ -4697,12 +4699,6 @@ public class Check {
return false;
}
}
while (existingPattern instanceof JCParenthesizedPattern parenthesized) {
existingPattern = parenthesized.pattern;
}
while (currentPattern instanceof JCParenthesizedPattern parenthesized) {
currentPattern = parenthesized.pattern;
}
if (currentPattern instanceof JCBindingPattern) {
return existingPattern instanceof JCBindingPattern;
} else if (currentPattern instanceof JCRecordPattern currentRecordPattern) {

View File

@ -32,13 +32,11 @@ 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.CaseTree;
import com.sun.source.tree.LambdaExpressionTree.BodyKind;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Scope.WriteableScope;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.tree.*;
@ -52,15 +50,22 @@ import com.sun.tools.javac.tree.JCTree.*;
import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Flags.BLOCK;
import com.sun.tools.javac.code.Kinds.Kind;
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.code.Types.UniqueType;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
/** This pass implements dataflow analysis for Java programs though
* different AST visitor steps. Liveness analysis (see AliveAnalyzer) checks that
@ -211,6 +216,7 @@ public class Flow {
private final JCDiagnostic.Factory diags;
private Env<AttrContext> attrEnv;
private Lint lint;
private final Infer infer;
public static Flow instance(Context context) {
Flow instance = context.get(flowKey);
@ -334,6 +340,7 @@ public class Flow {
types = Types.instance(context);
chk = Check.instance(context);
lint = Lint.instance(context);
infer = Infer.instance(context);
rs = Resolve.instance(context);
diags = JCDiagnostic.Factory.instance(context);
Source source = Source.instance(context);
@ -647,21 +654,7 @@ public class Flow {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
if(tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
visitVarDef(jcVariableDecl);
} else if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
visitRecordPattern(jcRecordPattern);
Set<Symbol> coveredSymbols =
coveredSymbols(jcRecordPattern.pos(), List.of(jcRecordPattern));
boolean isExhaustive =
isExhaustive(jcRecordPattern.pos(), tree.elementType, coveredSymbols);
if (!isExhaustive) {
log.error(tree, Errors.ForeachNotExhaustiveOnType(jcRecordPattern.type, tree.elementType));
}
}
visitVarDef(tree.var);
ListBuffer<PendingExit> prevPendingExits = pendingExits;
scan(tree.expr);
pendingExits = new ListBuffer<>();
@ -705,8 +698,7 @@ public class Flow {
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases);
if (exhaustiveSwitch) {
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.cases);
tree.isExhaustive |= isExhaustive(tree.selector.pos(), tree.selector.type, coveredSymbols);
tree.isExhaustive |= exhausts(tree.selector, tree.cases);
if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustiveStatement);
}
@ -740,10 +732,9 @@ public class Flow {
}
}
}
Set<Symbol> coveredSymbols = coveredSymbolsForCases(tree.pos(), tree.cases);
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) ||
isExhaustive(tree.selector.pos(), tree.selector.type, coveredSymbols);
exhausts(tree.selector, tree.cases);
if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustive);
}
@ -751,211 +742,444 @@ public class Flow {
alive = alive.or(resolveYields(tree, prevPendingExits));
}
private Set<Symbol> coveredSymbolsForCases(DiagnosticPosition pos,
List<JCCase> cases) {
HashSet<JCTree> labelValues = cases.stream()
.flatMap(c -> c.labels.stream())
.filter(TreeInfo::unguardedCaseLabel)
.filter(l -> !l.hasTag(DEFAULTCASELABEL))
.map(l -> l.hasTag(CONSTANTCASELABEL) ? ((JCConstantCaseLabel) l).expr
: ((JCPatternCaseLabel) l).pat)
.collect(Collectors.toCollection(HashSet::new));
return coveredSymbols(pos, labelValues);
sealed interface PatternDescription {
public static PatternDescription from(Types types, Type selectorType, JCPattern pattern) {
if (pattern instanceof JCBindingPattern binding) {
Type type = types.isSubtype(selectorType, binding.type)
? selectorType : binding.type;
return new BindingPattern(type);
} else if (pattern instanceof JCRecordPattern record) {
Type[] componentTypes = ((ClassSymbol) record.type.tsym).getRecordComponents()
.map(r -> types.memberType(record.type, r))
.toArray(s -> new Type[s]);
PatternDescription[] nestedDescriptions =
new PatternDescription[record.nested.size()];
int i = 0;
for (List<JCPattern> it = record.nested;
it.nonEmpty();
it = it.tail, i++) {
nestedDescriptions[i] = PatternDescription.from(types, componentTypes[i], it.head);
}
return new RecordPattern(record.type, componentTypes, nestedDescriptions);
} else {
throw Assert.error();
}
}
}
private Set<Symbol> coveredSymbols(DiagnosticPosition pos,
Iterable<? extends JCTree> labels) {
Set<Symbol> coveredSymbols = new HashSet<>();
Map<UniqueType, List<JCRecordPattern>> deconstructionPatternsByType = new HashMap<>();
record BindingPattern(Type type) implements PatternDescription {
@Override
public int hashCode() {
return type.tsym.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof BindingPattern other &&
type.tsym == other.type.tsym;
}
@Override
public String toString() {
return type.tsym + " _";
}
}
for (JCTree labelValue : labels) {
switch (labelValue.getTag()) {
case BINDINGPATTERN, PARENTHESIZEDPATTERN -> {
Type primaryPatternType = TreeInfo.primaryPatternType((JCPattern) labelValue);
if (!primaryPatternType.hasTag(NONE)) {
coveredSymbols.add(primaryPatternType.tsym);
record RecordPattern(Type recordType, int _hashCode, Type[] fullComponentTypes, PatternDescription... nested) implements PatternDescription {
public RecordPattern(Type recordType, Type[] fullComponentTypes, PatternDescription[] nested) {
this(recordType, hashCode(-1, recordType, nested), fullComponentTypes, nested);
}
@Override
public int hashCode() {
return _hashCode;
}
@Override
public boolean equals(Object o) {
return o instanceof RecordPattern other &&
recordType.tsym == other.recordType.tsym &&
Arrays.equals(nested, other.nested);
}
public int hashCode(int excludeComponent) {
return hashCode(excludeComponent, recordType, nested);
}
public static int hashCode(int excludeComponent, Type recordType, PatternDescription... nested) {
int hash = 5;
hash = 41 * hash + recordType.tsym.hashCode();
for (int i = 0; i < nested.length; i++) {
if (i != excludeComponent) {
hash = 41 * hash + nested[i].hashCode();
}
}
return hash;
}
@Override
public String toString() {
return recordType.tsym + "(" + Arrays.stream(nested)
.map(pd -> pd.toString())
.collect(Collectors.joining(", ")) + ")";
}
}
private boolean exhausts(JCExpression selector, List<JCCase> cases) {
Set<PatternDescription> patternSet = new HashSet<>();
Map<Symbol, Set<Symbol>> enum2Constants = new HashMap<>();
for (JCCase c : cases) {
if (!TreeInfo.unguardedCase(c))
continue;
for (var l : c.labels) {
if (l instanceof JCPatternCaseLabel patternLabel) {
for (Type component : components(selector.type)) {
patternSet.add(PatternDescription.from(types, component, patternLabel.pat));
}
}
case RECORDPATTERN -> {
JCRecordPattern dpat = (JCRecordPattern) labelValue;
UniqueType type = new UniqueType(dpat.type, types);
List<JCRecordPattern> augmentedPatterns =
deconstructionPatternsByType.getOrDefault(type, List.nil())
.prepend(dpat);
deconstructionPatternsByType.put(type, augmentedPatterns);
}
default -> {
Assert.check(labelValue instanceof JCExpression, labelValue.getTag().name());
JCExpression expr = (JCExpression) labelValue;
if (expr.hasTag(IDENT) && ((JCIdent) expr).sym.isEnum())
coveredSymbols.add(((JCIdent) expr).sym);
}
}
}
for (Entry<UniqueType, List<JCRecordPattern>> e : deconstructionPatternsByType.entrySet()) {
if (e.getValue().stream().anyMatch(r -> r.nested.size() != r.record.getRecordComponents().size())) {
coveredSymbols.add(syms.errSymbol);
} else if (coversDeconstructionFromComponent(pos, e.getKey().type, e.getValue(), 0)) {
coveredSymbols.add(e.getKey().type.tsym);
}
}
return coveredSymbols;
}
private boolean coversDeconstructionFromComponent(DiagnosticPosition pos,
Type recordType,
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(recordType, components.get(component));
List<JCPattern> nestedComponentPatterns = deconstructionPatterns.map(d -> d.nested.get(component));
Set<Symbol> coveredSymbolsForComponent = coveredSymbols(pos,
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, recordType, 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) {
List<Symbol> todo = List.from(covered);
while (todo.nonEmpty()) {
Symbol sym = todo.head;
todo = todo.tail;
switch (sym.kind) {
case VAR -> {
Iterable<Symbol> constants = sym.owner
.members()
.getSymbols(s -> s.isEnum() &&
s.kind == VAR);
boolean hasAll = StreamSupport.stream(constants.spliterator(), false)
.allMatch(covered::contains);
if (hasAll && covered.add(sym.owner)) {
todo = todo.prepend(sym.owner);
}
}
case TYP -> {
for (Type sup : types.directSupertypes(sym.type)) {
if (sup.tsym.kind == TYP) {
if (isTransitivelyCovered(pos, seltype, sup.tsym, covered) &&
covered.add(sup.tsym)) {
todo = todo.prepend(sup.tsym);
}
}
} else if (l instanceof JCConstantCaseLabel constantLabel) {
Symbol s = TreeInfo.symbol(constantLabel.expr);
if (s != null && s.isEnum()) {
enum2Constants.computeIfAbsent(s.owner, x -> {
Set<Symbol> result = new HashSet<>();
s.owner.members()
.getSymbols(sym -> sym.kind == Kind.VAR && sym.isEnum())
.forEach(result::add);
return result;
}).remove(s);
}
}
}
}
}
private boolean isTransitivelyCovered(DiagnosticPosition pos, Type seltype,
Symbol sealed, Set<Symbol> covered) {
for (Entry<Symbol, Set<Symbol>> e : enum2Constants.entrySet()) {
if (e.getValue().isEmpty()) {
patternSet.add(new BindingPattern(e.getKey().type));
}
}
List<PatternDescription> patterns = List.from(patternSet);
try {
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
return true;
if (sealed.kind == TYP && sealed.isAbstract() && sealed.isSealed()) {
return ((ClassSymbol) sealed).permitted
.stream()
.filter(s -> {
return types.isCastable(seltype, s.type/*, types.noWarnings*/);
})
.allMatch(s -> isTransitivelyCovered(pos, seltype, s, covered));
boolean repeat = true;
while (repeat) {
List<PatternDescription> updatedPatterns;
updatedPatterns = reduceBindingPatterns(selector.type, patterns);
updatedPatterns = reduceNestedPatterns(updatedPatterns);
updatedPatterns = reduceRecordPatterns(updatedPatterns);
repeat = updatedPatterns != patterns;
patterns = updatedPatterns;
if (checkCovered(selector.type, patterns)) {
return true;
}
}
return false;
return checkCovered(selector.type, patterns);
} catch (CompletionFailure cf) {
chk.completionError(pos, cf);
return true;
chk.completionError(selector.pos(), cf);
return true; //error recovery
}
}
private boolean isExhaustive(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
transitiveCovers(pos, seltype, covered);
private boolean checkCovered(Type seltype, List<PatternDescription> patterns) {
for (Type seltypeComponent : components(seltype)) {
for (PatternDescription pd : patterns) {
if (pd instanceof BindingPattern bp &&
types.isSubtype(seltypeComponent, types.erasure(bp.type))) {
return true;
}
}
}
return false;
}
private List<Type> components(Type seltype) {
return switch (seltype.getTag()) {
case CLASS -> {
if (seltype.isCompound()) {
if (seltype.isIntersection()) {
yield ((Type.IntersectionClassType) seltype).getComponents()
.stream()
.anyMatch(t -> isExhaustive(pos, t, covered));
.flatMap(t -> components(t).stream())
.collect(List.collector());
}
yield false;
yield List.nil();
}
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 -> {
yield covered.contains(types.erasure(seltype).tsym);
yield List.of(types.erasure(seltype));
}
case TYPEVAR -> components(((TypeVar) seltype).getUpperBound());
default -> List.of(types.erasure(seltype));
};
}
/* In a set of patterns, search for a sub-set of binding patterns that
* in combination exhaust their sealed supertype. If such a sub-set
* is found, it is removed, and replaced with a binding pattern
* for the sealed supertype.
*/
private List<PatternDescription> reduceBindingPatterns(Type selectorType, List<PatternDescription> patterns) {
Set<Symbol> existingBindings = patterns.stream()
.filter(pd -> pd instanceof BindingPattern)
.map(pd -> ((BindingPattern) pd).type.tsym)
.collect(Collectors.toSet());
for (PatternDescription pdOne : patterns) {
if (pdOne instanceof BindingPattern bpOne) {
Set<PatternDescription> toRemove = new HashSet<>();
Set<PatternDescription> toAdd = new HashSet<>();
for (Type sup : types.directSupertypes(bpOne.type)) {
ClassSymbol clazz = (ClassSymbol) sup.tsym;
if (clazz.isSealed() && clazz.isAbstract() &&
//if a binding pattern for clazz already exists, no need to analyze it again:
!existingBindings.contains(clazz)) {
ListBuffer<PatternDescription> bindings = new ListBuffer<>();
//do not reduce to types unrelated to the selector type:
Type clazzErasure = types.erasure(clazz.type);
if (components(selectorType).stream()
.map(types::erasure)
.noneMatch(c -> types.isSubtype(clazzErasure, c))) {
continue;
}
Set<Symbol> permitted = allPermittedSubTypes(clazz, csym -> {
Type instantiated;
if (csym.type.allparams().isEmpty()) {
instantiated = csym.type;
} else {
instantiated = infer.instantiatePatternType(selectorType, csym);
}
return instantiated != null && types.isCastable(selectorType, instantiated);
});
for (PatternDescription pdOther : patterns) {
if (pdOther instanceof BindingPattern bpOther) {
boolean reduces = false;
Set<Symbol> currentPermittedSubTypes =
allPermittedSubTypes((ClassSymbol) bpOther.type.tsym, s -> true);
PERMITTED: for (Iterator<Symbol> it = permitted.iterator(); it.hasNext();) {
Symbol perm = it.next();
for (Symbol currentPermitted : currentPermittedSubTypes) {
if (types.isSubtype(types.erasure(currentPermitted.type),
types.erasure(perm.type))) {
it.remove();
continue PERMITTED;
}
}
if (types.isSubtype(types.erasure(perm.type),
types.erasure(bpOther.type))) {
it.remove();
reduces = true;
}
}
if (reduces) {
bindings.append(pdOther);
}
}
}
if (permitted.isEmpty()) {
toRemove.addAll(bindings);
toAdd.add(new BindingPattern(clazz.type));
}
}
}
if (!toAdd.isEmpty() || !toRemove.isEmpty()) {
for (PatternDescription pd : toRemove) {
patterns = List.filter(patterns, pd);
}
for (PatternDescription pd : toAdd) {
patterns = patterns.prepend(pd);
}
return patterns;
}
}
}
return patterns;
}
private Set<Symbol> allPermittedSubTypes(ClassSymbol root, Predicate<ClassSymbol> accept) {
Set<Symbol> permitted = new HashSet<>();
List<ClassSymbol> permittedSubtypesClosure = List.of(root);
while (permittedSubtypesClosure.nonEmpty()) {
ClassSymbol current = permittedSubtypesClosure.head;
permittedSubtypesClosure = permittedSubtypesClosure.tail;
if (current.isSealed() && current.isAbstract()) {
for (Symbol sym : current.permitted) {
ClassSymbol csym = (ClassSymbol) sym;
if (accept.test(csym)) {
permittedSubtypesClosure = permittedSubtypesClosure.prepend(csym);
permitted.add(csym);
}
}
}
}
return permitted;
}
/* Among the set of patterns, find sub-set of patterns such:
* $record($prefix$, $nested, $suffix$)
* Where $record, $prefix$ and $suffix$ is the same for each pattern
* in the set, and the patterns only differ in one "column" in
* the $nested pattern.
* Then, the set of $nested patterns is taken, and passed recursively
* to reduceNestedPatterns and to reduceBindingPatterns, to
* simplify the pattern. If that succeeds, the original found sub-set
* of patterns is replaced with a new set of patterns of the form:
* $record($prefix$, $resultOfReduction, $suffix$)
*/
private List<PatternDescription> reduceNestedPatterns(List<PatternDescription> patterns) {
/* implementation note:
* finding a sub-set of patterns that only differ in a single
* column is time-consuming task, so this method speeds it up by:
* - group the patterns by their record class
* - for each column (nested pattern) do:
* -- group patterns by their hash
* -- in each such by-hash group, find sub-sets that only differ in
* the chosen column, and then call reduceBindingPatterns and reduceNestedPatterns
* on patterns in the chosen column, as described above
*/
var groupByRecordClass =
patterns.stream()
.filter(pd -> pd instanceof RecordPattern)
.map(pd -> (RecordPattern) pd)
.collect(groupingBy(pd -> (ClassSymbol) pd.recordType.tsym));
for (var e : groupByRecordClass.entrySet()) {
int nestedPatternsCount = e.getKey().getRecordComponents().size();
for (int mismatchingCandidate = 0;
mismatchingCandidate < nestedPatternsCount;
mismatchingCandidate++) {
int mismatchingCandidateFin = mismatchingCandidate;
var groupByHashes =
e.getValue()
.stream()
//error recovery, ignore patterns with incorrect number of nested patterns:
.filter(pd -> pd.nested.length == nestedPatternsCount)
.collect(groupingBy(pd -> pd.hashCode(mismatchingCandidateFin)));
for (var candidates : groupByHashes.values()) {
var candidatesArr = candidates.toArray(RecordPattern[]::new);
for (int firstCandidate = 0;
firstCandidate < candidatesArr.length;
firstCandidate++) {
RecordPattern rpOne = candidatesArr[firstCandidate];
ListBuffer<RecordPattern> join = new ListBuffer<>();
join.append(rpOne);
NEXT_PATTERN: for (int nextCandidate = 0;
nextCandidate < candidatesArr.length;
nextCandidate++) {
if (firstCandidate == nextCandidate) {
continue;
}
RecordPattern rpOther = candidatesArr[nextCandidate];
if (rpOne.recordType.tsym == rpOther.recordType.tsym) {
for (int i = 0; i < rpOne.nested.length; i++) {
if (i != mismatchingCandidate &&
!rpOne.nested[i].equals(rpOther.nested[i])) {
continue NEXT_PATTERN;
}
}
join.append(rpOther);
}
}
var nestedPatterns = join.stream().map(rp -> rp.nested[mismatchingCandidateFin]).collect(List.collector());
var updatedPatterns = reduceNestedPatterns(nestedPatterns);
updatedPatterns = reduceRecordPatterns(updatedPatterns);
updatedPatterns = reduceBindingPatterns(rpOne.fullComponentTypes()[mismatchingCandidateFin], updatedPatterns);
if (nestedPatterns != updatedPatterns) {
ListBuffer<PatternDescription> result = new ListBuffer<>();
Set<PatternDescription> toRemove = Collections.newSetFromMap(new IdentityHashMap<>());
toRemove.addAll(join);
for (PatternDescription p : patterns) {
if (!toRemove.contains(p)) {
result.append(p);
}
}
for (PatternDescription nested : updatedPatterns) {
PatternDescription[] newNested =
Arrays.copyOf(rpOne.nested, rpOne.nested.length);
newNested[mismatchingCandidateFin] = nested;
result.append(new RecordPattern(rpOne.recordType(),
rpOne.fullComponentTypes(),
newNested));
}
return result.toList();
}
}
}
}
}
return patterns;
}
/* In the set of patterns, find those for which, given:
* $record($nested1, $nested2, ...)
* all the $nestedX pattern cover the given record component,
* and replace those with a simple binding pattern over $record.
*/
private List<PatternDescription> reduceRecordPatterns(List<PatternDescription> patterns) {
var newPatterns = new ListBuffer<PatternDescription>();
boolean modified = false;
for (PatternDescription pd : patterns) {
if (pd instanceof RecordPattern rpOne) {
PatternDescription reducedPattern = reduceRecordPattern(rpOne);
if (reducedPattern != rpOne) {
newPatterns.append(reducedPattern);
modified = true;
continue;
}
}
newPatterns.append(pd);
}
return modified ? newPatterns.toList() : patterns;
}
private PatternDescription reduceRecordPattern(PatternDescription pattern) {
if (pattern instanceof RecordPattern rpOne) {
Type[] componentType = rpOne.fullComponentTypes();
//error recovery, ignore patterns with incorrect number of nested patterns:
if (componentType.length != rpOne.nested.length) {
return pattern;
}
PatternDescription[] reducedNestedPatterns = null;
boolean covered = true;
for (int i = 0; i < componentType.length; i++) {
PatternDescription newNested = reduceRecordPattern(rpOne.nested[i]);
if (newNested != rpOne.nested[i]) {
if (reducedNestedPatterns == null) {
reducedNestedPatterns = Arrays.copyOf(rpOne.nested, rpOne.nested.length);
}
reducedNestedPatterns[i] = newNested;
}
covered &= newNested instanceof BindingPattern bp &&
types.isSubtype(types.erasure(componentType[i]), types.erasure(bp.type));
}
if (covered) {
return new BindingPattern(rpOne.recordType);
} else if (reducedNestedPatterns != null) {
return new RecordPattern(rpOne.recordType, rpOne.fullComponentTypes(), reducedNestedPatterns);
}
}
return pattern;
}
public void visitTry(JCTry tree) {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
@ -1374,11 +1598,7 @@ public class Flow {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
if(tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
visitVarDef(jcVariableDecl);
} else if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
visitRecordPattern(jcRecordPattern);
}
visitVarDef(tree.var);
ListBuffer<PendingExit> prevPendingExits = pendingExits;
scan(tree.expr);
pendingExits = new ListBuffer<>();
@ -2177,10 +2397,6 @@ public class Flow {
void scanPattern(JCTree tree) {
scan(tree);
if (inits.isReset()) {
inits.assign(initsWhenTrue);
uninits.assign(uninitsWhenTrue);
}
}
/** Analyze a condition. Make sure to set (un)initsWhenTrue(WhenFalse)
@ -2559,6 +2775,8 @@ public class Flow {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
visitVarDef(tree.var);
ListBuffer<PendingExit> prevPendingExits = pendingExits;
FlowKind prevFlowKind = flowKind;
flowKind = FlowKind.NORMAL;
@ -2567,13 +2785,7 @@ public class Flow {
final Bits initsStart = new Bits(inits);
final Bits uninitsStart = new Bits(uninits);
if(tree.varOrRecordPattern instanceof JCVariableDecl jcVariableDecl) {
visitVarDef(jcVariableDecl);
letInit(tree.pos(), jcVariableDecl.sym);
} else if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
visitRecordPattern(jcRecordPattern);
}
letInit(tree.pos(), tree.var.sym);
pendingExits = new ListBuffer<>();
int prevErrors = log.nerrors;
do {
@ -2625,17 +2837,10 @@ public class Flow {
for (JCCaseLabel pat : c.labels) {
scanPattern(pat);
}
if (l.head.stats.isEmpty() &&
l.tail.nonEmpty() &&
l.tail.head.labels.size() == 1 &&
TreeInfo.isNullCaseLabel(l.tail.head.labels.head)) {
//handling:
//case Integer i:
//case null:
//joining these two cases together - processing Integer i pattern,
//but statements from case null:
l = l.tail;
c = l.head;
scan(c.guard);
if (inits.isReset()) {
inits.assign(initsWhenTrue);
uninits.assign(uninitsWhenTrue);
}
scan(c.stats);
if (c.completesNormally && c.caseKind == JCCase.RULE) {
@ -3028,12 +3233,6 @@ public class Flow {
initParam(tree.var);
}
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
scan(tree.pat);
scan(tree.guard);
}
void referenced(Symbol sym) {
unrefdResources.remove(sym);
}
@ -3104,6 +3303,7 @@ public class Flow {
class CaptureAnalyzer extends BaseAnalyzer {
JCTree currentTree; //local class or lambda
WriteableScope declaredInsideGuard;
@Override
void markDead() {
@ -3117,7 +3317,7 @@ public class Flow {
sym.pos < currentTree.getStartPosition()) {
switch (currentTree.getTag()) {
case CLASSDEF:
case PATTERNCASELABEL:
case CASE:
case LAMBDA:
if ((sym.flags() & (EFFECTIVELY_FINAL | FINAL)) == 0) {
reportEffectivelyFinalError(pos, sym);
@ -3131,15 +3331,20 @@ public class Flow {
tree = TreeInfo.skipParens(tree);
if (tree.hasTag(IDENT) || tree.hasTag(SELECT)) {
Symbol sym = TreeInfo.symbol(tree);
if (currentTree != null &&
sym.kind == VAR &&
sym.owner.kind == MTH &&
((VarSymbol)sym).pos < currentTree.getStartPosition()) {
if (currentTree != null) {
switch (currentTree.getTag()) {
case CLASSDEF:
case CASE:
case LAMBDA:
reportEffectivelyFinalError(tree, sym);
case CLASSDEF, LAMBDA -> {
if (sym.kind == VAR &&
sym.owner.kind == MTH &&
((VarSymbol)sym).pos < currentTree.getStartPosition()) {
reportEffectivelyFinalError(tree, sym);
}
}
case CASE -> {
if (!declaredInsideGuard.includes(sym)) {
log.error(tree.pos(), Errors.CannotAssignNotDeclaredGuard(sym));
}
}
}
}
}
@ -3148,7 +3353,7 @@ public class Flow {
void reportEffectivelyFinalError(DiagnosticPosition pos, Symbol sym) {
Fragment subKey = switch (currentTree.getTag()) {
case LAMBDA -> Fragments.Lambda;
case PATTERNCASELABEL -> Fragments.Guard;
case CASE -> Fragments.Guard;
case CLASSDEF -> Fragments.InnerCls;
default -> throw new AssertionError("Unexpected tree kind: " + currentTree.getTag());
};
@ -3188,20 +3393,21 @@ public class Flow {
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
scan(tree.pattern);
}
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
scan(tree.pat);
JCTree prevTree = currentTree;
try {
currentTree = tree;
scan(tree.guard);
} finally {
currentTree = prevTree;
public void visitCase(JCCase tree) {
scan(tree.labels);
if (tree.guard != null) {
JCTree prevTree = currentTree;
WriteableScope prevDeclaredInsideGuard = declaredInsideGuard;
try {
currentTree = tree;
declaredInsideGuard = WriteableScope.create(attrEnv.enclClass.sym);
scan(tree.guard);
} finally {
currentTree = prevTree;
declaredInsideGuard = prevDeclaredInsideGuard;
}
}
scan(tree.stats);
}
@Override
@ -3256,6 +3462,14 @@ public class Flow {
super.visitTry(tree);
}
@Override
public void visitVarDef(JCVariableDecl tree) {
if (declaredInsideGuard != null) {
declaredInsideGuard.enter(tree.sym);
}
super.visitVarDef(tree);
}
@Override
public void visitYield(JCYield tree) {
scan(tree.value);

View File

@ -703,7 +703,7 @@ public class LambdaToMethod extends TreeTranslator {
JCBreak br = make.Break(null);
breaks.add(br);
List<JCStatement> stmts = entry.getValue().append(br).toList();
cases.add(make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(make.Literal(entry.getKey()))), stmts, null));
cases.add(make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(make.Literal(entry.getKey()))), null, stmts, null));
}
JCSwitch sw = make.Switch(deserGetter("getImplMethodName", syms.stringType), cases.toList());
for (JCBreak br : breaks) {

View File

@ -28,7 +28,6 @@ package com.sun.tools.javac.comp;
import java.util.*;
import java.util.stream.Collectors;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Kinds.KindSelector;
import com.sun.tools.javac.code.Scope.WriteableScope;
@ -3558,18 +3557,13 @@ public class Lower extends TreeTranslator {
Type elemtype = types.elemtype(tree.expr.type);
JCExpression loopvarinit = make.Indexed(make.Ident(arraycache),
make.Ident(index)).setType(elemtype);
Assert.check(tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
JCVariableDecl jcVariableDecl = (JCVariableDecl) tree.varOrRecordPattern;
JCVariableDecl loopvardef = (JCVariableDecl)make.VarDef(jcVariableDecl.mods,
jcVariableDecl.name,
jcVariableDecl.vartype,
loopvarinit).setType(jcVariableDecl.type);
loopvardef.sym = jcVariableDecl.sym;
JCVariableDecl loopvardef = (JCVariableDecl)make.VarDef(tree.var.mods,
tree.var.name,
tree.var.vartype,
loopvarinit).setType(tree.var.type);
loopvardef.sym = tree.var.sym;
JCBlock body = make.
Block(0, List.of(loopvardef, tree.body));
Block(0, List.of(loopvardef, tree.body));
result = translate(make.
ForLoop(loopinit,
@ -3648,26 +3642,22 @@ public class Lower extends TreeTranslator {
itvar.type,
List.nil());
JCExpression vardefinit = make.App(make.Select(make.Ident(itvar), next));
Assert.check(tree.getDeclarationKind() == EnhancedForLoopTree.DeclarationKind.VARIABLE);
JCVariableDecl var = (JCVariableDecl) tree.varOrRecordPattern;
if (var.type.isPrimitive())
if (tree.var.type.isPrimitive())
vardefinit = make.TypeCast(types.cvarUpperBound(iteratorTarget), vardefinit);
else
vardefinit = make.TypeCast(var.type, vardefinit);
JCVariableDecl indexDef = (JCVariableDecl) make.VarDef(var.mods,
var.name,
var.vartype,
vardefinit).setType(var.type);
indexDef.sym = var.sym;
vardefinit = make.TypeCast(tree.var.type, vardefinit);
JCVariableDecl indexDef = (JCVariableDecl)make.VarDef(tree.var.mods,
tree.var.name,
tree.var.vartype,
vardefinit).setType(tree.var.type);
indexDef.sym = tree.var.sym;
JCBlock body = make.Block(0, List.of(indexDef, tree.body));
body.endpos = TreeInfo.endPos(tree.body);
result = translate(make.
ForLoop(List.of(init),
cond,
List.nil(),
body));
ForLoop(List.of(init),
cond,
List.nil(),
body));
patchTargets(body, tree, result);
}
@ -3753,7 +3743,7 @@ public class Lower extends TreeTranslator {
List<JCExpression> params = matchException ? List.of(makeNull(), makeNull())
: List.nil();
JCThrow thr = make.Throw(makeNewClass(exception, params));
JCCase c = make.Case(JCCase.STATEMENT, List.of(make.DefaultCaseLabel()), List.of(thr), null);
JCCase c = make.Case(JCCase.STATEMENT, List.of(make.DefaultCaseLabel()), null, List.of(thr), null);
cases = cases.prepend(c);
}
@ -3780,6 +3770,7 @@ public class Lower extends TreeTranslator {
while (patterns.tail.nonEmpty()) {
convertedCases.append(make_at(c.pos()).Case(JCCase.STATEMENT,
List.of(patterns.head),
null,
List.nil(),
null));
patterns = patterns.tail;
@ -3874,7 +3865,7 @@ public class Lower extends TreeTranslator {
VarSymbol label = (VarSymbol)TreeInfo.symbol(((JCConstantCaseLabel) c.labels.head).expr);
pat = map.caseValue(label);
}
newCases.append(make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(pat)), c.stats, null));
newCases.append(make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(pat)), null, c.stats, null));
} else {
newCases.append(c);
}
@ -4046,6 +4037,7 @@ public class Lower extends TreeTranslator {
caseBuffer.append(make.Case(JCCase.STATEMENT,
List.of(make.ConstantCaseLabel(make.Literal(hashCode))),
null,
lb.toList(),
null));
}
@ -4081,6 +4073,7 @@ public class Lower extends TreeTranslator {
lb.append(make.Case(JCCase.STATEMENT, caseExpr == null ? List.of(make.DefaultCaseLabel())
: List.of(make.ConstantCaseLabel(caseExpr)),
null,
oneCase.stats, null));
}

View File

@ -142,7 +142,7 @@ 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 TYPETEST:
case TYPETEST:
case PARENS: case RECORDPATTERN:
case CONDEXPR: //error recovery:
return matchBindings;

View File

@ -957,10 +957,6 @@ class ThisEscapeAnalyzer extends TreeScanner {
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
}
@Override
public void visitRecordPattern(JCRecordPattern that) {
}

View File

@ -36,6 +36,8 @@ 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.MethodHandleSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type.ClassType;
@ -55,7 +57,6 @@ import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCBindingPattern;
import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
@ -94,7 +95,6 @@ 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.JCPatternCaseLabel;
import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
@ -105,6 +105,8 @@ import com.sun.tools.javac.tree.JCTree.LetExpr;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
/**
@ -196,8 +198,7 @@ public class TransPatterns extends TreeTranslator {
@Override
public void visitTypeTest(JCInstanceOf tree) {
if (tree.pattern instanceof JCPattern pattern) {
//first, resolve any parenthesized and record patterns:
pattern = TreeInfo.skipParens(pattern);
//first, resolve any record patterns:
JCExpression extraConditions = null;
if (pattern instanceof JCRecordPattern recordPattern) {
UnrolledRecordPattern unrolledRecordPattern = unrollRecordPattern(recordPattern);
@ -280,11 +281,6 @@ public class TransPatterns extends TreeTranslator {
}
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
result = translate(tree.pattern);
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
//record patterns should be resolved by the constructs that use them.
@ -314,7 +310,7 @@ public class TransPatterns extends TreeTranslator {
while (components.nonEmpty()) {
RecordComponent component = components.head;
Type componentType = types.erasure(nestedFullComponentTypes.head);
JCPattern nestedPattern = TreeInfo.skipParens(nestedPatterns.head);
JCPattern nestedPattern = nestedPatterns.head;
JCBindingPattern nestedBinding;
boolean allowNull;
if (nestedPattern instanceof JCRecordPattern nestedRecordPattern) {
@ -390,8 +386,6 @@ public class TransPatterns extends TreeTranslator {
Type seltype = selector.type.hasTag(BOT)
? syms.objectType
: selector.type;
Assert.check(preview.isEnabled());
Assert.check(preview.usesPreview(env.toplevel.sourcefile));
//rewrite pattern matching switches, performed in several steps:
//1. record patterns are unrolled into a binding pattern and guards using unrollRecordPattern
@ -436,16 +430,19 @@ public class TransPatterns extends TreeTranslator {
//note the selector is evaluated only once and stored in a temporary variable
ListBuffer<JCCase> newCases = new ListBuffer<>();
for (List<JCCase> c = cases; c.nonEmpty(); c = c.tail) {
c.head.labels = c.head.labels.map(l -> {
JCCase cse = c.head;
cse.labels = cse.labels.map(l -> {
if (l instanceof JCPatternCaseLabel patternLabel) {
JCPattern pattern = TreeInfo.skipParens(patternLabel.pat);
JCPattern pattern = patternLabel.pat;
if (pattern instanceof JCRecordPattern recordPattern) {
UnrolledRecordPattern deconstructed = unrollRecordPattern(recordPattern);
JCExpression guard = deconstructed.newGuard();
if (patternLabel.guard != null) {
guard = mergeConditions(guard, patternLabel.guard);
if (cse.guard != null) {
cse.guard = mergeConditions(guard, cse.guard);
} else {
cse.guard = guard;
}
return make.PatternCaseLabel(deconstructed.primaryPattern(), guard);
return make.PatternCaseLabel(deconstructed.primaryPattern());
}
}
return l;
@ -534,8 +531,8 @@ public class TransPatterns extends TreeTranslator {
try {
currentValue = temp;
JCExpression test = (JCExpression) this.<JCTree>translate(label.pat);
if (label.guard != null) {
JCExpression guard = translate(label.guard);
if (c.guard != null) {
JCExpression guard = translate(c.guard);
test = makeBinary(Tag.AND, test, guard);
}
c.stats = translate(c.stats);
@ -716,12 +713,12 @@ public class TransPatterns extends TreeTranslator {
replaceNested.scan(accummulated);
JCExpression newGuard;
JCInstanceOf instanceofCheck;
if (accummulatedFirstLabel.guard instanceof JCBinary binOp) {
if (accummulated.guard instanceof JCBinary binOp) {
newGuard = binOp.rhs;
instanceofCheck = (JCInstanceOf) binOp.lhs;
} else {
newGuard = null;
instanceofCheck = (JCInstanceOf) accummulatedFirstLabel.guard;
instanceofCheck = (JCInstanceOf) accummulated.guard;
}
JCBindingPattern binding = (JCBindingPattern) instanceofCheck.pattern;
hasUnconditional =
@ -730,11 +727,12 @@ public class TransPatterns extends TreeTranslator {
List<JCCaseLabel> newLabel;
if (hasUnconditional) {
newLabel = List.of(make.ConstantCaseLabel(makeNull()),
make.PatternCaseLabel(binding, newGuard));
make.PatternCaseLabel(binding));
} else {
newLabel = List.of(make.PatternCaseLabel(binding, newGuard));
newLabel = List.of(make.PatternCaseLabel(binding));
}
nestedCases.add(make.Case(CaseKind.STATEMENT, newLabel, accummulated.stats, null));
nestedCases.add(make.Case(CaseKind.STATEMENT, newLabel, newGuard,
accummulated.stats, null));
lastGuard = newGuard;
}
if (lastGuard != null || !hasUnconditional) {
@ -745,6 +743,7 @@ public class TransPatterns extends TreeTranslator {
? List.of(make.DefaultCaseLabel())
: List.of(make.ConstantCaseLabel(makeNull()),
make.DefaultCaseLabel()),
null,
List.of(continueSwitch),
null));
}
@ -752,9 +751,9 @@ public class TransPatterns extends TreeTranslator {
newSwitch.patternSwitch = true;
JCPatternCaseLabel leadingTest =
(JCPatternCaseLabel) accummulator.first().labels.head;
leadingTest.guard = null;
result.add(make.Case(CaseKind.STATEMENT,
List.of(leadingTest),
null,
List.of(newSwitch),
null));
} else {
@ -776,14 +775,14 @@ public class TransPatterns extends TreeTranslator {
if (c.head.labels.size() == 1 &&
c.head.labels.head instanceof JCPatternCaseLabel patternLabel) {
if (patternLabel.guard instanceof JCBinary binOp &&
if (c.head.guard instanceof JCBinary binOp &&
binOp.lhs instanceof JCInstanceOf instanceofCheck &&
instanceofCheck.pattern instanceof JCBindingPattern binding) {
currentBinding = ((JCBindingPattern) patternLabel.pat).var.sym;
currentNullable = instanceofCheck.allowNull;
currentNestedExpression = instanceofCheck.expr;
currentNestedBinding = binding.var.sym;
} else if (patternLabel.guard instanceof JCInstanceOf instanceofCheck &&
} else if (c.head.guard instanceof JCInstanceOf instanceofCheck &&
instanceofCheck.pattern instanceof JCBindingPattern binding) {
currentBinding = ((JCBindingPattern) patternLabel.pat).var.sym;
currentNullable = instanceofCheck.allowNull;
@ -835,10 +834,15 @@ public class TransPatterns extends TreeTranslator {
} else {
return (LoadableConstant) principalType;
}
} else if (l.hasTag(Tag.CONSTANTCASELABEL)&& !TreeInfo.isNullCaseLabel(l)) {
} else if (l.hasTag(Tag.CONSTANTCASELABEL) && !TreeInfo.isNullCaseLabel(l)) {
JCExpression expr = ((JCConstantCaseLabel) l).expr;
if ((expr.type.tsym.flags_field & Flags.ENUM) != 0) {
return LoadableConstant.String(((JCIdent) expr).name.toString());
Symbol sym = TreeInfo.symbol(expr);
if (sym != null && sym.isEnum() && sym.kind == Kind.VAR) {
if (selector.tsym.isEnum()) {
return LoadableConstant.String(sym.getSimpleName().toString());
} else {
return createEnumDesc(l.pos(), (ClassSymbol) sym.owner, sym.getSimpleName());
}
} else {
Assert.checkNonNull(expr.type.constValue());
@ -854,6 +858,38 @@ public class TransPatterns extends TreeTranslator {
}
}
private LoadableConstant createEnumDesc(DiagnosticPosition pos, ClassSymbol enumClass, Name constant) {
MethodSymbol classDesc = rs.resolveInternalMethod(pos, env, syms.classDescType, names.of, List.of(syms.stringType), List.nil());
MethodSymbol enumDesc = rs.resolveInternalMethod(pos, env, syms.enumDescType, names.of, List.of(syms.classDescType, syms.stringType), List.nil());
return invokeMethodWrapper(pos,
enumDesc.asHandle(),
invokeMethodWrapper(pos,
classDesc.asHandle(),
LoadableConstant.String(enumClass.flatname.toString())),
LoadableConstant.String(constant.toString()));
}
private LoadableConstant invokeMethodWrapper(DiagnosticPosition pos, MethodHandleSymbol toCall, LoadableConstant... params) {
List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType,
syms.stringType,
new ClassType(syms.classType.getEnclosingType(),
List.of(syms.botType), //XXX - botType
syms.classType.tsym),
syms.methodHandleType,
types.makeArrayType(syms.objectType));
MethodSymbol bsm = rs.resolveInternalMethod(pos, env, syms.constantBootstrapsType,
names.invoke, bsm_staticArgs, List.nil());
LoadableConstant[] actualParams = new LoadableConstant[params.length + 1];
actualParams[0] = toCall;
System.arraycopy(params, 0, actualParams, 1, params.length);
return new DynamicVarSymbol(bsm.name, bsm.owner, bsm.asHandle(), toCall.getReturnType(), actualParams);
}
@Override
public void visitBinary(JCBinary tree) {
bindingContext = new BasicBindingContext();
@ -898,77 +934,6 @@ public class TransPatterns extends TreeTranslator {
}
}
@Override
public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
bindingContext = new BasicBindingContext();
VarSymbol prevCurrentValue = currentValue;
try {
if (tree.varOrRecordPattern instanceof JCRecordPattern jcRecordPattern) {
/**
* A statement of the form
*
* <pre>
* for (<pattern> : coll ) stmt ;
* </pre>
*
* (where coll implements {@code Iterable<R>}) gets translated to
*
* <pre>{@code
* for (<type-of-coll-item> N$temp : coll) {
* switch (N$temp) {
* case <pattern>: stmt;
* case null: throw new MatchException();
* }
* }</pre>
*
*/
Type selectorType = types.classBound(tree.elementType);
currentValue = new VarSymbol(Flags.FINAL | Flags.SYNTHETIC,
names.fromString("patt" + tree.pos + target.syntheticNameChar() + "temp"),
selectorType,
currentMethodSym);
JCStatement newForVariableDeclaration =
make.at(tree.pos).VarDef(currentValue, null).setType(selectorType);
List<JCExpression> nestedNPEParams = List.of(makeNull());
JCNewClass nestedNPE = makeNewClass(syms.nullPointerExceptionType, nestedNPEParams);
List<JCExpression> matchExParams = List.of(makeNull(), nestedNPE);
JCThrow thr = make.Throw(makeNewClass(syms.matchExceptionType, matchExParams));
JCCase caseNull = make.Case(JCCase.STATEMENT, List.of(make.ConstantCaseLabel(makeNull())), List.of(thr), null);
JCCase casePattern = make.Case(CaseTree.CaseKind.STATEMENT,
List.of(make.PatternCaseLabel(jcRecordPattern, null)),
List.of(translate(tree.body)),
null);
JCSwitch switchBody =
make.Switch(make.Ident(currentValue).setType(selectorType),
List.of(caseNull, casePattern));
switchBody.patternSwitch = true;
// re-using the same node to eliminate the need to re-patch targets (break/continue)
tree.varOrRecordPattern = newForVariableDeclaration.setType(selectorType);
tree.expr = translate(tree.expr);
tree.body = translate(switchBody);
JCTree.JCEnhancedForLoop newForEach = tree;
result = bindingContext.decorateStatement(newForEach);
} else {
super.visitForeachLoop(tree);
result = bindingContext.decorateStatement(tree);
}
} finally {
currentValue = prevCurrentValue;
bindingContext.pop();
}
}
@Override
public void visitWhileLoop(JCWhileLoop tree) {
bindingContext = new BasicBindingContext();

View File

@ -512,7 +512,7 @@ public class TransTypes extends TreeTranslator {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
tree.varOrRecordPattern = translate(tree.varOrRecordPattern, null);
tree.var = translate(tree.var, null);
Type iterableType = tree.expr.type;
tree.expr = translate(tree.expr, erasure(tree.expr.type));
if (types.elemtype(tree.expr.type) == null)
@ -551,6 +551,7 @@ public class TransTypes extends TreeTranslator {
public void visitCase(JCCase tree) {
tree.labels = translate(tree.labels, null);
tree.guard = translate(tree.guard, syms.booleanType);
tree.stats = translate(tree.stats);
result = tree;
}
@ -569,7 +570,6 @@ public class TransTypes extends TreeTranslator {
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
tree.pat = translate(tree.pat, null);
tree.guard = translate(tree.guard, syms.booleanType);
result = tree;
}
@ -584,12 +584,6 @@ public class TransTypes extends TreeTranslator {
result = retype(tree, tree.type, pt);
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
tree.pattern = translate(tree.pattern, null);
result = tree;
}
public void visitRecordPattern(JCRecordPattern tree) {
tree.fullComponentTypes = tree.record.getRecordComponents()
.map(rc -> types.memberType(tree.type, rc));

View File

@ -297,7 +297,9 @@ public class TreeDiffer extends TreeScanner {
@Override
public void visitCase(JCCase tree) {
JCCase that = (JCCase) parameter;
result = scan(tree.labels, that.labels) && scan(tree.stats, that.stats);
result = scan(tree.labels, that.labels) &&
scan(tree.guard, that.guard) &&
scan(tree.stats, that.stats);
}
@Override
@ -309,7 +311,7 @@ public class TreeDiffer extends TreeScanner {
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
JCPatternCaseLabel that = (JCPatternCaseLabel) parameter;
result = scan(tree.pat, that.pat) && scan(tree.guard, that.guard);
result = scan(tree.pat, that.pat);
}
@Override
@ -388,7 +390,7 @@ public class TreeDiffer extends TreeScanner {
public void visitForeachLoop(JCEnhancedForLoop tree) {
JCEnhancedForLoop that = (JCEnhancedForLoop) parameter;
result =
scan(tree.varOrRecordPattern, that.varOrRecordPattern)
scan(tree.var, that.var)
&& scan(tree.expr, that.expr)
&& scan(tree.body, that.body);
}

View File

@ -293,7 +293,7 @@ implements CRTFlags {
public void visitForeachLoop(JCEnhancedForLoop tree) {
SourceRange sr = new SourceRange(startPos(tree), endPos(tree));
sr.mergeWith(csp(tree.varOrRecordPattern));
sr.mergeWith(csp(tree.var));
sr.mergeWith(csp(tree.expr));
sr.mergeWith(csp(tree.body));
result = sr;
@ -323,6 +323,7 @@ implements CRTFlags {
public void visitCase(JCCase tree) {
SourceRange sr = new SourceRange(startPos(tree), endPos(tree));
sr.mergeWith(csp(tree.labels));
sr.mergeWith(csp(tree.guard));
sr.mergeWith(csp(tree.stats));
result = sr;
}
@ -343,7 +344,6 @@ implements CRTFlags {
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
SourceRange sr = new SourceRange(startPos(tree), endPos(tree));
sr.mergeWith(csp(tree.pat));
sr.mergeWith(csp(tree.guard));
result = sr;
}

View File

@ -941,6 +941,15 @@ public class ClassWriter extends ClassFile {
*/
void writeBootstrapMethods() {
int alenIdx = writeAttr(names.BootstrapMethods);
int lastBootstrapMethods;
do {
lastBootstrapMethods = poolWriter.bootstrapMethods.size();
for (BsmKey bsmKey : java.util.List.copyOf(poolWriter.bootstrapMethods.keySet())) {
for (LoadableConstant arg : bsmKey.staticArgs) {
poolWriter.putConstant(arg);
}
}
} while (lastBootstrapMethods < poolWriter.bootstrapMethods.size());
databuf.appendChar(poolWriter.bootstrapMethods.size());
for (BsmKey bsmKey : poolWriter.bootstrapMethods.keySet()) {
//write BSM handle

View File

@ -1535,11 +1535,6 @@ public class JavaCompiler {
super.visitRecordPattern(that);
}
@Override
public void visitParenthesizedPattern(JCTree.JCParenthesizedPattern tree) {
hasPatterns = true;
super.visitParenthesizedPattern(tree);
}
@Override
public void visitSwitch(JCSwitch tree) {
hasPatterns |= tree.patternSwitch;
super.visitSwitch(tree);

View File

@ -830,48 +830,52 @@ public class JavacParser implements Parser {
/** parses patterns.
*/
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, true, false);
accept(RPAREN);
pattern = toP(F.at(startPos).ParenthesizedPattern(p));
mods = mods != null ? mods : optFinal(0);
JCExpression e;
if (parsedType == null) {
boolean var = token.kind == IDENTIFIER && token.name() == names.var;
e = unannotatedType(allowVar, TYPE | NOLAMBDA);
if (var) {
e = null;
}
} else {
mods = mods != null ? mods : optFinal(0);
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 {
e = parsedType;
}
if (token.kind == LPAREN) {
//deconstruction pattern:
checkSourceLevel(Feature.RECORD_PATTERNS);
ListBuffer<JCPattern> nested = new ListBuffer<>();
if (!peekToken(RPAREN)) {
do {
nextToken();
}
accept(RPAREN);
pattern = toP(F.at(pos).RecordPattern(e, nested.toList()));
JCPattern nestedPattern = parsePattern(token.pos, null, null, true, false);
nested.append(nestedPattern);
} while (token.kind == COMMA);
} else {
//type test pattern:
JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), e, null));
pattern = toP(F.at(pos).BindingPattern(var));
nextToken();
}
accept(RPAREN);
pattern = toP(F.at(pos).RecordPattern(e, nested.toList()));
if (mods.annotations.nonEmpty()) {
log.error(mods.annotations.head.pos(), Errors.RecordPatternsAnnotationsNotAllowed);
}
new TreeScanner() {
@Override
public void visitAnnotatedType(JCAnnotatedType tree) {
log.error(tree.pos(), Errors.RecordPatternsAnnotationsNotAllowed);
}
}.scan(e);
} else {
//type test pattern:
JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), e, null));
if (e == null) {
var.startPos = pos;
}
pattern = toP(F.at(pos).BindingPattern(var));
}
return pattern;
}
@ -1620,6 +1624,7 @@ public class JavacParser implements Parser {
allowDefault = TreeInfo.isNullCaseLabel(label);
};
}
JCExpression guard = parseGuard(pats.last());
List<JCStatement> stats = null;
JCTree body = null;
CaseTree.CaseKind kind;
@ -1645,7 +1650,7 @@ public class JavacParser implements Parser {
kind = JCCase.STATEMENT;
break;
}
caseExprs.append(toP(F.at(casePos).Case(kind, pats.toList(), stats, body)));
caseExprs.append(toP(F.at(casePos).Case(kind, pats.toList(), guard, stats, body)));
return caseExprs.toList();
}
@ -2910,47 +2915,25 @@ public class JavacParser implements Parser {
case FOR: {
nextToken();
accept(LPAREN);
JCTree pattern;
ForInitResult initResult = analyzeForInit();
if (initResult == ForInitResult.RecordPattern) {
int patternPos = token.pos;
JCModifiers mods = optFinal(0);
int typePos = token.pos;
JCExpression type = unannotatedType(false);
pattern = parsePattern(patternPos, mods, type, false, false);
if (pattern != null) {
checkSourceLevel(token.pos, Feature.PATTERN_SWITCH);
}
List<JCStatement> inits = token.kind == SEMI ? List.nil() : forInit();
if (inits.length() == 1 &&
inits.head.hasTag(VARDEF) &&
((JCVariableDecl) inits.head).init == null &&
token.kind == COLON) {
JCVariableDecl var = (JCVariableDecl)inits.head;
accept(COLON);
JCExpression expr = parseExpression();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForeachLoop(pattern, expr, body);
return F.at(pos).ForeachLoop(var, expr, body);
} else {
List<JCStatement> inits = token.kind == SEMI ? List.nil() : forInit();
if (inits.length() == 1 &&
inits.head.hasTag(VARDEF) &&
((JCVariableDecl) inits.head).init == null &&
token.kind == COLON) {
JCVariableDecl var = (JCVariableDecl) inits.head;
accept(COLON);
JCExpression expr = parseExpression();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForeachLoop(var, expr, body);
} else {
accept(SEMI);
JCExpression cond = token.kind == SEMI ? null : parseExpression();
accept(SEMI);
List<JCExpressionStatement> steps = token.kind == RPAREN ? List.nil() : forUpdate();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForLoop(inits, cond, steps, body);
}
accept(SEMI);
JCExpression cond = token.kind == SEMI ? null : parseExpression();
accept(SEMI);
List<JCExpressionStatement> steps = token.kind == RPAREN ? List.nil() : forUpdate();
accept(RPAREN);
JCStatement body = parseStatementAsBlock();
return F.at(pos).ForLoop(inits, cond, steps, body);
}
}
case WHILE: {
@ -3067,91 +3050,6 @@ public class JavacParser implements Parser {
}
}
private enum ForInitResult {
LocalVarDecl,
RecordPattern
}
@SuppressWarnings("fallthrough")
ForInitResult analyzeForInit() {
boolean inType = false;
boolean inSelectionAndParenthesis = false;
int typeParameterPossibleStart = -1;
outer: for (int lookahead = 0; ; lookahead++) {
TokenKind tk = S.token(lookahead).kind;
switch (tk) {
case DOT:
if (inType) break; // in qualified type
case COMMA:
typeParameterPossibleStart = lookahead;
break;
case QUES:
// "?" only allowed in a type parameter position - otherwise it's an expression
if (typeParameterPossibleStart == lookahead - 1) break;
else return ForInitResult.LocalVarDecl;
case EXTENDS: case SUPER: case AMP:
case GTGTGT: case GTGT: case GT:
case FINAL: case ELLIPSIS:
break;
case BYTE: case SHORT: case INT: case LONG: case FLOAT:
case DOUBLE: case BOOLEAN: case CHAR: case VOID:
if (peekToken(lookahead, IDENTIFIER)) {
return inSelectionAndParenthesis ? ForInitResult.RecordPattern
: ForInitResult.LocalVarDecl;
}
break;
case LPAREN:
if (lookahead != 0 && inType) {
inSelectionAndParenthesis = true;
inType = false;
}
break;
case RPAREN:
// a method call in the init part or a record pattern?
if (inSelectionAndParenthesis) {
if (peekToken(lookahead, DOT) ||
peekToken(lookahead, SEMI) ||
peekToken(lookahead, ARROW)) {
return ForInitResult.LocalVarDecl;
}
else if(peekToken(lookahead, COLON)) {
return ForInitResult.RecordPattern;
}
break;
}
case UNDERSCORE:
case ASSERT:
case ENUM:
case IDENTIFIER:
if (lookahead == 0) {
inType = true;
}
break;
case MONKEYS_AT: {
int prevLookahead = lookahead;
lookahead = skipAnnotation(lookahead);
if (typeParameterPossibleStart == prevLookahead - 1) {
// move possible start of type param after the anno
typeParameterPossibleStart = lookahead;
}
break;
}
case LBRACKET:
if (peekToken(lookahead, RBRACKET)) {
return inSelectionAndParenthesis ? ForInitResult.RecordPattern
: ForInitResult.LocalVarDecl;
}
return ForInitResult.LocalVarDecl;
case LT:
typeParameterPossibleStart = lookahead;
break;
default:
//this includes EOF
return ForInitResult.LocalVarDecl;
}
}
}
@Override
public JCStatement parseStatement() {
return parseStatementAsBlock();
@ -3234,6 +3132,7 @@ public class JavacParser implements Parser {
checkSourceLevel(Feature.SWITCH_MULTIPLE_CASE_LABELS);
allowDefault = TreeInfo.isNullCaseLabel(label);
};
JCExpression guard = parseGuard(pats.last());
CaseTree.CaseKind caseKind;
JCTree body = null;
if (token.kind == ARROW) {
@ -3251,7 +3150,7 @@ public class JavacParser implements Parser {
caseKind = JCCase.STATEMENT;
stats = blockStatements();
}
c = F.at(pos).Case(caseKind, pats.toList(), stats, body);
c = F.at(pos).Case(caseKind, pats.toList(), guard, stats, body);
if (stats.isEmpty())
storeEnd(c, S.prevToken().endPos);
return cases.append(c).toList();
@ -3259,6 +3158,7 @@ public class JavacParser implements Parser {
case DEFAULT: {
nextToken();
JCCaseLabel defaultPattern = toP(F.at(pos).DefaultCaseLabel());
JCExpression guard = parseGuard(defaultPattern);
CaseTree.CaseKind caseKind;
JCTree body = null;
if (token.kind == ARROW) {
@ -3276,7 +3176,7 @@ public class JavacParser implements Parser {
caseKind = JCCase.STATEMENT;
stats = blockStatements();
}
c = F.at(pos).Case(caseKind, List.of(defaultPattern), stats, body);
c = F.at(pos).Case(caseKind, List.of(defaultPattern), guard, stats, body);
if (stats.isEmpty())
storeEnd(c, S.prevToken().endPos);
return cases.append(c).toList();
@ -3304,12 +3204,7 @@ public class JavacParser implements Parser {
if (pattern) {
checkSourceLevel(token.pos, Feature.PATTERN_SWITCH);
JCPattern p = parsePattern(patternPos, mods, null, false, true);
JCExpression guard = null;
if (token.kind == IDENTIFIER && token.name() == names.when) {
nextToken();
guard = term(EXPR | NOLAMBDA);
}
return toP(F.at(patternPos).PatternCaseLabel(p, guard));
return toP(F.at(patternPos).PatternCaseLabel(p));
} else {
JCExpression expr = term(EXPR | NOLAMBDA);
return toP(F.at(patternPos).ConstantCaseLabel(expr));
@ -3319,6 +3214,22 @@ public class JavacParser implements Parser {
return label;
}
private JCExpression parseGuard(JCCaseLabel label) {
JCExpression guard = null;
if (token.kind == IDENTIFIER && token.name() == names.when) {
int pos = token.pos;
nextToken();
guard = term(EXPR | NOLAMBDA);
if (!(label instanceof JCPatternCaseLabel)) {
guard = syntaxError(pos, List.of(guard), Errors.GuardNotAllowed);
}
}
return guard;
}
@SuppressWarnings("fallthrough")
PatternResult analyzePattern(int lookahead) {
int typeDepth = 0;

View File

@ -528,9 +528,16 @@ compiler.err.duplicate.unconditional.pattern=\
compiler.err.unconditional.pattern.and.default=\
switch has both an unconditional pattern and a default label
compiler.err.guard.not.allowed=\
guards are only allowed for case with a pattern
compiler.err.guard.has.constant.expression.false=\
this case label has a guard that is a constant expression with value ''false''
# 0: symbol
compiler.err.cannot.assign.not.declared.guard=\
cannot assign to {0}, as it was not declared inside the guard
# 0: type, 1: type
compiler.err.constant.label.not.compatible=\
constant label of type {0} is not compatible with switch selector type {1}
@ -619,10 +626,6 @@ compiler.err.foreach.not.applicable.to.type=\
required: {1}\n\
found: {0}
# 0: type, 1: type
compiler.err.foreach.not.exhaustive.on.type=\
Pattern {0} is not exhaustive on {1}
compiler.err.fp.number.too.large=\
floating-point number too large
@ -1500,10 +1503,6 @@ compiler.misc.varargs.trustme.on.reifiable.varargs=\
compiler.err.instanceof.reifiable.not.safe=\
{0} cannot be safely cast to {1}
# 0: type, 1: type
compiler.err.instanceof.pattern.no.subtype=\
expression type {0} is a subtype of pattern type {1}
# 0: symbol
compiler.misc.varargs.trustme.on.non.varargs.meth=\
Method {0} is not a varargs method.
@ -3854,6 +3853,9 @@ compiler.err.instance.initializer.not.allowed.in.records=\
compiler.err.static.declaration.not.allowed.in.inner.classes=\
static declarations not allowed in inner classes
compiler.err.record.patterns.annotations.not.allowed=\
annotations not allowed on record patterns
############################################
# messages previously at javac.properties

View File

@ -240,7 +240,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
/** Patterns.
*/
BINDINGPATTERN,
PARENTHESIZEDPATTERN,
RECORDPATTERN,
/* Case labels.
@ -1222,13 +1221,11 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
* The enhanced for loop.
*/
public static class JCEnhancedForLoop extends JCStatement implements EnhancedForLoopTree {
public JCTree varOrRecordPattern;
public JCVariableDecl var;
public JCExpression expr;
public JCStatement body;
public Type elementType;
protected JCEnhancedForLoop(JCTree varOrRecordPattern, JCExpression expr, JCStatement body) {
this.varOrRecordPattern = varOrRecordPattern;
protected JCEnhancedForLoop(JCVariableDecl var, JCExpression expr, JCStatement body) {
this.var = var;
this.expr = expr;
this.body = body;
}
@ -1238,11 +1235,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
@DefinedBy(Api.COMPILER_TREE)
public Kind getKind() { return Kind.ENHANCED_FOR_LOOP; }
@DefinedBy(Api.COMPILER_TREE)
public JCVariableDecl getVariable() {
return varOrRecordPattern instanceof JCVariableDecl var ? var : null;
}
@DefinedBy(Api.COMPILER_TREE)
public JCTree getVariableOrRecordPattern() { return varOrRecordPattern; }
public JCVariableDecl getVariable() { return var; }
@DefinedBy(Api.COMPILER_TREE)
public JCExpression getExpression() { return expr; }
@DefinedBy(Api.COMPILER_TREE)
@ -1255,10 +1248,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public Tag getTag() {
return FOREACHLOOP;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public EnhancedForLoopTree.DeclarationKind getDeclarationKind() {
return varOrRecordPattern.hasTag(VARDEF) ? DeclarationKind.VARIABLE : DeclarationKind.PATTERN;
}
}
/**
@ -1334,15 +1323,18 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public static final CaseKind RULE = CaseKind.RULE;
public final CaseKind caseKind;
public List<JCCaseLabel> labels;
public JCExpression guard;
public List<JCStatement> stats;
public JCTree body;
public boolean completesNormally;
protected JCCase(CaseKind caseKind, List<JCCaseLabel> labels,
JCExpression guard,
List<JCStatement> stats, JCTree body) {
Assert.checkNonNull(labels);
Assert.check(labels.isEmpty() || labels.head != null);
this.caseKind = caseKind;
this.labels = labels;
this.guard = guard;
this.stats = stats;
this.body = body;
}
@ -1365,6 +1357,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
@Override @DefinedBy(Api.COMPILER_TREE)
public List<JCCaseLabel> getLabels() { return labels; }
@Override @DefinedBy(Api.COMPILER_TREE)
public JCExpression getGuard() { return guard; }
@Override @DefinedBy(Api.COMPILER_TREE)
public List<JCStatement> getStatements() {
return caseKind == CaseKind.STATEMENT ? stats : null;
}
@ -2252,10 +2246,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
@DefinedBy(Api.COMPILER_TREE)
public JCExpression getExpression() { return expr; }
@DefinedBy(Api.COMPILER_TREE)
public TestKind getTestKind() {
return pattern instanceof JCPatternCaseLabel ? TestKind.PATTERN : TestKind.TYPE;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public <R,D> R accept(TreeVisitor<R,D> v, D d) {
return v.visitInstanceOf(this, d);
@ -2378,11 +2368,9 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
implements PatternCaseLabelTree {
public JCPattern pat;
public JCExpression guard;
protected JCPatternCaseLabel(JCPattern pat, JCExpression guard) {
protected JCPatternCaseLabel(JCPattern pat) {
this.pat = pat;
this.guard = guard;
}
@Override @DefinedBy(Api.COMPILER_TREE)
@ -2390,11 +2378,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
return pat;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public JCExpression getGuard() {
return guard;
}
@Override
public void accept(Visitor v) {
v.visitPatternCaseLabel(this);
@ -2418,41 +2401,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
}
public static class JCParenthesizedPattern extends JCPattern
implements ParenthesizedPatternTree {
public JCPattern pattern;
public JCParenthesizedPattern(JCPattern pattern) {
this.pattern = pattern;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public PatternTree getPattern() {
return pattern;
}
@Override
public void accept(Visitor v) {
v.visitParenthesizedPattern(this);
}
@DefinedBy(Api.COMPILER_TREE)
public Kind getKind() {
return Kind.PARENTHESIZED_PATTERN;
}
@Override
@DefinedBy(Api.COMPILER_TREE)
public <R, D> R accept(TreeVisitor<R, D> v, D d) {
return v.visitParenthesizedPattern(this, d);
}
@Override
public Tag getTag() {
return PARENTHESIZEDPATTERN;
}
}
public static class JCRecordPattern extends JCPattern
implements DeconstructionPatternTree {
public JCExpression deconstructor;
@ -3487,11 +3435,11 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
JCExpression cond,
List<JCExpressionStatement> step,
JCStatement body);
JCEnhancedForLoop ForeachLoop(JCTree var, JCExpression expr, JCStatement body);
JCEnhancedForLoop ForeachLoop(JCVariableDecl var, JCExpression expr, JCStatement body);
JCLabeledStatement Labelled(Name label, JCStatement body);
JCSwitch Switch(JCExpression selector, List<JCCase> cases);
JCSwitchExpression SwitchExpression(JCExpression selector, List<JCCase> cases);
JCCase Case(CaseTree.CaseKind caseKind, List<JCCaseLabel> labels,
JCCase Case(CaseTree.CaseKind caseKind, List<JCCaseLabel> labels, JCExpression guard,
List<JCStatement> stats, JCTree body);
JCSynchronized Synchronized(JCExpression lock, JCBlock body);
JCTry Try(JCBlock body, List<JCCatch> catchers, JCBlock finalizer);
@ -3601,7 +3549,6 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public void visitDefaultCaseLabel(JCDefaultCaseLabel that) { visitTree(that); }
public void visitConstantCaseLabel(JCConstantCaseLabel that) { visitTree(that); }
public void visitPatternCaseLabel(JCPatternCaseLabel 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); }

View File

@ -814,7 +814,7 @@ public class Pretty extends JCTree.Visitor {
public void visitForeachLoop(JCEnhancedForLoop tree) {
try {
print("for (");
printExpr(tree.varOrRecordPattern);
printExpr(tree.var);
print(" : ");
printExpr(tree.expr);
print(") ");
@ -862,6 +862,10 @@ public class Pretty extends JCTree.Visitor {
print("case ");
printExprs(tree.labels);
}
if (tree.guard != null) {
print(" when ");
print(tree.guard);
}
if (tree.caseKind == JCCase.STATEMENT) {
print(':');
println();
@ -904,10 +908,6 @@ public class Pretty extends JCTree.Visitor {
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
try {
print(tree.pat);
if (tree.guard != null) {
print(" when ");
print(tree.guard);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
@ -941,17 +941,6 @@ public class Pretty extends JCTree.Visitor {
}
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern patt) {
try {
print('(');
printExpr(patt.pattern);
print(')');
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void visitRecordPattern(JCRecordPattern tree) {
try {

View File

@ -153,6 +153,7 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
public JCTree visitCase(CaseTree node, P p) {
JCCase t = (JCCase) node;
List<JCCaseLabel> labels = copy(t.labels, p);
JCExpression guard = copy(t.guard, p);
List<JCStatement> stats = copy(t.stats, p);
JCTree body;
if (node.getCaseKind() == CaseTree.CaseKind.RULE) {
@ -161,7 +162,7 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
} else {
body = null;
}
return M.at(t.pos).Case(t.caseKind, labels, stats, body);
return M.at(t.pos).Case(t.caseKind, labels, guard, stats, body);
}
@DefinedBy(Api.COMPILER_TREE)
@ -223,10 +224,10 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
@DefinedBy(Api.COMPILER_TREE)
public JCTree visitEnhancedForLoop(EnhancedForLoopTree node, P p) {
JCEnhancedForLoop t = (JCEnhancedForLoop) node;
JCTree varOrRecordPattern = copy(t.varOrRecordPattern, p);
JCVariableDecl var = copy(t.var, p);
JCExpression expr = copy(t.expr, p);
JCStatement body = copy(t.body, p);
return M.at(t.pos).ForeachLoop(varOrRecordPattern, expr, body);
return M.at(t.pos).ForeachLoop(var, expr, body);
}
@DefinedBy(Api.COMPILER_TREE)
@ -505,13 +506,6 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
return M.at(t.pos).BindingPattern(var);
}
@DefinedBy(Api.COMPILER_TREE)
public JCTree visitParenthesizedPattern(ParenthesizedPatternTree node, P p) {
JCParenthesizedPattern t = (JCParenthesizedPattern) node;
JCPattern pattern = copy(t.pattern, p);
return M.at(t.pos).ParenthesizedPattern(pattern);
}
@DefinedBy(Api.COMPILER_TREE)
public JCTree visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) {
JCDefaultCaseLabel t = (JCDefaultCaseLabel) node;
@ -529,8 +523,7 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
public JCTree visitPatternCaseLabel(PatternCaseLabelTree node, P p) {
JCPatternCaseLabel t = (JCPatternCaseLabel) node;
JCPattern pat = copy(t.pat, p);
JCExpression guard = copy(t.guard, p);
return M.at(t.pos).PatternCaseLabel(pat, guard);
return M.at(t.pos).PatternCaseLabel(pat);
}
@DefinedBy(Api.COMPILER_TREE)

View File

@ -648,10 +648,6 @@ public class TreeInfo {
return getEndPos(((JCWhileLoop) tree).body, endPosTable);
case ANNOTATED_TYPE:
return getEndPos(((JCAnnotatedType) tree).underlyingType, endPosTable);
case PARENTHESIZEDPATTERN: {
JCParenthesizedPattern node = (JCParenthesizedPattern) tree;
return getEndPos(node.pattern, endPosTable);
}
case ERRONEOUS: {
JCErroneous node = (JCErroneous)tree;
if (node.errs != null && node.errs.nonEmpty())
@ -855,15 +851,6 @@ public class TreeInfo {
return tree;
}
/** Skip parens and return the enclosed expression
*/
public static JCPattern skipParens(JCPattern tree) {
while (tree.hasTag(PARENTHESIZEDPATTERN)) {
tree = ((JCParenthesizedPattern) tree).pattern;
}
return tree;
}
/** Return the types of a list of trees.
*/
public static List<Type> types(List<? extends JCTree> trees) {
@ -1358,7 +1345,6 @@ public class TreeInfo {
public static Type primaryPatternType(JCTree pat) {
return switch (pat.getTag()) {
case BINDINGPATTERN -> pat.type;
case PARENTHESIZEDPATTERN -> primaryPatternType(((JCParenthesizedPattern) pat).pattern);
case RECORDPATTERN -> ((JCRecordPattern) pat).type;
default -> throw new AssertionError();
};
@ -1367,7 +1353,6 @@ public class TreeInfo {
public static JCTree primaryPatternTypeTree(JCTree pat) {
return switch (pat.getTag()) {
case BINDINGPATTERN -> ((JCBindingPattern) pat).var.vartype;
case PARENTHESIZEDPATTERN -> primaryPatternTypeTree(((JCParenthesizedPattern) pat).pattern);
case RECORDPATTERN -> ((JCRecordPattern) pat).deconstructor;
default -> throw new AssertionError();
};
@ -1380,11 +1365,8 @@ public class TreeInfo {
.anyMatch(l -> TreeInfo.isNullCaseLabel(l));
}
public static boolean unguardedCaseLabel(JCCaseLabel cse) {
if (!cse.hasTag(PATTERNCASELABEL)) {
return true;
}
JCExpression guard = ((JCPatternCaseLabel) cse).guard;
public static boolean unguardedCase(JCCase cse) {
JCExpression guard = cse.guard;
if (guard == null) {
return true;
}

View File

@ -274,8 +274,8 @@ public class TreeMaker implements JCTree.Factory {
return tree;
}
public JCEnhancedForLoop ForeachLoop(JCTree varOrRecordPattern, JCExpression expr, JCStatement body) {
JCEnhancedForLoop tree = new JCEnhancedForLoop(varOrRecordPattern, expr, body);
public JCEnhancedForLoop ForeachLoop(JCVariableDecl var, JCExpression expr, JCStatement body) {
JCEnhancedForLoop tree = new JCEnhancedForLoop(var, expr, body);
tree.pos = pos;
return tree;
}
@ -293,8 +293,8 @@ public class TreeMaker implements JCTree.Factory {
}
public JCCase Case(CaseTree.CaseKind caseKind, List<JCCaseLabel> labels,
List<JCStatement> stats, JCTree body) {
JCCase tree = new JCCase(caseKind, labels, stats, body);
JCExpression guard, List<JCStatement> stats, JCTree body) {
JCCase tree = new JCCase(caseKind, labels, guard, stats, body);
tree.pos = pos;
return tree;
}
@ -501,14 +501,8 @@ public class TreeMaker implements JCTree.Factory {
return tree;
}
public JCPatternCaseLabel PatternCaseLabel(JCPattern pat, JCExpression guard) {
JCPatternCaseLabel tree = new JCPatternCaseLabel(pat, guard);
tree.pos = pos;
return tree;
}
public JCParenthesizedPattern ParenthesizedPattern(JCPattern pattern) {
JCParenthesizedPattern tree = new JCParenthesizedPattern(pattern);
public JCPatternCaseLabel PatternCaseLabel(JCPattern pat) {
JCPatternCaseLabel tree = new JCPatternCaseLabel(pat);
tree.pos = pos;
return tree;
}

View File

@ -162,7 +162,7 @@ public class TreeScanner extends Visitor {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
scan(tree.varOrRecordPattern);
scan(tree.var);
scan(tree.expr);
scan(tree.body);
}
@ -178,6 +178,7 @@ public class TreeScanner extends Visitor {
public void visitCase(JCCase tree) {
scan(tree.labels);
scan(tree.guard);
scan(tree.stats);
}
@ -319,12 +320,6 @@ public class TreeScanner extends Visitor {
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
scan(tree.pat);
scan(tree.guard);
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
scan(tree.pattern);
}
@Override

View File

@ -189,7 +189,7 @@ public class TreeTranslator extends JCTree.Visitor {
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
tree.varOrRecordPattern = translate(tree.varOrRecordPattern);
tree.var = translate(tree.var);
tree.expr = translate(tree.expr);
tree.body = translate(tree.body);
result = tree;
@ -208,6 +208,7 @@ public class TreeTranslator extends JCTree.Visitor {
public void visitCase(JCCase tree) {
tree.labels = translate(tree.labels);
tree.guard = translate(tree.guard);
tree.stats = translate(tree.stats);
result = tree;
}
@ -377,13 +378,6 @@ public class TreeTranslator extends JCTree.Visitor {
@Override
public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
tree.pat = translate(tree.pat);
tree.guard = translate(tree.guard);
result = tree;
}
@Override
public void visitParenthesizedPattern(JCParenthesizedPattern tree) {
tree.pattern = translate(tree.pattern);
result = tree;
}

View File

@ -92,9 +92,11 @@ public class Names {
public final Name hasNext;
public final Name hashCode;
public final Name init;
public final Name invoke;
public final Name iterator;
public final Name length;
public final Name next;
public final Name of;
public final Name ordinal;
public final Name provider;
public final Name serialVersionUID;
@ -221,6 +223,7 @@ public class Names {
// pattern switches
public final Name typeSwitch;
public final Name enumSwitch;
public final Name enumConstant;
// templated string
public final Name process;
@ -282,9 +285,11 @@ public class Names {
hasNext = fromString("hasNext");
hashCode = fromString("hashCode");
init = fromString("<init>");
invoke = fromString("invoke");
iterator = fromString("iterator");
length = fromString("length");
next = fromString("next");
of = fromString("of");
ordinal = fromString("ordinal");
provider = fromString("provider");
serialVersionUID = fromString("serialVersionUID");
@ -415,6 +420,7 @@ public class Names {
// pattern switches
typeSwitch = fromString("typeSwitch");
enumSwitch = fromString("enumSwitch");
enumConstant = fromString("enumConstant");
}
protected Name.Table createTable(Options options) {

View File

@ -24,15 +24,20 @@
*/
import java.io.Serializable;
import java.lang.Enum.EnumDesc;
import java.lang.constant.ClassDesc;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.runtime.SwitchBootstraps;
import java.util.concurrent.atomic.AtomicBoolean;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
/**
@ -158,6 +163,28 @@ public class SwitchBootstrapsTest {
}
}
public void testSwitchLabelTypes() throws Throwable {
enum E {A}
try {
testType(E.A, 0, -1, E.A);
fail("Didn't get the expected exception.");
} catch (IllegalArgumentException ex) {
//OK, expected
}
}
public void testSwitchQualifiedEnum() throws Throwable {
enum E {A, B, C}
Object[] labels = new Object[] {
EnumDesc.of(ClassDesc.of(E.class.getName()), "A"),
EnumDesc.of(ClassDesc.of(E.class.getName()), "B"),
EnumDesc.of(ClassDesc.of(E.class.getName()), "C")
};
testType(E.A, 0, 0, labels);
testType(E.B, 0, 1, labels);
testType(E.C, 0, 2, labels);
}
public void testNullLabels() throws Throwable {
MethodType switchType = MethodType.methodType(int.class, Object.class, int.class);
try {
@ -188,4 +215,100 @@ public class SwitchBootstrapsTest {
//OK
}
}
private static AtomicBoolean enumInitialized = new AtomicBoolean();
public void testEnumInitialization1() throws Throwable {
enumInitialized.set(false);
enum E {
A;
static {
enumInitialized.set(true);
}
}
MethodType enumSwitchType = MethodType.methodType(int.class, E.class, int.class);
CallSite invocation = (CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, new Object[] {"A"});
assertFalse(enumInitialized.get());
assertEquals(invocation.dynamicInvoker().invoke(null, 0), -1);
assertFalse(enumInitialized.get());
E e = E.A;
assertTrue(enumInitialized.get());
assertEquals(invocation.dynamicInvoker().invoke(e, 0), 0);
}
public void testEnumInitialization2() throws Throwable {
enumInitialized.set(false);
enum E {
A;
static {
enumInitialized.set(true);
}
}
MethodType switchType = MethodType.methodType(int.class, Object.class, int.class);
Object[] labels = new Object[] {
EnumDesc.of(ClassDesc.of(E.class.getName()), "A"),
"test"
};
CallSite invocation = (CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels);
assertFalse(enumInitialized.get());
assertEquals(invocation.dynamicInvoker().invoke(null, 0), -1);
assertFalse(enumInitialized.get());
assertEquals(invocation.dynamicInvoker().invoke("test", 0), 1);
assertFalse(enumInitialized.get());
E e = E.A;
assertTrue(enumInitialized.get());
assertEquals(invocation.dynamicInvoker().invoke(e, 0), 0);
}
public void testIncorrectEnumLabels() throws Throwable {
try {
testEnum(E1.B, 0, -1, "B", 1);
fail("Didn't get the expected exception.");
} catch (IllegalArgumentException ex) {
//OK
}
try {
testEnum(E1.B, 0, -1, "B", null);
fail("Didn't get the expected exception.");
} catch (IllegalArgumentException ex) {
//OK
}
}
public void testIncorrectEnumStartIndex() throws Throwable {
try {
testEnum(E1.B, -1, -1, "B");
fail("Didn't get the expected exception.");
} catch (IndexOutOfBoundsException ex) {
//OK
}
try {
testEnum(E1.B, 2, -1, "B");
fail("Didn't get the expected exception.");
} catch (IndexOutOfBoundsException ex) {
//OK
}
}
public void testIncorrectTypeStartIndex() throws Throwable {
try {
testType("", -1, -1, "");
fail("Didn't get the expected exception.");
} catch (IndexOutOfBoundsException ex) {
//OK
}
try {
testType("", 2, -1, "");
fail("Didn't get the expected exception.");
} catch (IndexOutOfBoundsException ex) {
//OK
}
}
}

View File

@ -99,10 +99,7 @@ public class ConditionalExpressionResolvePending extends ComboInstance<Condition
case "FALSE" -> False;
case "SNIPPET" -> snippet;
default -> throw new UnsupportedOperationException(pname);
})
.withOption("--enable-preview")
.withOption("-source")
.withOption(String.valueOf(Runtime.version().feature()));
});
task.generate(result -> {
try {

View File

@ -25,7 +25,7 @@
* @test
* @bug 8286797
* @summary Guards of constant value false are not permitted
* @compile/fail/ref=T8286797.out --enable-preview -source ${jdk.version} -XDrawDiagnostics -XDshould-stop.at=FLOW T8286797.java
* @compile/fail/ref=T8286797.out -XDrawDiagnostics -XDshould-stop.at=FLOW T8286797.java
*/
public class T8286797 {

View File

@ -1,5 +1,3 @@
T8286797.java:35:32: compiler.err.guard.has.constant.expression.false
T8286797.java:43:34: compiler.err.guard.has.constant.expression.false
- compiler.note.preview.filename: T8286797.java, DEFAULT
- compiler.note.preview.recompile
2 errors

View File

@ -25,7 +25,7 @@
* @bug 8295447
* @summary NullPointerException with invalid pattern matching construct in constructor call
* @modules jdk.compiler
* @compile/fail/ref=T8295447.out -XDrawDiagnostics --enable-preview -source ${jdk.version} T8295447.java
* @compile/fail/ref=T8295447.out -XDrawDiagnostics T8295447.java
*/
public class T8295447 {
class Foo {

View File

@ -1,6 +1,4 @@
T8295447.java:33:29: compiler.err.deconstruction.pattern.only.records: T8295447.Foo
T8295447.java:37:29: compiler.err.deconstruction.pattern.only.records: T8295447.Foo
T8295447.java:44:44: compiler.err.deconstruction.pattern.only.records: java.awt.Point
- compiler.note.preview.filename: T8295447.java, DEFAULT
- compiler.note.preview.recompile
3 errors

View File

@ -28,7 +28,6 @@
* @modules java.compiler
* jdk.jdeps/com.sun.tools.javap
* @build toolbox.JavapTask
* @enablePreview
* @run main Patterns
*/

View File

@ -81,7 +81,7 @@ public class RuleSwitchBreaks extends LineNumberTestBase {
""",
List.of(1, 3, 4, 5, 6, 7, 9, 11),
true,
List.of("--enable-preview", "-source", System.getProperty("java.specification.version")),
List.of(),
"TestGuards")
};

View File

@ -141,6 +141,7 @@ compiler.warn.invalid.path # this warning is genera
compiler.err.invalid.path # this error is generated only in Windows systems
compiler.note.multiple.elements # needs user code
compiler.err.preview.feature.disabled.classfile # preview feature support: needs compilation against classfile
compiler.warn.preview.feature.use # preview feature support: not generated currently
compiler.warn.preview.feature.use.classfile # preview feature support: needs compilation against classfile
compiler.note.preview.plural.additional # preview feature support: diag test causes intermittent failures (see JDK-8201498)
compiler.misc.bad.intersection.target.for.functional.expr # currently not generated, should be removed?

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 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
@ -21,32 +21,14 @@
* questions.
*/
/*
* @test
* @bug 8297602
* @summary Compiler crash with type annotation and generic record during pattern matching
* @enablePreview
* @compile --enable-preview -source ${jdk.version} -XDrawDiagnostics T8297602.java
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
public class T8297602
{
void meth(Foo<Integer> p) {
switch(p) {
case Foo<@Annot(field = "") Integer>(): {}
};
if (p instanceof Foo<@Annot(field = "") Integer>()) {
// key: compiler.err.cannot.assign.not.declared.guard
class CannotAssignNotDeclaredGuard {
void test(Object i) {
final boolean b;
switch (i) {
case Object o when b = true -> {}
default -> {}
}
}
@Target({ElementType.TYPE_USE})
@interface Annot {
String field();
}
record Foo<T>() { }
}

View File

@ -25,9 +25,6 @@
// key: compiler.misc.inner.cls
// key: compiler.misc.lambda
// key: compiler.misc.guard
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class CantRefNonEffectivelyFinalVar {
void test() {

View File

@ -21,9 +21,9 @@
* questions.
*/
// key: compiler.err.feature.not.supported.in.source
// key: compiler.misc.feature.case.null
// key: compiler.warn.preview.feature.use
// options: --enable-preview -source ${jdk.version} -Xlint:preview
// options: -source 20 -Xlint:-options
class CaseNull {
private void doSwitch(String s) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.constant.label.not.compatible
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class ConstantLabelNotCompatible {
private void doSwitch(Object o) {

View File

@ -22,9 +22,6 @@
*/
// 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) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.default.label.not.allowed
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class DefaultLabelNotAllowed {
private void doSwitch(Object o) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.duplicate.unconditional.pattern
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class DuplicateUnconditionalPattern {
private void doSwitch(Object o) {

View File

@ -22,6 +22,7 @@
*/
// key: compiler.err.enum.label.must.be.unqualified.enum
// options: --release 20
class EnumLabelUnqualified {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 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
@ -21,20 +21,13 @@
* questions.
*/
// key: compiler.err.feature.not.supported.in.source.plural
// key: compiler.misc.feature.deconstruction.patterns
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// key: compiler.err.foreach.not.exhaustive.on.type
// options: --enable-preview -source ${jdk.version} -Xlint:preview
// options: -source 20 -Xlint:-options
import java.util.List;
class FeatureDeconstructionPatterns {
Object o;
boolean b = o instanceof R(String s);
class ForeachNotExhaustive {
void m(List<Object> points) {
for (Point(var x, var y): points) {
System.out.println();
}
}
record Point(Integer x, Integer y) { }
record R(Object o) {}
}

View File

@ -21,11 +21,9 @@
* questions.
*/
// key: compiler.err.feature.not.supported.in.source.plural
// key: compiler.misc.feature.unconditional.patterns.in.instanceof
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:-options,preview
import java.util.*;
// options: -source 20 -Xlint:-options
class FeatureUnconditionalTypesInstanceof {
String s;

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.flows.through.from.pattern
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class FlowsThroughToPattern {
private void doSwitch(Object o) {

View File

@ -22,11 +22,6 @@
*/
// key: compiler.err.flows.through.to.pattern
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.case.null
// key: compiler.warn.preview.feature.use
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class FlowsThroughToPattern {
private void doSwitch(Object o) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.guard.has.constant.expression.false
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class GuardHasConstantFalse {
private void doSwitch(Object o) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 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
@ -21,10 +21,13 @@
* questions.
*/
// key: compiler.err.instanceof.pattern.no.subtype
// key: compiler.err.guard.not.allowed
class InstanceofPatternNoSubtype {
boolean test(Object o) {
return o instanceof Object obj;
class GuardNotAllowed {
private void doSwitch(int i, boolean b) {
switch (i) {
case 0 when b -> {}
default -> {}
}
}
}

View File

@ -22,9 +22,6 @@
*/
// 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) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.invalid.case.label.combination
// key: compiler.misc.feature.case.null
// key: compiler.warn.preview.feature.use
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class InvalidCaseLabelCombination {
private void doSwitch(Integer i) {

View File

@ -23,9 +23,6 @@
// key: compiler.misc.not.applicable.types
// key: compiler.err.prob.found.req
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: --enable-preview --source ${jdk.version}
class NotApplicableTypes {
void t(int i) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.not.exhaustive.statement
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: --enable-preview --source ${jdk.version}
class NotExhaustive {
void t(Object o) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.pattern.dominated
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class PatternDominated {
private void doSwitch(Object o) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.pattern.expected
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: --enable-preview -source ${jdk.version}
class PatternSwitch {
private void doSwitch(Object o) {

View File

@ -21,9 +21,9 @@
* questions.
*/
// key: compiler.err.feature.not.supported.in.source.plural
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
// options: -source 20 -Xlint:-options
class PatternSwitch {
private void doSwitch(Object o) {

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.pattern.type.cannot.infer
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.deconstruction.patterns
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class PatternTypeCannotInfer {
interface A<T> {}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023, 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.record.patterns.annotations.not.allowed
class RecordPatternsAnnotationsNotAllowed {
public boolean test(Object o) {
return o instanceof @Deprecated R(String s);
}
record R(String s) {}
}

View File

@ -22,9 +22,6 @@
*/
// key: compiler.err.unconditional.pattern.and.default
// key: compiler.misc.feature.pattern.switch
// key: compiler.warn.preview.feature.use.plural
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class UnconditionalPatternAndDefault {
private void doSwitch(Object o) {

View File

@ -3,7 +3,7 @@
* @bug 4936393 8050021
* @summary enum switch case labels required to be unqualified.
* @author gafter
* @compile/fail/ref=EnumSwitch2.out -XDrawDiagnostics EnumSwitch2.java
* @compile/fail/ref=EnumSwitch2.out -XDrawDiagnostics --release 20 EnumSwitch2.java
*/
enum E1 { a, b, c, d, e }

View File

@ -722,7 +722,7 @@ public class DPrinter {
@Override
public void visitForeachLoop(JCEnhancedForLoop tree) {
printTree("var", tree.varOrRecordPattern);
printTree("var", tree.var);
printTree("expr", tree.expr);
printTree("body", tree.body);
}

View File

@ -2342,6 +2342,59 @@ public class JavacParserTest extends TestCase {
}
}
@Test
void testGuardRecovery() throws IOException {
String code = """
package t;
class Test {
private int t(Integer i, boolean b) {
switch (i) {
case 0 when b -> {}
case null when b -> {}
default when b -> {}
}
return switch (i) {
case 0 when b -> 0;
case null when b -> 0;
default when b -> 0;
};
}
}""";
DiagnosticCollector<JavaFileObject> coll =
new DiagnosticCollector<>();
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null,
null, Arrays.asList(new MyFileObject(code)));
CompilationUnitTree cut = ct.parse().iterator().next();
new TreeScanner<Void, Void>() {
@Override
public Void visitCase(CaseTree node, Void p) {
assertNotNull(node.getGuard());
assertEquals("guard kind", Kind.ERRONEOUS, node.getGuard().getKind());
assertEquals("guard content",
List.of("b"),
((ErroneousTree) node.getGuard()).getErrorTrees()
.stream()
.map(t -> t.toString()).toList());
return super.visitCase(node, p);
}
}.scan(cut, null);
List<String> codes = new LinkedList<>();
for (Diagnostic<? extends JavaFileObject> d : coll.getDiagnostics()) {
codes.add(d.getLineNumber() + ":" + d.getColumnNumber() + ":" + d.getCode());
}
assertEquals("testUsupportedTextBlock: " + codes,
List.of("5:20:compiler.err.guard.not.allowed",
"6:23:compiler.err.guard.not.allowed",
"7:21:compiler.err.guard.not.allowed",
"10:20:compiler.err.guard.not.allowed",
"11:23:compiler.err.guard.not.allowed",
"12:21:compiler.err.guard.not.allowed"),
codes);
}
void run(String[] args) throws Exception {
int passed = 0, failed = 0;
final Pattern p = (args != null && args.length > 0)

View File

@ -0,0 +1,42 @@
/*
* @test /nodynamiccopyright/
* @bug 8300543
* @summary Verify error related to annotations and patterns
* @compile/fail/ref=AnnotationErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW AnnotationErrors.java
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
public class AnnotationErrors {
private void test(Object o, G<String> g) {
boolean b1 = o instanceof @DA R(var s);
boolean b2 = o instanceof @DTA R(var s);
boolean b3 = o instanceof @TA R(var s);
boolean b5 = g instanceof G<@DTA String>(var s);
boolean b6 = g instanceof G<@TA String>(var s);
switch (o) {
case @DA R(var s) when b1 -> {}
case @DTA R(var s) when b1 -> {}
case @TA R(var s) when b1 -> {}
default -> {}
}
switch (g) {
case G<@DTA String>(var s) when b1 -> {}
case G<@TA String>(var s) when b1 -> {}
default -> {}
}
}
record R(String s) {}
record G<T>(T t) {}
@Target(ElementType.LOCAL_VARIABLE)
@interface DA {}
@Target({ElementType.TYPE_USE, ElementType.LOCAL_VARIABLE})
@interface DTA {}
@Target(ElementType.TYPE_USE)
@interface TA {}
}

View File

@ -0,0 +1,11 @@
AnnotationErrors.java:14:35: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:15:35: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:16:35: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:17:37: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:18:37: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:20:18: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:21:18: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:22:18: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:26:20: compiler.err.record.patterns.annotations.not.allowed
AnnotationErrors.java:27:20: compiler.err.record.patterns.annotations.not.allowed
10 errors

View File

@ -85,16 +85,14 @@ public class CaseStructureTest extends ComboInstance<CaseStructureTest> {
protected void doWork() throws Throwable {
String labelSeparator = asCaseLabelElements ? ", " : ": case ";
String labels = Arrays.stream(caseLabels).filter(l -> l != CaseLabel.NONE).map(l -> l.code).collect(Collectors.joining(labelSeparator, "case ", ": break;"));
boolean hasDefault = Arrays.stream(caseLabels).anyMatch(l -> l == CaseLabel.DEFAULT || l == CaseLabel.TYPE_PATTERN || l == CaseLabel.PARENTHESIZED_PATTERN);
boolean hasDefault = Arrays.stream(caseLabels).anyMatch(l -> l == CaseLabel.DEFAULT || l == CaseLabel.TYPE_PATTERN);
ComboTask task = newCompilationTask()
.withSourceFromTemplate(MAIN_TEMPLATE.replace("#{CASES}", labels).replace("#{DEFAULT}", hasDefault ? "" : "default: break;"))
.withOption("--enable-preview")
.withOption("-source").withOption(JAVA_VERSION);
.withSourceFromTemplate(MAIN_TEMPLATE.replace("#{CASES}", labels).replace("#{DEFAULT}", hasDefault ? "" : "default: break;"));
task.generate(result -> {
boolean shouldPass = true;
long patternCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.TYPE_PATTERN || l == CaseLabel.PARENTHESIZED_PATTERN).count();
long patternCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.TYPE_PATTERN).count();
long constantCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.CONSTANT).count();
long nullCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.NULL).count();
long defaultCases = Arrays.stream(caseLabels).filter(l -> l == CaseLabel.DEFAULT).count();
@ -136,7 +134,6 @@ public class CaseStructureTest extends ComboInstance<CaseStructureTest> {
public enum CaseLabel implements ComboParameter {
NONE(""),
TYPE_PATTERN("Integer i"),
PARENTHESIZED_PATTERN("(Integer i)"),
CONSTANT("1"),
NULL("null"),
DEFAULT("default");

View File

@ -25,8 +25,8 @@
* @test
* @bug 8291769 8301858 8304694 8304883
* @summary Verify more complex switches work properly
* @compile --enable-preview -source ${jdk.version} DeconstructionDesugaring.java
* @run main/othervm --enable-preview DeconstructionDesugaring
* @compile DeconstructionDesugaring.java
* @run main DeconstructionDesugaring
*/
import java.util.Objects;
@ -79,7 +79,7 @@ public class DeconstructionDesugaring {
private int runCheckStatement(Object o) {
switch (o) {
case (((R1((((R2((((String s))))))))))) -> { return 1; }
case R1(R2(String s)) -> { return 1; }
case R1(R2(Integer i)) -> { return 2; }
case R1(R2(Double d)) -> { return 3; }
case R1(R2(CharSequence cs)) -> { return 4; }
@ -93,7 +93,7 @@ public class DeconstructionDesugaring {
private int runCheckExpression(Object o) {
return switch (o) {
case (((R1((((R2((((String s))))))))))) -> 1;
case R1(R2(String s)) -> 1;
case R1(R2(Integer i)) -> 2;
case R1(R2(Double d)) -> 3;
case R1(R2(CharSequence cs)) -> 4;

View File

@ -1,9 +1,9 @@
/**
* @test /nodynamiccopyright/
* @summary Verify error reports for erroneous deconstruction patterns are sensible
* @enablePreview
* @compile/fail/ref=DeconstructionPatternErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW -XDdev DeconstructionPatternErrors.java
*/
import java.util.ArrayList;
import java.util.List;

View File

@ -26,7 +26,4 @@ DeconstructionPatternErrors.java:31:29: compiler.err.prob.found.req: (compiler.m
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:44:9: compiler.err.not.exhaustive.statement
- compiler.note.preview.filename: DeconstructionPatternErrors.java, DEFAULT
- compiler.note.preview.recompile
29 errors
28 errors

View File

@ -28,37 +28,22 @@
* jdk.compiler/com.sun.tools.javac.parser
* jdk.compiler/com.sun.tools.javac.tree
* jdk.compiler/com.sun.tools.javac.util
* @enablePreview
*/
import com.sun.source.tree.CaseLabelTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ConstantCaseLabelTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.PatternCaseLabelTree;
import com.sun.source.tree.PatternTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.parser.JavacParser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
public class DisambiguatePatterns {
@ -68,11 +53,11 @@ public class DisambiguatePatterns {
ExpressionType.PATTERN);
test.disambiguationTest("String s when s.isEmpty()",
ExpressionType.PATTERN);
test.disambiguationTest("(String s)",
test.disambiguationTest("String s",
ExpressionType.PATTERN);
test.disambiguationTest("(@Ann String s)",
test.disambiguationTest("@Ann String s",
ExpressionType.PATTERN);
test.disambiguationTest("((String s))",
test.disambiguationTest("String s",
ExpressionType.PATTERN);
test.disambiguationTest("(String) s",
ExpressionType.EXPRESSION);
@ -92,7 +77,7 @@ public class DisambiguatePatterns {
ExpressionType.EXPRESSION);
test.disambiguationTest("(a << b || a < b | a >>> b)",
ExpressionType.EXPRESSION);
test.disambiguationTest("(a < c.d > b)",
test.disambiguationTest("a < c.d > b",
ExpressionType.PATTERN);
test.disambiguationTest("a<? extends c.d> b",
ExpressionType.PATTERN);
@ -124,44 +109,14 @@ public class DisambiguatePatterns {
ExpressionType.EXPRESSION);
test.disambiguationTest("a & b",
ExpressionType.EXPRESSION);
test.forDisambiguationTest("T[] a", ForType.ENHANCED_FOR);
test.forDisambiguationTest("T[].class.getName()", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("T[].class", ForType.TRADITIONAL_FOR, "compiler.err.not.stmt");
test.forDisambiguationTest("R(T[] a)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("Point(Integer a, Integer b)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("ForEachPatterns.Point(Integer a, Integer b)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<Integer>(Integer a, Integer b)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("@Annot(field = \"test\") Point p", ForType.ENHANCED_FOR);
test.forDisambiguationTest("GPoint<Point>(Point(Integer a, Integer b), Point c)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<Point>(Point(var a, Integer b), Point c)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<VoidPoint>(VoidPoint(), VoidPoint())", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("RecordOfLists(List<Integer> lr)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("RecordOfLists2(List<List<Integer>> lr)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("GPoint<@Annot(field = \"\") ? extends Point>(var x, var y)", ForType.ENHANCED_FOR_WITH_PATTERNS);
test.forDisambiguationTest("method()", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("method(), method()", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("method2((Integer a) -> 42)", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("m(cond ? b() : i)", ForType.TRADITIONAL_FOR);
test.forDisambiguationTest("m((GPoint<?>)null, cond ? b() : i)", ForType.TRADITIONAL_FOR);
}
private final ParserFactory factory;
private final List<String> errors = new ArrayList<>();
public DisambiguatePatterns() throws URISyntaxException {
public DisambiguatePatterns() {
Context context = new Context();
context.put(DiagnosticListener.class, d -> {
if (d.getKind() == Diagnostic.Kind.ERROR) {
errors.add(d.getCode());
}
});
JavacFileManager jfm = new JavacFileManager(context, true, Charset.defaultCharset());
Options.instance(context).put(Option.PREVIEW, "");
SimpleJavaFileObject source =
new SimpleJavaFileObject(new URI("mem://Test.java"), JavaFileObject.Kind.SOURCE) {};
Log.instance(context).useSource(source);
factory = ParserFactory.instance(context);
}
@ -192,66 +147,9 @@ public class DisambiguatePatterns {
}
}
void forDisambiguationTest(String snippet, ForType forType, String... expectedErrors) {
errors.clear();
String codeTemplate = switch (forType) {
case TRADITIONAL_FOR ->
"""
public class Test {
private void test() {
for (SNIPPET; ;) {
}
}
}
""";
case ENHANCED_FOR, ENHANCED_FOR_WITH_PATTERNS ->
"""
public class Test {
private void test() {
for (SNIPPET : collection) {
}
}
}
""";
};
String code = codeTemplate.replace("SNIPPET", snippet);
JavacParser parser = factory.newParser(code, false, false, false);
CompilationUnitTree result = parser.parseCompilationUnit();
if (!Arrays.asList(expectedErrors).equals(errors)) {
throw new AssertionError("Expected errors: " + Arrays.asList(expectedErrors) +
", actual: " + errors +
", for: " + code);
}
ClassTree clazz = (ClassTree) result.getTypeDecls().get(0);
MethodTree method = (MethodTree) clazz.getMembers().get(0);
StatementTree st = method.getBody().getStatements().get(0);
if (forType == ForType.TRADITIONAL_FOR) {
if (st.getKind() != Kind.FOR_LOOP) {
throw new AssertionError("Unpected statement: " + st);
}
} else {
EnhancedForLoopTree ef = (EnhancedForLoopTree) st;
ForType actualType = switch (ef.getVariableOrRecordPattern()) {
case PatternTree pattern -> ForType.ENHANCED_FOR_WITH_PATTERNS;
default -> ForType.ENHANCED_FOR;
};
if (forType != actualType) {
throw new AssertionError("Expected: " + forType + ", actual: " + actualType +
", for: " + code + ", parsed: " + result);
}
}
}
enum ExpressionType {
PATTERN,
EXPRESSION;
}
enum ForType {
TRADITIONAL_FOR,
ENHANCED_FOR,
ENHANCED_FOR_WITH_PATTERNS;
}
}

View File

@ -25,9 +25,9 @@
* @test
* @bug 8262891 8290709
* @summary Check the pattern domination error are reported correctly.
* @enablePreview
* @compile/fail/ref=Domination.out -XDrawDiagnostics Domination.java
*/
public class Domination {
int testDominatesError1(Object o) {
switch (o) {
@ -180,8 +180,8 @@ public class Domination {
record R(int a) {}
Object o = null;
switch (o) {
case ((R r)): return 1;
case ((R(int a))): return -1;
case R r: return 1;
case R(int a): return -1;
}
}
@ -189,8 +189,8 @@ public class Domination {
record R(int a) {}
Object o = null;
switch (o) {
case ((R(int a))): return 1;
case ((R(int a))): return -1;
case R(int a): return 1;
case R(int a): return -1;
}
}

View File

@ -16,6 +16,4 @@ Domination.java:184:18: compiler.err.pattern.dominated
Domination.java:193:18: compiler.err.pattern.dominated
Domination.java:202:18: compiler.err.pattern.dominated
Domination.java:211:18: compiler.err.pattern.dominated
- compiler.note.preview.filename: Domination.java, DEFAULT
- compiler.note.preview.recompile
18 errors

View File

@ -23,7 +23,6 @@
/**
* @test
* @enablePreview
* @compile EmptyRecordClass.java
*/

View File

@ -25,7 +25,6 @@
* @test
* @bug 8262891
* @summary Verify pattern switches work properly when the set of enum constant changes.
* @enablePreview
* @compile EnumTypeChanges.java
* @compile EnumTypeChanges2.java
* @run main EnumTypeChanges

View File

@ -25,10 +25,9 @@
* @test
* @bug 8297118
* @summary Verify pattern switches work properly when the set of enum constant changes.
* @compile EnumTypeChangesNonPreview.java
* @compile --release 20 EnumTypeChangesNonPreview.java
* @compile EnumTypeChanges2.java
* @run main EnumTypeChangesNonPreview
* @run main/othervm --enable-preview EnumTypeChangesNonPreview
*/
import java.util.function.Function;

File diff suppressed because it is too large Load Diff

View File

@ -1,254 +0,0 @@
/*
* @test /nodynamiccopyright/
* @summary
* @enablePreview
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
public class ForEachPatterns {
public static void main(String[] args) {
List<Point> in = List.of(new Point(1, 2), new Point(2, 3));
List<IPoint> in_iface = List.of(new Point(1, 2), new Point(2, 3));
List inRaw = List.of(new Point(1, 2), new Point(2, 3), new Frog(3, 4));
List<PointEx> inWithPointEx = List.of(new PointEx(1, 2));
byte[] inBytes = { (byte) 127, (byte) 127 };
List<Point> inWithNullComponent = List.of(new Point(1, null), new Point(2, 3));
Point[] inArray = in.toArray(Point[]::new);
List<WithPrimitives> inWithPrimitives = List.of(new WithPrimitives(1, 2), new WithPrimitives(2, 3));
IParent recs [] = { new Rec(1) };
List<Point> inWithNull = new ArrayList<>();
{
inWithNull.add(new Point(2, 3));
inWithNull.add(null);
}
assertEquals(8, iteratorEnhancedFor(in));
assertEquals(8, arrayEnhancedFor(inArray));
assertEquals(8, simpleDecostructionPatternWithAccesses(in));
assertEx(ForEachPatterns::simpleDecostructionPatternWithAccesses, null, NullPointerException.class);
assertMatchExceptionWithNested(ForEachPatterns::simpleDecostructionPatternWithAccesses, inWithNull, NullPointerException.class);
assertEx(ForEachPatterns::simpleDecostructionPatternWithAccesses, inWithNullComponent, NullPointerException.class);
assertMatchExceptionWithNested(ForEachPatterns::simpleDecostructionPatternException, inWithPointEx, TestPatternFailed.class);
assertEx(ForEachPatterns::simpleDecostructionPatternWithAccesses, (List<Point>) inRaw, ClassCastException.class);
assertEquals(2, simpleDecostructionPatternNoComponentAccess(in));
assertMatchExceptionWithNested(ForEachPatterns::simpleDecostructionPatternNoComponentAccess, inWithNull, NullPointerException.class);
assertEquals(2, simpleDecostructionPatternNoComponentAccess(inWithNullComponent));
assertEquals(8, varAndConcrete(in));
assertEquals(3, returnFromEnhancedFor(in));
assertEquals(0, breakFromEnhancedFor(in));
assertEquals(254, primitiveWidening(inBytes));
assertEquals(8, sealedRecordPassBaseType(in_iface));
assertEquals(8, withPrimitives(inWithPrimitives));
assertEquals(List.of(Color.RED), JEPExample());
assertEquals(1, arrayWithSealed(recs));
}
static int iteratorEnhancedFor(List<Point> points) {
int result = 0;
for (Point(Integer a, Integer b) : points) {
result += a + b;
}
return result;
}
static int arrayEnhancedFor(Point[] points) {
int result = 0;
for (Point(Integer a, Integer b) : points) {
result += a + b;
}
return result;
}
static int simpleDecostructionPatternWithAccesses(List<Point> points) {
int result = 0;
for (Point(var a, var b): points) {
result += a + b;
}
return result;
}
static int simpleDecostructionPatternException(List<PointEx> points) {
int result = 0;
for (PointEx(var a, var b): points) {
result += a + b;
}
return result;
}
static int simpleDecostructionPatternNoComponentAccess(List<Point> points) {
int result = 0;
for (Point(var a, var b): points) {
result += 1;
}
return result;
}
static int varAndConcrete(List<Point> points) {
int result = 0;
for (Point(Integer a, var b): points) {
result += a + b;
}
return result;
}
static int returnFromEnhancedFor(List<Point> points) {
for (Point(var a, var b): points) {
return a + b;
}
return -1;
}
static int breakFromEnhancedFor(List<Point> points) {
int i = 1;
int result = 0;
for (Point(var a, var b): points) {
if (i == 1) break;
else result += a + b;
}
return result;
}
static int sealedRecordPassBaseType(List<IPoint> points) {
int result = 0;
for(Point(var x, var y) : points) {
result += (x + y);
}
return result;
}
static int withPrimitives(List<WithPrimitives> points) {
int result = 0;
for (WithPrimitives(int a, double b): points) {
result += a + (int) b;
}
return result;
}
// Simpler pos tests with local variable declarations
// Should pass now and in the future if local variable
// declaration is subsumed by patterns (not just record patterns)
static int primitiveWidening(byte[] inBytes) {
int acc = 0;
for (int i: inBytes) {
acc += i;
}
return acc;
}
static int applicability1(List<Point> points) {
for (IPoint p: points) {
System.out.println(p);
}
return -1;
}
static int applicability2(List<Object> points) {
for (Object p: points) {
System.out.println(p);
}
return -1;
}
static List<Color> JEPExample() {
Rectangle rect = new Rectangle(
new ColoredPoint(new Point(1,2), Color.RED),
new ColoredPoint(new Point(3,4), Color.GREEN)
);
Rectangle[] rArr = {rect};
return printUpperLeftColors(rArr);
}
//where
static List<Color> printUpperLeftColors(Rectangle[] r) {
List<Color> ret = new ArrayList<>();
for (Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr): r) {
ret.add(c);
}
return ret;
}
static int arrayWithSealed(IParent[] recs){
for (Rec(int a) : recs) {
return a;
}
return -1;
}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
sealed interface IParent permits Rec {}
record Rec(int a) implements IParent {}
sealed interface IPoint permits Point {}
record Point(Integer x, Integer y) implements IPoint { }
record GPoint<T>(T x, T y) { }
record VoidPoint() { }
record RecordOfLists(List<Integer> o) {}
record RecordOfLists2(List<List<Integer>> o) {}
@Target({ElementType.TYPE_USE, ElementType.LOCAL_VARIABLE})
@interface Annot {
String field();
}
record Frog(Integer x, Integer y) { }
record PointEx(Integer x, Integer y) {
@Override
public Integer x() {
throw new TestPatternFailed(EXCEPTION_MESSAGE);
}
}
record WithPrimitives(int x, double y) { }
static final String EXCEPTION_MESSAGE = "exception-message";
public static class TestPatternFailed extends AssertionError {
public TestPatternFailed(String message) {
super(message);
}
}
// error handling
static void fail(String message) {
throw new AssertionError(message);
}
static void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected: " + expected + "," +
"got: " + actual);
}
}
static <T> void assertMatchExceptionWithNested(Function<List<T>, Integer> f, List<T> points, Class<?> nestedExceptionClass) {
try {
f.apply(points);
fail("Expected an exception, but none happened!");
}
catch(Exception ex) {
assertEquals(MatchException.class, ex.getClass());
MatchException me = (MatchException) ex;
assertEquals(nestedExceptionClass, me.getCause().getClass());
}
}
static <T> void assertEx(Function<List<T>, Integer> f, List<T> points, Class<?> exceptionClass) {
try {
f.apply(points);
fail("Expected an exception, but none happened!");
}
catch(Exception ex) {
assertEquals(exceptionClass, ex.getClass());
}
}
}

View File

@ -1,46 +0,0 @@
/*
* @test /nodynamiccopyright/
* @summary
* @enablePreview
* @compile/fail/ref=ForEachPatternsErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW ForEachPatternsErrors.java
*/
import java.util.List;
public class ForEachPatternsErrors {
static void exhaustivity_error1(List<Object> points) {
for (Point(var x, var y): points) {
System.out.println();
}
}
static void exhaustivity_error2(List points) {
for (Point(var x, var y): points) {
System.out.println();
}
}
static void exhaustivity_error3(List<OPoint> opoints) {
for (OPoint(String s, String t) : opoints) {
System.out.println(s);
}
}
static void exhaustivity_error4(List<?> f) {
for (Rec(var x): f){
}
}
static void applicability_error(List<Object> points) {
for (Interface p: points) {
System.out.println(p);
}
}
record Rec(String x) { }
interface Interface {}
sealed interface IPoint permits Point {}
record Point(Integer x, Integer y) implements IPoint { }
record OPoint(Object x, Object y) { }
}

View File

@ -1,8 +0,0 @@
ForEachPatternsErrors.java:36:27: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Object, ForEachPatternsErrors.Interface)
ForEachPatternsErrors.java:13:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.Point, java.lang.Object
ForEachPatternsErrors.java:19:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.Point, java.lang.Object
ForEachPatternsErrors.java:25:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.OPoint, ForEachPatternsErrors.OPoint
ForEachPatternsErrors.java:31:9: compiler.err.foreach.not.exhaustive.on.type: ForEachPatternsErrors.Rec, compiler.misc.type.captureof: 1, ?
- compiler.note.preview.filename: ForEachPatternsErrors.java, DEFAULT
- compiler.note.preview.recompile
5 errors

View File

@ -1,12 +0,0 @@
/*
* @test /nodynamiccopyright/
* @summary
* @enablePreview
* @compile -XDfind=all ForEachTestAllAnalyzers.java
*/
public class ForEachTestAllAnalyzers {
private void test(Iterable<? extends R> l) {
for (R(Object a) : l) { }
}
record R(Object a) {}
}

View File

@ -24,7 +24,6 @@
/**
* @test
* @bug 8298184
* @enablePreview
* @compile GenericRecordDeconstructionPattern.java
* @run main GenericRecordDeconstructionPattern
*/
@ -47,8 +46,6 @@ public class GenericRecordDeconstructionPattern {
runTest(this::runSwitchInference3);
runTest(this::runSwitchInference4);
testInference3();
assertEquals(0, forEachInference(List.of(new Box(""))));
assertEquals(1, forEachInference(List.of(new Box(null))));
assertEquals(1, runIfSuperBound(new Box<>(new StringBuilder())));
assertEquals(1, runIfSuperBound(new Box<>(0)));
}
@ -106,13 +103,6 @@ public class GenericRecordDeconstructionPattern {
: -1;
}
int forEachInference(Iterable<I<String>> b) {
for (Box(var s) : b) {
return s == null ? 1 : s.length();
}
return -1;
}
void testInference3() {
I<I<String>> b = new Box<>(new Box<>(null));
assertEquals(1, runSwitchInferenceNested(b));

View File

@ -25,7 +25,6 @@
* @test
* @bug 8262891 8268663 8289894
* @summary Check guards implementation.
* @enablePreview
*/
import java.util.Objects;
@ -134,24 +133,24 @@ public class Guards {
String typeGuardAfterParenthesizedTrueSwitchStatement(Object o) {
switch (o) {
case (Integer i) when i == 0: o = String.valueOf(i); return "true";
case (Integer i) when i == 2: o = String.valueOf(i); return "second";
case Integer i when i == 0: o = String.valueOf(i); return "true";
case Integer i when i == 2: o = String.valueOf(i); return "second";
case Object x: return "any";
}
}
String typeGuardAfterParenthesizedTrueSwitchExpression(Object o) {
return switch (o) {
case (Integer i) when i == 0: o = String.valueOf(i); yield "true";
case (Integer i) when i == 2: o = String.valueOf(i); yield "second";
case Integer i when i == 0: o = String.valueOf(i); yield "true";
case Integer i when i == 2: o = String.valueOf(i); yield "second";
case Object x: yield "any";
};
}
String typeGuardAfterParenthesizedTrueIfStatement(Object o) {
if (o != null && o instanceof (Integer i) && i == 0) {
if (o != null && o instanceof Integer i && i == 0) {
return "true";
} else if (o != null && o instanceof (Integer i) && i == 2 && (o = i) != null) {
} else if (o != null && o instanceof Integer i && i == 2 && (o = i) != null) {
return "second";
} else {
return "any";

View File

@ -25,7 +25,6 @@
* @test
* @bug 8262891
* @summary Check errors reported for guarded patterns.
* @enablePreview
* @compile/fail/ref=GuardsErrors.out -XDrawDiagnostics GuardsErrors.java
*/
@ -41,4 +40,35 @@ public class GuardsErrors {
}
void variablesInGuards(Object o) {
final int i1;
int i2 = 0;
switch (o) {
case Integer v when (i1 = 0) == 0 -> {}
case Integer v when i2++ == 0 -> {}
case Integer v when ++i2 == 0 -> {}
case Integer v when new Predicate() {
public boolean test() {
final int i;
i = 2;
return i == 2;
}
}.test() -> {}
case Number v1 when v1 instanceof Integer v2 && (v2 = 0) == 0 -> {}
default -> {}
}
}
GuardsErrors(Object o) {
switch (o) {
case Integer v when (f = 0) == 0 -> {}
default -> throw new RuntimeException();
}
}
final int f;
interface Predicate {
public boolean test();
}
}

Some files were not shown because too many files have changed in this diff Show More