8268766: Desugaring of pattern matching enum switch should be improved
Reviewed-by: mcimadamore, psandoz
This commit is contained in:
parent
4f70759175
commit
fa08cc62df
@ -26,12 +26,11 @@
|
||||
package java.lang.runtime;
|
||||
|
||||
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.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
@ -53,12 +52,15 @@ public class SwitchBootstraps {
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
|
||||
private static final MethodHandle DO_SWITCH;
|
||||
private static final MethodHandle DO_TYPE_SWITCH;
|
||||
private static final MethodHandle DO_ENUM_SWITCH;
|
||||
|
||||
static {
|
||||
try {
|
||||
DO_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doSwitch",
|
||||
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));
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
@ -108,14 +110,13 @@ public class SwitchBootstraps {
|
||||
* 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}.
|
||||
* @throws Throwable if there is any error linking the call site
|
||||
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
|
||||
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
|
||||
*/
|
||||
public static CallSite typeSwitch(MethodHandles.Lookup lookup,
|
||||
String invocationName,
|
||||
MethodType invocationType,
|
||||
Object... labels) throws Throwable {
|
||||
Object... labels) {
|
||||
if (invocationType.parameterCount() != 2
|
||||
|| (!invocationType.returnType().equals(int.class))
|
||||
|| invocationType.parameterType(0).isPrimitive()
|
||||
@ -126,7 +127,7 @@ public class SwitchBootstraps {
|
||||
labels = labels.clone();
|
||||
Stream.of(labels).forEach(SwitchBootstraps::verifyLabel);
|
||||
|
||||
MethodHandle target = MethodHandles.insertArguments(DO_SWITCH, 2, (Object) labels);
|
||||
MethodHandle target = MethodHandles.insertArguments(DO_TYPE_SWITCH, 2, (Object) labels);
|
||||
return new ConstantCallSite(target);
|
||||
}
|
||||
|
||||
@ -142,7 +143,7 @@ public class SwitchBootstraps {
|
||||
}
|
||||
}
|
||||
|
||||
private static int doSwitch(Object target, int startIndex, Object[] labels) {
|
||||
private static int doTypeSwitch(Object target, int startIndex, Object[] labels) {
|
||||
if (target == null)
|
||||
return -1;
|
||||
|
||||
@ -167,4 +168,124 @@ public class SwitchBootstraps {
|
||||
return labels.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap method for linking an {@code invokedynamic} call site that
|
||||
* implements a {@code switch} on a target of an enum type. The static
|
||||
* arguments are used to encode the case labels associated to the switch
|
||||
* construct, where each label can be encoded in two ways:
|
||||
* <ul>
|
||||
* <li>as a {@code String} value, which represents the name of
|
||||
* the enum constant associated with the label</li>
|
||||
* <li>as a {@code Class} value, which represents the enum type
|
||||
* associated with a type test pattern</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The returned {@code CallSite}'s method handle will have
|
||||
* a return type of {@code int} and accepts two parameters: the first argument
|
||||
* will be an {@code Enum} instance ({@code target}) and the second
|
||||
* will be {@code int} ({@code restart}).
|
||||
* <p>
|
||||
* If the {@code target} is {@code null}, then the method of the call site
|
||||
* returns {@literal -1}.
|
||||
* <p>
|
||||
* If the {@code target} is not {@code null}, then the method of the call site
|
||||
* returns the index of the first element in the {@code labels} array starting from
|
||||
* the {@code restart} index matching one of the following conditions:
|
||||
* <ul>
|
||||
* <li>the element is of type {@code Class} that is assignable
|
||||
* from the target's class; or</li>
|
||||
* <li>the element is of type {@code String} and equals to the target
|
||||
* enum constant's {@link Enum#name()}.</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.
|
||||
*
|
||||
* @param lookup Represents a lookup context with the accessibility
|
||||
* privileges of the caller. When used with {@code invokedynamic},
|
||||
* this is stacked automatically by the VM.
|
||||
* @param invocationName unused
|
||||
* @param invocationType The invocation type of the {@code CallSite} with two parameters,
|
||||
* an enum type, an {@code int}, and {@code int} as a return type.
|
||||
* @param labels case labels - {@code String} constants and {@code Class} instances,
|
||||
* in any combination
|
||||
* @return a {@code CallSite} returning the first matching element as described above
|
||||
*
|
||||
* @throws NullPointerException if any argument is {@code null}
|
||||
* @throws IllegalArgumentException if any element in the labels array is null, if the
|
||||
* invocation type is not a method type whose first parameter type is an enum type,
|
||||
* second parameter of type {@code int} and whose return type is {@code int},
|
||||
* or if {@code labels} contains an element that is not of type {@code String} or
|
||||
* {@code Class} of the target enum type.
|
||||
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
|
||||
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
|
||||
*/
|
||||
public static CallSite enumSwitch(MethodHandles.Lookup lookup,
|
||||
String invocationName,
|
||||
MethodType invocationType,
|
||||
Object... labels) {
|
||||
if (invocationType.parameterCount() != 2
|
||||
|| (!invocationType.returnType().equals(int.class))
|
||||
|| invocationType.parameterType(0).isPrimitive()
|
||||
|| !invocationType.parameterType(0).isEnum()
|
||||
|| !invocationType.parameterType(1).equals(int.class))
|
||||
throw new IllegalArgumentException("Illegal invocation type " + invocationType);
|
||||
requireNonNull(labels);
|
||||
|
||||
labels = labels.clone();
|
||||
|
||||
Class<?> enumClass = invocationType.parameterType(0);
|
||||
labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray();
|
||||
|
||||
MethodHandle target =
|
||||
MethodHandles.insertArguments(DO_ENUM_SWITCH, 2, (Object) labels);
|
||||
target = target.asType(invocationType);
|
||||
|
||||
return new ConstantCallSite(target);
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Lookup lookup, 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);
|
||||
}
|
||||
return label;
|
||||
} else if (labelClass == String.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> enumClass = (Class<E>) enumClassTemplate;
|
||||
try {
|
||||
return ConstantBootstraps.enumConstant(lookup, (String) label, enumClass);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("label with illegal type found: " + labelClass +
|
||||
", expected label of type either String or Class");
|
||||
}
|
||||
}
|
||||
|
||||
private static int doEnumSwitch(Enum<?> target, int startIndex, Object[] labels) {
|
||||
if (target == null)
|
||||
return -1;
|
||||
|
||||
// Dumbest possible strategy
|
||||
Class<?> targetClass = target.getClass();
|
||||
for (int i = startIndex; i < labels.length; i++) {
|
||||
Object label = labels[i];
|
||||
if (label instanceof Class<?> c) {
|
||||
if (c.isAssignableFrom(targetClass))
|
||||
return i;
|
||||
} else if (label == target) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return labels.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Symbol.BindingSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.DynamicVarSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
import com.sun.tools.javac.code.Symtab;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
@ -58,6 +59,7 @@ import com.sun.tools.javac.tree.TreeMaker;
|
||||
import com.sun.tools.javac.tree.TreeTranslator;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.ListBuffer;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
import com.sun.tools.javac.util.Names;
|
||||
import com.sun.tools.javac.util.Options;
|
||||
|
||||
@ -93,6 +95,7 @@ import com.sun.tools.javac.tree.JCTree.LetExpr;
|
||||
import com.sun.tools.javac.tree.TreeInfo;
|
||||
import com.sun.tools.javac.util.Assert;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* This pass translates pattern-matching constructs, such as instanceof <pattern>.
|
||||
@ -275,7 +278,6 @@ public class TransPatterns extends TreeTranslator {
|
||||
boolean hasTotalPattern,
|
||||
boolean patternSwitch) {
|
||||
Type seltype = selector.type;
|
||||
boolean enumSwitch = (seltype.tsym.flags() & Flags.ENUM) != 0;
|
||||
|
||||
if (patternSwitch) {
|
||||
Assert.check(preview.isEnabled());
|
||||
@ -315,27 +317,6 @@ public class TransPatterns extends TreeTranslator {
|
||||
//-case null is always desugared to case -1, as the typeSwitch bootstrap method will
|
||||
// return -1 when the input is null
|
||||
//
|
||||
//a special case for switches over enums with pattern case
|
||||
//with only a single unguarded (type) pattern case, which is equivalent
|
||||
//to a default with additional binding variable assignment:
|
||||
//switch ($enum) {
|
||||
// case $constant1: $stats$
|
||||
// case $constant2: $stats$
|
||||
// case typeof($enum) e: $stats$
|
||||
//}
|
||||
//=>
|
||||
//switch ($enum) {
|
||||
// case $constant1: $stats$
|
||||
// case $constant2: $stats$
|
||||
// default: typeof($enum) e = $enum; $stats$
|
||||
//}
|
||||
//constant labels in switches over enums with one or more pattern cases
|
||||
//with guards are desugared into guards:
|
||||
//case $constant1: $stats$
|
||||
//=>
|
||||
//case typeof($enum) e && e == $constant1: $stats$
|
||||
//and handled as a normal pattern matching switch
|
||||
//
|
||||
//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) {
|
||||
@ -345,27 +326,6 @@ public class TransPatterns extends TreeTranslator {
|
||||
newCases.add(c.head);
|
||||
}
|
||||
}
|
||||
if (enumSwitch && hasGuards(newCases)) {
|
||||
for (JCCase c : newCases) {
|
||||
for (List<JCCaseLabel> l = c.labels; l.nonEmpty(); l = l.tail) {
|
||||
if (l.head.isExpression() && !TreeInfo.isNull(l.head)) {
|
||||
BindingSymbol temp = new BindingSymbol(Flags.SYNTHETIC,
|
||||
names.fromString("enumGuard" + c.pos +
|
||||
target.syntheticNameChar() + "temp"),
|
||||
seltype,
|
||||
currentMethodSym);
|
||||
JCBindingPattern binding =
|
||||
make.at(l.head.pos()).BindingPattern(make.VarDef(temp, null));
|
||||
binding.setType(seltype);
|
||||
l.head = make.GuardPattern(binding,
|
||||
makeBinary(Tag.EQ,
|
||||
make.Ident(temp),
|
||||
(JCExpression) l.head));
|
||||
}
|
||||
}
|
||||
}
|
||||
enumSwitch = false;
|
||||
}
|
||||
cases = newCases.toList();
|
||||
ListBuffer<JCStatement> statements = new ListBuffer<>();
|
||||
VarSymbol temp = new VarSymbol(Flags.SYNTHETIC,
|
||||
@ -395,46 +355,44 @@ public class TransPatterns extends TreeTranslator {
|
||||
currentMethodSym);
|
||||
statements.append(make.at(tree.pos).VarDef(index, makeLit(syms.intType, 0)));
|
||||
|
||||
if (enumSwitch) {
|
||||
selector = make.Ident(temp);
|
||||
} else {
|
||||
List<Type> staticArgTypes = List.of(syms.methodHandleLookupType,
|
||||
syms.stringType,
|
||||
syms.methodTypeType,
|
||||
types.makeArrayType(new ClassType(syms.classType.getEnclosingType(),
|
||||
List.of(new WildcardType(syms.objectType, BoundKind.UNBOUND,
|
||||
syms.boundClass)),
|
||||
syms.classType.tsym)));
|
||||
LoadableConstant[] staticArgValues =
|
||||
cases.stream()
|
||||
.flatMap(c -> c.labels.stream())
|
||||
.map(l -> toLoadableConstant(l))
|
||||
.filter(c -> c != null)
|
||||
.toArray(s -> new LoadableConstant[s]);
|
||||
List<Type> staticArgTypes = List.of(syms.methodHandleLookupType,
|
||||
syms.stringType,
|
||||
syms.methodTypeType,
|
||||
types.makeArrayType(new ClassType(syms.classType.getEnclosingType(),
|
||||
List.of(new WildcardType(syms.objectType, BoundKind.UNBOUND,
|
||||
syms.boundClass)),
|
||||
syms.classType.tsym)));
|
||||
LoadableConstant[] staticArgValues =
|
||||
cases.stream()
|
||||
.flatMap(c -> c.labels.stream())
|
||||
.map(l -> toLoadableConstant(l, seltype))
|
||||
.filter(c -> c != null)
|
||||
.toArray(s -> new LoadableConstant[s]);
|
||||
|
||||
Symbol bsm = rs.resolveInternalMethod(tree.pos(), env, syms.switchBootstrapsType,
|
||||
names.fromString("typeSwitch"), staticArgTypes, List.nil());
|
||||
boolean enumSelector = seltype.tsym.isEnum();
|
||||
Name bootstrapName = enumSelector ? names.enumSwitch : names.typeSwitch;
|
||||
Symbol bsm = rs.resolveInternalMethod(tree.pos(), env, syms.switchBootstrapsType,
|
||||
bootstrapName, staticArgTypes, List.nil());
|
||||
|
||||
MethodType indyType = new MethodType(
|
||||
List.of(syms.objectType, syms.intType),
|
||||
syms.intType,
|
||||
List.nil(),
|
||||
syms.methodClass
|
||||
);
|
||||
DynamicMethodSymbol dynSym = new DynamicMethodSymbol(names.fromString("typeSwitch"),
|
||||
syms.noSymbol,
|
||||
((MethodSymbol)bsm).asHandle(),
|
||||
indyType,
|
||||
staticArgValues);
|
||||
MethodType indyType = new MethodType(
|
||||
List.of(enumSelector ? seltype : syms.objectType, syms.intType),
|
||||
syms.intType,
|
||||
List.nil(),
|
||||
syms.methodClass
|
||||
);
|
||||
DynamicMethodSymbol dynSym = new DynamicMethodSymbol(bootstrapName,
|
||||
syms.noSymbol,
|
||||
((MethodSymbol)bsm).asHandle(),
|
||||
indyType,
|
||||
staticArgValues);
|
||||
|
||||
JCFieldAccess qualifier = make.Select(make.QualIdent(bsm.owner), dynSym.name);
|
||||
qualifier.sym = dynSym;
|
||||
qualifier.type = syms.intType;
|
||||
selector = make.Apply(List.nil(),
|
||||
qualifier,
|
||||
List.of(make.Ident(temp), make.Ident(index)))
|
||||
.setType(syms.intType);
|
||||
}
|
||||
JCFieldAccess qualifier = make.Select(make.QualIdent(bsm.owner), dynSym.name);
|
||||
qualifier.sym = dynSym;
|
||||
qualifier.type = syms.intType;
|
||||
selector = make.Apply(List.nil(),
|
||||
qualifier,
|
||||
List.of(make.Ident(temp), make.Ident(index)))
|
||||
.setType(syms.intType);
|
||||
|
||||
int i = 0;
|
||||
boolean previousCompletesNormally = false;
|
||||
@ -473,38 +431,27 @@ public class TransPatterns extends TreeTranslator {
|
||||
} else {
|
||||
c.stats = translate(c.stats);
|
||||
}
|
||||
if (enumSwitch) {
|
||||
var labels = c.labels;
|
||||
|
||||
while (labels.nonEmpty()) {
|
||||
if (labels.head.isPattern()) {
|
||||
labels.head = make.DefaultCaseLabel();
|
||||
}
|
||||
labels = labels.tail;
|
||||
}
|
||||
} else {
|
||||
ListBuffer<JCCaseLabel> translatedLabels = new ListBuffer<>();
|
||||
for (var p : c.labels) {
|
||||
if (p.hasTag(Tag.DEFAULTCASELABEL)) {
|
||||
translatedLabels.add(p);
|
||||
hasDefault = true;
|
||||
} else if (hasTotalPattern && !hasDefault &&
|
||||
c == lastCase && p.isPattern()) {
|
||||
//If the switch has total pattern, the last case will contain it.
|
||||
//Convert the total pattern to default:
|
||||
translatedLabels.add(make.DefaultCaseLabel());
|
||||
ListBuffer<JCCaseLabel> translatedLabels = new ListBuffer<>();
|
||||
for (var p : c.labels) {
|
||||
if (p.hasTag(Tag.DEFAULTCASELABEL)) {
|
||||
translatedLabels.add(p);
|
||||
hasDefault = true;
|
||||
} else if (hasTotalPattern && !hasDefault &&
|
||||
c == lastCase && p.isPattern()) {
|
||||
//If the switch has total pattern, the last case will contain it.
|
||||
//Convert the total pattern to default:
|
||||
translatedLabels.add(make.DefaultCaseLabel());
|
||||
} else {
|
||||
int value;
|
||||
if (p.isNullPattern()) {
|
||||
value = -1;
|
||||
} else {
|
||||
int value;
|
||||
if (p.isNullPattern()) {
|
||||
value = -1;
|
||||
} else {
|
||||
value = i++;
|
||||
}
|
||||
translatedLabels.add(make.Literal(value));
|
||||
value = i++;
|
||||
}
|
||||
translatedLabels.add(make.Literal(value));
|
||||
}
|
||||
c.labels = translatedLabels.toList();
|
||||
}
|
||||
c.labels = translatedLabels.toList();
|
||||
if (c.caseKind == CaseTree.CaseKind.STATEMENT) {
|
||||
previousCompletesNormally = c.completesNormally;
|
||||
} else {
|
||||
@ -538,29 +485,31 @@ public class TransPatterns extends TreeTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasGuards(Collection<JCCase> cases) {
|
||||
return cases.stream()
|
||||
.flatMap(c -> c.labels.stream())
|
||||
.filter(JCCaseLabel::isPattern)
|
||||
.anyMatch(l -> !TreeInfo.primaryPatternType((JCPattern) l).unconditional());
|
||||
}
|
||||
|
||||
private Type principalType(JCPattern p) {
|
||||
return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p).type()));
|
||||
}
|
||||
|
||||
private LoadableConstant toLoadableConstant(JCCaseLabel l) {
|
||||
private LoadableConstant toLoadableConstant(JCCaseLabel l, Type selector) {
|
||||
if (l.isPattern()) {
|
||||
return (LoadableConstant) principalType((JCPattern) l);
|
||||
Type principalType = principalType((JCPattern) l);
|
||||
if (types.isSubtype(selector, principalType)) {
|
||||
return (LoadableConstant) selector;
|
||||
} else {
|
||||
return (LoadableConstant) principalType;
|
||||
}
|
||||
} else if (l.isExpression() && !TreeInfo.isNull((JCExpression) l)) {
|
||||
Assert.checkNonNull(l.type.constValue());
|
||||
if ((l.type.tsym.flags_field & Flags.ENUM) != 0) {
|
||||
return LoadableConstant.String(((JCIdent) l).name.toString());
|
||||
} else {
|
||||
Assert.checkNonNull(l.type.constValue());
|
||||
|
||||
return switch (l.type.getTag()) {
|
||||
case BYTE, CHAR,
|
||||
SHORT, INT -> LoadableConstant.Int((Integer) l.type.constValue());
|
||||
case CLASS -> LoadableConstant.String((String) l.type.constValue());
|
||||
default -> throw new AssertionError();
|
||||
};
|
||||
return switch (l.type.getTag()) {
|
||||
case BYTE, CHAR,
|
||||
SHORT, INT -> LoadableConstant.Int((Integer) l.type.constValue());
|
||||
case CLASS -> LoadableConstant.String((String) l.type.constValue());
|
||||
default -> throw new AssertionError();
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -215,6 +215,10 @@ public class Names {
|
||||
public final Name permits;
|
||||
public final Name sealed;
|
||||
|
||||
// pattern switches
|
||||
public final Name typeSwitch;
|
||||
public final Name enumSwitch;
|
||||
|
||||
public final Name.Table table;
|
||||
|
||||
public Names(Context context) {
|
||||
@ -384,6 +388,10 @@ public class Names {
|
||||
// sealed types
|
||||
permits = fromString("permits");
|
||||
sealed = fromString("sealed");
|
||||
|
||||
// pattern switches
|
||||
typeSwitch = fromString("typeSwitch");
|
||||
enumSwitch = fromString("enumSwitch");
|
||||
}
|
||||
|
||||
protected Name.Table createTable(Options options) {
|
||||
|
@ -44,11 +44,14 @@ import static org.testng.Assert.fail;
|
||||
public class SwitchBootstrapsTest {
|
||||
|
||||
public static final MethodHandle BSM_TYPE_SWITCH;
|
||||
public static final MethodHandle BSM_ENUM_SWITCH;
|
||||
|
||||
static {
|
||||
try {
|
||||
BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch",
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class));
|
||||
BSM_ENUM_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "enumSwitch",
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class));
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError("Should not happen", e);
|
||||
@ -62,8 +65,16 @@ public class SwitchBootstrapsTest {
|
||||
assertEquals(-1, (int) indy.invoke(null, start));
|
||||
}
|
||||
|
||||
private void testEnum(Enum<?> target, int start, int result, Object... labels) throws Throwable {
|
||||
MethodType switchType = MethodType.methodType(int.class, target.getClass(), int.class);
|
||||
MethodHandle indy = ((CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker();
|
||||
assertEquals((int) indy.invoke(target, start), result);
|
||||
assertEquals(-1, (int) indy.invoke(null, start));
|
||||
}
|
||||
|
||||
public enum E1 {
|
||||
A;
|
||||
A,
|
||||
B;
|
||||
}
|
||||
|
||||
public enum E2 {
|
||||
@ -98,6 +109,24 @@ public class SwitchBootstrapsTest {
|
||||
testType("", 2, 2, String.class, String.class, String.class);
|
||||
}
|
||||
|
||||
public void testEnums() throws Throwable {
|
||||
testEnum(E1.A, 0, 2, "B", "C", "A", E1.class);
|
||||
testEnum(E1.B, 0, 0, "B", "C", "A", E1.class);
|
||||
testEnum(E1.B, 1, 3, "B", "C", "A", E1.class);
|
||||
try {
|
||||
testEnum(E1.B, 1, 3, "B", "C", "A", E2.class);
|
||||
fail("Didn't get the expected exception.");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
//OK
|
||||
}
|
||||
try {
|
||||
testEnum(E1.B, 1, 3, "B", "C", "A", String.class);
|
||||
fail("Didn't get the expected exception.");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
//OK
|
||||
}
|
||||
}
|
||||
|
||||
public void testWrongSwitchTypes() throws Throwable {
|
||||
MethodType[] switchTypes = new MethodType[] {
|
||||
MethodType.methodType(int.class, Object.class),
|
||||
@ -112,6 +141,20 @@ public class SwitchBootstrapsTest {
|
||||
//OK, expected
|
||||
}
|
||||
}
|
||||
MethodType[] enumSwitchTypes = new MethodType[] {
|
||||
MethodType.methodType(int.class, Enum.class),
|
||||
MethodType.methodType(int.class, Object.class, int.class),
|
||||
MethodType.methodType(int.class, double.class, int.class),
|
||||
MethodType.methodType(int.class, Enum.class, Integer.class)
|
||||
};
|
||||
for (MethodType enumSwitchType : enumSwitchTypes) {
|
||||
try {
|
||||
BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType);
|
||||
fail("Didn't get the expected exception.");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
//OK, expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testNullLabels() throws Throwable {
|
||||
@ -129,5 +172,19 @@ public class SwitchBootstrapsTest {
|
||||
} catch (IllegalArgumentException ex) {
|
||||
//OK
|
||||
}
|
||||
MethodType enumSwitchType = MethodType.methodType(int.class, E1.class, int.class);
|
||||
try {
|
||||
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, (Object[]) null);
|
||||
fail("Didn't get the expected exception.");
|
||||
} catch (NullPointerException ex) {
|
||||
//OK
|
||||
}
|
||||
try {
|
||||
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType,
|
||||
new Object[] {1, null, String.class});
|
||||
fail("Didn't get the expected exception.");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
//OK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
80
test/langtools/tools/javac/patterns/EnumTypeChanges.java
Normal file
80
test/langtools/tools/javac/patterns/EnumTypeChanges.java
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8262891
|
||||
* @summary Verify pattern switches work properly when the set of enum constant changes.
|
||||
* @compile --enable-preview -source ${jdk.version} EnumTypeChanges.java
|
||||
* @compile --enable-preview -source ${jdk.version} EnumTypeChanges2.java
|
||||
* @run main/othervm --enable-preview EnumTypeChanges
|
||||
*/
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.Objects;
|
||||
|
||||
public class EnumTypeChanges {
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
new EnumTypeChanges().run();
|
||||
}
|
||||
|
||||
void run() throws Exception {
|
||||
doRun(this::statementEnum);
|
||||
doRun(this::expressionEnum);
|
||||
}
|
||||
|
||||
void doRun(Function<EnumTypeChangesEnum, String> c) throws Exception {
|
||||
assertEquals("A", c.apply(EnumTypeChangesEnum.A));
|
||||
assertEquals("D", c.apply(EnumTypeChangesEnum.valueOf("C")));
|
||||
}
|
||||
|
||||
String statementEnum(EnumTypeChangesEnum e) {
|
||||
switch (e) {
|
||||
case A -> { return "A"; }
|
||||
case EnumTypeChangesEnum e1 && false -> throw new AssertionError();
|
||||
case B -> { return "B"; }
|
||||
default -> { return "D"; }
|
||||
}
|
||||
}
|
||||
|
||||
String expressionEnum(EnumTypeChangesEnum e) {
|
||||
return switch (e) {
|
||||
case A -> "A";
|
||||
case EnumTypeChangesEnum e1 && false -> throw new AssertionError();
|
||||
case B -> "B";
|
||||
default -> "D";
|
||||
};
|
||||
}
|
||||
|
||||
private static void assertEquals(Object o1, Object o2) {
|
||||
if (!Objects.equals(o1, o2)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum EnumTypeChangesEnum {
|
||||
A,
|
||||
B;
|
||||
}
|
27
test/langtools/tools/javac/patterns/EnumTypeChanges2.java
Normal file
27
test/langtools/tools/javac/patterns/EnumTypeChanges2.java
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.
|
||||
*/
|
||||
|
||||
enum EnumTypeChangesEnum {
|
||||
A,
|
||||
C;
|
||||
}
|
@ -53,8 +53,12 @@ public class Switches {
|
||||
runEnumTest(this::testEnumExpression2);
|
||||
runEnumTest(this::testEnumWithGuards1);
|
||||
runEnumTest(this::testEnumWithGuards2);
|
||||
runEnumTest(this::testEnumWithGuards3);
|
||||
runEnumTest(this::testEnumWithGuards4);
|
||||
runEnumTest(this::testEnumWithGuardsExpression1);
|
||||
runEnumTest(this::testEnumWithGuardsExpression2);
|
||||
runEnumTest(this::testEnumWithGuardsExpression3);
|
||||
runEnumTest(this::testEnumWithGuardsExpression4);
|
||||
runEnumTest(this::testStringWithGuards1);
|
||||
runEnumTest(this::testStringWithGuardsExpression1);
|
||||
runEnumTest(this::testIntegerWithGuards1);
|
||||
@ -288,6 +292,46 @@ public class Switches {
|
||||
};
|
||||
}
|
||||
|
||||
String testEnumWithGuards3(E e) {
|
||||
switch (e) {
|
||||
case A: return "a";
|
||||
case B: return "b";
|
||||
case Object x && "C".equals(x.toString()): return "C";
|
||||
case C: return "broken";
|
||||
case null, E x: return String.valueOf(x);
|
||||
}
|
||||
}
|
||||
|
||||
String testEnumWithGuardsExpression3(E e) {
|
||||
return switch (e) {
|
||||
case A -> "a";
|
||||
case B -> "b";
|
||||
case Object x && "C".equals(x.toString()) -> "C";
|
||||
case C -> "broken";
|
||||
case null, E x -> String.valueOf(x);
|
||||
};
|
||||
}
|
||||
|
||||
String testEnumWithGuards4(E e) {
|
||||
switch (e) {
|
||||
case A: return "a";
|
||||
case B: return "b";
|
||||
case Runnable x && "C".equals(x.toString()): return "C";
|
||||
case C: return "broken";
|
||||
case null, E x: return String.valueOf(x);
|
||||
}
|
||||
}
|
||||
|
||||
String testEnumWithGuardsExpression4(E e) {
|
||||
return switch (e) {
|
||||
case A -> "a";
|
||||
case B -> "b";
|
||||
case Runnable x && "C".equals(x.toString()) -> "C";
|
||||
case C -> "broken";
|
||||
case null, E x -> String.valueOf(x);
|
||||
};
|
||||
}
|
||||
|
||||
String testStringWithGuards1(E e) {
|
||||
switch (e != null ? e.name() : null) {
|
||||
case "A": return "a";
|
||||
@ -520,7 +564,9 @@ public class Switches {
|
||||
}
|
||||
}
|
||||
|
||||
public enum E {
|
||||
public enum E implements Runnable {
|
||||
A, B, C;
|
||||
|
||||
@Override public void run() {}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user