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: + * + *

+ * + * @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 booleanLiterals = new HashSet<>(); for (JCCase c : cases) { if (!TreeInfo.unguardedCase(c)) continue; @@ -780,19 +786,29 @@ public class Flow { patternSet.add(makePatternDescription(component, patternLabel.pat)); } } else if (l instanceof JCConstantCaseLabel constantLabel) { - Symbol s = TreeInfo.symbol(constantLabel.expr); - if (s != null && s.isEnum()) { - enum2Constants.computeIfAbsent(s.owner, x -> { - Set result = new HashSet<>(); - s.owner.members() - .getSymbols(sym -> sym.kind == Kind.VAR && sym.isEnum()) - .forEach(result::add); - return result; - }).remove(s); + if (types.unboxedTypeOrType(selector.type).hasTag(TypeTag.BOOLEAN)) { + Object value = ((JCLiteral) constantLabel.expr).value; + booleanLiterals.add(value); + } else { + Symbol s = TreeInfo.symbol(constantLabel.expr); + if (s != null && s.isEnum()) { + enum2Constants.computeIfAbsent(s.owner, x -> { + Set result = new HashSet<>(); + s.owner.members() + .getSymbols(sym -> sym.kind == Kind.VAR && sym.isEnum()) + .forEach(result::add); + return result; + }).remove(s); + } } } } } + + if (types.unboxedTypeOrType(selector.type).hasTag(TypeTag.BOOLEAN) && booleanLiterals.size() == 2) { + return true; + } + for (Entry> e : enum2Constants.entrySet()) { if (e.getValue().isEmpty()) { patternSet.add(new BindingPattern(e.getKey().type)); @@ -823,8 +839,7 @@ public class Flow { private boolean checkCovered(Type seltype, Iterable patterns) { for (Type seltypeComponent : components(seltype)) { for (PatternDescription pd : patterns) { - if (pd instanceof BindingPattern bp && - types.isSubtype(seltypeComponent, types.erasure(bp.type))) { + if(isBpCovered(seltypeComponent, pd)) { return true; } } @@ -1104,8 +1119,7 @@ public class Flow { reducedNestedPatterns[i] = newNested; } - covered &= newNested instanceof BindingPattern bp && - types.isSubtype(types.erasure(componentType[i]), types.erasure(bp.type)); + covered &= isBpCovered(componentType[i], newNested); } if (covered) { return new BindingPattern(rpOne.recordType); @@ -1281,6 +1295,30 @@ public class Flow { } } + private boolean isBpCovered(Type componentType, PatternDescription newNested) { + if (newNested instanceof BindingPattern bp) { + var seltype = types.erasure(componentType); + + if (seltype.isPrimitive()) { + if (types.isSameType(bp.type, types.boxedClass(seltype).type)) { + return true; + } + + // if the target is unconditionally exact to the pattern, target is covered + if (types.isUnconditionallyExact(seltype, bp.type)) { + return true; + } + } else if (seltype.isReference() && bp.type.isPrimitive() && types.isCastable(seltype, bp.type)) { + return true; + } else { + if (types.isSubtype(seltype, types.erasure(bp.type))) { + return true; + } + } + } + return false; + } + /** * This pass implements the second step of the dataflow analysis, namely * the exception analysis. This is to ensure that every checked exception that is @@ -3462,7 +3500,7 @@ public class Flow { sealed interface PatternDescription { } public PatternDescription makePatternDescription(Type selectorType, JCPattern pattern) { if (pattern instanceof JCBindingPattern binding) { - Type type = types.isSubtype(selectorType, binding.type) + Type type = !selectorType.isPrimitive() && types.isSubtype(selectorType, binding.type) ? selectorType : binding.type; return new BindingPattern(type); } else if (pattern instanceof JCRecordPattern record) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index 39807aeaa5e..6e15f5acf5b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -102,6 +102,7 @@ public class Lower extends TreeTranslator { private final PkgInfo pkginfoOpt; private final boolean optimizeOuterThis; private final boolean useMatchException; + private final HashMap typePairToName; @SuppressWarnings("this-escape") protected Lower(Context context) { @@ -133,6 +134,7 @@ public class Lower extends TreeTranslator { Preview preview = Preview.instance(context); useMatchException = Feature.PATTERN_SWITCH.allowedInSource(source) && (preview.isEnabled() || !preview.isPreview(Feature.PATTERN_SWITCH)); + typePairToName = TypePairs.initialize(syms); } /** The currently enclosing class. @@ -2921,6 +2923,178 @@ public class Lower extends TreeTranslator { result = tree; } + /** + * All the exactness checks between primitive types that require a run-time + * check are in {@code java.lang.runtime.ExactConversionsSupport}. Those methods + * are in the form {@code ExactConversionsSupport.isToExact} where both + * {@code S} and {@code T} are primitive types and correspond to the runtime + * action that will be executed to check whether a certain value (that is passed + * as a parameter) can be converted to {@code T} without loss of information. + * + * Rewrite {@code instanceof if expr : Object} and Type is primitive type: + * + * {@snippet : + * Object v = ... + * if (v instanceof float) + * => + * if (let tmp$123 = v; tmp$123 instanceof Float) + * } + * + * Rewrite {@code instanceof if expr : wrapper reference type} + * + * {@snippet : + * Integer v = ... + * if (v instanceof float) + * => + * if (let tmp$123 = v; tmp$123 != null && ExactConversionsSupport.intToFloatExact(tmp$123.intValue())) + * } + * + * Rewrite {@code instanceof if expr : primitive} + * + * {@snippet : + * int v = ... + * if (v instanceof float) + * => + * if (let tmp$123 = v; ExactConversionsSupport.intToFloatExact(tmp$123)) + * } + * + * More rewritings: + *
    + *
  • If the {@code instanceof} check is unconditionally exact rewrite to true.
  • + *
  • If expression type is {@code Byte}, {@code Short}, {@code Integer}, ..., an + * unboxing conversion followed by a widening primitive conversion.
  • + *
  • If expression type is a supertype: {@code Number}, a narrowing reference + * conversion followed by an unboxing conversion.
  • + *
+ */ + public void visitTypeTest(JCInstanceOf tree) { + if (tree.expr.type.isPrimitive() || tree.pattern.type.isPrimitive()) { + JCExpression exactnessCheck = null; + JCExpression instanceOfExpr = translate(tree.expr); + + // preserving the side effects of the value + VarSymbol dollar_s = new VarSymbol(FINAL | SYNTHETIC, + names.fromString("tmp" + tree.pos + this.target.syntheticNameChar()), + tree.expr.type, + currentMethodSym); + JCStatement var = make.at(tree.pos()) + .VarDef(dollar_s, instanceOfExpr).setType(dollar_s.type); + + if (types.isUnconditionallyExact(tree.expr.type, tree.pattern.type)) { + exactnessCheck = make + .LetExpr(List.of(var), make.Literal(BOOLEAN, 1).setType(syms.booleanType.constType(1))) + .setType(syms.booleanType); + } + else if (tree.expr.type.isReference()) { + JCExpression nullCheck = makeBinary(NE, + make.Ident(dollar_s), + makeNull()); + if (types.isUnconditionallyExact(types.unboxedType(tree.expr.type), tree.pattern.type)) { + exactnessCheck = make + .LetExpr(List.of(var), nullCheck) + .setType(syms.booleanType); + } else if (types.unboxedType(tree.expr.type).isPrimitive()) { + exactnessCheck = getExactnessCheck(tree, + boxIfNeeded(make.Ident(dollar_s), types.unboxedType(tree.expr.type))); + } else { + exactnessCheck = make.at(tree.pos()) + .TypeTest(tree.expr, make.Type(types.boxedClass(tree.pattern.type).type)) + .setType(syms.booleanType); + } + + exactnessCheck = make.LetExpr(List.of(var), makeBinary(AND, + nullCheck, + exactnessCheck)) + .setType(syms.booleanType); + } + else if (tree.expr.type.isPrimitive()) { + JCIdent argument = make.Ident(dollar_s); + + JCExpression exactnessCheckCall = + getExactnessCheck(tree, argument); + + exactnessCheck = make.LetExpr(List.of(var), exactnessCheckCall) + .setType(syms.booleanType); + } + + result = exactnessCheck; + } else { + tree.expr = translate(tree.expr); + tree.pattern = translate(tree.pattern); + result = tree; + } + } + + // TypePairs should be in sync with the corresponding record in SwitchBootstraps + record TypePairs(TypeSymbol from, TypeSymbol to) { + public static TypePairs of(Symtab syms, Type from, Type to) { + if (from == syms.byteType || from == syms.shortType || from == syms.charType) { + from = syms.intType; + } + return new TypePairs(from, to); + } + + public TypePairs(Type from, Type to) { + this(from.tsym, to.tsym); + } + + public static HashMap initialize(Symtab syms) { + HashMap typePairToName = new HashMap<>(); + typePairToName.put(new TypePairs(syms.byteType, syms.charType), "isIntToCharExact"); // redirected + typePairToName.put(new TypePairs(syms.shortType, syms.byteType), "isIntToByteExact"); // redirected + typePairToName.put(new TypePairs(syms.shortType, syms.charType), "isIntToCharExact"); // redirected + typePairToName.put(new TypePairs(syms.charType, syms.byteType), "isIntToByteExact"); // redirected + typePairToName.put(new TypePairs(syms.charType, syms.shortType), "isIntToShortExact"); // redirected + typePairToName.put(new TypePairs(syms.intType, syms.byteType), "isIntToByteExact"); + typePairToName.put(new TypePairs(syms.intType, syms.shortType), "isIntToShortExact"); + typePairToName.put(new TypePairs(syms.intType, syms.charType), "isIntToCharExact"); + typePairToName.put(new TypePairs(syms.intType, syms.floatType), "isIntToFloatExact"); + typePairToName.put(new TypePairs(syms.longType, syms.byteType), "isLongToByteExact"); + typePairToName.put(new TypePairs(syms.longType, syms.shortType), "isLongToShortExact"); + typePairToName.put(new TypePairs(syms.longType, syms.charType), "isLongToCharExact"); + typePairToName.put(new TypePairs(syms.longType, syms.intType), "isLongToIntExact"); + typePairToName.put(new TypePairs(syms.longType, syms.floatType), "isLongToFloatExact"); + typePairToName.put(new TypePairs(syms.longType, syms.doubleType), "isLongToDoubleExact"); + typePairToName.put(new TypePairs(syms.floatType, syms.byteType), "isFloatToByteExact"); + typePairToName.put(new TypePairs(syms.floatType, syms.shortType), "isFloatToShortExact"); + typePairToName.put(new TypePairs(syms.floatType, syms.charType), "isFloatToCharExact"); + typePairToName.put(new TypePairs(syms.floatType, syms.intType), "isFloatToIntExact"); + typePairToName.put(new TypePairs(syms.floatType, syms.longType), "isFloatToLongExact"); + typePairToName.put(new TypePairs(syms.doubleType, syms.byteType), "isDoubleToByteExact"); + typePairToName.put(new TypePairs(syms.doubleType, syms.shortType), "isDoubleToShortExact"); + typePairToName.put(new TypePairs(syms.doubleType, syms.charType), "isDoubleToCharExact"); + typePairToName.put(new TypePairs(syms.doubleType, syms.intType), "isDoubleToIntExact"); + typePairToName.put(new TypePairs(syms.doubleType, syms.longType), "isDoubleToLongExact"); + typePairToName.put(new TypePairs(syms.doubleType, syms.floatType), "isDoubleToFloatExact"); + return typePairToName; + } + } + + private JCExpression getExactnessCheck(JCInstanceOf tree, JCExpression argument) { + TypePairs pair = TypePairs.of(syms, types.unboxedTypeOrType(tree.expr.type), tree.pattern.type); + + Name exactnessFunction = names.fromString(typePairToName.get(pair)); + + // Resolve the exactness method + Symbol ecsym = lookupMethod(tree.pos(), + exactnessFunction, + syms.exactConversionsSupportType, + List.of(pair.from.type)); + + // Generate the method call ExactnessChecks.(); + JCFieldAccess select = make.Select( + make.QualIdent(syms.exactConversionsSupportType.tsym), + exactnessFunction); + select.sym = ecsym; + select.setType(syms.booleanType); + + JCExpression exactnessCheck = make.Apply(List.nil(), + select, + List.of(argument)); + exactnessCheck.setType(syms.booleanType); + return exactnessCheck; + } + public void visitNewClass(JCNewClass tree) { ClassSymbol c = (ClassSymbol)tree.constructor.owner; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index aedafa20c3e..089d9236dec 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -236,7 +236,7 @@ public class TransPatterns extends TreeTranslator { Type principalType = types.erasure(TreeInfo.primaryPatternType((pattern))); JCExpression resultExpression = (JCExpression) this.translate(pattern); - if (!tree.allowNull && !principalType.isPrimitive()) { + if (!tree.allowNull || !types.isSubtype(currentValue.type, principalType)) { resultExpression = makeBinary(Tag.AND, makeTypeTest(make.Ident(currentValue), make.Type(principalType)), @@ -496,12 +496,14 @@ public class TransPatterns extends TreeTranslator { .toArray(s -> new LoadableConstant[s]); boolean enumSelector = seltype.tsym.isEnum(); + boolean primitiveSelector = seltype.isPrimitive(); Name bootstrapName = enumSelector ? names.enumSwitch : names.typeSwitch; MethodSymbol bsm = rs.resolveInternalMethod(tree.pos(), env, syms.switchBootstrapsType, bootstrapName, staticArgTypes, List.nil()); + Type resolvedSelectorType = seltype; MethodType indyType = new MethodType( - List.of(enumSelector ? seltype : syms.objectType, syms.intType), + List.of(resolvedSelectorType, syms.intType), syms.intType, List.nil(), syms.methodClass @@ -735,6 +737,81 @@ public class TransPatterns extends TreeTranslator { } } + private Symbol.DynamicVarSymbol makePrimitive(DiagnosticPosition pos, Type primitiveType) { + Assert.checkNonNull(currentClass); + + List bsm_staticArgs = List.of(syms.methodHandleLookupType, + syms.stringType, + new ClassType(syms.classType.getEnclosingType(), + List.of(syms.constantBootstrapsType), + syms.classType.tsym)); + + Name bootstrapName = names.fromString("primitiveClass"); + MethodSymbol bsm = rs.resolveInternalMethod(pos, env, syms.constantBootstrapsType, + bootstrapName, bsm_staticArgs, List.nil()); + + PrimitiveGenerator primitiveGenerator = new PrimitiveGenerator(); + primitiveGenerator.assembleSig(primitiveType); + return new Symbol.DynamicVarSymbol(names.fromString(primitiveGenerator.sb.toString()), + syms.noSymbol, + new Symbol.MethodHandleSymbol(bsm), + syms.classType, + new LoadableConstant[]{}); + } + + private Symbol.DynamicVarSymbol makeBooleanConstant(DiagnosticPosition pos, int constant) { + Assert.checkNonNull(currentClass); + + List bsm_staticArgs = List.of(syms.methodHandleLookupType, + syms.stringType, + new ClassType(syms.classType.getEnclosingType(), + List.of(syms.constantBootstrapsType), + syms.classType.tsym)); + + Name bootstrapName = names.fromString("getStaticFinal"); + MethodSymbol bsm = rs.resolveInternalMethod(pos, env, syms.constantBootstrapsType, + bootstrapName, bsm_staticArgs, List.nil()); + + return new Symbol.DynamicVarSymbol(constant == 0 ? names.fromString("FALSE") : names.fromString("TRUE"), + syms.noSymbol, + new Symbol.MethodHandleSymbol(bsm), + types.boxedTypeOrType(syms.booleanType), + new LoadableConstant[]{}); + } + + private class PrimitiveGenerator extends Types.SignatureGenerator { + + /** + * An output buffer for type signatures. + */ + StringBuilder sb = new StringBuilder(); + + PrimitiveGenerator() { + super(types); + } + + @Override + protected void append(char ch) { + sb.append(ch); + } + + @Override + protected void append(byte[] ba) { + sb.append(new String(ba)); + } + + @Override + protected void append(Name name) { + sb.append(name.toString()); + } + + @Override + public String toString() { + return sb.toString(); + } + } + + JCMethodInvocation makeApply(JCExpression selector, Name name, List args) { MethodSymbol method = rs.resolveInternalMethod( currentClassTree.pos(), env, @@ -823,7 +900,8 @@ public class TransPatterns extends TreeTranslator { } JCBindingPattern binding = (JCBindingPattern) instanceofCheck.pattern; hasUnconditional = - instanceofCheck.allowNull && + (!types.erasure(binding.type).isPrimitive() ? instanceofCheck.allowNull : + types.isUnconditionallyExact(commonNestedExpression.type, types.erasure(binding.type))) && accList.tail.isEmpty(); List newLabel; @@ -854,6 +932,7 @@ public class TransPatterns extends TreeTranslator { } JCSwitch newSwitch = make.Switch(commonNestedExpression, nestedCases.toList()); newSwitch.patternSwitch = true; + newSwitch.hasUnconditionalPattern = hasUnconditional; JCPatternCaseLabel leadingTest = (JCPatternCaseLabel) accummulator.first().labels.head; leadingTest.syntheticGuard = null; @@ -930,16 +1009,20 @@ public class TransPatterns extends TreeTranslator { } private Type principalType(JCTree p) { - return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p))); + return types.erasure(TreeInfo.primaryPatternType(p)); } private LoadableConstant toLoadableConstant(JCCaseLabel l, Type selector) { if (l.hasTag(Tag.PATTERNCASELABEL)) { Type principalType = principalType(((JCPatternCaseLabel) l).pat); - if (types.isSubtype(selector, principalType)) { - return (LoadableConstant) selector; + if (((JCPatternCaseLabel) l).pat.type.isReference()) { + if (types.isSubtype(selector, principalType)) { + return (LoadableConstant) selector; + } else { + return (LoadableConstant) principalType; + } } else { - return (LoadableConstant) principalType; + return makePrimitive(l.pos(), principalType); } } else if (l.hasTag(Tag.CONSTANTCASELABEL) && !TreeInfo.isNullCaseLabel(l)) { JCExpression expr = ((JCConstantCaseLabel) l).expr; @@ -954,8 +1037,11 @@ public class TransPatterns extends TreeTranslator { Assert.checkNonNull(expr.type.constValue()); return switch (expr.type.getTag()) { - case BYTE, CHAR, - SHORT, INT -> LoadableConstant.Int((Integer) expr.type.constValue()); + case BOOLEAN -> makeBooleanConstant(l.pos(), (Integer) expr.type.constValue()); + case BYTE, CHAR, SHORT, INT -> LoadableConstant.Int((Integer) expr.type.constValue()); + case LONG -> LoadableConstant.Long((Long) expr.type.constValue()); + case FLOAT -> LoadableConstant.Float((Float) expr.type.constValue()); + case DOUBLE -> LoadableConstant.Double((Double) expr.type.constValue()); case CLASS -> LoadableConstant.String((String) expr.type.constValue()); default -> throw new AssertionError(); }; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index c4f00d9d46e..2a472b37181 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -549,6 +549,12 @@ compiler.err.duplicate.unconditional.pattern=\ compiler.err.unconditional.pattern.and.default=\ switch has both an unconditional pattern and a default label +compiler.err.unconditional.pattern.and.both.boolean.values=\ + switch has both boolean values and an unconditional pattern + +compiler.err.default.and.both.boolean.values=\ + switch has both boolean values and a default label + compiler.err.guard.not.allowed=\ guards are only allowed for case with a pattern @@ -563,10 +569,6 @@ compiler.err.cannot.assign.not.declared.guard=\ compiler.err.constant.label.not.compatible=\ constant label of type {0} is not compatible with switch selector type {1} -# 0: type -compiler.err.selector.type.not.allowed=\ - selector type {0} is not allowed - compiler.err.flows.through.to.pattern=\ illegal fall-through to a pattern @@ -2665,10 +2667,6 @@ compiler.warn.prob.found.req=\ compiler.misc.inconvertible.types=\ {0} cannot be converted to {1} -# 0: type, 1: type -compiler.misc.not.applicable.types=\ - pattern of type {1} is not applicable at {0} - # 0: type, 1: type compiler.misc.possible.loss.of.precision=\ possible lossy conversion from {0} to {1} @@ -3200,6 +3198,9 @@ compiler.misc.feature.deconstruction.patterns=\ compiler.misc.feature.unnamed.variables=\ unnamed variables +compiler.misc.feature.primitive.patterns=\ + primitive patterns + compiler.misc.feature.records=\ records diff --git a/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java b/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java index 663ca164a3d..e2a5446393b 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java @@ -209,14 +209,14 @@ class CompletenessAnalyzer { THROWS(TokenKind.THROWS, XDECL|XBRACESNEEDED), // throws // Primarive type names - BOOLEAN(TokenKind.BOOLEAN, XEXPR1|XDECL1), // boolean - BYTE(TokenKind.BYTE, XEXPR1|XDECL1), // byte - CHAR(TokenKind.CHAR, XEXPR1|XDECL1), // char - DOUBLE(TokenKind.DOUBLE, XEXPR1|XDECL1), // double - FLOAT(TokenKind.FLOAT, XEXPR1|XDECL1), // float - INT(TokenKind.INT, XEXPR1|XDECL1), // int - LONG(TokenKind.LONG, XEXPR1|XDECL1), // long - SHORT(TokenKind.SHORT, XEXPR1|XDECL1), // short + BOOLEAN(TokenKind.BOOLEAN, XEXPR1|XDECL1|XTERM), // boolean + BYTE(TokenKind.BYTE, XEXPR1|XDECL1|XTERM), // byte + CHAR(TokenKind.CHAR, XEXPR1|XDECL1|XTERM), // char + DOUBLE(TokenKind.DOUBLE, XEXPR1|XDECL1|XTERM), // double + FLOAT(TokenKind.FLOAT, XEXPR1|XDECL1|XTERM), // float + INT(TokenKind.INT, XEXPR1|XDECL1|XTERM), // int + LONG(TokenKind.LONG, XEXPR1|XDECL1|XTERM), // long + SHORT(TokenKind.SHORT, XEXPR1|XDECL1|XTERM), // short VOID(TokenKind.VOID, XEXPR1|XDECL1), // void // Modifiers keywords @@ -805,8 +805,24 @@ class CompletenessAnalyzer { } public Completeness parseExpression() { - while (token.kind.isExpression()) + while (token.kind.isExpression()) { + CT prevToken = in.prevCT; nextToken(); + // primitive types can only appear in the end of an `instanceof` expression + switch (token.kind) { + case EOF: + switch (in.prevCT.kind) { + case BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE, BOOLEAN: + switch (prevToken.kind) { + case INSTANCEOF: + return Completeness.COMPLETE; + default: + return Completeness.DEFINITELY_INCOMPLETE; + } + } + } + } + return Completeness.COMPLETE; } diff --git a/test/jdk/java/lang/runtime/ExactnessConversionsSupportTest.java b/test/jdk/java/lang/runtime/ExactnessConversionsSupportTest.java new file mode 100644 index 00000000000..8c862ae729b --- /dev/null +++ b/test/jdk/java/lang/runtime/ExactnessConversionsSupportTest.java @@ -0,0 +1,236 @@ +/* + * 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. + * + * 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. + */ + +import org.testng.annotations.Test; + +import java.io.Serializable; +import java.lang.Enum.EnumDesc; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.AccessFlag; +import java.lang.runtime.ExactConversionsSupport; +import java.lang.runtime.SwitchBootstraps; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.testng.Assert.*; + +/** + * @test + * @bug 8304487 + * @summary Verify boundary and special cases of exact conversion predicates + * @enablePreview + * @modules java.base/jdk.internal.classfile + * @compile ExactnessConversionsSupportTest.java + * @run testng/othervm ExactnessConversionsSupportTest + */ +@Test +public class ExactnessConversionsSupportTest { + + public static void main(String[] args) { + testByte(); + testShort(); + testChar(); + testInt(); + testLong(); + testFloat(); + testDouble(); + } + + public static void testByte() { + assertEquals(true, ExactConversionsSupport.isIntToByteExact((byte) (Byte.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToByteExact((byte) (0))); + assertEquals(true, ExactConversionsSupport.isIntToByteExact((byte) (Byte.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isIntToByteExact((short) (Short.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToByteExact((short) (0))); + assertEquals(false, ExactConversionsSupport.isIntToByteExact((short) (Short.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isIntToByteExact((char) (Character.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToByteExact((char) (Character.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isIntToByteExact(Integer.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isIntToByteExact(0)); + assertEquals(false, ExactConversionsSupport.isIntToByteExact(Integer.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isLongToByteExact(Long.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isLongToByteExact(0L)); + assertEquals(false, ExactConversionsSupport.isLongToByteExact(Long.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToByteExact(Float.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isFloatToByteExact((float) 0)); + assertEquals(false, ExactConversionsSupport.isFloatToByteExact(Float.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToByteExact(Float.NaN)); + assertEquals(false, ExactConversionsSupport.isFloatToByteExact(Float.POSITIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isFloatToByteExact(Float.NEGATIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isFloatToByteExact(-0.0f)); + assertEquals(true, ExactConversionsSupport.isFloatToByteExact(+0.0f)); + assertEquals(false, ExactConversionsSupport.isDoubleToByteExact(Double.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isDoubleToByteExact(0d)); + assertEquals(false, ExactConversionsSupport.isDoubleToByteExact(Double.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isDoubleToByteExact(Double.NaN)); + assertEquals(false, ExactConversionsSupport.isDoubleToByteExact(Double.POSITIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isDoubleToByteExact(Double.NEGATIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isDoubleToByteExact(-0.0d)); + assertEquals(true, ExactConversionsSupport.isDoubleToByteExact(+0.0d)); + } + public static void testShort() { + assertEquals(true, ExactConversionsSupport.isIntToShortExact((byte) (Byte.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((byte) (0))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((byte) (Byte.MIN_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((short) (Short.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((short) (0))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((short) (Short.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isIntToShortExact((char) (Character.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((char) (Character.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isIntToShortExact((Integer.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToShortExact((0))); + assertEquals(false, ExactConversionsSupport.isIntToShortExact((Integer.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isLongToShortExact(Long.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isLongToShortExact(0L)); + assertEquals(false, ExactConversionsSupport.isLongToShortExact(Long.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(Float.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isFloatToShortExact(0f)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(Float.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(Float.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(Float.NaN)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(Float.POSITIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(Float.NEGATIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isFloatToShortExact(-0.0f)); + assertEquals(true, ExactConversionsSupport.isFloatToShortExact(+0.0f)); + assertEquals(false, ExactConversionsSupport.isDoubleToShortExact(Double.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isDoubleToShortExact((double) 0)); + assertEquals(false, ExactConversionsSupport.isDoubleToShortExact(Double.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isDoubleToShortExact(Double.NaN)); + assertEquals(false, ExactConversionsSupport.isDoubleToShortExact(Double.POSITIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isDoubleToShortExact(Double.NEGATIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isDoubleToShortExact(-0.0d)); + assertEquals(true, ExactConversionsSupport.isDoubleToShortExact(+0.0d)); + } + public static void testChar() { + assertEquals(true, ExactConversionsSupport.isIntToCharExact((byte) (Byte.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToCharExact((byte) (0))); + assertEquals(false, ExactConversionsSupport.isIntToCharExact((byte) (Byte.MIN_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToCharExact((short) (Short.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToCharExact((short) (0))); + assertEquals(false, ExactConversionsSupport.isIntToCharExact((short) (Short.MIN_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToCharExact((char) (Character.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToCharExact((char) (Character.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isIntToCharExact (Integer.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isIntToCharExact(0)); + assertEquals(false, ExactConversionsSupport.isIntToCharExact(Integer.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isLongToCharExact(Long.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isLongToCharExact(0l)); + assertEquals(false, ExactConversionsSupport.isLongToCharExact(Long.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToCharExact(Float.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isFloatToCharExact((float) 0)); + assertEquals(false, ExactConversionsSupport.isFloatToCharExact(Float.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isFloatToCharExact(Float.NaN)); + assertEquals(false, ExactConversionsSupport.isFloatToCharExact(Float.POSITIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isFloatToCharExact(Float.NEGATIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isFloatToCharExact(-0.0f)); + assertEquals(true, ExactConversionsSupport.isFloatToCharExact(+0.0f)); + assertEquals(false, ExactConversionsSupport.isDoubleToCharExact(Double.MAX_VALUE)); + assertEquals(true, ExactConversionsSupport.isDoubleToCharExact((double) 0)); + assertEquals(false, ExactConversionsSupport.isDoubleToCharExact(Double.MIN_VALUE)); + assertEquals(false, ExactConversionsSupport.isDoubleToCharExact(Double.NaN)); + assertEquals(false, ExactConversionsSupport.isDoubleToCharExact(Double.POSITIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isDoubleToCharExact(Double.NEGATIVE_INFINITY)); + assertEquals(false, ExactConversionsSupport.isDoubleToCharExact(-0.0d)); + assertEquals(true, ExactConversionsSupport.isDoubleToCharExact(+0.0d)); + } + public static void testInt() { + assertEquals(false, ExactConversionsSupport.isLongToIntExact((Long.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isLongToIntExact((0L))); + assertEquals(false, ExactConversionsSupport.isLongToIntExact((Long.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isFloatToIntExact((Float.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isFloatToIntExact(((float) 0))); + assertEquals(false, ExactConversionsSupport.isFloatToIntExact((Float.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isFloatToIntExact((Float.NaN))); + assertEquals(false, ExactConversionsSupport.isFloatToIntExact((Float.POSITIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isFloatToIntExact((Float.NEGATIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isFloatToIntExact((-0.0f))); + assertEquals(true, ExactConversionsSupport.isFloatToIntExact((+0.0f))); + assertEquals(false, ExactConversionsSupport.isDoubleToIntExact((Double.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isDoubleToIntExact(((double) 0))); + assertEquals(false, ExactConversionsSupport.isDoubleToIntExact((Double.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isDoubleToIntExact((Double.NaN))); + assertEquals(false, ExactConversionsSupport.isDoubleToIntExact((Double.POSITIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isDoubleToIntExact((Double.NEGATIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isDoubleToIntExact((-0.0d))); + assertEquals(true, ExactConversionsSupport.isDoubleToIntExact((+0.0d))); + } + public static void testLong() { + assertEquals(false, ExactConversionsSupport.isFloatToLongExact((Float.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isFloatToLongExact(((float) 0))); + assertEquals(false, ExactConversionsSupport.isFloatToLongExact((Float.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isFloatToLongExact((Float.NaN))); + assertEquals(false, ExactConversionsSupport.isFloatToLongExact((Float.POSITIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isFloatToLongExact((Float.NEGATIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isFloatToLongExact((-0.0f))); + assertEquals(true, ExactConversionsSupport.isFloatToLongExact((+0.0f))); + assertEquals(false, ExactConversionsSupport.isDoubleToLongExact((Double.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isDoubleToLongExact(((double) 0))); + assertEquals(false, ExactConversionsSupport.isDoubleToLongExact((Double.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isDoubleToLongExact((Double.NaN))); + assertEquals(false, ExactConversionsSupport.isDoubleToLongExact((Double.POSITIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isDoubleToLongExact((Double.NEGATIVE_INFINITY))); + assertEquals(false, ExactConversionsSupport.isDoubleToLongExact((-0.0d))); + assertEquals(true, ExactConversionsSupport.isDoubleToLongExact((+0.0d))); + } + public static void testFloat() { + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((byte) (Byte.MAX_VALUE)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((byte) (0)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((byte) (Byte.MIN_VALUE)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((short) (Short.MAX_VALUE)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((short) (0)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((short) (Short.MIN_VALUE)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((char) (Character.MAX_VALUE)))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact(((char) (Character.MIN_VALUE)))); + assertEquals(false, ExactConversionsSupport.isIntToFloatExact( (Integer.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact((0))); + assertEquals(true, ExactConversionsSupport.isIntToFloatExact((Integer.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isLongToFloatExact((Long.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isLongToFloatExact((0l))); + assertEquals(true, ExactConversionsSupport.isLongToFloatExact((Long.MIN_VALUE))); + assertEquals(false, ExactConversionsSupport.isDoubleToFloatExact((Double.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isDoubleToFloatExact(((double) 0))); + assertEquals(false, ExactConversionsSupport.isDoubleToFloatExact((Double.MIN_VALUE))); + assertEquals(true, ExactConversionsSupport.isDoubleToFloatExact((Double.NaN))); + assertEquals(true, ExactConversionsSupport.isDoubleToFloatExact((Double.POSITIVE_INFINITY))); + assertEquals(true, ExactConversionsSupport.isDoubleToFloatExact((Double.NEGATIVE_INFINITY))); + assertEquals(true, ExactConversionsSupport.isDoubleToFloatExact((-0.0d))); + assertEquals(true, ExactConversionsSupport.isDoubleToFloatExact((+0.0d))); + } + public static void testDouble() { + assertEquals(false, ExactConversionsSupport.isLongToDoubleExact((Long.MAX_VALUE))); + assertEquals(true, ExactConversionsSupport.isLongToDoubleExact((0L))); + assertEquals(true, ExactConversionsSupport.isLongToDoubleExact((Long.MIN_VALUE))); + } + + static void assertEquals(boolean expected, boolean actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +} diff --git a/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java b/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java index 4e3b905b2cb..4d79f62a01d 100644 --- a/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java +++ b/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java @@ -46,6 +46,7 @@ import static org.testng.Assert.fail; * @test * @bug 8318144 * @enablePreview + * @modules java.base/jdk.internal.classfile * @compile SwitchBootstrapsTest.java * @run testng/othervm SwitchBootstrapsTest */ @@ -111,12 +112,9 @@ public class SwitchBootstrapsTest { testType(Short.valueOf((short) 1), 0, 0, 1, Integer.class); testType(Character.valueOf((char) 1), 0, 0, 1, Integer.class); testType(Integer.valueOf((int) 1), 0, 0, 1, Integer.class); - try { - testType(1, 0, 1, 1.0, Integer.class); - fail("Didn't get the expected exception."); - } catch (IllegalArgumentException ex) { - //OK - } + testType(1, 0, 1, 1.0d, Integer.class); + testType(1, 0, 1, 1.0f, Integer.class); + testType(1, 0, 1, true, Integer.class); testType("", 0, 0, String.class, String.class, String.class, String.class, String.class); testType("", 1, 1, String.class, String.class, String.class, String.class, String.class); testType("", 2, 2, String.class, String.class, String.class, String.class, String.class); @@ -124,6 +122,15 @@ public class SwitchBootstrapsTest { testType("", 3, 3, String.class, String.class, String.class, String.class, String.class); testType("", 4, 4, String.class, String.class, String.class, String.class, String.class); testType("", 0, 0); + testType(new Object() { + @Override + public boolean equals(Object obj) { + if (obj instanceof Long i) { + return i == 1; + } + return super.equals(obj); + } + }, 0, 1, 1L); } public void testEnums() throws Throwable { @@ -178,7 +185,6 @@ public class SwitchBootstrapsTest { public void testWrongSwitchTypes() throws Throwable { MethodType[] switchTypes = new MethodType[] { MethodType.methodType(int.class, Object.class), - MethodType.methodType(int.class, double.class, int.class), MethodType.methodType(int.class, Object.class, Integer.class) }; for (MethodType switchType : switchTypes) { diff --git a/test/langtools/jdk/jshell/CompletenessTest.java b/test/langtools/jdk/jshell/CompletenessTest.java index 79d81965689..09fcbfbdd39 100644 --- a/test/langtools/jdk/jshell/CompletenessTest.java +++ b/test/langtools/jdk/jshell/CompletenessTest.java @@ -396,4 +396,9 @@ public class CompletenessTest extends KullaTesting { assertStatus("int[] m = {1, 2}, n = new int[0]; int i;", COMPLETE, "int[] m = {1, 2}, n = new int[0];"); } + + public void testInstanceOf() { + assertStatus("i instanceof Integer", COMPLETE, "i instanceof Integer"); + assertStatus("i instanceof int", COMPLETE, "i instanceof int"); + } } diff --git a/test/langtools/jdk/jshell/PrimitiveInstanceOfTest.java b/test/langtools/jdk/jshell/PrimitiveInstanceOfTest.java new file mode 100644 index 00000000000..6a12392ffd9 --- /dev/null +++ b/test/langtools/jdk/jshell/PrimitiveInstanceOfTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, 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 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @build KullaTesting TestingInputStream + * @run testng PrimitiveInstanceOfTest + */ +import jdk.jshell.JShell; +import org.testng.annotations.Test; + +import java.util.function.Consumer; + +import static org.testng.Assert.assertEquals; + +@Test +public class PrimitiveInstanceOfTest extends KullaTesting { + + public void testInstanceOf() { + assertEval("int i = 42;"); + assertEval("i instanceof Integer"); + assertEval("i instanceof int"); + } + + public void testInstanceOfRef() { + assertEval("Integer i = 42;"); + assertEval("i instanceof Integer"); + assertEval("i instanceof Number"); + } + + @org.testng.annotations.BeforeMethod + public void setUp() { + super.setUp(bc -> bc.compilerOptions("--source", System.getProperty("java.specification.version"), "--enable-preview").remoteVMOptions("--enable-preview")); + } +} diff --git a/test/langtools/tools/javac/diags/examples/NotApplicableTypes.java b/test/langtools/tools/javac/diags/examples/DefaultAndBothBoolean.java similarity index 69% rename from test/langtools/tools/javac/diags/examples/NotApplicableTypes.java rename to test/langtools/tools/javac/diags/examples/DefaultAndBothBoolean.java index 6146d2b6d9e..dac10c13954 100644 --- a/test/langtools/tools/javac/diags/examples/NotApplicableTypes.java +++ b/test/langtools/tools/javac/diags/examples/DefaultAndBothBoolean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -21,13 +21,16 @@ * questions. */ -// key: compiler.misc.not.applicable.types -// key: compiler.err.prob.found.req - -class NotApplicableTypes { - void t(int i) { - switch (i) { - case Integer j -> {} - } +// key: compiler.err.default.and.both.boolean.values +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview --source 23 +public class DefaultAndBothBoolean { + private int test(boolean sel) { + return switch (sel) { + case true -> 1; + case false -> 2; + default -> 3; + }; } -} +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/SelectorTypeNotAllowed.java b/test/langtools/tools/javac/diags/examples/PrimitivePatternMatching.java similarity index 80% rename from test/langtools/tools/javac/diags/examples/SelectorTypeNotAllowed.java rename to test/langtools/tools/javac/diags/examples/PrimitivePatternMatching.java index 056f3c56c8b..39c2ec7fe6d 100644 --- a/test/langtools/tools/javac/diags/examples/SelectorTypeNotAllowed.java +++ b/test/langtools/tools/javac/diags/examples/PrimitivePatternMatching.java @@ -21,12 +21,12 @@ * questions. */ -// key: compiler.err.selector.type.not.allowed +// key: compiler.misc.feature.primitive.patterns +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview -public class SelectorTypeNotAllowed { - private void noLong(long sel) { - switch (sel) { - default -> {} - } +class PrimitivePatternMatching { + boolean m(Object o) { + return o instanceof int s; } -} \ No newline at end of file +} diff --git a/test/langtools/tools/javac/diags/examples/TypeReqClassArray.java b/test/langtools/tools/javac/diags/examples/TypeReqClassArray.java index 79deed7c947..ba7eacf2a82 100644 --- a/test/langtools/tools/javac/diags/examples/TypeReqClassArray.java +++ b/test/langtools/tools/javac/diags/examples/TypeReqClassArray.java @@ -27,5 +27,13 @@ import java.util.*; class TypeReqClassArray { - boolean b = (this instanceof int); + interface Sig { + void m(int s); + } + + Sig consume(Sig s) { return s; } + + public void meth() { + Sig s = consume(int::new); + } } diff --git a/test/langtools/tools/javac/diags/examples/TypeReqRef.java b/test/langtools/tools/javac/diags/examples/TypeReqRef.java index 972dc22e401..d9ada8672c4 100644 --- a/test/langtools/tools/javac/diags/examples/TypeReqRef.java +++ b/test/langtools/tools/javac/diags/examples/TypeReqRef.java @@ -25,6 +25,7 @@ // key: compiler.err.type.found.req class TypeReqRef { - int i; - boolean b = (i instanceof Object); + + void method(Inner in) {} + class Inner {} } diff --git a/test/langtools/tools/javac/diags/examples/UnconditionalPatternAndBothBoolean.java b/test/langtools/tools/javac/diags/examples/UnconditionalPatternAndBothBoolean.java new file mode 100644 index 00000000000..69d60d10f59 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/UnconditionalPatternAndBothBoolean.java @@ -0,0 +1,36 @@ +/* + * 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. + * + * 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. + */ + +// key: compiler.err.unconditional.pattern.and.both.boolean.values +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview --source 23 +public class UnconditionalPatternAndBothBoolean { + private int test(boolean sel) { + return switch (sel) { + case true -> 1; + case false -> 2; + case boolean b -> 3; + }; + } +} \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/CastConversionMatch.java b/test/langtools/tools/javac/patterns/CastConversionMatch.java index ec249afee52..73261723048 100644 --- a/test/langtools/tools/javac/patterns/CastConversionMatch.java +++ b/test/langtools/tools/javac/patterns/CastConversionMatch.java @@ -3,7 +3,7 @@ * @bug 8231827 * @summary Match which involves a cast conversion * @compile/fail/ref=CastConversionMatch.out -XDrawDiagnostics CastConversionMatch.java - */ + * @compile --enable-preview --source ${jdk.version} CastConversionMatch.java */ public class CastConversionMatch { public static void meth() { diff --git a/test/langtools/tools/javac/patterns/CastConversionMatch.out b/test/langtools/tools/javac/patterns/CastConversionMatch.out index 8f747810aff..93385942d8b 100644 --- a/test/langtools/tools/javac/patterns/CastConversionMatch.out +++ b/test/langtools/tools/javac/patterns/CastConversionMatch.out @@ -1,2 +1,2 @@ -CastConversionMatch.java:11:26: compiler.err.type.found.req: int, (compiler.misc.type.req.class.array) +CastConversionMatch.java:11:26: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns) 1 error diff --git a/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.java b/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.java index b7671a86f44..4e1a7d7f669 100644 --- a/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.java +++ b/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.java @@ -27,13 +27,6 @@ public class DeconstructionPatternErrors { if (p instanceof GenRecord(Integer v)); //inconsistency in types if (p instanceof P2(var v, var v)); //duplicated variables if (p instanceof P6(P2(var v1, var v2), P2(var v1, var v2))); //duplicated variables - if (p instanceof P7(byte b)); //incorrect pattern type - if (p instanceof P7(long l)); //incorrect pattern type - switch (p) { - case P7(byte b) -> {} //incorrect pattern type - no exception should occur - case P7(long l) -> {} //incorrect pattern type - no exception should occur - default -> {} - } GenRecord r1 = null; if (r1 instanceof GenRecord(String s)) {} switch (r1) { diff --git a/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.out b/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.out index e27f3859221..f947142cd66 100644 --- a/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.out +++ b/test/langtools/tools/javac/patterns/DeconstructionPatternErrors.out @@ -1,7 +1,7 @@ -DeconstructionPatternErrors.java:42:37: compiler.err.illegal.start.of.type -DeconstructionPatternErrors.java:44:28: compiler.err.illegal.start.of.type -DeconstructionPatternErrors.java:46:42: compiler.err.expected: ';' -DeconstructionPatternErrors.java:46:43: compiler.err.not.stmt +DeconstructionPatternErrors.java:35:37: compiler.err.illegal.start.of.type +DeconstructionPatternErrors.java:37:28: compiler.err.illegal.start.of.type +DeconstructionPatternErrors.java:39:42: compiler.err.expected: ';' +DeconstructionPatternErrors.java:39:43: compiler.err.not.stmt DeconstructionPatternErrors.java:15:29: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.util.List, java.util.ArrayList) DeconstructionPatternErrors.java:16:29: compiler.err.instanceof.reifiable.not.safe: java.lang.Object, java.util.ArrayList DeconstructionPatternErrors.java:17:29: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, int) @@ -20,8 +20,4 @@ DeconstructionPatternErrors.java:27:13: compiler.err.instanceof.reifiable.not.sa DeconstructionPatternErrors.java:28:40: compiler.err.match.binding.exists DeconstructionPatternErrors.java:29:56: compiler.err.already.defined: kindname.variable, v1, kindname.method, meth() DeconstructionPatternErrors.java:29:64: compiler.err.already.defined: kindname.variable, v2, kindname.method, meth() -DeconstructionPatternErrors.java:30:29: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, byte) -DeconstructionPatternErrors.java:31:29: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, long) -DeconstructionPatternErrors.java:33:21: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, byte) -DeconstructionPatternErrors.java:34:21: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, long) -26 errors \ No newline at end of file +22 errors \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfComboTest.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfComboTest.java new file mode 100644 index 00000000000..bfc3993124b --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfComboTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023, 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 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @library /tools/lib /tools/javac/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox toolbox.JavacTask + * @build combo.ComboTestHelper + * @compile PrimitiveInstanceOfComboTest.java + * @run main PrimitiveInstanceOfComboTest + */ + +import combo.ComboInstance; +import combo.ComboParameter; +import combo.ComboTask; +import combo.ComboTestHelper; +import toolbox.ToolBox; + +public class PrimitiveInstanceOfComboTest extends ComboInstance { + private static final String JAVA_VERSION = System.getProperty("java.specification.version"); + + protected ToolBox tb; + + PrimitiveInstanceOfComboTest() { + super(); + tb = new ToolBox(); + } + + public static void main(String... args) throws Exception { + new ComboTestHelper() + .withDimension("TYPE1", (x, type1) -> x.type1 = type1, Type.values()) + .withDimension("TYPE2", (x, type2) -> x.type2 = type2, Type.values()) + .withFailMode(ComboTestHelper.FailMode.FAIL_FAST) + .run(PrimitiveInstanceOfComboTest::new); + } + + private Type type1; + private Type type2; + + private static final String test1 = + """ + public class Test { + public static void doTest(#{TYPE1} in) { + var r = (#{TYPE2}) in; + } + } + """; + + private static final String test2 = + """ + public class Test { + public static void doTest(#{TYPE1} in) { + var r = in instanceof #{TYPE2}; + } + } + """; + + @Override + protected void doWork() throws Throwable { + ComboTask task1 = newCompilationTask() + .withSourceFromTemplate(test1.replace("#{TYPE1}", type1.code).replace("#{TYPE2}", type2.code)) + .withOption("--enable-preview") + .withOption("-source").withOption(JAVA_VERSION);; + + ComboTask task2 = newCompilationTask() + .withSourceFromTemplate(test2.replace("#{TYPE1}", type1.code).replace("#{TYPE2}", type2.code)) + .withOption("--enable-preview") + .withOption("-source").withOption(JAVA_VERSION);; + + task1.generate(result1 -> { + task2.generate(result2 -> { + if (result1.hasErrors() ^ result2.hasErrors()) { + throw new AssertionError("Unexpected result: " + + "\n task1: " + result1.hasErrors() + ", info: " + result1.compilationInfo() + + "\n task1: " + result2.hasErrors() + ", info: " + result2.compilationInfo()); + } + }); + }); + } + + public enum Type implements ComboParameter { + BYTE("byte"), + SHORT("short"), + CHAR("char"), + INT("int"), + LONG("long"), + FLOAT("float"), + DOUBLE("double"), + BOOLEAN("boolean"), + + BYTE_r("Byte"), + SHORT_r("Short"), + CHAR_r("Character"), + INTEGER_r("Integer"), + LONG_r("Long"), + FLOAT_r("Float"), + DOUBLE_r("Double"), + BOOLEAN_r("Boolean"); + + private final String code; + + Type(String code) { + this.code = code; + } + + @Override + public String expand(String optParameter) { + throw new UnsupportedOperationException("Not supported."); + } + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfErrors.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfErrors.java new file mode 100644 index 00000000000..5f79cbc4aa0 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfErrors.java @@ -0,0 +1,27 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @enablePreview + * @compile/fail/ref=PrimitiveInstanceOfErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW PrimitiveInstanceOfErrors.java + */ +public class PrimitiveInstanceOfErrors { + public static boolean unboxingAndNarrowingPrimitiveNotAllowedPerCastingConversion() { + Long l_within_int_range = 42L; + Long l_outside_int_range = 999999999999999999L; + + return l_within_int_range instanceof int && !(l_outside_int_range instanceof int); + } + + public static boolean wideningReferenceConversionUnboxingAndNarrowingPrimitive(T i) { + return i instanceof byte; + } + + public static void boxingConversionsBetweenIncompatibleTypes() { + int i = 42; + + boolean ret1 = i instanceof Integer; // (Integer) i // OK and true + boolean ret2 = i instanceof Double; // error: incompatible types + boolean ret3 = i instanceof Short; // error: incompatible types + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfErrors.out b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfErrors.out new file mode 100644 index 00000000000..bf5d4a9de87 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfErrors.out @@ -0,0 +1,8 @@ +PrimitiveInstanceOfErrors.java:13:16: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Long, int) +PrimitiveInstanceOfErrors.java:13:55: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Long, int) +PrimitiveInstanceOfErrors.java:17:16: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: T, byte) +PrimitiveInstanceOfErrors.java:24:24: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Double) +PrimitiveInstanceOfErrors.java:25:24: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Short) +- compiler.note.preview.filename: PrimitiveInstanceOfErrors.java, DEFAULT +- compiler.note.preview.recompile +5 errors \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfNumericValueTests.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfNumericValueTests.java new file mode 100644 index 00000000000..4abc5d27d9f --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfNumericValueTests.java @@ -0,0 +1,275 @@ +/* + * 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. + * + * 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 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @enablePreview + * @compile PrimitiveInstanceOfNumericValueTests.java + * @run main/othervm PrimitiveInstanceOfNumericValueTests + */ +public class PrimitiveInstanceOfNumericValueTests { + + public static void main(String[] args) { + testByte(); + testShort(); + testChar(); + testInt(); + testLong(); + testFloat(); + testDouble(); + } + + public static void testByte() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof byte); + assertEquals(true, ((byte) (0)) instanceof byte); + assertEquals(true, ((byte) (Byte.MIN_VALUE)) instanceof byte); + assertEquals(false, ((short) (Short.MAX_VALUE)) instanceof byte); + assertEquals(true, ((short) (0)) instanceof byte); + assertEquals(false, ((short) (Short.MIN_VALUE)) instanceof byte); + assertEquals(false, ((char) (Character.MAX_VALUE)) instanceof byte); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof byte); + assertEquals(false, (Integer.MAX_VALUE) instanceof byte); + assertEquals(true, (0) instanceof byte); + assertEquals(false, (Integer.MIN_VALUE) instanceof byte); + assertEquals(false, (Long.MAX_VALUE) instanceof byte); + assertEquals(true, (0L) instanceof byte); + assertEquals(false, (Long.MIN_VALUE) instanceof byte); + assertEquals(false, (Float.MAX_VALUE) instanceof byte); + assertEquals(true, ((float) 0) instanceof byte); + assertEquals(false, (Float.MIN_VALUE) instanceof byte); + assertEquals(false, (Float.NaN) instanceof byte); + assertEquals(false, (Float.POSITIVE_INFINITY) instanceof byte); + assertEquals(false, (Float.NEGATIVE_INFINITY) instanceof byte); + assertEquals(false, (-0.0f) instanceof byte); + assertEquals(true, (+0.0f) instanceof byte); + assertEquals(false, (Double.MAX_VALUE) instanceof byte); + assertEquals(true, ((double) 0) instanceof byte); + assertEquals(false, (Double.MIN_VALUE) instanceof byte); + assertEquals(false, (Double.NaN) instanceof byte); + assertEquals(false, (Double.POSITIVE_INFINITY) instanceof byte); + assertEquals(false, (Double.NEGATIVE_INFINITY) instanceof byte); + assertEquals(false, (-0.0d) instanceof byte); + assertEquals(true, (+0.0d) instanceof byte); + } + public static void testShort() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof short); + assertEquals(true, ((byte) (0)) instanceof short); + assertEquals(true, ((byte) (Byte.MIN_VALUE)) instanceof short); + assertEquals(true, ((short) (Short.MAX_VALUE)) instanceof short); + assertEquals(true, ((short) (0)) instanceof short); + assertEquals(true, ((short) (Short.MIN_VALUE)) instanceof short); + assertEquals(false, ((char) (Character.MAX_VALUE)) instanceof short); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof short); + assertEquals(false, (Integer.MAX_VALUE) instanceof short); + assertEquals(true, (0) instanceof short); + assertEquals(false, (Integer.MIN_VALUE) instanceof short); + assertEquals(false, (Long.MAX_VALUE) instanceof short); + assertEquals(true, (0L) instanceof short); + assertEquals(false, (Long.MIN_VALUE) instanceof short); + assertEquals(false, (Float.MAX_VALUE) instanceof short); + assertEquals(true, ((float) 0) instanceof short); + assertEquals(false, (Float.MIN_VALUE) instanceof short); + assertEquals(false, (Float.MIN_VALUE) instanceof short); + assertEquals(false, (Float.NaN) instanceof short); + assertEquals(false, (Float.POSITIVE_INFINITY) instanceof short); + assertEquals(false, (Float.NEGATIVE_INFINITY) instanceof short); + assertEquals(false, (-0.0f) instanceof short); + assertEquals(true, (+0.0f) instanceof short); + assertEquals(false, (Double.MAX_VALUE) instanceof short); + assertEquals(true, ((double) 0) instanceof short); + assertEquals(false, (Double.MIN_VALUE) instanceof short); + assertEquals(false, (Double.NaN) instanceof short); + assertEquals(false, (Double.POSITIVE_INFINITY) instanceof short); + assertEquals(false, (Double.NEGATIVE_INFINITY) instanceof short); + assertEquals(false, (-0.0d) instanceof short); + assertEquals(true, (+0.0d) instanceof short); + } + public static void testChar() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof char); + assertEquals(true, ((byte) (0)) instanceof char); + assertEquals(false, ((byte) (Byte.MIN_VALUE)) instanceof char); + assertEquals(true, ((short) (Short.MAX_VALUE)) instanceof char); + assertEquals(true, ((short) (0)) instanceof char); + assertEquals(false, ((short) (Short.MIN_VALUE)) instanceof char); + assertEquals(true, ((char) (Character.MAX_VALUE)) instanceof char); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof char); + assertEquals(false, (Integer.MAX_VALUE) instanceof char); + assertEquals(true, (0) instanceof char); + assertEquals(false, (Integer.MIN_VALUE) instanceof char); + assertEquals(false, (Long.MAX_VALUE) instanceof char); + assertEquals(true, (0L) instanceof char); + assertEquals(false, (Long.MIN_VALUE) instanceof char); + assertEquals(false, (Float.MAX_VALUE) instanceof char); + assertEquals(true, ((float) 0) instanceof char); + assertEquals(false, (Float.MIN_VALUE) instanceof char); + assertEquals(false, (Float.NaN) instanceof char); + assertEquals(false, (Float.POSITIVE_INFINITY) instanceof char); + assertEquals(false, (Float.NEGATIVE_INFINITY) instanceof char); + assertEquals(false, (-0.0f) instanceof char); + assertEquals(true, (+0.0f) instanceof char); + assertEquals(false, (Double.MAX_VALUE) instanceof char); + assertEquals(true, ((double) 0) instanceof char); + assertEquals(false, (Double.MIN_VALUE) instanceof char); + assertEquals(false, (Double.NaN) instanceof char); + assertEquals(false, (Double.POSITIVE_INFINITY) instanceof char); + assertEquals(false, (Double.NEGATIVE_INFINITY) instanceof char); + assertEquals(false, (-0.0d) instanceof char); + assertEquals(true, (+0.0d) instanceof char); + } + public static void testInt() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof int); + assertEquals(true, ((byte) (0)) instanceof int); + assertEquals(true, ((byte) (Byte.MIN_VALUE)) instanceof int); + assertEquals(true, ((short) (Short.MAX_VALUE)) instanceof int); + assertEquals(true, ((short) (0)) instanceof int); + assertEquals(true, ((short) (Short.MIN_VALUE)) instanceof int); + assertEquals(true, ((char) (Character.MAX_VALUE)) instanceof int); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof int); + assertEquals(true, (Integer.MAX_VALUE) instanceof int); + assertEquals(true, (0) instanceof int); + assertEquals(true, (Integer.MIN_VALUE) instanceof int); + assertEquals(false, (Long.MAX_VALUE) instanceof int); + assertEquals(true, (0L) instanceof int); + assertEquals(false, (Long.MIN_VALUE) instanceof int); + assertEquals(false, (Float.MAX_VALUE) instanceof int); + assertEquals(true, ((float) 0) instanceof int); + assertEquals(false, (Float.MIN_VALUE) instanceof int); + assertEquals(false, (Float.NaN) instanceof int); + assertEquals(false, (Float.POSITIVE_INFINITY) instanceof int); + assertEquals(false, (Float.NEGATIVE_INFINITY) instanceof int); + assertEquals(false, (-0.0f) instanceof int); + assertEquals(true, (+0.0f) instanceof int); + assertEquals(false, (Double.MAX_VALUE) instanceof int); + assertEquals(true, ((double) 0) instanceof int); + assertEquals(false, (Double.MIN_VALUE) instanceof int); + assertEquals(false, (Double.NaN) instanceof int); + assertEquals(false, (Double.POSITIVE_INFINITY) instanceof int); + assertEquals(false, (Double.NEGATIVE_INFINITY) instanceof int); + assertEquals(false, (-0.0d) instanceof int); + assertEquals(true, (+0.0d) instanceof int); + } + public static void testLong() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof long); + assertEquals(true, ((byte) (0)) instanceof long); + assertEquals(true, ((byte) (Byte.MIN_VALUE)) instanceof long); + assertEquals(true, ((short) (Short.MAX_VALUE)) instanceof long); + assertEquals(true, ((short) (0)) instanceof long); + assertEquals(true, ((short) (Short.MIN_VALUE)) instanceof long); + assertEquals(true, ((char) (Character.MAX_VALUE)) instanceof long); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof long); + assertEquals(true, (Integer.MAX_VALUE) instanceof long); + assertEquals(true, (0L) instanceof long); + assertEquals(true, (Integer.MIN_VALUE) instanceof long); + assertEquals(true, (Long.MAX_VALUE) instanceof long); + assertEquals(true, (0) instanceof long); + assertEquals(true, (Long.MIN_VALUE) instanceof long); + assertEquals(false, (Float.MAX_VALUE) instanceof long); + assertEquals(true, ((float) 0) instanceof long); + assertEquals(false, (Float.MIN_VALUE) instanceof long); + assertEquals(false, (Float.NaN) instanceof long); + assertEquals(false, (Float.POSITIVE_INFINITY) instanceof long); + assertEquals(false, (Float.NEGATIVE_INFINITY) instanceof long); + assertEquals(false, (-0.0f) instanceof long); + assertEquals(true, (+0.0f) instanceof long); + assertEquals(false, (Double.MAX_VALUE) instanceof long); + assertEquals(true, ((double) 0) instanceof long); + assertEquals(false, (Double.MIN_VALUE) instanceof long); + assertEquals(false, (Double.NaN) instanceof long); + assertEquals(false, (Double.POSITIVE_INFINITY) instanceof long); + assertEquals(false, (Double.NEGATIVE_INFINITY) instanceof long); + assertEquals(false, (-0.0d) instanceof long); + assertEquals(true, (+0.0d) instanceof long); + } + public static void testFloat() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof float); + assertEquals(true, ((byte) (0) instanceof float)); + assertEquals(true, ((byte) (Byte.MIN_VALUE)) instanceof float); + assertEquals(true, ((short) (Short.MAX_VALUE)) instanceof float); + assertEquals(true, ((short) (0)) instanceof float); + assertEquals(true, ((short) (Short.MIN_VALUE)) instanceof float); + assertEquals(true, ((char) (Character.MAX_VALUE)) instanceof float); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof float); + assertEquals(false, (Integer.MAX_VALUE) instanceof float); + assertEquals(true, (0) instanceof float); + assertEquals(true, (Integer.MIN_VALUE) instanceof float); + assertEquals(false, (Long.MAX_VALUE) instanceof float); + assertEquals(true, (0L) instanceof float); + assertEquals(true, (Long.MIN_VALUE) instanceof float); + assertEquals(true, (Float.MAX_VALUE) instanceof float); + assertEquals(true, ((float) 0) instanceof float); + assertEquals(true, (Float.MIN_VALUE) instanceof float); + assertEquals(true, (Float.NaN) instanceof float); + assertEquals(true, (Float.POSITIVE_INFINITY) instanceof float); + assertEquals(true, (Float.NEGATIVE_INFINITY) instanceof float); + assertEquals(true, (-0.0f) instanceof float); + assertEquals(true, (+0.0f) instanceof float); + assertEquals(false, (Double.MAX_VALUE) instanceof float); + assertEquals(true, ((double) 0) instanceof float); + assertEquals(false, (Double.MIN_VALUE) instanceof float); + assertEquals(true, (Double.NaN) instanceof float); + assertEquals(true, (Double.POSITIVE_INFINITY) instanceof float); + assertEquals(true, (Double.NEGATIVE_INFINITY) instanceof float); + assertEquals(true, (-0.0d) instanceof float); + assertEquals(true, (+0.0d) instanceof float); + } + public static void testDouble() { + assertEquals(true, ((byte) (Byte.MAX_VALUE)) instanceof double); + assertEquals(true, ((byte) (0)) instanceof double); + assertEquals(true, ((byte) (Byte.MIN_VALUE)) instanceof double); + assertEquals(true, ((short) (Short.MAX_VALUE)) instanceof double); + assertEquals(true, ((short) (0)) instanceof double); + assertEquals(true, ((short) (Short.MIN_VALUE)) instanceof double); + assertEquals(true, ((char) (Character.MAX_VALUE)) instanceof double); + assertEquals(true, ((char) (Character.MIN_VALUE)) instanceof double); + assertEquals(true, (Integer.MAX_VALUE) instanceof double); + assertEquals(true, (0) instanceof double); + assertEquals(true, (Integer.MIN_VALUE) instanceof double); + assertEquals(false, (Long.MAX_VALUE) instanceof double); + assertEquals(true, (0L) instanceof double); + assertEquals(true, (Long.MIN_VALUE) instanceof double); + assertEquals(true, (Float.MAX_VALUE) instanceof double); + assertEquals(true, ((float) 0) instanceof double); + assertEquals(true, (Float.MIN_VALUE) instanceof double); + assertEquals(true, (Float.NaN) instanceof double); + assertEquals(true, (Float.POSITIVE_INFINITY) instanceof double); + assertEquals(true, (Float.NEGATIVE_INFINITY) instanceof double); + assertEquals(true, (-0.0f) instanceof double); + assertEquals(true, (+0.0f) instanceof double); + assertEquals(true, (Double.MAX_VALUE) instanceof double); + assertEquals(true, ((double) 0) instanceof double); + assertEquals(true, (Double.MIN_VALUE) instanceof double); + assertEquals(true, (Double.NaN) instanceof double); + assertEquals(true, (Double.POSITIVE_INFINITY) instanceof double); + assertEquals(true, (Double.NEGATIVE_INFINITY) instanceof double); + assertEquals(true, (-0.0d) instanceof double); + assertEquals(true, (+0.0d) instanceof double); + } + + static void assertEquals(boolean expected, boolean actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithRecordPatterns.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithRecordPatterns.java new file mode 100644 index 00000000000..aa4d53a8c12 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithRecordPatterns.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2023, 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 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @enablePreview + * @compile PrimitiveInstanceOfPatternOpWithRecordPatterns.java + * @run main/othervm PrimitiveInstanceOfPatternOpWithRecordPatterns + */ +public class PrimitiveInstanceOfPatternOpWithRecordPatterns { + + public static void main(String[] args) { + assertEquals(true, identityPrimitiveConversion()); + assertEquals(true, wideningPrimitiveConversion()); + assertEquals(true, narrowingPrimitiveConversion()); + assertEquals(true, wideningAndNarrowingPrimitiveConversion()); + assertEquals(true, boxingConversion()); + assertEquals(true, boxingAndWideningReferenceConversion()); + assertEquals(true, unboxing()); + assertEquals(true, unboxingWithObject()); + assertEquals(true, wideningReferenceConversionUnboxing()); + assertEquals(true, wideningReferenceConversionUnboxingAndWideningPrimitive()); + assertEquals(true, unboxingAndWideningPrimitiveExact()); + assertEquals(false, unboxingAndWideningPrimitiveNotExact()); + assertEquals(true, unboxingWhenNullAndWideningPrimitive()); + assertEquals(true, narrowingAndUnboxing()); + } + + public static boolean identityPrimitiveConversion() { + R_int r = new R_int(42); + return r instanceof R_int(int _); + } + + public static boolean wideningPrimitiveConversion() { + R_byte b = new R_byte((byte) 42); + R_short s = new R_short((short) 42); + R_char c = new R_char('a'); + + return b instanceof R_byte(int _) && s instanceof R_short(int _) && c instanceof R_char(int _); + } + + public static boolean narrowingPrimitiveConversion() { + R_long l_within_int_range = new R_long(42L); + R_long l_outside_int_range = new R_long(999999999999999999L); + + return l_within_int_range instanceof R_long(int _) && !(l_outside_int_range instanceof R_long(int _)); + } + + public static boolean wideningAndNarrowingPrimitiveConversion() { + R_byte b = new R_byte((byte) 42); + R_byte b2 = new R_byte((byte) -42); + R_char c = new R_char((char) 42); + return b instanceof R_byte(char _) && c instanceof R_char(byte _) && !(b2 instanceof R_byte(char _)); + } + + public static boolean boxingConversion() { + R_int i = new R_int(42); + + return i instanceof R_int(Integer _); + } + + public static boolean boxingAndWideningReferenceConversion() { + R_int i = new R_int(42); + return i instanceof R_int(Object _) && + i instanceof R_int(Number _) && + i instanceof R_int(Comparable _); + } + + public static boolean unboxing() { + R_Integer i = new R_Integer(Integer.valueOf(1)); + return i instanceof R_Integer(int _); + } + + public static boolean unboxingWithObject() { + R_Object o1 = new R_Object((int) 42); + R_Object o2 = new R_Object((byte) 42); + + return o1 instanceof R_Object(int i1) && + o2 instanceof R_Object(byte b1) && + !(o1 instanceof R_Object(byte b2) && + !(o2 instanceof R_Object(int i2))); + } + + public static boolean wideningReferenceConversionUnboxing() { + R_generic i = new R_generic(42); + return i instanceof R_generic(int _); + } + + public static boolean wideningReferenceConversionUnboxingAndWideningPrimitive() { + R_generic i = new R_generic(42); + return i instanceof R_generic(double _); + } + + public static boolean unboxingAndWideningPrimitiveExact() { + R_ByteValue b = new R_ByteValue(Byte.valueOf((byte)42)); + R_ShortValue s = new R_ShortValue(Short.valueOf((short)42)); + R_CharacterValue c = new R_CharacterValue(Character.valueOf('a')); + + return (b instanceof R_ByteValue(int _)) && (s instanceof R_ShortValue(int _)) && (c instanceof R_CharacterValue(int _)); + } + + public static boolean unboxingAndWideningPrimitiveNotExact() { + int smallestIntNotRepresentable = 16777217; // 2^24 + 1 + R_Integer i = new R_Integer(Integer.valueOf(smallestIntNotRepresentable)); + + return i instanceof R_Integer(float _); + } + + public static boolean unboxingWhenNullAndWideningPrimitive() { + R_ByteValue b = new R_ByteValue(null); + R_ShortValue s = new R_ShortValue(null); + R_CharacterValue c = new R_CharacterValue(null); + + return !(b instanceof R_ByteValue(int _)) && !(s instanceof R_ShortValue(int _)) && !(c instanceof R_CharacterValue(int _)); + } + + public static boolean narrowingAndUnboxing() { + R_Number n = new R_Number(Byte.valueOf((byte) 42)); + + return n instanceof R_Number(byte _); + } + + static void assertEquals(boolean expected, boolean actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } + + record R_int(int i) {} + record R_byte(byte b) {} + record R_short(short b) {} + record R_char(char c) {} + record R_long(long l) {} + record R_Integer(Integer i) {} + record R_Object(Object i) {} + record R_generic(int i) {} + record R_ByteValue(Byte b) {} + record R_ShortValue(Short s) {} + record R_CharacterValue(Character s) {} + record R_Number(Number s) {} +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java new file mode 100644 index 00000000000..352d2a8a851 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2023, 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 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @enablePreview + * @compile PrimitiveInstanceOfTypeComparisonOp.java + * @run main/othervm PrimitiveInstanceOfTypeComparisonOp + */ +public class PrimitiveInstanceOfTypeComparisonOp { + + public static void main(String[] args) { + assertEquals(true, identityPrimitiveConversion()); + assertEquals(true, wideningPrimitiveConversion()); + assertEquals(true, narrowingPrimitiveConversion()); + assertEquals(true, wideningAndNarrowingPrimitiveConversion()); + assertEquals(true, boxingConversion()); + assertEquals(true, boxingAndWideningReferenceConversion()); + assertEquals(true, unboxing()); + assertEquals(true, unboxingWithObject()); + assertEquals(true, wideningReferenceConversionUnboxing(42)); + assertEquals(true, wideningReferenceConversionUnboxingAndWideningPrimitive(42)); + assertEquals(true, unboxingAndWideningPrimitiveExact()); + assertEquals(false, unboxingAndWideningPrimitiveNotExact()); + assertEquals(true, unboxingWhenNullAndWideningPrimitive()); + assertEquals(true, narrowingAndUnboxing()); + assertEquals(true, patternExtractRecordComponent()); + assertEquals(true, exprMethod()); + assertEquals(true, exprStaticallyQualified()); + } + + public static boolean identityPrimitiveConversion() { + int i = 42; + return i instanceof int; + } + + public static boolean wideningPrimitiveConversion() { + byte b = (byte) 42; + short s = (short) 42; + char c = 'a'; + + return b instanceof int && s instanceof int && c instanceof int; + } + + public static boolean narrowingPrimitiveConversion() { + long l_within_int_range = 42L; + long l_outside_int_range = 999999999999999999L; + + return l_within_int_range instanceof int && !(l_outside_int_range instanceof int); + } + + public static boolean wideningAndNarrowingPrimitiveConversion() { + byte b = (byte) 42; + byte b2 = (byte) -42; + char c = (char) 42; + return b instanceof char && c instanceof byte && !(b2 instanceof char); + } + + public static boolean boxingConversion() { + int i = 42; + + return i instanceof Integer; + } + + public static boolean boxingAndWideningReferenceConversion() { + int i = 42; + return i instanceof Object && + i instanceof Number && + i instanceof Comparable; + } + + public static boolean unboxing() { + Integer i = Integer.valueOf(1); + return i instanceof int; + } + + public static boolean unboxingWithObject() { + Object o1 = (int) 42; + Object o2 = (byte) 42; + + return o1 instanceof int i1 && + o2 instanceof byte b1 && + !(o1 instanceof byte b2 && + !(o2 instanceof int i2)); + } + + public static boolean wideningReferenceConversionUnboxing(T i) { + return i instanceof int; + } + + public static boolean wideningReferenceConversionUnboxingAndWideningPrimitive(T i) { + return i instanceof double; + } + + public static boolean unboxingAndWideningPrimitiveExact() { + Byte b = Byte.valueOf((byte)42); + Short s = Short.valueOf((short)42); + Character c = Character.valueOf('a'); + + return (b instanceof int) && (s instanceof int) && (c instanceof int); + } + + public static boolean unboxingAndWideningPrimitiveNotExact() { + int smallestIntNotRepresentable = 16777217; // 2^24 + 1 + Integer i = Integer.valueOf(smallestIntNotRepresentable); + + return i instanceof float; + } + + public static boolean unboxingWhenNullAndWideningPrimitive() { + Byte b = null; + Short s = null; + Character c = null; + + return !(b instanceof int) && !(s instanceof int) && !(c instanceof int); + } + + public static boolean narrowingAndUnboxing() { + Number n = Byte.valueOf((byte) 42); + + return n instanceof byte; + } + + public record P(int i) { } + public static boolean patternExtractRecordComponent() { + Object p = new P(42); + if (p instanceof P(byte b)) { + return b == 42; + } + return false; + } + + public static int meth() {return 42;} + public static boolean exprMethod() { + return meth() instanceof int; + } + + public class A1 { + public static int i = 42; + } + public static boolean exprStaticallyQualified() { + return A1.i instanceof int; + } + + static void assertEquals(boolean expected, boolean actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.java new file mode 100644 index 00000000000..8f97ae2425e --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.java @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2023, 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 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @enablePreview + * @compile PrimitivePatternsSwitch.java + * @run main/othervm PrimitivePatternsSwitch + */ +public class PrimitivePatternsSwitch { + public static void main(String[] args) { + assertEquals(1, primitiveSwitch(42)); + assertEquals(2, primitiveSwitch(123)); + assertEquals(1, primitiveSwitchUnnamed(42)); + assertEquals(2, primitiveSwitchUnnamed(123)); + assertEquals(42, primitiveSwitch2()); + assertEquals(42, primitiveSwitch3()); + assertEquals(1, primitiveSwitch4(0.0f)); + assertEquals(2, primitiveSwitch4(1.0f)); + assertEquals(1, primitiveSwitchUnconditionallyExact(Byte.MAX_VALUE)); + assertEquals(42, exhaustive0()); + assertEquals(1, exhaustive1WithDefault()); + assertEquals(2, exhaustive2WithDefault()); + assertEquals(1, exhaustive1()); + assertEquals(1, exhaustive2()); + assertEquals(1, exhaustive3()); + assertEquals(1, exhaustive4()); + assertEquals(2, exhaustive5()); + assertEquals(1, exhaustive6()); + assertEquals(1, exhaustive7(true)); + assertEquals(1, exhaustive7s(true)); + assertEquals(1, exhaustive8(true)); + assertEquals(1, exhaustive9(true)); + assertEquals(1, exhaustive9(false)); + assertEquals(1, exhaustiveWithRecords1()); + assertEquals(1, exhaustiveWithRecords2()); + assertEquals(1, exhaustiveWithRecords4()); + assertEquals(1, exhaustiveWithRecords5()); + assertEquals(1, exhaustiveWithRecords6()); + assertEquals(2, ensureProperSelectionWithRecords()); + assertEquals(1, ensureProperSelectionWithRecords2()); + assertEquals(3, ensureProperSelectionWithRecords3()); + assertEquals(42, switchAndDowncastFromObjectPrimitive()); + assertEquals(42, dominationBetweenBoxedAndPrimitive()); + assertEquals(2, wideningAndUnboxing()); + assertEquals(2, wideningAndUnboxingInRecord()); + assertEquals(2, wideningAndInferredUnboxingInRecord()); + assertEquals(5f, switchOverBoxedFloat(0f)); + assertEquals(7f, switchOverBoxedFloat(1f)); + assertEquals(9f, switchOverBoxedFloat(2f)); + assertEquals(9f, switchOverBoxedFloat(2f)); + assertEquals(5f, switchOverPrimitiveDouble(0d)); + assertEquals(7f, switchOverPrimitiveDouble(1d)); + assertEquals(9f, switchOverPrimitiveDouble(2d)); + assertEquals(1, switchOverPrimitiveChar('a')); + assertEquals(-1, switchOverPrimitiveChar('x')); + assertTrue(switchOverBoxedBooleanWithUnconditional(Boolean.valueOf(true))); + assertTrue(switchOverBoxedBooleanWithUnconditional(true)); + assertTrue(!switchOverBoxedBooleanWithUnconditional(false)); + assertEquals(1, switchOverPrimitiveBooleanWithDefault(true)); + assertEquals(2, switchOverPrimitiveBooleanWithDefault(false)); + assertEquals(1, switchOverPrimitiveBoolean(true)); + assertEquals(2, switchOverPrimitiveBoolean(false)); + assertEquals(1, switchOverPrimitiveFloat(0.0f/0.0f)); + assertEquals(2, switchOverPrimitiveFloat((float) Math.pow(0.0f/0.0f, 0))); + assertEquals(3, switchOverPrimitiveFloat(0.0f)); + assertEquals(4, switchOverPrimitiveFloat(-0.0f)); + assertEquals(1, switchRedirectedExactnessMethods1('a')); + assertEquals(-1, switchRedirectedExactnessMethods1('\u03A9')); + assertEquals(1, switchRedirectedExactnessMethods2('\u03A9')); + assertEquals(-1, switchRedirectedExactnessMethods2('\uFFFF')); + assertEquals(1, switchLongAndUnconditional(32778L)); + assertEquals(2, switchLongAndUnconditional(42L)); + assertEquals(1, switchByte((byte) 128)); + assertEquals(2, switchByte((byte) 42)); + assertEquals(1, switchShort((short) 32778)); + assertEquals(2, switchShort((short) 42)); + assertEquals(1, switchInt(32778)); + assertEquals(2, switchInt(42)); + assertEquals(1, switchChar( '\u0010')); + assertEquals(2, switchChar('a')); + assertEquals(1, testIntInNonEnhancedSwitchStatement(1)); + assertEquals(0, testIntInNonEnhancedSwitchStatement(0)); + assertEquals(1, testFloatInEnhancedSwitchStatement(1.0f)); + assertEquals(0, testFloatInEnhancedSwitchStatement(0.0f)); + assertEquals(1, testDoubleInEnhancedSwitchStatement(1.0d)); + assertEquals(0, testDoubleInEnhancedSwitchStatement(0.0d)); + assertEquals(1, testLongInEnhancedSwitchStatement(1l)); + assertEquals(0, testLongInEnhancedSwitchStatement(0l)); + assertEquals(1, testBooleanInEnhancedSwitchStatement(true)); + assertEquals(0, testBooleanInEnhancedSwitchStatement(false)); + assertEquals(1, testByteWrapperToIntUnconditionallyExact()); + assertEquals(1, testIntegerWrapperToFloat()); + assertEquals(-1, testIntegerWrapperToFloatInexact()); + } + + public static int primitiveSwitch(int i) { + return switch (i) { + case int j when j == 42-> 1; + case int j -> 2; + }; + } + + public static int primitiveSwitchUnnamed(int i) { + return switch (i) { + case int _ when i == 42-> 1; + case int _ -> 2; + }; + } + + public static int primitiveSwitch2() { + Object o = Integer.valueOf(42); + switch (o) { + case int i: return i; + default: break; + } + return -1; + } + + public static int primitiveSwitch3() { + int i = 42; + switch (i) { + case Integer ii: return ii; + } + } + + public static int primitiveSwitch4(float f) { + return switch (f) { + case 0.0f -> 1; + case Float fi when fi == 1f -> 2; + case Float fi -> 3; + }; + } + + public static int primitiveSwitchUnconditionallyExact(byte c) { + return switch (c) { + case short _ -> 1; + }; + } + + public static int exhaustive0() { + Integer i = 42; + switch (i) { + case int j: return j; + } + } + + public static int exhaustive1WithDefault() { + int i = 42; + return switch (i) { + case byte b -> 1; + default -> 2; + }; + } + + public static int exhaustive2WithDefault() { + int i = 30000; + return switch (i) { + case byte b -> 1; + case short s -> 2; + default -> 3; + }; + } + + public static int exhaustive1() { + int i = 42; + return switch (i) { + case Integer p -> 1; + }; + } + + public static int exhaustive2() { + int i = 42; + return switch (i) { + case long d -> 1; + }; + } + + public static int exhaustive3() { + int i = 42; + return switch (i) { + case double d -> 1; + }; + } + + public static int exhaustive4() { + int i = 127; + return switch (i) { + case byte b -> 1; + case double d -> 2; + }; + } + + public static int exhaustive5() { + int i = 127 + 1; + return switch (i) { + case byte b -> 1; + case double d -> 2; + }; + } + + public static int exhaustive6() { + Integer i = Integer.valueOf(42); + return switch (i) { + case int p -> 1; + }; + } + + public static int exhaustive7(Boolean b) { + switch (b) { + case true: return 1; + case false: return 2; // with reminder, null, OK + } + } + + public static int exhaustive7s(Boolean b) { + return switch (b) { + case true -> 1; + case false -> 2; // with reminder, null, OK + }; + } + + public static int exhaustive8(Boolean b) { + switch (b) { + case boolean bb: return 1; + } + } + + public static int exhaustive9(boolean b) { + switch (b) { + case Boolean bb: return 1; + } + } + + public static int exhaustiveWithRecords1() { + R_int r = new R_int(42); + return switch (r) { + // exhaustive, because Integer exhaustive at type int + case R_int(Integer x) -> 1; + }; + } + + public static int exhaustiveWithRecords2() { + R_int r = new R_int(42); + return switch (r) { + // exhaustive, because double unconditional at int + case R_int(double x) -> 1; + }; + } + + public static int exhaustiveWithRecords4() { + R_Integer r = new R_Integer(42); + return switch (r) { + // exhaustive, because R_Integer(int) exhaustive at type R_Integer(Integer), because int exhaustive at type Integer + case R_Integer(int x) -> 1; + }; + } + + public static int exhaustiveWithRecords5() { + R_Integer r = new R_Integer(42); + return switch (r) { + // exhaustive, because double exhaustive at Integer + case R_Integer(double x) -> 1; + }; + } + + public static int exhaustiveWithRecords6() { + R_int r = new R_int(42); + return switch (r) { + case R_int(byte x) -> 1; + case R_int(int x) -> 2; + }; + } + + public static int ensureProperSelectionWithRecords() { + R_int r = new R_int(4242); + return switch (r) { + case R_int(byte x) -> 1; + case R_int(int x) -> 2; + }; + } + + public static int ensureProperSelectionWithRecords2() { + R_double r = new R_double(42); + switch (r) { + case R_double(int i): + return meth_int(i); + case R_double(double x): + return meth_double(x); + } + } + + public static int ensureProperSelectionWithRecords3() { + R_int r = new R_int(4242); + return switch (r) { + case R_int(byte x) -> 1; + case R_int(int x) when x == 236 -> 2; + case R_int(int x) -> 3; + }; + } + + public static int meth_int(int i) { return 1; } + public static int meth_double(double d) { return 2;} + + public static int switchAndDowncastFromObjectPrimitive() { + Object i = 42; + return switch (i) { + case Integer ib -> ib; + default -> -1; + }; + } + + public static int dominationBetweenBoxedAndPrimitive() { + Object i = 42; + return switch (i) { + case Integer ib -> ib; + case byte ip -> ip; + default -> -1; + }; + } + + static int wideningAndUnboxing() { + Number o = Integer.valueOf(42); + return switch (o) { + case byte b -> 1; + case int i -> 2; + case float f -> 3; + default -> 4; + }; + } + + static int wideningAndUnboxingInRecord() { + Box box = new Box<>(Integer.valueOf(42)); + return switch (box) { + case Box(byte b) -> 1; + case Box(int i) -> 2; + case Box(float f) -> 3; + default -> 4; + }; + } + + static int wideningAndInferredUnboxingInRecord() { + Box box = new Box<>(Integer.valueOf(42)); + return switch (box) { + case Box(byte b) -> 1; + case Box(int i) -> 2; + case Box(float f) -> 3; + default -> 4; + }; + } + + public static float switchOverBoxedFloat(Float f) { + return switch (f) { + case 0f -> 5f + 0f; + case Float fi when fi == 1f -> 6f + fi; + case Float fi -> 7f + fi; + }; + } + + public static double switchOverPrimitiveDouble(Double d) { + return switch (d) { + case 0d -> 5d + 0d; + case Double di when di == 1d -> 6d + di; + case Double di -> 7d + di; + }; + } + + public static boolean switchOverBoxedBooleanWithUnconditional(Boolean b) { + return switch (b) { + case true -> true; + case Boolean bi -> bi; + }; + } + + public static int switchOverPrimitiveBooleanWithDefault(boolean b) { + return switch (b) { + case true -> 1; + default -> 2; + }; + } + + public static int switchOverPrimitiveBoolean(boolean b) { + return switch (b) { + case true -> 1; + case false -> 2; + }; + } + + public static int switchOverPrimitiveChar(char c) { + return switch (c) { + case 'a' -> 1; + default -> -1; + }; + } + + public static final float NaNconstant = Float.NaN; + public static int switchOverPrimitiveFloat(float f) { + return switch (f) { + case NaNconstant -> 1; + case 1.0f -> 2; + case 0.0f -> 3; + case -0.0f -> 4; + default -> -1; + }; + } + + // tests that Exactness.char_byte is properly redirected to int_byte + public static int switchRedirectedExactnessMethods1(char c) { + return switch (c) { + case byte _ -> 1; + default -> -1; + }; + } + + // tests that Exactness.char_short is properly redirected to int_short + public static int switchRedirectedExactnessMethods2(char c) { + return switch (c) { + case short _ -> 1; + default -> -1; + }; + } + + // tests that Exactness.short_byte is properly redirected to int_byte + public static int switchRedirectedExactnessMethods2(short c) { + return switch (c) { + case byte _ -> 1; + default -> -1; + }; + } + + public static int switchLongAndUnconditional(long l) { + return switch (l) { + case 32778L -> 1; + case long c -> 2; + }; + } + + public static int switchByte(byte b) { + return switch (b) { + case (byte)128 -> 1; + case byte c -> 2; + }; + } + + public static int switchShort(short s) { + return switch (s) { + case (short)32778 -> 1; + case short c -> 2; + }; + } + + public static int switchInt(int i) { + return switch (i) { + case 32778 -> 1; + case int c -> 2; + }; + } + + public static int switchChar(char c) { + return switch (c) { + case '\u0010' -> 1; + case char cc -> 2; + }; + } + + public static int testIntInNonEnhancedSwitchStatement(int v1) { + int i = 0; + switch (v1) { + case 1: + i = 1; + break; + } + return i; + } + + public static int testFloatInEnhancedSwitchStatement(float v1) { + int i = 0; + switch (v1) { + case 1.0f: + i = 1; + break; + default: + i = 0; + } + return i; + } + + public static int testDoubleInEnhancedSwitchStatement(double v1) { + int i = 0; + switch (v1) { + case 1d: + i = 1; + break; + default: + i = 0; + } + return i; + } + + public static int testLongInEnhancedSwitchStatement(long v1) { + int i = 0; + switch (v1) { + case 1l: + i = 1; + break; + default: + i = 0; + } + return i; + } + + public static int testBooleanInEnhancedSwitchStatement(boolean v1) { + int i = 0; + switch (v1) { + case true: + i = 1; + break; + default: + i = 0; + } + return i; + } + + public static int testByteWrapperToIntUnconditionallyExact() { + Byte b = Byte.valueOf((byte) 42); + return switch (b) { + case int p -> 1; + }; + } + + public static int testIntegerWrapperToFloat() { + Integer i = Integer.valueOf(42); + return switch (i) { + case float p -> 1; + default -> -1; + }; + } + + public static int testIntegerWrapperToFloatInexact() { + Integer i = Integer.valueOf(Integer.MAX_VALUE); + return switch (i) { + case float p -> 1; + default -> -1; + }; + } + + + record R_Integer(Integer x) {} + record R_int(int x) {} + record R_double(double x) {} + record Box(N num) {} + + static void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } + + static void assertEquals(float expected, float actual) { + if (Float.compare(expected, actual) != 0) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } + + static void assertEquals(double expected, double actual) { + if (Double.compare(expected, actual) != 0) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } + + static void assertTrue(boolean actual) { + if (!actual) { + throw new AssertionError("Expected: true, but got false"); + } + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java new file mode 100644 index 00000000000..71e6193275b --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java @@ -0,0 +1,220 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8304487 + * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) + * @enablePreview + * @compile/fail/ref=PrimitivePatternsSwitchErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW PrimitivePatternsSwitchErrors.java + */ +public class PrimitivePatternsSwitchErrors { + record R_int(int x) {} + + public static void dominationBetweenPrimitivePatterns() { + int i = 42; + switch (i) { + case short s -> System.out.println("its a short"); + case byte b -> System.out.println("its a byte"); // Error - dominated! + default -> System.out.println("any other integral value"); + } + } + + public static int dominationWithRecordPatterns() { + R_int r = new R_int(42); + return switch (r) { + case R_int(int x) -> 1; + case R_int(byte x) -> 2; // Error - dominated! + }; + } + + public static int inconvertibleNestedComponent() { + R_int r = new R_int(42); + return switch (r) { + case R_int(Long x) -> 1; // inconvertible + }; + } + + public static int nonExhaustive1() { + int i = 42; + return switch (i) { // Error - not exhaustive + case short s -> s; + }; + } + + public static int nonExhaustive2() { + int i = 42; + return switch (i) { // Error - not exhaustive + case byte b -> 1; + case short s -> 2; + }; + } + + public static int nonExhaustive3() { + int i = 42; + return switch (i) { // Error - not exhaustive + case byte b -> 1; + case float f -> 2; + }; + } + + public static int dominationBetweenBoxedAndPrimitive() { + int i = 42; + return switch (i) { + case Integer ib -> ib; + case byte ip -> ip; // Error - dominated! + }; + } + + public static int constantDominatedWithPrimitivePattern() { + int i = 42; + return switch (i) { + case int j -> 42; + case 43 -> -1; // Error - dominated! + }; + } + + public static int constantDominatedWithFloatPrimitivePattern() { + float f = 42.0f; + return switch (f) { + case Float ff -> 42; + case 43.0f -> -1; // Error - dominated! + }; + } + + void switchLongOverByte(byte b) { + switch (b) { + case 0L: return ; + } + } + + void switchOverPrimitiveFloatFromInt(float f) { + switch (f) { + case 16777216: + break; + case 16777217: + break; + default: + break; + } + } + + void switchOverNotRepresentableFloat(Float f) { + switch (f) { + case 1.0f: + break; + case 0.999999999f: + break; + case Float fi: + break; + } + } + + int switchOverPrimitiveBooleanExhaustiveWithNonPermittedDefault(boolean b) { + return switch (b) { + case true -> 1; + case false -> 2; + default -> 3; + }; + } + + int switchOverPrimitiveBooleanExhaustiveWithNonPermittedDefaultStatement(boolean b) { + switch (b) { + case true: return 1; + case false: return 2; + default: return 3; + } + } + + int switchOverPrimitiveBooleanExhaustiveWithNonPermittedUnconditionalStatement(boolean b) { + switch (b) { + case true: return 1; + case false: return 2; + case boolean bb: return 3; // error + } + } + + void switchCombinationsNonIntegral() { + float f = 0f; + long l = 0L; + double d = 0d; + Float fB = 0F; + Long lB = 0L; + Double dB = 0D; + + switch (f) { + case 1l: return; + case 2f: return; + case 3d: return; + default: + } + + switch (l) { + case 1l: return; + case 2f: return; + case 3d: return; + default: + } + + switch (d) { + case 1l: return; + case 2f: return; + case 3d: return; + default: + } + + switch (fB) { + case 1l: return; + case 2f: return; + case 3d: return; + default: + } + + switch (lB) { + case 1l: return; + case 2f: return; + case 3d: return; + default: + } + + switch (dB) { + case 1l: return; + case 2f: return; + case 3d: return; + default: + } + } + + int switchOverPrimitiveBooleanExhaustiveWithNonPermittedUnconditional(boolean b) { + return switch (b) { + case true -> 1; + case false -> 2; + case boolean bb -> 3; // error + }; + } + + int duplicateUnconditionalWithPrimitives(int i) { + return switch (i) { + case int ii -> 1; + case long l -> 2; // error + }; + } + + int booleanSingleCase1(boolean b) { + return switch (b) { + case true -> 1; + }; + } + + int booleanSingleCase2(boolean b) { + switch (b) { + case true: return 1; + } + } + + void nullAndPrimitive() { + int i = 42; + switch (i) { + case short s -> System.out.println("its a short"); + case null -> System.out.println("oops"); + default -> System.out.println("any other integral value"); + } + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out new file mode 100644 index 00000000000..2026afe58c2 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out @@ -0,0 +1,37 @@ +PrimitivePatternsSwitchErrors.java:15:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:24:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:31:24: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Long) +PrimitivePatternsSwitchErrors.java:62:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:70:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:78:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:84:18: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: long, byte) +PrimitivePatternsSwitchErrors.java:90:18: compiler.err.constant.label.not.compatible: int, float +PrimitivePatternsSwitchErrors.java:92:18: compiler.err.constant.label.not.compatible: int, float +PrimitivePatternsSwitchErrors.java:103:13: compiler.err.duplicate.case.label +PrimitivePatternsSwitchErrors.java:114:13: compiler.err.default.and.both.boolean.values +PrimitivePatternsSwitchErrors.java:122:13: compiler.err.default.and.both.boolean.values +PrimitivePatternsSwitchErrors.java:130:18: compiler.err.unconditional.pattern.and.both.boolean.values +PrimitivePatternsSwitchErrors.java:143:18: compiler.err.constant.label.not.compatible: long, float +PrimitivePatternsSwitchErrors.java:145:18: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: double, float) +PrimitivePatternsSwitchErrors.java:151:18: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: float, long) +PrimitivePatternsSwitchErrors.java:152:18: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: double, long) +PrimitivePatternsSwitchErrors.java:157:18: compiler.err.constant.label.not.compatible: long, double +PrimitivePatternsSwitchErrors.java:158:18: compiler.err.constant.label.not.compatible: float, double +PrimitivePatternsSwitchErrors.java:164:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: long, java.lang.Float) +PrimitivePatternsSwitchErrors.java:166:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: double, java.lang.Float) +PrimitivePatternsSwitchErrors.java:172:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: float, java.lang.Long) +PrimitivePatternsSwitchErrors.java:173:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: double, java.lang.Long) +PrimitivePatternsSwitchErrors.java:178:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: long, java.lang.Double) +PrimitivePatternsSwitchErrors.java:179:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: float, java.lang.Double) +PrimitivePatternsSwitchErrors.java:189:18: compiler.err.unconditional.pattern.and.both.boolean.values +PrimitivePatternsSwitchErrors.java:196:18: compiler.err.duplicate.unconditional.pattern +PrimitivePatternsSwitchErrors.java:216:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: compiler.misc.type.null, int) +PrimitivePatternsSwitchErrors.java:30:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:37:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:44:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:52:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:201:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:207:9: compiler.err.not.exhaustive.statement +- compiler.note.preview.filename: PrimitivePatternsSwitchErrors.java, DEFAULT +- compiler.note.preview.recompile +34 errors \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/SourceLevelChecks.java b/test/langtools/tools/javac/patterns/SourceLevelChecks.java index cca3bf311c5..3b0fece12bc 100644 --- a/test/langtools/tools/javac/patterns/SourceLevelChecks.java +++ b/test/langtools/tools/javac/patterns/SourceLevelChecks.java @@ -128,7 +128,210 @@ public class SourceLevelChecks extends TestRunner { "1 error"); } + @Test + public void testNestedPrimitive1(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(R r) { + if (r instanceof R(byte b)) { + } + } + record R(int i) {} + } + """, + 21, + "Test.java:4:28: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testNestedPrimitive2(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(R r) { + switch (r) { + case R(byte b) -> {} + default -> {} + } + } + record R(int i) {} + } + """, + 21, + "Test.java:5:20: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testNestedPrimitive3(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(R r) { + if (r instanceof R(long l)) { + } + } + record R(int i) {} + } + """, + 21, + "Test.java:4:28: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testNestedPrimitive4(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(R r) { + switch (r) { + case R(long l) -> {} + default -> {} + } + } + record R(int i) {} + } + """, + 21, + "Test.java:5:20: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testPrimitive2Reference1(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(int i) { + if (i instanceof Integer j) {} + } + } + """, + 21, + "Test.java:4:13: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testPrimitive2Reference2(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(int i) { + switch (i) { + case Integer j -> {} + } + } + } + """, + 21, + "Test.java:5:18: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testReference2Primitive1(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(Integer i) { + if (i instanceof int j) {} + } + } + """, + 21, + "Test.java:4:26: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testReference2Primitive2(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(Integer i) { + switch (i) { + case int j -> {} + } + } + } + """, + 21, + "Test.java:5:18: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testPrimitivePatternObject1(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(Object o) { + if (o instanceof int j) {} + } + } + """, + 21, + "Test.java:4:26: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testPrimitivePatternObject2(Path base) throws Exception { + doTest(base, + """ + package test; + public class Test { + private void test(Object o) { + switch (o) { + case int j -> {} + } + } + record R(int i) {} + } + """, + 21, + "Test.java:5:18: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + + @Test + public void testOtherPrimitives(Path base) throws Exception { + for (String type : new String[] {"boolean", "long", "float", "double"}) { + doTest(base, + """ + package test; + public class Test { + private void test($type v) { + switch (v) { + default -> {} + } + } + } + """.replace("$type", type), + 21, + "Test.java:4:16: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.primitive.patterns)", + "1 error"); + } + } + private void doTest(Path base, String testCode, String... expectedErrors) throws IOException { + doTest(base, testCode, 17, expectedErrors); + } + + private void doTest(Path base, String testCode, int sourceLevel, String... expectedErrors) throws IOException { Path current = base.resolve("."); Path src = current.resolve("src"); Path classes = current.resolve("classes"); @@ -138,7 +341,7 @@ public class SourceLevelChecks extends TestRunner { var log = new JavacTask(tb) - .options("--release", "17", + .options("--release", "" + sourceLevel, "-XDrawDiagnostics") .outdir(classes) .files(tb.findJavaFiles(src)) diff --git a/test/langtools/tools/javac/patterns/SwitchErrors.java b/test/langtools/tools/javac/patterns/SwitchErrors.java index 68dbe67d563..6f93dbb192a 100644 --- a/test/langtools/tools/javac/patterns/SwitchErrors.java +++ b/test/langtools/tools/javac/patterns/SwitchErrors.java @@ -127,12 +127,6 @@ public class SwitchErrors { default: break; } } - void primitivePattern(Object o) { - switch (o) { - case int i: break; - default: break; - } - } void patternAndDefault1(Object o) { switch (o) { case String s, default: break; @@ -239,16 +233,6 @@ public class SwitchErrors { case CharSequence cs: break; } } - void primitiveToReference(int i) { - switch (i) { - case Integer j: break; - } - } - void referenceToPrimitive(Integer i) { - switch (i) { - case int j: break; - } - } void nullAndParenthesized1(Object o) { record R(Object o) {} switch (o) { diff --git a/test/langtools/tools/javac/patterns/SwitchErrors.out b/test/langtools/tools/javac/patterns/SwitchErrors.out index 413b33a73d3..9eea96ee272 100644 --- a/test/langtools/tools/javac/patterns/SwitchErrors.out +++ b/test/langtools/tools/javac/patterns/SwitchErrors.out @@ -1,18 +1,18 @@ SwitchErrors.java:66:18: compiler.err.default.label.not.allowed SwitchErrors.java:72:18: compiler.err.default.label.not.allowed SwitchErrors.java:72:27: compiler.err.default.label.not.allowed -SwitchErrors.java:138:28: compiler.err.default.label.not.allowed -SwitchErrors.java:144:18: compiler.err.default.label.not.allowed -SwitchErrors.java:149:18: compiler.err.default.label.not.allowed -SwitchErrors.java:154:18: compiler.err.default.label.not.allowed -SwitchErrors.java:213:29: compiler.err.default.label.not.allowed -SwitchErrors.java:220:32: compiler.err.default.label.not.allowed -SwitchErrors.java:227:32: compiler.err.default.label.not.allowed -SwitchErrors.java:283:20: compiler.err.illegal.start.of.type -SwitchErrors.java:286:28: compiler.err.illegal.start.of.type +SwitchErrors.java:132:28: compiler.err.default.label.not.allowed +SwitchErrors.java:138:18: compiler.err.default.label.not.allowed +SwitchErrors.java:143:18: compiler.err.default.label.not.allowed +SwitchErrors.java:148:18: compiler.err.default.label.not.allowed +SwitchErrors.java:207:29: compiler.err.default.label.not.allowed +SwitchErrors.java:214:32: compiler.err.default.label.not.allowed +SwitchErrors.java:221:32: compiler.err.default.label.not.allowed +SwitchErrors.java:267:20: compiler.err.illegal.start.of.type +SwitchErrors.java:270:28: compiler.err.illegal.start.of.type +SwitchErrors.java:290:42: compiler.err.expected2: :, -> +SwitchErrors.java:299:45: compiler.err.expected2: :, -> SwitchErrors.java:306:42: compiler.err.expected2: :, -> -SwitchErrors.java:315:45: compiler.err.expected2: :, -> -SwitchErrors.java:322:42: compiler.err.expected2: :, -> SwitchErrors.java:11:18: compiler.err.constant.label.not.compatible: java.lang.String, java.lang.Object SwitchErrors.java:17:18: compiler.err.constant.label.not.compatible: int, java.lang.Object SwitchErrors.java:23:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Integer) @@ -36,30 +36,27 @@ SwitchErrors.java:108:18: compiler.err.flows.through.to.pattern SwitchErrors.java:113:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Integer) SwitchErrors.java:119:18: compiler.err.instanceof.reifiable.not.safe: java.util.List, java.util.List SwitchErrors.java:125:18: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.class, SwitchErrors, null) -SwitchErrors.java:132:18: compiler.err.type.found.req: int, (compiler.misc.type.req.class.array) -SwitchErrors.java:143:18: compiler.err.flows.through.from.pattern -SwitchErrors.java:149:27: compiler.err.flows.through.from.pattern -SwitchErrors.java:155:18: compiler.err.flows.through.to.pattern -SwitchErrors.java:167:18: compiler.err.pattern.expected -SwitchErrors.java:173:78: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null) -SwitchErrors.java:179:73: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null) -SwitchErrors.java:186:21: compiler.err.invalid.case.label.combination -SwitchErrors.java:197:13: compiler.err.unconditional.pattern.and.default -SwitchErrors.java:195:29: compiler.err.flows.through.from.pattern -SwitchErrors.java:204:24: compiler.err.invalid.case.label.combination -SwitchErrors.java:220:21: compiler.err.invalid.case.label.combination -SwitchErrors.java:227:29: compiler.err.flows.through.from.pattern -SwitchErrors.java:239:18: compiler.err.duplicate.unconditional.pattern -SwitchErrors.java:244:18: compiler.err.prob.found.req: (compiler.misc.not.applicable.types: int, java.lang.Integer) -SwitchErrors.java:249:18: compiler.err.type.found.req: int, (compiler.misc.type.req.class.array) -SwitchErrors.java:255:24: compiler.err.invalid.case.label.combination -SwitchErrors.java:262:24: compiler.err.invalid.case.label.combination -SwitchErrors.java:269:18: compiler.err.flows.through.from.pattern -SwitchErrors.java:276:18: compiler.err.flows.through.from.pattern -SwitchErrors.java:292:49: compiler.err.cant.resolve.location.args: kindname.method, length, , , (compiler.misc.location: kindname.class, java.lang.Object, null) -SwitchErrors.java:294:55: compiler.err.cant.resolve.location.args: kindname.method, length, , , (compiler.misc.location: kindname.class, java.lang.Object, null) -SwitchErrors.java:300:26: compiler.err.pattern.type.cannot.infer -SwitchErrors.java:315:21: compiler.err.invalid.case.label.combination +SwitchErrors.java:137:18: compiler.err.flows.through.from.pattern +SwitchErrors.java:143:27: compiler.err.flows.through.from.pattern +SwitchErrors.java:149:18: compiler.err.flows.through.to.pattern +SwitchErrors.java:161:18: compiler.err.pattern.expected +SwitchErrors.java:167:78: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null) +SwitchErrors.java:173:73: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null) +SwitchErrors.java:180:21: compiler.err.invalid.case.label.combination +SwitchErrors.java:191:13: compiler.err.unconditional.pattern.and.default +SwitchErrors.java:189:29: compiler.err.flows.through.from.pattern +SwitchErrors.java:198:24: compiler.err.invalid.case.label.combination +SwitchErrors.java:214:21: compiler.err.invalid.case.label.combination +SwitchErrors.java:221:29: compiler.err.flows.through.from.pattern +SwitchErrors.java:233:18: compiler.err.duplicate.unconditional.pattern +SwitchErrors.java:239:24: compiler.err.invalid.case.label.combination +SwitchErrors.java:246:24: compiler.err.invalid.case.label.combination +SwitchErrors.java:253:18: compiler.err.flows.through.from.pattern +SwitchErrors.java:260:18: compiler.err.flows.through.from.pattern +SwitchErrors.java:276:49: compiler.err.cant.resolve.location.args: kindname.method, length, , , (compiler.misc.location: kindname.class, java.lang.Object, null) +SwitchErrors.java:278:55: compiler.err.cant.resolve.location.args: kindname.method, length, , , (compiler.misc.location: kindname.class, java.lang.Object, null) +SwitchErrors.java:284:26: compiler.err.pattern.type.cannot.infer +SwitchErrors.java:299:21: compiler.err.invalid.case.label.combination SwitchErrors.java:10:9: compiler.err.not.exhaustive.statement SwitchErrors.java:16:9: compiler.err.not.exhaustive.statement SwitchErrors.java:22:9: compiler.err.not.exhaustive.statement @@ -70,6 +67,6 @@ SwitchErrors.java:87:9: compiler.err.not.exhaustive.statement SwitchErrors.java:92:9: compiler.err.not.exhaustive.statement SwitchErrors.java:98:9: compiler.err.not.exhaustive.statement SwitchErrors.java:105:9: compiler.err.not.exhaustive.statement -SwitchErrors.java:159:9: compiler.err.not.exhaustive.statement -SwitchErrors.java:232:9: compiler.err.not.exhaustive.statement -74 errors \ No newline at end of file +SwitchErrors.java:153:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:226:9: compiler.err.not.exhaustive.statement +71 errors diff --git a/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.java b/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.java deleted file mode 100644 index 590125b0e37..00000000000 --- a/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * @test /nodynamiccopyright/ - * @bug 8206986 - * @summary Verify switch over boolean/long/float/double is not allowed. - * @compile/fail/ref=SwitchNoExtraTypes.out -XDrawDiagnostics SwitchNoExtraTypes.java - */ - -public class SwitchNoExtraTypes { - - private void switchBoolean(boolean b) { - switch (b) { - case true: return ; - } - } - - private void switchLong(long l) { - switch (l) { - case 0: return ; - } - } - - private void switchFloat(float f) { - switch (f) { - case 0: return ; - } - } - - private void switchDouble(double d) { - switch (d) { - case 0: return ; - } - } - -} diff --git a/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out b/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out deleted file mode 100644 index 32854d2de20..00000000000 --- a/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out +++ /dev/null @@ -1,5 +0,0 @@ -SwitchNoExtraTypes.java:11:16: compiler.err.selector.type.not.allowed: boolean -SwitchNoExtraTypes.java:17:16: compiler.err.selector.type.not.allowed: long -SwitchNoExtraTypes.java:23:16: compiler.err.selector.type.not.allowed: float -SwitchNoExtraTypes.java:29:16: compiler.err.selector.type.not.allowed: double -4 errors diff --git a/test/micro/org/openjdk/bench/jdk/preview/patterns/Exactness.java b/test/micro/org/openjdk/bench/jdk/preview/patterns/Exactness.java new file mode 100644 index 00000000000..675f5bda398 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/preview/patterns/Exactness.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023, 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. + */ +package org.openjdk.bench.jdk.preview.patterns; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Tests Exactness methods + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations=5, time=1) +@Measurement(iterations=5, time=1) +@Threads(2) +@Fork(value = 1, + jvmArgsPrepend = {"-Djmh.blackhole.mode=COMPILER", + "--enable-preview"}) +@State(Scope.Thread) +@SuppressWarnings("preview") +public class Exactness { + + private static boolean int_float_based_on_leading_trailing(int n) { + if (n == Integer.MIN_VALUE) + return true; + n = Math.abs(n); + return Float.PRECISION >= // 24 + (32 - (Integer.numberOfLeadingZeros(n) + + Integer.numberOfTrailingZeros(n))) ; + } + @Benchmark + public void test_int_float_based_on_leading_trailing(Blackhole bh) { + for(int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) { + bh.consume(int_float_based_on_leading_trailing(n)); + } + } + + private static boolean int_float_based_on_filtering(int n) { + return n == (int)(float)n && n != Integer.MAX_VALUE; + } + @Benchmark + public void test_int_float_based_on_filtering(Blackhole bh) { + for(int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) { + bh.consume(int_float_based_on_filtering(n)); + } + } + + private static boolean long_float_based_on_leading_trailing(long n) { + if (n == Long.MIN_VALUE) + return true; + n = Math.abs(n); + return Float.PRECISION >= // 24 + (64 - (Long.numberOfLeadingZeros(n) + + Long.numberOfTrailingZeros(n))) ; + } + @Benchmark + public void test_long_float_based_on_leading_trailing(Blackhole bh) { + for(int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) { + bh.consume(long_float_based_on_leading_trailing(n)); + } + } + + private static boolean long_float_based_on_filtering(long n) { + return n == (long)(float)n && n != Long.MAX_VALUE; + } + @Benchmark + public void test_long_float_based_on_filtering(Blackhole bh) { + for(int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) { + bh.consume(long_float_based_on_filtering(n)); + } + } + + private static boolean long_double_based_on_leading_trailing(long n) { + if (n == Long.MIN_VALUE) + return true; + n = Math.abs(n); + return Double.PRECISION >= // 53 + (64 - (Long.numberOfLeadingZeros(n) + + Long.numberOfTrailingZeros(n))) ; + } + @Benchmark + public void test_long_double_based_on_leading_trailing(Blackhole bh) { + for(int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) { + bh.consume(long_double_based_on_leading_trailing(n)); + } + } + + private static boolean long_double_based_on_filtering(long n) { + return n == (long)(double)n && n != Long.MAX_VALUE; + } + @Benchmark + public void test_long_double_based_on_filtering(Blackhole bh) { + for(int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) { + bh.consume(long_double_based_on_filtering(n)); + } + } + + private static boolean float_int_based_on_compare(float n) { + return Double.compare((double)n, (double)((int)n)) == 0; + } + @Benchmark + public void test_float_int_based_on_compare(Blackhole bh) { + float n = -Float.MAX_VALUE; + while (n <= Float.MAX_VALUE) { + bh.consume(float_int_based_on_compare(n)); + n = Math.nextUp(n); + } + } + + private static boolean isNegativeZero(float n) { + return Float.floatToRawIntBits(n) == Integer.MIN_VALUE; + } + private static boolean float_int_based_on_filtering(float n) { + return n == (float)(int)n && n != 0x1p31f && !isNegativeZero(n); + } + @Benchmark + public void test_float_int_based_on_filtering(Blackhole bh) { + float n = -Float.MAX_VALUE; + while (n <= Float.MAX_VALUE) { + bh.consume(float_int_based_on_filtering(n)); + n = Math.nextUp(n); + } + } +}