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;
}
}