diff --git a/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java b/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java
new file mode 100644
index 00000000000..6e17a4b85a0
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.runtime;
+
+/**
+ * A testing conversion of a value is exact if it yields a result without loss
+ * of information or throwing an exception. Otherwise, it is inexact. Some
+ * conversions are always exact regardless of the value. These conversions are
+ * said to be unconditionally exact.
+ *
+ * For example, a conversion from {@code int} to {@code byte} for the value 10
+ * is exact because the result, 10, is the same as the original value. In
+ * contrast, if the {@code int} variable {@code i} stores the value 1000 then a
+ * narrowing primitive conversion to {@code byte} will yield the result -24.
+ * Loss of information has occurred: both the magnitude and the sign of the
+ * result are different than those of the original value. As such, a conversion
+ * from {@code int} to {@code byte} for the value 1000 is inexact. Finally a
+ * widening primitive conversion from {@code byte} to {@code int} is
+ * unconditionally exact because it will always succeed with no loss of
+ * information about the magnitude of the numeric value.
+ *
+ * The methods in this class provide the run-time support for the exactness
+ * checks of testing conversions from a primitive type to primitive type. These
+ * methods may be used, for example, by Java compiler implementations to
+ * implement checks for {@code instanceof} and pattern matching runtime
+ * implementations. Unconditionally exact testing conversions do not require a
+ * corresponding action at run time and, for this reason, methods corresponding
+ * to these exactness checks are omitted here.
+ *
+ * The run time conversion checks examine whether loss of information would
+ * occur if a testing conversion would be to be applied. In those cases where a
+ * floating-point primitive type is involved, and the value of the testing
+ * conversion is either signed zero, signed infinity or {@code NaN}, these
+ * methods comply with the following:
+ *
+ *
+ *
Converting a floating-point negative zero to an integer type is considered
+ * inexact.
+ *
Converting a floating-point {@code NaN} or infinity to an integer type is
+ * considered inexact.
+ *
Converting a floating-point {@code NaN} or infinity or signed zero to another
+ * floating-point type is considered exact.
+ *
+ *
+ * @jls 5.7.1 Exact Testing Conversions
+ * @jls 5.7.2 Unconditionally Exact Testing Conversions
+ * @jls 15.20.2 The instanceof Operator
+ *
+ * @implNote Some exactness checks describe a test which can be redirected
+ * safely through one of the existing methods. Those are omitted too (i.e.,
+ * {@code byte} to {@code char} can be redirected to
+ * {@link ExactConversionsSupport#isIntToCharExact(int)}, {@code short} to
+ * {@code byte} can be redirected to
+ * {@link ExactConversionsSupport#isIntToByteExact(int)} and similarly for
+ * {@code short} to {@code char}, {@code char} to {@code byte} and {@code char}
+ * to {@code short} to the corresponding methods that take an {@code int}).
+ *
+ * @since 23
+ */
+public final class ExactConversionsSupport {
+
+ private ExactConversionsSupport() { }
+
+ /**
+ * Exactness method from int to byte
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isIntToByteExact(int n) {return n == (int)(byte)n;}
+
+ /**
+ * Exactness method from int to short
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isIntToShortExact(int n) {return n == (int)(short)n;}
+
+ /**
+ * Exactness method from int to char
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isIntToCharExact(int n) {return n == (int)(char)n;}
+
+ /**
+ * Exactness method from int to float
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ *
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isIntToFloatExact(int n) {
+ return n == (int)(float)n && n != Integer.MAX_VALUE;
+ }
+ /**
+ * Exactness method from long to byte
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isLongToByteExact(long n) {return n == (long)(byte)n;}
+
+ /**
+ * Exactness method from long to short
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isLongToShortExact(long n) {return n == (long)(short)n;}
+
+ /**
+ * Exactness method from long to char
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isLongToCharExact(long n) {return n == (long)(char)n;}
+
+ /**
+ * Exactness method from long to int
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ */
+ public static boolean isLongToIntExact(long n) {return n == (long)(int)n;}
+
+ /**
+ * Exactness method from long to float
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isLongToFloatExact(long n) {
+ return n == (long)(float)n && n != Long.MAX_VALUE;
+ }
+
+ /**
+ * Exactness method from long to double
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isLongToDoubleExact(long n) {
+ return n == (long)(double)n && n != Long.MAX_VALUE;
+ }
+
+ /**
+ * Exactness method from float to byte
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isFloatToByteExact(float n) {
+ return n == (float)(byte)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from float to short
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isFloatToShortExact(float n) {
+ return n == (float)(short)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from float to char
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isFloatToCharExact(float n) {
+ return n == (float)(char)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from float to int
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isFloatToIntExact(float n) {
+ return n == (float)(int)n && n != 0x1p31f && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from float to long
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isFloatToLongExact(float n) {
+ return n == (float)(long)n && n != 0x1p63f && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from double to byte
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isDoubleToByteExact(double n) {
+ return n == (double)(byte)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from double to short
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isDoubleToShortExact(double n){
+ return n == (double)(short)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from double to char
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isDoubleToCharExact(double n) {
+ return n == (double)(char)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from double to int
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isDoubleToIntExact(double n) {
+ return n == (double)(int)n && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from double to long
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isDoubleToLongExact(double n) {
+ return n == (double)(long)n && n != 0x1p63 && !isNegativeZero(n);
+ }
+
+ /**
+ * Exactness method from double to float
+ * @param n value
+ * @return true if and only if the passed value can be converted exactly to the target type
+ * @implSpec relies on the notion of representation equivalence defined in the
+ * specification of the {@linkplain Double} class.
+ */
+ public static boolean isDoubleToFloatExact(double n) {
+ return n == (double)(float)n || n != n;
+ }
+
+ private static boolean isNegativeZero(float n) {
+ return Float.floatToRawIntBits(n) == Integer.MIN_VALUE;
+ }
+
+ private static boolean isNegativeZero(double n) {
+ return Double.doubleToRawLongBits(n) == Long.MIN_VALUE;
+ }
+}
diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
index 6d9bd457584..8cc67807450 100644
--- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
+++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024, 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
@@ -26,7 +26,9 @@
package java.lang.runtime;
import java.lang.Enum.EnumDesc;
+import java.lang.classfile.CodeBuilder;
import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDesc;
import java.lang.constant.ConstantDescs;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.CallSite;
@@ -40,16 +42,21 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import jdk.internal.access.SharedSecrets;
import java.lang.classfile.ClassFile;
import java.lang.classfile.Label;
import java.lang.classfile.instruction.SwitchCase;
+import jdk.internal.misc.PreviewFeatures;
import jdk.internal.vm.annotation.Stable;
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
+import java.util.HashMap;
+import java.util.Map;
import static java.util.Objects.requireNonNull;
+import sun.invoke.util.Wrapper;
/**
* Bootstrap methods for linking {@code invokedynamic} call sites that implement
@@ -65,6 +72,7 @@ public class SwitchBootstraps {
private static final Object SENTINEL = new Object();
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+ private static final boolean previewEnabled = PreviewFeatures.isEnabled();
private static final MethodHandle NULL_CHECK;
private static final MethodHandle IS_ZERO;
@@ -74,6 +82,8 @@ public class SwitchBootstraps {
private static final MethodTypeDesc TYPES_SWITCH_DESCRIPTOR =
MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;ILjava/util/function/BiPredicate;Ljava/util/List;)I");
+ private static final Map typePairToName;
+
static {
try {
NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull",
@@ -89,6 +99,7 @@ public class SwitchBootstraps {
catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
+ typePairToName = TypePairs.initialize();
}
/**
@@ -134,12 +145,15 @@ public class SwitchBootstraps {
* and {@code Class} and {@code EnumDesc} instances, in any combination
* @return a {@code CallSite} returning the first matching element as described above
*
- * @throws NullPointerException if any argument is {@code null}
- * @throws IllegalArgumentException if any element in the labels array is null, if the
- * invocation type is not not a method type of first parameter of a reference type,
- * second parameter of type {@code int} and with {@code int} as its return type,
- * or if {@code labels} contains an element that is not of type {@code String},
- * {@code Integer}, {@code Class} or {@code EnumDesc}.
+ * @throws NullPointerException if any argument is {@code null}
+ * @throws IllegalArgumentException if any element in the labels array is null
+ * @throws IllegalArgumentException if the invocation type is not a method type of first parameter of a reference type,
+ * second parameter of type {@code int} and with {@code int} as its return type,
+ * @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code String},
+ * {@code Integer}, {@code Long}, {@code Float}, {@code Double}, {@code Boolean},
+ * {@code Class} or {@code EnumDesc}.
+ * @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code Boolean}
+ * when {@code target} is a {@code Boolean.class}.
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
*/
@@ -147,31 +161,39 @@ public class SwitchBootstraps {
String invocationName,
MethodType invocationType,
Object... labels) {
+ Class> selectorType = invocationType.parameterType(0);
if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class))
- || invocationType.parameterType(0).isPrimitive()
|| !invocationType.parameterType(1).equals(int.class))
throw new IllegalArgumentException("Illegal invocation type " + invocationType);
requireNonNull(labels);
- labels = labels.clone();
- Stream.of(labels).forEach(SwitchBootstraps::verifyLabel);
+ Stream.of(labels).forEach(l -> verifyLabel(l, selectorType));
- MethodHandle target = generateInnerClass(lookup, labels);
+ MethodHandle target = generateTypeSwitch(lookup, selectorType, labels);
target = withIndexCheck(target, labels.length);
return new ConstantCallSite(target);
}
- private static void verifyLabel(Object label) {
+ private static void verifyLabel(Object label, Class> selectorType) {
if (label == null) {
throw new IllegalArgumentException("null label found");
}
Class> labelClass = label.getClass();
+
if (labelClass != Class.class &&
labelClass != String.class &&
labelClass != Integer.class &&
+
+ ((labelClass != Float.class &&
+ labelClass != Long.class &&
+ labelClass != Double.class &&
+ labelClass != Boolean.class) ||
+ ((selectorType.equals(boolean.class) || selectorType.equals(Boolean.class)) && labelClass != Boolean.class && labelClass != Class.class) ||
+ !previewEnabled) &&
+
labelClass != EnumDesc.class) {
throw new IllegalArgumentException("label with illegal type found: " + label.getClass());
}
@@ -266,11 +288,11 @@ public class SwitchBootstraps {
MethodHandles.guardWithTest(MethodHandles.dropArguments(NULL_CHECK, 0, int.class),
MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class),
MethodHandles.guardWithTest(MethodHandles.dropArguments(IS_ZERO, 1, Object.class),
- generateInnerClass(lookup, labels),
+ generateTypeSwitch(lookup, invocationType.parameterType(0), labels),
MethodHandles.insertArguments(MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap())));
target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0);
} else {
- target = generateInnerClass(lookup, labels);
+ target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels);
}
target = target.asType(invocationType);
@@ -381,137 +403,233 @@ public class SwitchBootstraps {
* ...
* }
*/
- @SuppressWarnings("removal")
- private static MethodHandle generateInnerClass(MethodHandles.Lookup caller, Object[] labels) {
+ private static Consumer generateTypeSwitchSkeleton(Class> selectorType, Object[] labelConstants, List> enumDescs, List> extraClassLabels) {
+ int SELECTOR_OBJ = 0;
+ int RESTART_IDX = 1;
+ int ENUM_CACHE = 2;
+ int EXTRA_CLASS_LABELS = 3;
+
+ return cb -> {
+ cb.aload(SELECTOR_OBJ);
+ Label nonNullLabel = cb.newLabel();
+ cb.if_nonnull(nonNullLabel);
+ cb.iconst_m1();
+ cb.ireturn();
+ cb.labelBinding(nonNullLabel);
+ if (labelConstants.length == 0) {
+ cb.constantInstruction(0)
+ .ireturn();
+ return;
+ }
+ cb.iload(RESTART_IDX);
+ Label dflt = cb.newLabel();
+ record Element(Label target, Label next, Object caseLabel) { }
+ List cases = new ArrayList<>();
+ List switchCases = new ArrayList<>();
+ Object lastLabel = null;
+ for (int idx = labelConstants.length - 1; idx >= 0; idx--) {
+ Object currentLabel = labelConstants[idx];
+ Label target = cb.newLabel();
+ Label next;
+ if (lastLabel == null) {
+ next = dflt;
+ } else if (lastLabel.equals(currentLabel)) {
+ next = cases.getLast().next();
+ } else {
+ next = cases.getLast().target();
+ }
+ lastLabel = currentLabel;
+ cases.add(new Element(target, next, currentLabel));
+ switchCases.add(SwitchCase.of(idx, target));
+ }
+ cases = cases.reversed();
+ switchCases = switchCases.reversed();
+ cb.tableswitch(0, labelConstants.length - 1, dflt, switchCases);
+ for (int idx = 0; idx < cases.size(); idx++) {
+ Element element = cases.get(idx);
+ Label next = element.next();
+ cb.labelBinding(element.target());
+ if (element.caseLabel() instanceof Class> classLabel) {
+ if (unconditionalExactnessCheck(selectorType, classLabel)) {
+ //nothing - unconditionally use this case
+ } else if (classLabel.isPrimitive()) {
+ if (!selectorType.isPrimitive() && !Wrapper.isWrapperNumericOrBooleanType(selectorType)) {
+ // Object o = ...
+ // o instanceof Wrapped(float)
+ cb.aload(SELECTOR_OBJ);
+ cb.instanceof_(Wrapper.forBasicType(classLabel)
+ .wrapperType()
+ .describeConstable()
+ .orElseThrow());
+ cb.ifeq(next);
+ } else if (!unconditionalExactnessCheck(Wrapper.asPrimitiveType(selectorType), classLabel)) {
+ // Integer i = ... or int i = ...
+ // o instanceof float
+ Label notNumber = cb.newLabel();
+ cb.aload(SELECTOR_OBJ);
+ cb.instanceof_(ConstantDescs.CD_Number);
+ if (selectorType == long.class || selectorType == float.class || selectorType == double.class) {
+ cb.ifeq(next);
+ } else {
+ cb.ifeq(notNumber);
+ }
+ cb.aload(SELECTOR_OBJ);
+ cb.checkcast(ConstantDescs.CD_Number);
+ if (selectorType == long.class) {
+ cb.invokevirtual(ConstantDescs.CD_Number,
+ "longValue",
+ MethodTypeDesc.of(ConstantDescs.CD_long));
+ } else if (selectorType == float.class) {
+ cb.invokevirtual(ConstantDescs.CD_Number,
+ "floatValue",
+ MethodTypeDesc.of(ConstantDescs.CD_float));
+ } else if (selectorType == double.class) {
+ cb.invokevirtual(ConstantDescs.CD_Number,
+ "doubleValue",
+ MethodTypeDesc.of(ConstantDescs.CD_double));
+ } else {
+ Label compare = cb.newLabel();
+ cb.invokevirtual(ConstantDescs.CD_Number,
+ "intValue",
+ MethodTypeDesc.of(ConstantDescs.CD_int));
+ cb.goto_(compare);
+ cb.labelBinding(notNumber);
+ cb.aload(SELECTOR_OBJ);
+ cb.instanceof_(ConstantDescs.CD_Character);
+ cb.ifeq(next);
+ cb.aload(SELECTOR_OBJ);
+ cb.checkcast(ConstantDescs.CD_Character);
+ cb.invokevirtual(ConstantDescs.CD_Character,
+ "charValue",
+ MethodTypeDesc.of(ConstantDescs.CD_char));
+ cb.labelBinding(compare);
+ }
+
+ TypePairs typePair = TypePairs.of(Wrapper.asPrimitiveType(selectorType), classLabel);
+ String methodName = typePairToName.get(typePair);
+ cb.invokestatic(ExactConversionsSupport.class.describeConstable().orElseThrow(),
+ methodName,
+ MethodTypeDesc.of(ConstantDescs.CD_boolean, typePair.from.describeConstable().orElseThrow()));
+ cb.ifeq(next);
+ }
+ } else {
+ Optional classLabelConstableOpt = classLabel.describeConstable();
+ if (classLabelConstableOpt.isPresent()) {
+ cb.aload(SELECTOR_OBJ);
+ cb.instanceof_(classLabelConstableOpt.orElseThrow());
+ cb.ifeq(next);
+ } else {
+ cb.aload(EXTRA_CLASS_LABELS);
+ cb.constantInstruction(extraClassLabels.size());
+ cb.invokeinterface(ConstantDescs.CD_List,
+ "get",
+ MethodTypeDesc.of(ConstantDescs.CD_Object,
+ ConstantDescs.CD_int));
+ cb.checkcast(ConstantDescs.CD_Class);
+ cb.aload(SELECTOR_OBJ);
+ cb.invokevirtual(ConstantDescs.CD_Class,
+ "isInstance",
+ MethodTypeDesc.of(ConstantDescs.CD_boolean,
+ ConstantDescs.CD_Object));
+ cb.ifeq(next);
+ extraClassLabels.add(classLabel);
+ }
+ }
+ } else if (element.caseLabel() instanceof EnumDesc> enumLabel) {
+ int enumIdx = enumDescs.size();
+ enumDescs.add(enumLabel);
+ cb.aload(ENUM_CACHE);
+ cb.constantInstruction(enumIdx);
+ cb.invokestatic(ConstantDescs.CD_Integer,
+ "valueOf",
+ MethodTypeDesc.of(ConstantDescs.CD_Integer,
+ ConstantDescs.CD_int));
+ cb.aload(SELECTOR_OBJ);
+ cb.invokeinterface(BiPredicate.class.describeConstable().orElseThrow(),
+ "test",
+ MethodTypeDesc.of(ConstantDescs.CD_boolean,
+ ConstantDescs.CD_Object,
+ ConstantDescs.CD_Object));
+ cb.ifeq(next);
+ } else if (element.caseLabel() instanceof String stringLabel) {
+ cb.ldc(stringLabel);
+ cb.aload(SELECTOR_OBJ);
+ cb.invokevirtual(ConstantDescs.CD_Object,
+ "equals",
+ MethodTypeDesc.of(ConstantDescs.CD_boolean,
+ ConstantDescs.CD_Object));
+ cb.ifeq(next);
+ } else if (element.caseLabel() instanceof Integer integerLabel) {
+ Label compare = cb.newLabel();
+ Label notNumber = cb.newLabel();
+ cb.aload(SELECTOR_OBJ);
+ cb.instanceof_(ConstantDescs.CD_Number);
+ cb.ifeq(notNumber);
+ cb.aload(SELECTOR_OBJ);
+ cb.checkcast(ConstantDescs.CD_Number);
+ cb.invokevirtual(ConstantDescs.CD_Number,
+ "intValue",
+ MethodTypeDesc.of(ConstantDescs.CD_int));
+ cb.goto_(compare);
+ cb.labelBinding(notNumber);
+ cb.aload(SELECTOR_OBJ);
+ cb.instanceof_(ConstantDescs.CD_Character);
+ cb.ifeq(next);
+ cb.aload(SELECTOR_OBJ);
+ cb.checkcast(ConstantDescs.CD_Character);
+ cb.invokevirtual(ConstantDescs.CD_Character,
+ "charValue",
+ MethodTypeDesc.of(ConstantDescs.CD_char));
+ cb.labelBinding(compare);
+
+ cb.ldc(integerLabel);
+ cb.if_icmpne(next);
+ } else if ((element.caseLabel() instanceof Long ||
+ element.caseLabel() instanceof Float ||
+ element.caseLabel() instanceof Double ||
+ element.caseLabel() instanceof Boolean)) {
+ if (element.caseLabel() instanceof Boolean c) {
+ cb.constantInstruction(c ? 1 : 0);
+ } else {
+ cb.constantInstruction((ConstantDesc) element.caseLabel());
+ }
+ cb.invokestatic(element.caseLabel().getClass().describeConstable().orElseThrow(),
+ "valueOf",
+ MethodTypeDesc.of(element.caseLabel().getClass().describeConstable().orElseThrow(),
+ Wrapper.asPrimitiveType(element.caseLabel().getClass()).describeConstable().orElseThrow()));
+ cb.aload(SELECTOR_OBJ);
+ cb.invokevirtual(ConstantDescs.CD_Object,
+ "equals",
+ MethodTypeDesc.of(ConstantDescs.CD_boolean,
+ ConstantDescs.CD_Object));
+ cb.ifeq(next);
+ } else {
+ throw new InternalError("Unsupported label type: " +
+ element.caseLabel().getClass());
+ }
+ cb.constantInstruction(idx);
+ cb.ireturn();
+ }
+ cb.labelBinding(dflt);
+ cb.constantInstruction(cases.size());
+ cb.ireturn();
+ };
+ }
+
+ /*
+ * Construct the method handle that represents the method int typeSwitch(Object, int, BiPredicate, List)
+ */
+ private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Class> selectorType, Object[] labelConstants) {
List> enumDescs = new ArrayList<>();
List> extraClassLabels = new ArrayList<>();
- byte[] classBytes = ClassFile.of().build(ClassDesc.of(typeSwitchClassName(caller.lookupClass())), clb -> {
- clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC)
- .withMethodBody("typeSwitch",
- TYPES_SWITCH_DESCRIPTOR,
- ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
- cb -> {
- cb.aload(0);
- Label nonNullLabel = cb.newLabel();
- cb.if_nonnull(nonNullLabel);
- cb.iconst_m1();
- cb.ireturn();
- cb.labelBinding(nonNullLabel);
- if (labels.length == 0) {
- cb.constantInstruction(0)
- .ireturn();
- return ;
- }
- cb.iload(1);
- Label dflt = cb.newLabel();
- record Element(Label target, Label next, Object caseLabel) {}
- List cases = new ArrayList<>();
- List switchCases = new ArrayList<>();
- Object lastLabel = null;
- for (int idx = labels.length - 1; idx >= 0; idx--) {
- Object currentLabel = labels[idx];
- Label target = cb.newLabel();
- Label next;
- if (lastLabel == null) {
- next = dflt;
- } else if (lastLabel.equals(currentLabel)) {
- next = cases.getLast().next();
- } else {
- next = cases.getLast().target();
- }
- lastLabel = currentLabel;
- cases.add(new Element(target, next, currentLabel));
- switchCases.add(SwitchCase.of(idx, target));
- }
- cases = cases.reversed();
- switchCases = switchCases.reversed();
- cb.tableswitch(0, labels.length - 1, dflt, switchCases);
- for (int idx = 0; idx < cases.size(); idx++) {
- Element element = cases.get(idx);
- Label next = element.next();
- cb.labelBinding(element.target());
- if (element.caseLabel() instanceof Class> classLabel) {
- Optional classLabelConstableOpt = classLabel.describeConstable();
- if (classLabelConstableOpt.isPresent()) {
- cb.aload(0);
- cb.instanceof_(classLabelConstableOpt.orElseThrow());
- cb.ifeq(next);
- } else {
- cb.aload(3);
- cb.constantInstruction(extraClassLabels.size());
- cb.invokeinterface(ConstantDescs.CD_List,
- "get",
- MethodTypeDesc.of(ConstantDescs.CD_Object,
- ConstantDescs.CD_int));
- cb.checkcast(ConstantDescs.CD_Class);
- cb.aload(0);
- cb.invokevirtual(ConstantDescs.CD_Class,
- "isInstance",
- MethodTypeDesc.of(ConstantDescs.CD_boolean,
- ConstantDescs.CD_Object));
- cb.ifeq(next);
- extraClassLabels.add(classLabel);
- }
- } else if (element.caseLabel() instanceof EnumDesc> enumLabel) {
- int enumIdx = enumDescs.size();
- enumDescs.add(enumLabel);
- cb.aload(2);
- cb.constantInstruction(enumIdx);
- cb.invokestatic(ConstantDescs.CD_Integer,
- "valueOf",
- MethodTypeDesc.of(ConstantDescs.CD_Integer,
- ConstantDescs.CD_int));
- cb.aload(0);
- cb.invokeinterface(BiPredicate.class.describeConstable().orElseThrow(),
- "test",
- MethodTypeDesc.of(ConstantDescs.CD_boolean,
- ConstantDescs.CD_Object,
- ConstantDescs.CD_Object));
- cb.ifeq(next);
- } else if (element.caseLabel() instanceof String stringLabel) {
- cb.ldc(stringLabel);
- cb.aload(0);
- cb.invokevirtual(ConstantDescs.CD_Object,
- "equals",
- MethodTypeDesc.of(ConstantDescs.CD_boolean,
- ConstantDescs.CD_Object));
- cb.ifeq(next);
- } else if (element.caseLabel() instanceof Integer integerLabel) {
- Label compare = cb.newLabel();
- Label notNumber = cb.newLabel();
- cb.aload(0);
- cb.instanceof_(ConstantDescs.CD_Number);
- cb.ifeq(notNumber);
- cb.aload(0);
- cb.checkcast(ConstantDescs.CD_Number);
- cb.invokevirtual(ConstantDescs.CD_Number,
- "intValue",
- MethodTypeDesc.of(ConstantDescs.CD_int));
- cb.goto_(compare);
- cb.labelBinding(notNumber);
- cb.aload(0);
- cb.instanceof_(ConstantDescs.CD_Character);
- cb.ifeq(next);
- cb.aload(0);
- cb.checkcast(ConstantDescs.CD_Character);
- cb.invokevirtual(ConstantDescs.CD_Character,
- "charValue",
- MethodTypeDesc.of(ConstantDescs.CD_char));
- cb.labelBinding(compare);
- cb.ldc(integerLabel);
- cb.if_icmpne(next);
- } else {
- throw new InternalError("Unsupported label type: " +
- element.caseLabel().getClass());
- }
- cb.constantInstruction(idx);
- cb.ireturn();
- }
- cb.labelBinding(dflt);
- cb.constantInstruction(cases.size());
- cb.ireturn();
- });
+ byte[] classBytes = ClassFile.of().build(ClassDesc.of(typeSwitchClassName(caller.lookupClass())),
+ clb -> {
+ clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC)
+ .withMethodBody("typeSwitch",
+ TYPES_SWITCH_DESCRIPTOR,
+ ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
+ generateTypeSwitchSkeleton(selectorType, labelConstants, enumDescs, extraClassLabels));
});
try {
@@ -525,8 +643,13 @@ public class SwitchBootstraps {
int.class,
BiPredicate.class,
List.class));
- return MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(EnumDesc[]::new)),
- List.copyOf(extraClassLabels));
+ typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(EnumDesc[]::new)),
+ List.copyOf(extraClassLabels));
+ typeSwitch = MethodHandles.explicitCastArguments(typeSwitch,
+ MethodType.methodType(int.class,
+ selectorType,
+ int.class));
+ return typeSwitch;
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
@@ -541,4 +664,61 @@ public class SwitchBootstraps {
}
return name + "$$TypeSwitch";
}
+
+ // this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives
+ private static boolean unconditionalExactnessCheck(Class> selectorType, Class> targetType) {
+ Wrapper selectorWrapper = Wrapper.forBasicType(selectorType);
+ Wrapper targetWrapper = Wrapper.forBasicType(targetType);
+ if (selectorType.isPrimitive() && targetType.equals(selectorWrapper.wrapperType())) {
+ return true;
+ }
+ else if (selectorType.equals(targetType) ||
+ ((selectorType.equals(byte.class) && !targetType.equals(char.class)) ||
+ (selectorType.equals(short.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))) ||
+ (selectorType.equals(char.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))) ||
+ (selectorType.equals(int.class) && (targetType.equals(double.class) || targetType.equals(long.class))) ||
+ (selectorType.equals(float.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))))) return true;
+ return false;
+ }
+
+ // TypePairs should be in sync with the corresponding record in Lower
+ record TypePairs(Class> from, Class> to) {
+ public static TypePairs of(Class> from, Class> to) {
+ if (from == byte.class || from == short.class || from == char.class) {
+ from = int.class;
+ }
+ return new TypePairs(from, to);
+ }
+
+ public static Map initialize() {
+ Map typePairToName = new HashMap<>();
+ typePairToName.put(new TypePairs(byte.class, char.class), "isIntToCharExact"); // redirected
+ typePairToName.put(new TypePairs(short.class, byte.class), "isIntToByteExact"); // redirected
+ typePairToName.put(new TypePairs(short.class, char.class), "isIntToCharExact"); // redirected
+ typePairToName.put(new TypePairs(char.class, byte.class), "isIntToByteExact"); // redirected
+ typePairToName.put(new TypePairs(char.class, short.class), "isIntToShortExact"); // redirected
+ typePairToName.put(new TypePairs(int.class, byte.class), "isIntToByteExact");
+ typePairToName.put(new TypePairs(int.class, short.class), "isIntToShortExact");
+ typePairToName.put(new TypePairs(int.class, char.class), "isIntToCharExact");
+ typePairToName.put(new TypePairs(int.class, float.class), "isIntToFloatExact");
+ typePairToName.put(new TypePairs(long.class, byte.class), "isLongToByteExact");
+ typePairToName.put(new TypePairs(long.class, short.class), "isLongToShortExact");
+ typePairToName.put(new TypePairs(long.class, char.class), "isLongToCharExact");
+ typePairToName.put(new TypePairs(long.class, int.class), "isLongToIntExact");
+ typePairToName.put(new TypePairs(long.class, float.class), "isLongToFloatExact");
+ typePairToName.put(new TypePairs(long.class, double.class), "isLongToDoubleExact");
+ typePairToName.put(new TypePairs(float.class, byte.class), "isFloatToByteExact");
+ typePairToName.put(new TypePairs(float.class, short.class), "isFloatToShortExact");
+ typePairToName.put(new TypePairs(float.class, char.class), "isFloatToCharExact");
+ typePairToName.put(new TypePairs(float.class, int.class), "isFloatToIntExact");
+ typePairToName.put(new TypePairs(float.class, long.class), "isFloatToLongExact");
+ typePairToName.put(new TypePairs(double.class, byte.class), "isDoubleToByteExact");
+ typePairToName.put(new TypePairs(double.class, short.class), "isDoubleToShortExact");
+ typePairToName.put(new TypePairs(double.class, char.class), "isDoubleToCharExact");
+ typePairToName.put(new TypePairs(double.class, int.class), "isDoubleToIntExact");
+ typePairToName.put(new TypePairs(double.class, long.class), "isDoubleToLongExact");
+ typePairToName.put(new TypePairs(double.class, float.class), "isDoubleToFloatExact");
+ return typePairToName;
+ }
+ }
}
diff --git a/src/java.base/share/classes/sun/invoke/util/Wrapper.java b/src/java.base/share/classes/sun/invoke/util/Wrapper.java
index bb3e617fcc3..76aede09487 100644
--- a/src/java.base/share/classes/sun/invoke/util/Wrapper.java
+++ b/src/java.base/share/classes/sun/invoke/util/Wrapper.java
@@ -25,23 +25,25 @@
package sun.invoke.util;
+import java.util.Map;
+import static sun.invoke.util.Wrapper.NumericClasses.*;
import jdk.internal.vm.annotation.DontInline;
public enum Wrapper {
- // wrapperType simple primitiveType simple char emptyArray format
- BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0], Format.unsigned( 1)),
+ // wrapperType simple primitiveType simple char emptyArray format numericClass superClass
+ BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0], Format.unsigned( 1), 0, 0),
// These must be in the order defined for widening primitive conversions in JLS 5.1.2
// Avoid boxing integral types here to defer initialization of internal caches
- BYTE ( Byte.class, "Byte", byte.class, "byte", 'B', new byte[0], Format.signed( 8)),
- SHORT ( Short.class, "Short", short.class, "short", 'S', new short[0], Format.signed( 16)),
- CHAR (Character.class, "Character", char.class, "char", 'C', new char[0], Format.unsigned(16)),
- INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0], Format.signed( 32)),
- LONG ( Long.class, "Long", long.class, "long", 'J', new long[0], Format.signed( 64)),
- FLOAT ( Float.class, "Float", float.class, "float", 'F', new float[0], Format.floating(32)),
- DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0], Format.floating(64)),
- OBJECT ( Object.class, "Object", Object.class, "Object", 'L', new Object[0], Format.other( 1)),
+ BYTE ( Byte.class, "Byte", byte.class, "byte", 'B', new byte[0], Format.signed( 8), BYTE_CLASS, BYTE_SUPERCLASSES),
+ SHORT ( Short.class, "Short", short.class, "short", 'S', new short[0], Format.signed( 16), SHORT_CLASS, SHORT_SUPERCLASSES),
+ CHAR (Character.class, "Character", char.class, "char", 'C', new char[0], Format.unsigned(16), CHAR_CLASS, CHAR_SUPERCLASSES),
+ INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0], Format.signed( 32), INT_CLASS, INT_SUPERCLASSES),
+ LONG ( Long.class, "Long", long.class, "long", 'J', new long[0], Format.signed( 64), LONG_CLASS, LONG_SUPERCLASSES),
+ FLOAT ( Float.class, "Float", float.class, "float", 'F', new float[0], Format.floating(32), FLOAT_CLASS, FLOAT_SUPERCLASSES),
+ DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0], Format.floating(64), DOUBLE_CLASS, DOUBLE_CLASS),
+ OBJECT ( Object.class, "Object", Object.class, "Object", 'L', new Object[0], Format.other( 1), 0, 0),
// VOID must be the last type, since it is "assignable" from any other type:
- VOID ( Void.class, "Void", void.class, "void", 'V', null, Format.other( 0)),
+ VOID ( Void.class, "Void", void.class, "void", 'V', null, Format.other( 0), 0, 0),
;
public static final int COUNT = 10;
@@ -52,16 +54,20 @@ public enum Wrapper {
private final String basicTypeString;
private final Object emptyArray;
private final int format;
+ private final int numericClass;
+ private final int superClasses;
private final String wrapperSimpleName;
private final String primitiveSimpleName;
- private Wrapper(Class> wtype, String wtypeName, Class> ptype, String ptypeName, char tchar, Object emptyArray, int format) {
+ private Wrapper(Class> wtype, String wtypeName, Class> ptype, String ptypeName, char tchar, Object emptyArray, int format, int numericClass, int superClasses) {
this.wrapperType = wtype;
this.primitiveType = ptype;
this.basicTypeChar = tchar;
this.basicTypeString = String.valueOf(this.basicTypeChar);
this.emptyArray = emptyArray;
this.format = format;
+ this.numericClass = numericClass;
+ this.superClasses = superClasses;
this.wrapperSimpleName = wtypeName;
this.primitiveSimpleName = ptypeName;
}
@@ -424,6 +430,11 @@ public enum Wrapper {
return findWrapperType(type) != null;
}
+ /** Query: Is the given type a wrapper, such as {@code Integer}, {@code Byte}, etc excluding {@code Void} and {@code Object}? */
+ public static boolean isWrapperNumericOrBooleanType(Class> type) {
+ return isWrapperType(type) && findWrapperType(type) != VOID && findWrapperType(type) != OBJECT;
+ }
+
/** Query: Is the given type a primitive, such as {@code int} or {@code void}? */
public static boolean isPrimitiveType(Class> type) {
return type.isPrimitive();
@@ -628,4 +639,34 @@ public enum Wrapper {
values[i+vpos] = value;
}
}
+
+ // NumericClasses should be in sync with com.sun.tools.javac.code.TypeTag.NumericClasses
+ public static class NumericClasses {
+ public static final int BYTE_CLASS = 1;
+ public static final int CHAR_CLASS = 2;
+ public static final int SHORT_CLASS = 4;
+ public static final int INT_CLASS = 8;
+ public static final int LONG_CLASS = 16;
+ public static final int FLOAT_CLASS = 32;
+ public static final int DOUBLE_CLASS = 64;
+
+ static final int BYTE_SUPERCLASSES = BYTE_CLASS | SHORT_CLASS | INT_CLASS |
+ LONG_CLASS | FLOAT_CLASS | DOUBLE_CLASS;
+
+ static final int CHAR_SUPERCLASSES = CHAR_CLASS | INT_CLASS |
+ LONG_CLASS | FLOAT_CLASS | DOUBLE_CLASS;
+
+ static final int SHORT_SUPERCLASSES = SHORT_CLASS | INT_CLASS |
+ LONG_CLASS | FLOAT_CLASS | DOUBLE_CLASS;
+
+ static final int INT_SUPERCLASSES = INT_CLASS | LONG_CLASS | FLOAT_CLASS | DOUBLE_CLASS;
+
+ static final int LONG_SUPERCLASSES = LONG_CLASS | FLOAT_CLASS | DOUBLE_CLASS;
+
+ static final int FLOAT_SUPERCLASSES = FLOAT_CLASS | DOUBLE_CLASS;
+ }
+
+ public boolean isStrictSubRangeOf(Wrapper target) {
+ return (this.superClasses & target.numericClass) != 0 && this != target;
+ }
}
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java
index fa911b5c04a..32a7d068715 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java
@@ -210,6 +210,7 @@ public class Preview {
case STRING_TEMPLATES -> true;
case IMPLICIT_CLASSES -> true;
case SUPER_INIT -> true;
+ case PRIMITIVE_PATTERNS -> true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true'
//for those selected features, and 'false' for all the others.
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java
index 870d6b1629a..54e5cfec32a 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2024, 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
@@ -254,6 +254,7 @@ public enum Source {
IMPLICIT_CLASSES(JDK21, Fragments.FeatureImplicitClasses, DiagKind.PLURAL),
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
UNNAMED_VARIABLES(JDK22, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL),
+ PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL),
SUPER_INIT(JDK22, Fragments.FeatureSuperInit, DiagKind.NORMAL),
;
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
index 8e270dec387..b1dd2186d7e 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
@@ -163,6 +163,7 @@ public class Symtab {
*/
public final Type objectType;
public final Type objectMethodsType;
+ public final Type exactConversionsSupportType;
public final Type objectsType;
public final Type classType;
public final Type classLoaderType;
@@ -545,6 +546,7 @@ public class Symtab {
// Enter predefined classes. All are assumed to be in the java.base module.
objectType = enterClass("java.lang.Object");
objectMethodsType = enterClass("java.lang.runtime.ObjectMethods");
+ exactConversionsSupportType = enterClass("java.lang.runtime.ExactConversionsSupport");
objectsType = enterClass("java.util.Objects");
classType = enterClass("java.lang.Class");
stringType = enterClass("java.lang.String");
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java
index 6dd1ea4b6b8..94449476071 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java
@@ -186,6 +186,10 @@ public enum TypeTag {
return (this.superClasses & tag.numericClass) != 0;
}
+ public boolean isInSuperClassesOf(TypeTag tag) {
+ return (this.numericClass & tag.superClasses) != 0;
+ }
+
/** Returns the number of type tags.
*/
public static int getTypeTagCount() {
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
index 7af1193c6ce..4b2f32521a3 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
@@ -5024,6 +5024,52 @@ public class Types {
}
//
+ //
+ /** Check unconditionality between any combination of reference or primitive types.
+ *
+ * Rules:
+ * an identity conversion
+ * a widening reference conversion
+ * a widening primitive conversion (delegates to `checkUnconditionallyExactPrimitives`)
+ * a boxing conversion
+ * a boxing conversion followed by a widening reference conversion
+ *
+ * @param source Source primitive or reference type
+ * @param target Target primitive or reference type
+ */
+ public boolean isUnconditionallyExact(Type source, Type target) {
+ if (isSameType(source, target)) {
+ return true;
+ }
+
+ return target.isPrimitive()
+ ? isUnconditionallyExactPrimitives(source, target)
+ : isSubtype(boxedTypeOrType(erasure(source)), target);
+ }
+
+ /** Check unconditionality between primitive types.
+ *
+ * - widening from one integral type to another,
+ * - widening from one floating point type to another,
+ * - widening from byte, short, or char to a floating point type,
+ * - widening from int to double.
+ *
+ * @param selectorType Type of selector
+ * @param targetType Target type
+ */
+ public boolean isUnconditionallyExactPrimitives(Type selectorType, Type targetType) {
+ if (isSameType(selectorType, targetType)) {
+ return true;
+ }
+
+ return (selectorType.isPrimitive() && targetType.isPrimitive()) &&
+ ((selectorType.hasTag(BYTE) && !targetType.hasTag(CHAR)) ||
+ (selectorType.hasTag(SHORT) && (selectorType.getTag().isStrictSubRangeOf(targetType.getTag()))) ||
+ (selectorType.hasTag(CHAR) && (selectorType.getTag().isStrictSubRangeOf(targetType.getTag()))) ||
+ (selectorType.hasTag(INT) && (targetType.hasTag(DOUBLE) || targetType.hasTag(LONG))) ||
+ (selectorType.hasTag(FLOAT) && (selectorType.getTag().isStrictSubRangeOf(targetType.getTag()))));
+ }
+ //
//
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
index bcc16452935..f5a9d43162e 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
@@ -1682,18 +1682,19 @@ public class Attr extends JCTree.Visitor {
try {
boolean enumSwitch = (seltype.tsym.flags() & Flags.ENUM) != 0;
boolean stringSwitch = types.isSameType(seltype, syms.stringType);
+ boolean booleanSwitch = types.isSameType(types.unboxedTypeOrType(seltype), syms.booleanType);
boolean errorEnumSwitch = TreeInfo.isErrorEnumSwitch(selector, cases);
boolean intSwitch = types.isAssignable(seltype, syms.intType);
- boolean errorPrimitiveSwitch = seltype.isPrimitive() && !intSwitch;
boolean patternSwitch;
+ if (seltype.isPrimitive() && !intSwitch) {
+ preview.checkSourceLevel(selector.pos(), Feature.PRIMITIVE_PATTERNS);
+ patternSwitch = true;
+ }
if (!enumSwitch && !stringSwitch && !errorEnumSwitch &&
- !intSwitch && !errorPrimitiveSwitch) {
+ !intSwitch) {
preview.checkSourceLevel(selector.pos(), Feature.PATTERN_SWITCH);
patternSwitch = true;
} else {
- if (errorPrimitiveSwitch) {
- log.error(selector.pos(), Errors.SelectorTypeNotAllowed(seltype));
- }
patternSwitch = cases.stream()
.flatMap(c -> c.labels.stream())
.anyMatch(l -> l.hasTag(PATTERNCASELABEL) ||
@@ -1709,6 +1710,7 @@ public class Attr extends JCTree.Visitor {
boolean hasNullPattern = false; // Is there a null pattern?
CaseTree.CaseKind caseKind = null;
boolean wasError = false;
+ JCCaseLabel unconditionalCaseLabel = null;
for (List l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
if (caseKind == null) {
@@ -1776,10 +1778,15 @@ public class Attr extends JCTree.Visitor {
} else if (!constants.add(s)) {
log.error(label.pos(), Errors.DuplicateCaseLabel);
}
- } else if (!stringSwitch && !intSwitch && !errorPrimitiveSwitch) {
- log.error(label.pos(), Errors.ConstantLabelNotCompatible(pattype, seltype));
- } else if (!constants.add(pattype.constValue())) {
- log.error(c.pos(), Errors.DuplicateCaseLabel);
+ }
+ else {
+ if (!stringSwitch && !intSwitch &&
+ !((pattype.getTag().isInSuperClassesOf(LONG) || pattype.getTag().equals(BOOLEAN)) &&
+ types.isSameType(types.unboxedTypeOrType(seltype), pattype))) {
+ log.error(label.pos(), Errors.ConstantLabelNotCompatible(pattype, seltype));
+ } else if (!constants.add(pattype.constValue())) {
+ log.error(c.pos(), Errors.DuplicateCaseLabel);
+ }
}
}
}
@@ -1788,6 +1795,8 @@ public class Attr extends JCTree.Visitor {
log.error(label.pos(), Errors.DuplicateDefaultLabel);
} else if (hasUnconditionalPattern) {
log.error(label.pos(), Errors.UnconditionalPatternAndDefault);
+ } else if (booleanSwitch && constants.containsAll(Set.of(0, 1))) {
+ log.error(label.pos(), Errors.DefaultAndBothBooleanValues);
}
hasDefault = true;
matchBindings = MatchBindingsComputer.EMPTY;
@@ -1796,12 +1805,13 @@ public class Attr extends JCTree.Visitor {
JCPattern pat = patternlabel.pat;
attribExpr(pat, switchEnv, seltype);
Type primaryType = TreeInfo.primaryPatternType(pat);
- if (!primaryType.hasTag(TYPEVAR)) {
+
+ if (primaryType.isPrimitive()) {
+ preview.checkSourceLevel(pat.pos(), Feature.PRIMITIVE_PATTERNS);
+ } else if (!primaryType.hasTag(TYPEVAR)) {
primaryType = chk.checkClassOrArrayType(pat.pos(), primaryType);
}
- if (!errorPrimitiveSwitch) {
- checkCastablePattern(pat.pos(), seltype, primaryType);
- }
+ checkCastablePattern(pat.pos(), seltype, primaryType);
Type patternType = types.erasure(primaryType);
JCExpression guard = c.guard;
if (guardBindings == null && guard != null) {
@@ -1824,15 +1834,17 @@ public class Attr extends JCTree.Visitor {
boolean unconditional =
unguarded &&
!patternType.isErroneous() &&
- types.isSubtype(types.boxedTypeOrType(types.erasure(seltype)),
- patternType);
+ types.isUnconditionallyExact(seltype, patternType);
if (unconditional) {
if (hasUnconditionalPattern) {
log.error(pat.pos(), Errors.DuplicateUnconditionalPattern);
} else if (hasDefault) {
log.error(pat.pos(), Errors.UnconditionalPatternAndDefault);
+ } else if (booleanSwitch && constants.containsAll(Set.of(0, 1))) {
+ log.error(pat.pos(), Errors.UnconditionalPatternAndBothBooleanValues);
}
hasUnconditionalPattern = true;
+ unconditionalCaseLabel = label;
}
lastPatternErroneous = patternType.isErroneous();
} else {
@@ -1859,7 +1871,7 @@ public class Attr extends JCTree.Visitor {
}
if (patternSwitch) {
chk.checkSwitchCaseStructure(cases);
- chk.checkSwitchCaseLabelDominated(cases);
+ chk.checkSwitchCaseLabelDominated(unconditionalCaseLabel, cases);
}
if (switchTree.hasTag(SWITCH)) {
((JCSwitch) switchTree).hasUnconditionalPattern =
@@ -4097,8 +4109,13 @@ public class Attr extends JCTree.Visitor {
}
public void visitTypeTest(JCInstanceOf tree) {
- Type exprtype = chk.checkNullOrRefType(
- tree.expr.pos(), attribExpr(tree.expr, env));
+ Type exprtype = attribExpr(tree.expr, env);
+ if (exprtype.isPrimitive()) {
+ preview.checkSourceLevel(tree.expr.pos(), Feature.PRIMITIVE_PATTERNS);
+ } else {
+ exprtype = chk.checkNullOrRefType(
+ tree.expr.pos(), exprtype);
+ }
Type clazztype;
JCTree typeTree;
if (tree.pattern.getTag() == BINDINGPATTERN ||
@@ -4119,20 +4136,24 @@ public class Attr extends JCTree.Visitor {
typeTree = tree.pattern;
chk.validate(typeTree, env, false);
}
- if (!clazztype.hasTag(TYPEVAR)) {
- clazztype = chk.checkClassOrArrayType(typeTree.pos(), clazztype);
- }
- if (!clazztype.isErroneous() && !types.isReifiable(clazztype)) {
- boolean valid = false;
- if (allowReifiableTypesInInstanceof) {
- valid = checkCastablePattern(tree.expr.pos(), exprtype, clazztype);
- } else {
- log.error(DiagnosticFlag.SOURCE_LEVEL, tree.pos(),
- Feature.REIFIABLE_TYPES_INSTANCEOF.error(this.sourceName));
- allowReifiableTypesInInstanceof = true;
+ if (clazztype.isPrimitive()) {
+ preview.checkSourceLevel(tree.pattern.pos(), Feature.PRIMITIVE_PATTERNS);
+ } else {
+ if (!clazztype.hasTag(TYPEVAR)) {
+ clazztype = chk.checkClassOrArrayType(typeTree.pos(), clazztype);
}
- if (!valid) {
- clazztype = types.createErrorType(clazztype);
+ if (!clazztype.isErroneous() && !types.isReifiable(clazztype)) {
+ boolean valid = false;
+ if (allowReifiableTypesInInstanceof) {
+ valid = checkCastablePattern(tree.expr.pos(), exprtype, clazztype);
+ } else {
+ log.error(DiagnosticFlag.SOURCE_LEVEL, tree.pos(),
+ Feature.REIFIABLE_TYPES_INSTANCEOF.error(this.sourceName));
+ allowReifiableTypesInInstanceof = true;
+ }
+ if (!valid) {
+ clazztype = types.createErrorType(clazztype);
+ }
}
}
chk.checkCastable(tree.expr.pos(), exprtype, clazztype);
@@ -4152,12 +4173,9 @@ public class Attr extends JCTree.Visitor {
diags.fragment(Fragments.InconvertibleTypes(exprType, pattType)));
return false;
} else if ((exprType.isPrimitive() || pattType.isPrimitive()) &&
- (!exprType.isPrimitive() ||
- !pattType.isPrimitive() ||
- !types.isSameType(exprType, pattType))) {
- chk.basicHandler.report(pos,
- diags.fragment(Fragments.NotApplicableTypes(exprType, pattType)));
- return false;
+ (!exprType.isPrimitive() || !pattType.isPrimitive() || !types.isSameType(exprType, pattType))) {
+ preview.checkSourceLevel(pos, Feature.PRIMITIVE_PATTERNS);
+ return true;
} else if (warner.hasLint(LintCategory.UNCHECKED)) {
log.error(pos,
Errors.InstanceofReifiableNotSafe(exprType, pattType));
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
index f9b228203d4..834d976a7c8 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
@@ -4800,11 +4800,13 @@ public class Check {
return false;
}
- void checkSwitchCaseLabelDominated(List cases) {
+ void checkSwitchCaseLabelDominated(JCCaseLabel unconditionalCaseLabel, List cases) {
List> caseLabels = List.nil();
boolean seenDefault = false;
boolean seenDefaultLabel = false;
boolean warnDominatedByDefault = false;
+ boolean unconditionalFound = false;
+
for (List l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
for (JCCaseLabel label : c.labels) {
@@ -4832,10 +4834,11 @@ public class Check {
JCCase testCase = caseAndLabel.fst;
JCCaseLabel testCaseLabel = caseAndLabel.snd;
Type testType = labelType(testCaseLabel);
- if (types.isSubtype(currentType, testType) &&
+ boolean dominated = false;
+ if (unconditionalCaseLabel == testCaseLabel) unconditionalFound = true;
+ if (types.isUnconditionallyExact(currentType, testType) &&
!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) {
//the current label is potentially dominated by the existing (test) label, check:
- boolean dominated = false;
if (label instanceof JCConstantCaseLabel) {
dominated |= !(testCaseLabel instanceof JCConstantCaseLabel) &&
TreeInfo.unguardedCase(testCase);
@@ -4845,9 +4848,15 @@ public class Check {
dominated = patternDominated(testPatternCaseLabel.pat,
patternCL.pat);
}
- if (dominated) {
- log.error(label.pos(), Errors.PatternDominated);
- }
+ }
+
+ // Domination can occur even when we have not an unconditional pair between case labels.
+ if (unconditionalFound && unconditionalCaseLabel != label) {
+ dominated = true;
+ }
+
+ if (dominated) {
+ log.error(label.pos(), Errors.PatternDominated);
}
}
caseLabels = caseLabels.prepend(Pair.of(c, label));
@@ -4858,23 +4867,16 @@ public class Check {
private Type labelType(JCCaseLabel label) {
return types.erasure(switch (label.getTag()) {
case PATTERNCASELABEL -> ((JCPatternCaseLabel) label).pat.type;
- case CONSTANTCASELABEL -> types.boxedTypeOrType(((JCConstantCaseLabel) label).expr.type);
+ case CONSTANTCASELABEL -> ((JCConstantCaseLabel) label).expr.type;
default -> throw Assert.error("Unexpected tree kind: " + label.getTag());
});
}
private boolean patternDominated(JCPattern existingPattern, JCPattern currentPattern) {
Type existingPatternType = types.erasure(existingPattern.type);
Type currentPatternType = types.erasure(currentPattern.type);
- if (existingPatternType.isPrimitive() ^ currentPatternType.isPrimitive()) {
+ if (!types.isUnconditionallyExact(currentPatternType, existingPatternType)) {
return false;
}
- if (existingPatternType.isPrimitive()) {
- return types.isSameType(existingPatternType, currentPatternType);
- } else {
- if (!types.isSubtype(currentPatternType, existingPatternType)) {
- return false;
- }
- }
if (currentPattern instanceof JCBindingPattern ||
currentPattern instanceof JCAnyPattern) {
return existingPattern instanceof JCBindingPattern ||
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
index e1f5b6f38a0..9de7a6292db 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
@@ -757,9 +757,14 @@ public class Flow {
}
}
}
- tree.isExhaustive = tree.hasUnconditionalPattern ||
- TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) ||
- exhausts(tree.selector, tree.cases);
+
+ if (tree.hasUnconditionalPattern ||
+ TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases)) {
+ tree.isExhaustive = true;
+ } else {
+ tree.isExhaustive = exhausts(tree.selector, tree.cases);
+ }
+
if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustive);
}
@@ -770,6 +775,7 @@ public class Flow {
private boolean exhausts(JCExpression selector, List cases) {
Set patternSet = new HashSet<>();
Map> enum2Constants = new HashMap<>();
+ Set