8268766: Desugaring of pattern matching enum switch should be improved

Reviewed-by: mcimadamore, psandoz
This commit is contained in:
Jan Lahoda 2021-07-08 11:56:53 +00:00
parent 4f70759175
commit fa08cc62df
7 changed files with 422 additions and 134 deletions

View File

@ -26,12 +26,11 @@
package java.lang.runtime; package java.lang.runtime;
import java.lang.invoke.CallSite; import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.ConstantCallSite; import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.internal.javac.PreviewFeature; import jdk.internal.javac.PreviewFeature;
@ -53,12 +52,15 @@ public class SwitchBootstraps {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 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 { static {
try { 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)); 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) { catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(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, * 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}, * or if {@code labels} contains an element that is not of type {@code String},
* {@code Integer} or {@code Class}. * {@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.6 The CONSTANT_NameAndType_info Structure
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
*/ */
public static CallSite typeSwitch(MethodHandles.Lookup lookup, public static CallSite typeSwitch(MethodHandles.Lookup lookup,
String invocationName, String invocationName,
MethodType invocationType, MethodType invocationType,
Object... labels) throws Throwable { Object... labels) {
if (invocationType.parameterCount() != 2 if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class)) || (!invocationType.returnType().equals(int.class))
|| invocationType.parameterType(0).isPrimitive() || invocationType.parameterType(0).isPrimitive()
@ -126,7 +127,7 @@ public class SwitchBootstraps {
labels = labels.clone(); labels = labels.clone();
Stream.of(labels).forEach(SwitchBootstraps::verifyLabel); 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); 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) if (target == null)
return -1; return -1;
@ -167,4 +168,124 @@ public class SwitchBootstraps {
return labels.length; 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;
}
} }

View File

@ -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.BindingSymbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol; 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.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type; 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.tree.TreeTranslator;
import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer; 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.Names;
import com.sun.tools.javac.util.Options; 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.tree.TreeInfo;
import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.List;
import java.util.Iterator;
/** /**
* This pass translates pattern-matching constructs, such as instanceof <pattern>. * This pass translates pattern-matching constructs, such as instanceof <pattern>.
@ -275,7 +278,6 @@ public class TransPatterns extends TreeTranslator {
boolean hasTotalPattern, boolean hasTotalPattern,
boolean patternSwitch) { boolean patternSwitch) {
Type seltype = selector.type; Type seltype = selector.type;
boolean enumSwitch = (seltype.tsym.flags() & Flags.ENUM) != 0;
if (patternSwitch) { if (patternSwitch) {
Assert.check(preview.isEnabled()); 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 //-case null is always desugared to case -1, as the typeSwitch bootstrap method will
// return -1 when the input is null // 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 //note the selector is evaluated only once and stored in a temporary variable
ListBuffer<JCCase> newCases = new ListBuffer<>(); ListBuffer<JCCase> newCases = new ListBuffer<>();
for (List<JCCase> c = cases; c.nonEmpty(); c = c.tail) { for (List<JCCase> c = cases; c.nonEmpty(); c = c.tail) {
@ -345,27 +326,6 @@ public class TransPatterns extends TreeTranslator {
newCases.add(c.head); 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(); cases = newCases.toList();
ListBuffer<JCStatement> statements = new ListBuffer<>(); ListBuffer<JCStatement> statements = new ListBuffer<>();
VarSymbol temp = new VarSymbol(Flags.SYNTHETIC, VarSymbol temp = new VarSymbol(Flags.SYNTHETIC,
@ -395,46 +355,44 @@ public class TransPatterns extends TreeTranslator {
currentMethodSym); currentMethodSym);
statements.append(make.at(tree.pos).VarDef(index, makeLit(syms.intType, 0))); statements.append(make.at(tree.pos).VarDef(index, makeLit(syms.intType, 0)));
if (enumSwitch) { List<Type> staticArgTypes = List.of(syms.methodHandleLookupType,
selector = make.Ident(temp); syms.stringType,
} else { syms.methodTypeType,
List<Type> staticArgTypes = List.of(syms.methodHandleLookupType, types.makeArrayType(new ClassType(syms.classType.getEnclosingType(),
syms.stringType, List.of(new WildcardType(syms.objectType, BoundKind.UNBOUND,
syms.methodTypeType, syms.boundClass)),
types.makeArrayType(new ClassType(syms.classType.getEnclosingType(), syms.classType.tsym)));
List.of(new WildcardType(syms.objectType, BoundKind.UNBOUND, LoadableConstant[] staticArgValues =
syms.boundClass)), cases.stream()
syms.classType.tsym))); .flatMap(c -> c.labels.stream())
LoadableConstant[] staticArgValues = .map(l -> toLoadableConstant(l, seltype))
cases.stream() .filter(c -> c != null)
.flatMap(c -> c.labels.stream()) .toArray(s -> new LoadableConstant[s]);
.map(l -> toLoadableConstant(l))
.filter(c -> c != null)
.toArray(s -> new LoadableConstant[s]);
Symbol bsm = rs.resolveInternalMethod(tree.pos(), env, syms.switchBootstrapsType, boolean enumSelector = seltype.tsym.isEnum();
names.fromString("typeSwitch"), staticArgTypes, List.nil()); Name bootstrapName = enumSelector ? names.enumSwitch : names.typeSwitch;
Symbol bsm = rs.resolveInternalMethod(tree.pos(), env, syms.switchBootstrapsType,
bootstrapName, staticArgTypes, List.nil());
MethodType indyType = new MethodType( MethodType indyType = new MethodType(
List.of(syms.objectType, syms.intType), List.of(enumSelector ? seltype : syms.objectType, syms.intType),
syms.intType, syms.intType,
List.nil(), List.nil(),
syms.methodClass syms.methodClass
); );
DynamicMethodSymbol dynSym = new DynamicMethodSymbol(names.fromString("typeSwitch"), DynamicMethodSymbol dynSym = new DynamicMethodSymbol(bootstrapName,
syms.noSymbol, syms.noSymbol,
((MethodSymbol)bsm).asHandle(), ((MethodSymbol)bsm).asHandle(),
indyType, indyType,
staticArgValues); staticArgValues);
JCFieldAccess qualifier = make.Select(make.QualIdent(bsm.owner), dynSym.name); JCFieldAccess qualifier = make.Select(make.QualIdent(bsm.owner), dynSym.name);
qualifier.sym = dynSym; qualifier.sym = dynSym;
qualifier.type = syms.intType; qualifier.type = syms.intType;
selector = make.Apply(List.nil(), selector = make.Apply(List.nil(),
qualifier, qualifier,
List.of(make.Ident(temp), make.Ident(index))) List.of(make.Ident(temp), make.Ident(index)))
.setType(syms.intType); .setType(syms.intType);
}
int i = 0; int i = 0;
boolean previousCompletesNormally = false; boolean previousCompletesNormally = false;
@ -473,38 +431,27 @@ public class TransPatterns extends TreeTranslator {
} else { } else {
c.stats = translate(c.stats); c.stats = translate(c.stats);
} }
if (enumSwitch) { ListBuffer<JCCaseLabel> translatedLabels = new ListBuffer<>();
var labels = c.labels; for (var p : c.labels) {
if (p.hasTag(Tag.DEFAULTCASELABEL)) {
while (labels.nonEmpty()) { translatedLabels.add(p);
if (labels.head.isPattern()) { hasDefault = true;
labels.head = make.DefaultCaseLabel(); } else if (hasTotalPattern && !hasDefault &&
} c == lastCase && p.isPattern()) {
labels = labels.tail; //If the switch has total pattern, the last case will contain it.
} //Convert the total pattern to default:
} else { translatedLabels.add(make.DefaultCaseLabel());
ListBuffer<JCCaseLabel> translatedLabels = new ListBuffer<>(); } else {
for (var p : c.labels) { int value;
if (p.hasTag(Tag.DEFAULTCASELABEL)) { if (p.isNullPattern()) {
translatedLabels.add(p); value = -1;
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 { } else {
int value; value = i++;
if (p.isNullPattern()) {
value = -1;
} else {
value = i++;
}
translatedLabels.add(make.Literal(value));
} }
translatedLabels.add(make.Literal(value));
} }
c.labels = translatedLabels.toList();
} }
c.labels = translatedLabels.toList();
if (c.caseKind == CaseTree.CaseKind.STATEMENT) { if (c.caseKind == CaseTree.CaseKind.STATEMENT) {
previousCompletesNormally = c.completesNormally; previousCompletesNormally = c.completesNormally;
} else { } 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) { private Type principalType(JCPattern p) {
return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p).type())); return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p).type()));
} }
private LoadableConstant toLoadableConstant(JCCaseLabel l) { private LoadableConstant toLoadableConstant(JCCaseLabel l, Type selector) {
if (l.isPattern()) { 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)) { } 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()) { return switch (l.type.getTag()) {
case BYTE, CHAR, case BYTE, CHAR,
SHORT, INT -> LoadableConstant.Int((Integer) l.type.constValue()); SHORT, INT -> LoadableConstant.Int((Integer) l.type.constValue());
case CLASS -> LoadableConstant.String((String) l.type.constValue()); case CLASS -> LoadableConstant.String((String) l.type.constValue());
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
}
} else { } else {
return null; return null;
} }

View File

@ -215,6 +215,10 @@ public class Names {
public final Name permits; public final Name permits;
public final Name sealed; public final Name sealed;
// pattern switches
public final Name typeSwitch;
public final Name enumSwitch;
public final Name.Table table; public final Name.Table table;
public Names(Context context) { public Names(Context context) {
@ -384,6 +388,10 @@ public class Names {
// sealed types // sealed types
permits = fromString("permits"); permits = fromString("permits");
sealed = fromString("sealed"); sealed = fromString("sealed");
// pattern switches
typeSwitch = fromString("typeSwitch");
enumSwitch = fromString("enumSwitch");
} }
protected Name.Table createTable(Options options) { protected Name.Table createTable(Options options) {

View File

@ -44,11 +44,14 @@ import static org.testng.Assert.fail;
public class SwitchBootstrapsTest { public class SwitchBootstrapsTest {
public static final MethodHandle BSM_TYPE_SWITCH; public static final MethodHandle BSM_TYPE_SWITCH;
public static final MethodHandle BSM_ENUM_SWITCH;
static { static {
try { try {
BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch", BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class)); 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) { catch (ReflectiveOperationException e) {
throw new AssertionError("Should not happen", e); throw new AssertionError("Should not happen", e);
@ -62,8 +65,16 @@ public class SwitchBootstrapsTest {
assertEquals(-1, (int) indy.invoke(null, start)); 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 { public enum E1 {
A; A,
B;
} }
public enum E2 { public enum E2 {
@ -98,6 +109,24 @@ public class SwitchBootstrapsTest {
testType("", 2, 2, String.class, String.class, String.class); 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 { public void testWrongSwitchTypes() throws Throwable {
MethodType[] switchTypes = new MethodType[] { MethodType[] switchTypes = new MethodType[] {
MethodType.methodType(int.class, Object.class), MethodType.methodType(int.class, Object.class),
@ -112,6 +141,20 @@ public class SwitchBootstrapsTest {
//OK, expected //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 { public void testNullLabels() throws Throwable {
@ -129,5 +172,19 @@ public class SwitchBootstrapsTest {
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
//OK //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
}
} }
} }

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

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

View File

@ -53,8 +53,12 @@ public class Switches {
runEnumTest(this::testEnumExpression2); runEnumTest(this::testEnumExpression2);
runEnumTest(this::testEnumWithGuards1); runEnumTest(this::testEnumWithGuards1);
runEnumTest(this::testEnumWithGuards2); runEnumTest(this::testEnumWithGuards2);
runEnumTest(this::testEnumWithGuards3);
runEnumTest(this::testEnumWithGuards4);
runEnumTest(this::testEnumWithGuardsExpression1); runEnumTest(this::testEnumWithGuardsExpression1);
runEnumTest(this::testEnumWithGuardsExpression2); runEnumTest(this::testEnumWithGuardsExpression2);
runEnumTest(this::testEnumWithGuardsExpression3);
runEnumTest(this::testEnumWithGuardsExpression4);
runEnumTest(this::testStringWithGuards1); runEnumTest(this::testStringWithGuards1);
runEnumTest(this::testStringWithGuardsExpression1); runEnumTest(this::testStringWithGuardsExpression1);
runEnumTest(this::testIntegerWithGuards1); 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) { String testStringWithGuards1(E e) {
switch (e != null ? e.name() : null) { switch (e != null ? e.name() : null) {
case "A": return "a"; case "A": return "a";
@ -520,7 +564,9 @@ public class Switches {
} }
} }
public enum E { public enum E implements Runnable {
A, B, C; A, B, C;
@Override public void run() {}
} }
} }