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;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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::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() {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user