diff --git a/make/CompileInterimLangtools.gmk b/make/CompileInterimLangtools.gmk index e860169891f..f0312a5e8fa 100644 --- a/make/CompileInterimLangtools.gmk +++ b/make/CompileInterimLangtools.gmk @@ -49,7 +49,8 @@ TARGETS += $(patsubst %, $(BUILDTOOLS_OUTPUTDIR)/gensrc/%/module-info.java, \ $(INTERIM_LANGTOOLS_MODULES)) $(eval $(call SetupCopyFiles, COPY_PREVIEW_FEATURES, \ - FILES := $(TOPDIR)/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java, \ + FILES := $(TOPDIR)/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java \ + $(TOPDIR)/src/java.base/share/classes/jdk/internal/javac/NoPreview.java, \ DEST := $(BUILDTOOLS_OUTPUTDIR)/gensrc/java.base.interim/jdk/internal/javac/, \ )) diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java new file mode 100644 index 00000000000..a396b092d8c --- /dev/null +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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; + +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Stream; + +import jdk.internal.javac.PreviewFeature; + +import static java.util.Objects.requireNonNull; + +/** + * Bootstrap methods for linking {@code invokedynamic} call sites that implement + * the selection functionality of the {@code switch} statement. The bootstraps + * take additional static arguments corresponding to the {@code case} labels + * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}. + * + * @since 17 + */ +@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING) +public class SwitchBootstraps { + + private SwitchBootstraps() {} + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final MethodHandle DO_SWITCH; + + static { + try { + DO_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doSwitch", + MethodType.methodType(int.class, Object.class, int.class, Object[].class)); + } + catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on a target of a reference type. The static + * arguments are an array of case labels which must be non-null and of type + * {@code String} or {@code Integer} or {@code Class}. + *

+ * The type of the returned {@code CallSite}'s method handle will have + * a return type of {@code int}. It has two parameters: the first argument + * will be an {@code Object} instance ({@code target}) and the second + * will be {@code int} ({@code restart}). + *

+ * If the {@code target} is {@code null}, then the method of the call site + * returns {@literal -1}. + *

+ * If the {@code target} is not {@code null}, then the method of the call site + * returns the index of the first element in the {@code labels} array starting from + * the {@code restart} index matching one of the following conditions: + *

+ *

+ * If no element in the {@code labels} array matches the target, then + * the method of the call site return the length of the {@code labels} array. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName unused + * @param invocationType The invocation type of the {@code CallSite} with two parameters, + * a reference type, an {@code int}, and {@code int} as a return type. + * @param labels case labels - {@code String} and {@code Integer} constants + * and {@code Class} instances, in any combination + * @return a {@code CallSite} returning the first matching element as described above + * + * @throws NullPointerException if any argument is {@code null} + * @throws IllegalArgumentException if any element in the labels array is null, if the + * invocation type is not 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} or {@code Class}. + * @throws Throwable if there is any error linking the call site + * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure + * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures + */ + public static CallSite typeSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + Object... labels) throws Throwable { + 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); + + MethodHandle target = MethodHandles.insertArguments(DO_SWITCH, 2, (Object) labels); + return new ConstantCallSite(target); + } + + private static void verifyLabel(Object label) { + if (label == null) { + throw new IllegalArgumentException("null label found"); + } + Class labelClass = label.getClass(); + if (labelClass != Class.class && + labelClass != String.class && + labelClass != Integer.class) { + throw new IllegalArgumentException("label with illegal type found: " + label.getClass()); + } + } + + private static int doSwitch(Object target, int startIndex, Object[] labels) { + if (target == null) + return -1; + + // Dumbest possible strategy + Class targetClass = target.getClass(); + for (int i = startIndex; i < labels.length; i++) { + Object label = labels[i]; + if (label instanceof Class c) { + if (c.isAssignableFrom(targetClass)) + return i; + } else if (label instanceof Integer constant) { + if (target instanceof Number input && constant.intValue() == input.intValue()) { + return i; + } else if (target instanceof Character input && constant.intValue() == input.charValue()) { + return i; + } + } else if (label.equals(target)) { + return i; + } + } + + return labels.length; + } + +} diff --git a/src/java.base/share/classes/jdk/internal/javac/NoPreview.java b/src/java.base/share/classes/jdk/internal/javac/NoPreview.java new file mode 100644 index 00000000000..0337d52752e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/javac/NoPreview.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 jdk.internal.javac; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The element annotated with this annotation should not be marked as a preview element. + */ +@Target({ElementType.METHOD, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.PACKAGE, + ElementType.MODULE, + ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface NoPreview { +} diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 6ec5eeae5ca..c11390d3937 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -58,6 +58,7 @@ public @interface PreviewFeature { * This one can only be removed after JDK 17 */ SEALED_CLASSES, + SWITCH_PATTERN_MATCHING, /** * A key for testing. */ diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/CaseLabelTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/CaseLabelTree.java new file mode 100644 index 00000000000..422e7575c74 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/CaseLabelTree.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 com.sun.source.tree; + +import jdk.internal.javac.PreviewFeature; + +/** + * A marker interface for {@code Tree}s that may be used as {@link CaseTree} labels. + * + * @since 17 + */ +@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) +public interface CaseLabelTree extends Tree {} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/CaseTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/CaseTree.java index d3fbb6b2f30..0a25df0a856 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/CaseTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/CaseTree.java @@ -27,6 +27,8 @@ package com.sun.source.tree; import java.util.List; +import jdk.internal.javac.PreviewFeature; + /** * A tree node for a {@code case} in a {@code switch} statement or expression. * @@ -65,6 +67,16 @@ public interface CaseTree extends Tree { */ List getExpressions(); + /** + * Returns the labels for this case. + * For {@code default} case return a list with a single element, {@link DefaultCaseLabelTree}. + * + * @return labels for this case + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + List getLabels(); + /** * For case with kind {@linkplain CaseKind#STATEMENT}, * returns the statements labeled by the case. diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/DefaultCaseLabelTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/DefaultCaseLabelTree.java new file mode 100644 index 00000000000..7cbe33b3fa0 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/DefaultCaseLabelTree.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 com.sun.source.tree; + +import jdk.internal.javac.PreviewFeature; + +/** + * A case label that marks {@code default} in {@code case null, default}. + * + * @since 17 + */ +@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) +public interface DefaultCaseLabelTree extends CaseLabelTree {} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/ExpressionTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/ExpressionTree.java index bd6b17c4e0f..8377e853ffa 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/ExpressionTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/ExpressionTree.java @@ -25,6 +25,8 @@ package com.sun.source.tree; +import jdk.internal.javac.NoPreview; + /** * A tree node used as the base class for the different types of * expressions. @@ -35,4 +37,5 @@ package com.sun.source.tree; * @author Jonathan Gibbons * @since 1.6 */ -public interface ExpressionTree extends Tree {} +@NoPreview +public interface ExpressionTree extends Tree, CaseLabelTree {} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/GuardedPatternTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/GuardedPatternTree.java new file mode 100644 index 00000000000..75eba374e84 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/GuardedPatternTree.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, 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 com.sun.source.tree; + +import jdk.internal.javac.PreviewFeature; + +/** + * A guard pattern tree. + * + * @since 17 + */ +@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) +public interface GuardedPatternTree extends PatternTree { + + /** + * The guarded pattern expression. + * @return the guarded pattern + */ + public PatternTree getPattern(); + + /** + * The guard expression. + * @return the guard expression + */ + public ExpressionTree getExpression(); + +} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/ParenthesizedPatternTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/ParenthesizedPatternTree.java new file mode 100644 index 00000000000..ff4ebb7a16b --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/ParenthesizedPatternTree.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005, 2014, 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 com.sun.source.tree; + +import jdk.internal.javac.PreviewFeature; + +/** + * A tree node for a parenthesized pattern. + * + * For example: + *

+ *   ( pattern )
+ * 
+ * + * @jls 14.30.1 Kinds of Patterns + * + * @since 17 + */ +@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) +public interface ParenthesizedPatternTree extends PatternTree { + /** + * Returns the pattern within the parentheses. + * @return the pattern + */ + PatternTree getPattern(); +} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/PatternTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/PatternTree.java index 283fa20e63a..c8c1b7ae9b1 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/PatternTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/PatternTree.java @@ -31,4 +31,4 @@ package com.sun.source.tree; * * @since 16 */ -public interface PatternTree extends Tree {} +public interface PatternTree extends Tree, CaseLabelTree {} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java index 3a6cb0910c9..9de5bb7443f 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java @@ -25,6 +25,8 @@ package com.sun.source.tree; +import jdk.internal.javac.PreviewFeature; + /** * Common interface for all nodes in an abstract syntax tree. * @@ -226,6 +228,30 @@ public interface Tree { */ BINDING_PATTERN(BindingPatternTree.class), + /** + * Used for instances of {@link GuardedPatternTree}. + * + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + GUARDED_PATTERN(GuardedPatternTree.class), + + /** + * Used for instances of {@link ParenthesizedPatternTree}. + * + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + PARENTHESIZED_PATTERN(ParenthesizedPatternTree.class), + + /** + * Used for instances of {@link DefaultCaseLabelTree}. + * + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + DEFAULT_CASE_LABEL(DefaultCaseLabelTree.class), + /** * Used for instances of {@link PrimitiveTypeTree}. */ diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java index cbb6f720f20..b38ed112e93 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java @@ -25,6 +25,8 @@ package com.sun.source.tree; +import jdk.internal.javac.PreviewFeature; + /** * A visitor of trees, in the style of the visitor design pattern. * Classes implementing this interface are used to operate @@ -266,6 +268,16 @@ public interface TreeVisitor { */ R visitBindingPattern(BindingPatternTree node, P p); + /** + * Visits a DefaultCaseLabelTree node. + * @param node the node being visited + * @param p a parameter value + * @return a result value + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p); + /** * Visits a MethodTree node. * @param node the node being visited @@ -290,6 +302,26 @@ public interface TreeVisitor { */ R visitNewArray(NewArrayTree node, P p); + /** + * Visits a GuardPatternTree node. + * @param node the node being visited + * @param p a parameter value + * @return a result value + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + R visitGuardedPattern(GuardedPatternTree node, P p); + + /** + * Visits a ParenthesizedPatternTree node. + * @param node the node being visited + * @param p a parameter value + * @return a result value + * @since 17 + */ + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + R visitParenthesizedPattern(ParenthesizedPatternTree node, P p); + /** * Visits a NewClassTree node. * @param node the node being visited diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java index 03ff696a980..89a1fe2c9fc 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java @@ -26,6 +26,7 @@ package com.sun.source.util; import com.sun.source.tree.*; +import jdk.internal.javac.PreviewFeature; /** * A simple visitor for tree nodes. @@ -564,6 +565,20 @@ public class SimpleTreeVisitor implements TreeVisitor { return defaultAction(node, p); } + /** + * {@inheritDoc} This implementation calls {@code defaultAction}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of {@code defaultAction} + * @since 17 + */ + @Override + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) { + return defaultAction(node, p); + } + /** * {@inheritDoc} This implementation calls {@code defaultAction}. * @@ -588,6 +603,34 @@ public class SimpleTreeVisitor implements TreeVisitor { return defaultAction(node, p); } + /** + * {@inheritDoc} This implementation calls {@code defaultAction}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of {@code defaultAction} + * @since 17 + */ + @Override + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + public R visitParenthesizedPattern(ParenthesizedPatternTree node, P p) { + return defaultAction(node, p); + } + + /** + * {@inheritDoc} This implementation calls {@code defaultAction}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of {@code defaultAction} + * @since 17 + */ + @Override + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + public R visitGuardedPattern(GuardedPatternTree node, P p) { + return defaultAction(node, p); + } + /** * {@inheritDoc} This implementation calls {@code defaultAction}. * diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java b/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java index dc4295fb96b..8ffbccb578a 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java @@ -26,6 +26,7 @@ package com.sun.source.util; import com.sun.source.tree.*; +import jdk.internal.javac.PreviewFeature; /** * A TreeVisitor that visits all the child tree nodes. @@ -695,6 +696,20 @@ public class TreeScanner implements TreeVisitor { return scan(node.getVariable(), p); } + /** + * {@inheritDoc} This implementation returns {@code null}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + * @since 17 + */ + @Override + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + public R visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) { + return null; + } + /** * {@inheritDoc} This implementation scans the children in left to right order. * @@ -721,6 +736,35 @@ public class TreeScanner implements TreeVisitor { return scan(node.getExpression(), p); } + /** + * {@inheritDoc} This implementation scans the children in left to right order. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + * @since 17 + */ + @Override + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + public R visitParenthesizedPattern(ParenthesizedPatternTree node, P p) { + return scan(node.getPattern(), p); + } + + /** + * {@inheritDoc} This implementation scans the children in left to right order. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + * @since 17 + */ + @Override + @PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING, reflective=true) + public R visitGuardedPattern(GuardedPatternTree node, P p) { + R r = scan(node.getPattern(), p); + return scanAndReduce(node.getExpression(), p, r); + } + /** * {@inheritDoc} This implementation scans the children in left to right order. * 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 b9efac8113f..1e5052e6da5 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 @@ -50,6 +50,7 @@ import static com.sun.tools.javac.code.Flags.RECORD; import static com.sun.tools.javac.code.Flags.SEALED; import static com.sun.tools.javac.code.Flags.NON_SEALED; import static com.sun.tools.javac.main.Option.PREVIEW; +import com.sun.tools.javac.util.JCDiagnostic; /** * Helper class to handle preview language features. This class maps certain language features @@ -79,6 +80,7 @@ public class Preview { private final Lint lint; private final Log log; + private final Source source; private static final Context.Key previewKey = new Context.Key<>(); @@ -96,7 +98,7 @@ public class Preview { enabled = options.isSet(PREVIEW); log = Log.instance(context); lint = Lint.instance(context); - Source source = Source.instance(context); + source = Source.instance(context); this.previewHandler = new MandatoryWarningHandler(log, source, lint.isEnabled(LintCategory.PREVIEW), true, "preview", LintCategory.PREVIEW); forcePreview = options.isSet("forcePreview"); @@ -183,6 +185,9 @@ public class Preview { */ public boolean isPreview(Feature feature) { return switch (feature) { + case CASE_NULL -> true; + case PATTERN_SWITCH -> 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. @@ -236,4 +241,19 @@ public class Preview { previewHandler.clear(); } + public void checkSourceLevel(DiagnosticPosition pos, Feature feature) { + if (isPreview(feature) && !isEnabled()) { + //preview feature without --preview flag, error + log.error(JCDiagnostic.DiagnosticFlag.SOURCE_LEVEL, pos, disabledError(feature)); + } else { + if (!feature.allowedInSource(source)) { + log.error(JCDiagnostic.DiagnosticFlag.SOURCE_LEVEL, pos, + feature.error(source.name)); + } + if (isEnabled() && isPreview(feature)) { + warnPreview(pos, feature); + } + } + } + } 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 b39b7bbe358..07969019104 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 @@ -224,6 +224,8 @@ public enum Source { REIFIABLE_TYPES_INSTANCEOF(JDK16, Fragments.FeatureReifiableTypesInstanceof, DiagKind.PLURAL), RECORDS(JDK16, Fragments.FeatureRecords, DiagKind.PLURAL), SEALED_CLASSES(JDK17, Fragments.FeatureSealedClasses, DiagKind.PLURAL), + CASE_NULL(JDK17, Fragments.FeatureCaseNull, DiagKind.NORMAL), + PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL), REDUNDANT_STRICTFP(JDK17), ; 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 af9f1aeebea..cf68737af40 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 @@ -220,6 +220,7 @@ public class Symtab { public final Type previewFeatureInternalType; public final Type typeDescriptorType; public final Type recordType; + public final Type switchBootstrapsType; public final Type valueBasedType; /** The symbol representing the length field of an array. @@ -585,6 +586,7 @@ public class Symtab { previewFeatureInternalType = enterSyntheticAnnotation("jdk.internal.PreviewFeature+Annotation"); typeDescriptorType = enterClass("java.lang.invoke.TypeDescriptor"); recordType = enterClass("java.lang.Record"); + switchBootstrapsType = enterClass("java.lang.runtime.SwitchBootstraps"); valueBasedType = enterClass("jdk.internal.ValueBased"); synthesizeEmptyInterfaceIfMissing(autoCloseableType); 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 644b6f9ab24..6567e8a6cd2 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 @@ -1657,15 +1657,28 @@ public class Attr extends JCTree.Visitor { boolean enumSwitch = (seltype.tsym.flags() & Flags.ENUM) != 0; boolean stringSwitch = types.isSameType(seltype, syms.stringType); boolean errorEnumSwitch = TreeInfo.isErrorEnumSwitch(selector, cases); - if (!enumSwitch && !stringSwitch) - seltype = chk.checkType(selector.pos(), seltype, syms.intType); + boolean patternSwitch; + if (!enumSwitch && !stringSwitch && !errorEnumSwitch && + !types.isAssignable(seltype, syms.intType)) { + preview.checkSourceLevel(selector.pos(), Feature.PATTERN_SWITCH); + patternSwitch = true; + } else { + patternSwitch = cases.stream() + .flatMap(c -> c.labels.stream()) + .anyMatch(l -> l.isPattern()); + } // Attribute all cases and // check that there are no duplicate case labels or default clauses. Set labels = new HashSet<>(); // The set of case labels. - boolean hasDefault = false; // Is there a default label? + List coveredTypes = List.nil(); + boolean hasDefault = false; // Is there a default label? + boolean hasTotalPattern = false; // Is there a total pattern? + boolean hasNullPattern = false; // Is there a null pattern? CaseTree.CaseKind caseKind = null; boolean wasError = false; + MatchBindings prevBindings = null; + boolean prevCompletedNormally = false; for (List l = cases; l.nonEmpty(); l = l.tail) { JCCase c = l.head; if (caseKind == null) { @@ -1675,17 +1688,29 @@ public class Attr extends JCTree.Visitor { Errors.SwitchMixingCaseTypes); wasError = true; } - if (c.getExpressions().nonEmpty()) { - for (JCExpression pat : c.getExpressions()) { - if (TreeInfo.isNull(pat)) { - log.error(pat.pos(), - Errors.SwitchNullNotAllowed); + MatchBindings currentBindings = prevBindings; + boolean wasTotalPattern = hasTotalPattern; + for (JCCaseLabel pat : c.labels) { + if (pat.isExpression()) { + JCExpression expr = (JCExpression) pat; + if (TreeInfo.isNull(expr)) { + preview.checkSourceLevel(expr.pos(), Feature.CASE_NULL); + if (hasNullPattern) { + log.error(c.pos(), Errors.DuplicateCaseLabel); + } else if (wasTotalPattern) { + log.error(c.pos(), Errors.PatternDominated); + } + hasNullPattern = true; + attribExpr(expr, switchEnv, seltype); + matchBindings = new MatchBindings(matchBindings.bindingsWhenTrue, matchBindings.bindingsWhenFalse, true); } else if (enumSwitch) { - Symbol sym = enumConstant(pat, seltype); + Symbol sym = enumConstant(expr, seltype); if (sym == null) { - log.error(pat.pos(), Errors.EnumLabelMustBeUnqualifiedEnum); + log.error(expr.pos(), Errors.EnumLabelMustBeUnqualifiedEnum); } else if (!labels.add(sym)) { log.error(c.pos(), Errors.DuplicateCaseLabel); + } else { + checkCaseLabelDominated(pat.pos(), coveredTypes, sym.type); } } else if (errorEnumSwitch) { //error recovery: the selector is erroneous, and all the case labels @@ -1699,30 +1724,88 @@ public class Attr extends JCTree.Visitor { rs.basicLogResolveHelper = prevResolveHelper; } } else { - Type pattype = attribExpr(pat, switchEnv, seltype); + Type pattype = attribExpr(expr, switchEnv, seltype); if (!pattype.hasTag(ERROR)) { + if (!stringSwitch && !types.isAssignable(seltype, syms.intType)) { + log.error(pat.pos(), Errors.ConstantLabelNotCompatible(pattype, seltype)); + } if (pattype.constValue() == null) { - log.error(pat.pos(), + log.error(expr.pos(), (stringSwitch ? Errors.StringConstReq : Errors.ConstExprReq)); } else if (!labels.add(pattype.constValue())) { log.error(c.pos(), Errors.DuplicateCaseLabel); + } else { + checkCaseLabelDominated(pat.pos(), coveredTypes, types.boxedTypeOrType(pattype)); } } } + } else if (pat.hasTag(DEFAULTCASELABEL)) { + if (hasDefault) { + log.error(pat.pos(), Errors.DuplicateDefaultLabel); + } else if (hasTotalPattern) { + log.error(pat.pos(), Errors.TotalPatternAndDefault); + } else if (matchBindings.bindingsWhenTrue.nonEmpty()) { + //there was a pattern, and the execution flows into a default: + log.error(pat.pos(), Errors.FlowsThroughFromPattern); + } + hasDefault = true; + matchBindings = MatchBindingsComputer.EMPTY; + } else { + if (prevCompletedNormally) { + log.error(pat.pos(), Errors.FlowsThroughToPattern); + } + //binding pattern + attribExpr(pat, switchEnv); + var primary = TreeInfo.primaryPatternType((JCPattern) pat); + Type primaryType = primary.type(); + if (!primaryType.hasTag(TYPEVAR)) { + primaryType = chk.checkClassOrArrayType(pat.pos(), primaryType); + } + checkCastablePattern(pat.pos(), seltype, primaryType); + Type patternType = types.erasure(primaryType); + boolean isTotal = primary.unconditional() && + !patternType.isErroneous() && + types.isSubtype(types.erasure(seltype), patternType); + if (isTotal) { + if (hasTotalPattern) { + log.error(pat.pos(), Errors.DuplicateTotalPattern); + } else if (hasDefault) { + log.error(pat.pos(), Errors.TotalPatternAndDefault); + } + hasTotalPattern = true; + } + checkCaseLabelDominated(pat.pos(), coveredTypes, patternType); + if (primary.unconditional() && !patternType.isErroneous()) { + coveredTypes = coveredTypes.prepend(patternType); + } } - } else if (hasDefault) { - log.error(c.pos(), Errors.DuplicateDefaultLabel); - } else { - hasDefault = true; + currentBindings = matchBindingsComputer.switchCase(pat, currentBindings, matchBindings); + prevCompletedNormally = !TreeInfo.isNull(pat); } Env caseEnv = - switchEnv.dup(c, env.info.dup(switchEnv.info.scope.dup())); + bindingEnv(switchEnv, c, currentBindings.bindingsWhenTrue); try { attribCase.accept(c, caseEnv); } finally { caseEnv.info.scope.leave(); } addVars(c.stats, switchEnv.info.scope); + + boolean completesNormally = c.caseKind == CaseTree.CaseKind.STATEMENT ? flow.aliveAfter(caseEnv, c, make) : false; + prevBindings = completesNormally ? currentBindings : null; + prevCompletedNormally = + completesNormally && + !(c.labels.size() == 1 && + TreeInfo.isNull(c.labels.head) && c.stats.isEmpty()); + } + if (switchTree.hasTag(SWITCH)) { + ((JCSwitch) switchTree).hasTotalPattern = hasDefault || hasTotalPattern; + ((JCSwitch) switchTree).patternSwitch = patternSwitch; + } else if (switchTree.hasTag(SWITCH_EXPRESSION)) { + ((JCSwitchExpression) switchTree).hasTotalPattern = hasDefault || hasTotalPattern; + ((JCSwitchExpression) switchTree).patternSwitch = patternSwitch; + } else { + Assert.error(switchTree.getTag().name()); } } finally { switchEnv.info.scope.leave(); @@ -1737,6 +1820,14 @@ public class Attr extends JCTree.Visitor { switchScope.enter(((JCVariableDecl) stat).sym); } } + private void checkCaseLabelDominated(DiagnosticPosition pos, + List coveredTypes, Type patternType) { + for (Type existing : coveredTypes) { + if (types.isSubtype(patternType, existing)) { + log.error(pos, Errors.PatternDominated); + } + } + } // where /** Return the selected enumeration constant symbol, or null. */ private Symbol enumConstant(JCTree tree, Type enumType) { @@ -2080,7 +2171,11 @@ public class Attr extends JCTree.Visitor { }; Env bindingEnv(Env env, List bindings) { - Env env1 = env.dup(env.tree, env.info.dup(env.info.scope.dup())); + return bindingEnv(env, env.tree, bindings); + } + + Env bindingEnv(Env env, JCTree newTree, List bindings) { + Env env1 = env.dup(newTree, env.info.dup(env.info.scope.dup())); bindings.forEach(env1.info.scope::enter); return env1; } @@ -3994,16 +4089,7 @@ public class Attr extends JCTree.Visitor { if (!clazztype.isErroneous() && !types.isReifiable(clazztype)) { boolean valid = false; if (allowReifiableTypesInInstanceof) { - Warner warner = new Warner(); - if (!types.isCastable(exprtype, clazztype, warner)) { - chk.basicHandler.report(tree.expr.pos(), - diags.fragment(Fragments.InconvertibleTypes(exprtype, clazztype))); - } else if (warner.hasLint(LintCategory.UNCHECKED)) { - log.error(tree.expr.pos(), - Errors.InstanceofReifiableNotSafe(exprtype, clazztype)); - } else { - valid = true; - } + valid = checkCastablePattern(tree.expr.pos(), exprtype, clazztype); } else { log.error(DiagnosticFlag.SOURCE_LEVEL, tree.pos(), Feature.REIFIABLE_TYPES_INSTANCEOF.error(this.sourceName)); @@ -4018,6 +4104,23 @@ public class Attr extends JCTree.Visitor { result = check(tree, syms.booleanType, KindSelector.VAL, resultInfo); } + private boolean checkCastablePattern(DiagnosticPosition pos, + Type exprType, + Type pattType) { + Warner warner = new Warner(); + if (!types.isCastable(exprType, pattType, warner)) { + chk.basicHandler.report(pos, + diags.fragment(Fragments.InconvertibleTypes(exprType, pattType))); + return false; + } else if (warner.hasLint(LintCategory.UNCHECKED)) { + log.error(pos, + Errors.InstanceofReifiableNotSafe(exprType, pattType)); + return false; + } else { + return true; + } + } + public void visitBindingPattern(JCBindingPattern tree) { ResultInfo varInfo = new ResultInfo(KindSelector.TYP, resultInfo.pt, resultInfo.checkContext); tree.type = tree.var.type = attribTree(tree.var.vartype, env, varInfo); @@ -4034,6 +4137,25 @@ public class Attr extends JCTree.Visitor { matchBindings = new MatchBindings(List.of(v), List.nil()); } + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern tree) { + attribExpr(tree.pattern, env); + } + + @Override + public void visitGuardPattern(JCGuardPattern tree) { + attribExpr(tree.patt, env); + MatchBindings afterPattern = matchBindings; + Env bodyEnv = bindingEnv(env, matchBindings.bindingsWhenTrue); + try { + attribExpr(tree.expr, bodyEnv, syms.booleanType); + } finally { + bodyEnv.info.scope.leave(); + } + result = tree.type = tree.patt.type; + matchBindings = matchBindingsComputer.guardedPattern(tree, afterPattern, matchBindings); + } + public void visitIndexed(JCArrayAccess tree) { Type owntype = types.createErrorType(tree.type); Type atype = attribExpr(tree.indexed, env); 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 b3326e2fb0c..d720ac81fb9 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 @@ -39,6 +39,7 @@ import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.resources.CompilerProperties.Warnings; import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.TreeInfo.PatternPrimaryType; import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.JCDiagnostic.Error; @@ -53,7 +54,10 @@ import static com.sun.tools.javac.code.Flags.BLOCK; import static com.sun.tools.javac.code.Kinds.Kind.*; import static com.sun.tools.javac.code.TypeTag.BOOLEAN; import static com.sun.tools.javac.code.TypeTag.VOID; +import com.sun.tools.javac.resources.CompilerProperties.Fragments; +import com.sun.tools.javac.tree.JCTree.JCParenthesizedPattern; import static com.sun.tools.javac.tree.JCTree.Tag.*; +import com.sun.tools.javac.util.JCDiagnostic.Fragment; /** This pass implements dataflow analysis for Java programs though * different AST visitor steps. Liveness analysis (see AliveAnalyzer) checks that @@ -661,16 +665,13 @@ public class Flow { ListBuffer prevPendingExits = pendingExits; pendingExits = new ListBuffer<>(); scan(tree.selector); - boolean hasDefault = false; + Set constants = tree.patternSwitch ? allSwitchConstants(tree.selector) : null; for (List l = tree.cases; l.nonEmpty(); l = l.tail) { alive = Liveness.ALIVE; JCCase c = l.head; - if (c.pats.isEmpty()) - hasDefault = true; - else { - for (JCExpression pat : c.pats) { - scan(pat); - } + for (JCCaseLabel pat : c.labels) { + scan(pat); + handleConstantCaseLabel(constants, pat); } scanStats(c.stats); c.completesNormally = alive != Liveness.DEAD; @@ -686,7 +687,11 @@ public class Flow { l.tail.head.pos(), Warnings.PossibleFallThroughIntoCase); } - if (!hasDefault) { + if ((constants == null || !constants.isEmpty()) && !tree.hasTotalPattern && + tree.patternSwitch && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases)) { + log.error(tree, Errors.NotExhaustiveStatement); + } + if (!tree.hasTotalPattern) { alive = Liveness.ALIVE; } alive = alive.or(resolveBreaks(tree, prevPendingExits)); @@ -697,33 +702,14 @@ public class Flow { ListBuffer prevPendingExits = pendingExits; pendingExits = new ListBuffer<>(); scan(tree.selector); - Set constants = null; - TypeSymbol selectorSym = tree.selector.type.tsym; - if ((selectorSym.flags() & ENUM) != 0) { - constants = new HashSet<>(); - Predicate enumConstantFilter = - s -> (s.flags() & ENUM) != 0 && s.kind == Kind.VAR; - for (Symbol s : selectorSym.members().getSymbols(enumConstantFilter)) { - constants.add(s.name); - } - } - boolean hasDefault = false; + Set constants = allSwitchConstants(tree.selector); Liveness prevAlive = alive; for (List l = tree.cases; l.nonEmpty(); l = l.tail) { alive = Liveness.ALIVE; JCCase c = l.head; - if (c.pats.isEmpty()) - hasDefault = true; - else { - for (JCExpression pat : c.pats) { - scan(pat); - if (constants != null) { - if (pat.hasTag(IDENT)) - constants.remove(((JCIdent) pat).name); - if (pat.type != null) - constants.remove(pat.type.constValue()); - } - } + for (JCCaseLabel pat : c.labels) { + scan(pat); + handleConstantCaseLabel(constants, pat); } scanStats(c.stats); if (alive == Liveness.ALIVE) { @@ -737,7 +723,7 @@ public class Flow { } c.completesNormally = alive != Liveness.DEAD; } - if ((constants == null || !constants.isEmpty()) && !hasDefault && + if ((constants == null || !constants.isEmpty()) && !tree.hasTotalPattern && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases)) { log.error(tree, Errors.NotExhaustive); } @@ -745,6 +731,39 @@ public class Flow { alive = alive.or(resolveYields(tree, prevPendingExits)); } + private Set allSwitchConstants(JCExpression selector) { + Set constants = null; + TypeSymbol selectorSym = selector.type.tsym; + if ((selectorSym.flags() & ENUM) != 0) { + constants = new HashSet<>(); + Predicate enumConstantFilter = + s -> (s.flags() & ENUM) != 0 && s.kind == Kind.VAR; + for (Symbol s : selectorSym.members().getSymbols(enumConstantFilter)) { + constants.add(s.name); + } + } else if (selectorSym.isAbstract() && selectorSym.isSealed() && selectorSym.kind == Kind.TYP) { + constants = new HashSet<>(); + constants.addAll(((ClassSymbol) selectorSym).permitted); + } + return constants; + } + + private void handleConstantCaseLabel(Set constants, JCCaseLabel pat) { + if (constants != null) { + if (pat.isExpression()) { + JCExpression expr = (JCExpression) pat; + if (expr.hasTag(IDENT)) + constants.remove(((JCIdent) expr).name); + } else if (pat.isPattern()) { + PatternPrimaryType patternType = TreeInfo.primaryPatternType((JCPattern) pat); + + if (patternType.unconditional()) { + constants.remove(patternType.type().tsym); + } + } + } + } + public void visitTry(JCTry tree) { ListBuffer prevPendingExits = pendingExits; pendingExits = new ListBuffer<>(); @@ -1194,7 +1213,7 @@ public class Flow { scan(selector); for (List l = cases; l.nonEmpty(); l = l.tail) { JCCase c = l.head; - scan(c.pats); + scan(c.labels); scan(c.stats); } if (tree.hasTag(SWITCH_EXPRESSION)) { @@ -2352,52 +2371,54 @@ public class Flow { } public void visitSwitch(JCSwitch tree) { - handleSwitch(tree, tree.selector, tree.cases); + handleSwitch(tree, tree.selector, tree.cases, tree.hasTotalPattern); } public void visitSwitchExpression(JCSwitchExpression tree) { - handleSwitch(tree, tree.selector, tree.cases); + handleSwitch(tree, tree.selector, tree.cases, tree.hasTotalPattern); } - private void handleSwitch(JCTree tree, JCExpression selector, List cases) { + private void handleSwitch(JCTree tree, JCExpression selector, + List cases, boolean hasTotalPattern) { ListBuffer prevPendingExits = pendingExits; pendingExits = new ListBuffer<>(); int nextadrPrev = nextadr; scanExpr(selector); final Bits initsSwitch = new Bits(inits); final Bits uninitsSwitch = new Bits(uninits); - boolean hasDefault = false; for (List l = cases; l.nonEmpty(); l = l.tail) { inits.assign(initsSwitch); uninits.assign(uninits.andSet(uninitsSwitch)); JCCase c = l.head; - if (c.pats.isEmpty()) { - hasDefault = true; - } else { - for (JCExpression pat : c.pats) { - scanExpr(pat); - } + for (JCCaseLabel pat : c.labels) { + scan(pat); } - if (hasDefault) { - inits.assign(initsSwitch); - uninits.assign(uninits.andSet(uninitsSwitch)); + if (l.head.stats.isEmpty() && + l.tail.nonEmpty() && + l.tail.head.labels.size() == 1 && + l.tail.head.labels.head.isExpression() && + TreeInfo.isNull(l.tail.head.labels.head)) { + //handling: + //case Integer i: + //case null: + //joining these two cases together - processing Integer i pattern, + //but statements from case null: + l = l.tail; + c = l.head; } scan(c.stats); if (c.completesNormally && c.caseKind == JCCase.RULE) { scanSyntheticBreak(make, tree); } addVars(c.stats, initsSwitch, uninitsSwitch); - if (!hasDefault) { - inits.assign(initsSwitch); - uninits.assign(uninits.andSet(uninitsSwitch)); - } // Warn about fall-through if lint switch fallthrough enabled. } - if (!hasDefault) { + if (!hasTotalPattern) { if (tree.hasTag(SWITCH_EXPRESSION)) { markDead(); } else { - inits.andSet(initsSwitch); + inits.assign(initsSwitch); + uninits.assign(uninits.andSet(uninitsSwitch)); } } if (tree.hasTag(SWITCH_EXPRESSION)) { @@ -2852,6 +2873,7 @@ public class Flow { } break; } + case GUARDPATTERN: case LAMBDA: if ((sym.flags() & (EFFECTIVELY_FINAL | FINAL)) == 0) { reportEffectivelyFinalError(pos, sym); @@ -2875,6 +2897,7 @@ public class Flow { reportInnerClsNeedsFinalError(tree, sym); break; } + case GUARDPATTERN: case LAMBDA: reportEffectivelyFinalError(tree, sym); } @@ -2883,8 +2906,12 @@ public class Flow { } void reportEffectivelyFinalError(DiagnosticPosition pos, Symbol sym) { - String subKey = currentTree.hasTag(LAMBDA) ? - "lambda" : "inner.cls"; + Fragment subKey = switch (currentTree.getTag()) { + case LAMBDA -> Fragments.Lambda; + case GUARDPATTERN -> Fragments.Guard; + case CLASSDEF -> Fragments.InnerCls; + default -> throw new AssertionError("Unexpected tree kind: " + currentTree.getTag()); + }; log.error(pos, Errors.CantRefNonEffectivelyFinalVar(sym, diags.fragment(subKey))); } @@ -2920,6 +2947,18 @@ public class Flow { } } + @Override + public void visitGuardPattern(JCGuardPattern tree) { + scan(tree.patt); + JCTree prevTree = currentTree; + try { + currentTree = tree; + scan(tree.expr); + } finally { + currentTree = prevTree; + } + } + @Override public void visitIdent(JCIdent tree) { if (tree.sym.kind == VAR) { 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 653cc5c0038..0a9f10cb75f 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 @@ -3583,18 +3583,25 @@ public class Lower extends TreeTranslator { } public void visitSwitch(JCSwitch tree) { - handleSwitch(tree, tree.selector, tree.cases); + List cases = tree.patternSwitch ? addDefaultIfNeeded(tree.cases) : tree.cases; + handleSwitch(tree, tree.selector, cases); } @Override public void visitSwitchExpression(JCSwitchExpression tree) { - if (tree.cases.stream().noneMatch(c -> c.pats.isEmpty())) { + List cases = addDefaultIfNeeded(tree.cases); + handleSwitch(tree, tree.selector, cases); + } + + private List addDefaultIfNeeded(List cases) { + if (cases.stream().flatMap(c -> c.labels.stream()).noneMatch(p -> p.hasTag(Tag.DEFAULTCASELABEL))) { JCThrow thr = make.Throw(makeNewClass(syms.incompatibleClassChangeErrorType, List.nil())); - JCCase c = make.Case(JCCase.STATEMENT, List.nil(), List.of(thr), null); - tree.cases = tree.cases.append(c); + JCCase c = make.Case(JCCase.STATEMENT, List.of(make.DefaultCaseLabel()), List.of(thr), null); + cases = cases.append(c); } - handleSwitch(tree, tree.selector, tree.cases); + + return cases; } private void handleSwitch(JCTree tree, JCExpression selector, List cases) { @@ -3602,7 +3609,7 @@ public class Lower extends TreeTranslator { ListBuffer convertedCases = new ListBuffer<>(); for (JCCase c : cases) { - switch (c.pats.size()) { + switch (c.labels.size()) { case 0: //default case 1: //single label convertedCases.append(c); @@ -3613,7 +3620,7 @@ public class Lower extends TreeTranslator { //case C1: //case C2: //case C3: ... - List patterns = c.pats; + List patterns = c.labels; while (patterns.tail.nonEmpty()) { convertedCases.append(make_at(c.pos()).Case(JCCase.STATEMENT, List.of(patterns.head), @@ -3621,7 +3628,7 @@ public class Lower extends TreeTranslator { null)); patterns = patterns.tail; } - c.pats = patterns; + c.labels = patterns; convertedCases.append(c); break; } @@ -3629,7 +3636,7 @@ public class Lower extends TreeTranslator { for (JCCase c : convertedCases) { if (c.caseKind == JCCase.RULE && c.completesNormally) { - JCBreak b = make_at(c.pos()).Break(null); + JCBreak b = make.at(TreeInfo.endPos(c.stats.last())).Break(null); b.target = tree; c.stats = c.stats.append(b); } @@ -3642,9 +3649,8 @@ public class Lower extends TreeTranslator { (selector.type.tsym.flags() & ENUM) != 0; boolean stringSwitch = selsuper != null && types.isSameType(selector.type, syms.stringType); - Type target = enumSwitch ? selector.type : - (stringSwitch? syms.stringType : syms.intType); - selector = translate(selector, target); + boolean boxedSwitch = !enumSwitch && !stringSwitch && !selector.type.isPrimitive(); + selector = translate(selector, selector.type); cases = translateCases(cases); if (tree.hasTag(SWITCH)) { ((JCSwitch) tree).selector = selector; @@ -3659,6 +3665,10 @@ public class Lower extends TreeTranslator { result = visitEnumSwitch(tree, selector, cases); } else if (stringSwitch) { result = visitStringSwitch(tree, selector, cases); + } else if (boxedSwitch) { + //An switch over boxed primitive. Pattern matching switches are already translated + //by TransPatterns, so all non-primitive types are only boxed primitives: + result = visitBoxedPrimitiveSwitch(tree, selector, cases); } else { result = tree; } @@ -3672,14 +3682,42 @@ public class Lower extends TreeTranslator { names.ordinal, selector.type, List.nil()); - JCArrayAccess newSelector = make.Indexed(map.mapVar, - make.App(make.Select(selector, - ordinalMethod))); + JCExpression newSelector; + + if (cases.stream().anyMatch(c -> TreeInfo.isNull(c.labels.head))) { + //for enum switches with case null, do: + //switch ($selector != null ? $mapVar[$selector.ordinal()] : -1) {...} + //replacing case null with case -1: + VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC, + names.fromString("s" + tree.pos + this.target.syntheticNameChar()), + selector.type, + currentMethodSym); + JCStatement var = make.at(tree.pos()).VarDef(dollar_s, selector).setType(dollar_s.type); + newSelector = make.Indexed(map.mapVar, + make.App(make.Select(make.Ident(dollar_s), + ordinalMethod))); + newSelector = + make.LetExpr(List.of(var), + make.Conditional(makeBinary(NE, make.Ident(dollar_s), makeNull()), + newSelector, + makeLit(syms.intType, -1)) + .setType(newSelector.type)) + .setType(newSelector.type); + } else { + newSelector = make.Indexed(map.mapVar, + make.App(make.Select(selector, + ordinalMethod))); + } ListBuffer newCases = new ListBuffer<>(); for (JCCase c : cases) { - if (c.pats.nonEmpty()) { - VarSymbol label = (VarSymbol)TreeInfo.symbol(c.pats.head); - JCLiteral pat = map.forConstant(label); + if (c.labels.head.isExpression()) { + JCExpression pat; + if (TreeInfo.isNull(c.labels.head)) { + pat = makeLit(syms.intType, -1); + } else { + VarSymbol label = (VarSymbol)TreeInfo.symbol((JCExpression) c.labels.head); + pat = map.forConstant(label); + } newCases.append(make.Case(JCCase.STATEMENT, List.of(pat), c.stats, null)); } else { newCases.append(c); @@ -3756,23 +3794,30 @@ public class Lower extends TreeTranslator { Map> hashToString = new LinkedHashMap<>(alternatives + 1, 1.0f); int casePosition = 0; + JCCase nullCase = null; + int nullCaseLabel = -1; for(JCCase oneCase : caseList) { - if (oneCase.pats.nonEmpty()) { // pats is empty for a "default" case - JCExpression expression = oneCase.pats.head; - String labelExpr = (String) expression.type.constValue(); - Integer mapping = caseLabelToPosition.put(labelExpr, casePosition); - Assert.checkNull(mapping); - int hashCode = labelExpr.hashCode(); - - Set stringSet = hashToString.get(hashCode); - if (stringSet == null) { - stringSet = new LinkedHashSet<>(1, 1.0f); - stringSet.add(labelExpr); - hashToString.put(hashCode, stringSet); + if (oneCase.labels.head.isExpression()) { + if (TreeInfo.isNull(oneCase.labels.head)) { + nullCase = oneCase; + nullCaseLabel = casePosition; } else { - boolean added = stringSet.add(labelExpr); - Assert.check(added); + JCExpression expression = (JCExpression) oneCase.labels.head; + String labelExpr = (String) expression.type.constValue(); + Integer mapping = caseLabelToPosition.put(labelExpr, casePosition); + Assert.checkNull(mapping); + int hashCode = labelExpr.hashCode(); + + Set stringSet = hashToString.get(hashCode); + if (stringSet == null) { + stringSet = new LinkedHashSet<>(1, 1.0f); + stringSet.add(labelExpr); + hashToString.put(hashCode, stringSet); + } else { + boolean added = stringSet.add(labelExpr); + Assert.check(added); + } } } casePosition++; @@ -3847,7 +3892,14 @@ public class Lower extends TreeTranslator { } switch1.cases = caseBuffer.toList(); - stmtList.append(switch1); + + if (nullCase != null) { + stmtList.append(make.If(makeBinary(NE, make.Ident(dollar_s), makeNull()), switch1, make.Exec(make.Assign(make.Ident(dollar_tmp), + make.Literal(nullCaseLabel)). + setType(dollar_tmp.type))).setType(syms.intType)); + } else { + stmtList.append(switch1); + } // Make isomorphic switch tree replacing string labels // with corresponding integer ones from the label to @@ -3855,16 +3907,18 @@ public class Lower extends TreeTranslator { ListBuffer lb = new ListBuffer<>(); for(JCCase oneCase : caseList ) { - boolean isDefault = (oneCase.pats.isEmpty()); - JCExpression caseExpr; + boolean isDefault = !oneCase.labels.head.isExpression(); + JCCaseLabel caseExpr; if (isDefault) caseExpr = null; - else { - caseExpr = make.Literal(caseLabelToPosition.get((String)TreeInfo.skipParens(oneCase.pats.head). + else if (oneCase == nullCase) { + caseExpr = make.Literal(nullCaseLabel); + } else { + caseExpr = make.Literal(caseLabelToPosition.get((String)TreeInfo.skipParens((JCExpression) oneCase.labels.head). type.constValue())); } - lb.append(make.Case(JCCase.STATEMENT, caseExpr == null ? List.nil() : List.of(caseExpr), + lb.append(make.Case(JCCase.STATEMENT, caseExpr == null ? List.of(make.DefaultCaseLabel()) : List.of(caseExpr), oneCase.stats, null)); } @@ -3898,6 +3952,70 @@ public class Lower extends TreeTranslator { } } + private JCTree visitBoxedPrimitiveSwitch(JCTree tree, JCExpression selector, List cases) { + JCExpression newSelector; + + if (cases.stream().anyMatch(c -> TreeInfo.isNull(c.labels.head))) { + //a switch over a boxed primitive, with a null case. Pick two constants that are + //not used by any branch in the case (c1 and c2), close to other constants that are + //used in the switch. Then do: + //switch ($selector != null ? $selector != c1 ? $selector : c2 : c1) {...} + //replacing case null with case c1 + Set constants = new LinkedHashSet<>(); + JCCase nullCase = null; + + for (JCCase c : cases) { + if (TreeInfo.isNull(c.labels.head)) { + nullCase = c; + } else if (!c.labels.head.hasTag(DEFAULTCASELABEL)) { + constants.add((int) c.labels.head.type.constValue()); + } + } + + Assert.checkNonNull(nullCase); + + int nullValue = constants.isEmpty() ? 0 : constants.iterator().next(); + + while (constants.contains(nullValue)) nullValue++; + + constants.add(nullValue); + nullCase.labels.head = makeLit(syms.intType, nullValue); + + int replacementValue = nullValue; + + while (constants.contains(replacementValue)) replacementValue++; + + VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC, + names.fromString("s" + tree.pos + this.target.syntheticNameChar()), + selector.type, + currentMethodSym); + JCStatement var = make.at(tree.pos()).VarDef(dollar_s, selector).setType(dollar_s.type); + JCExpression nullValueReplacement = + make.Conditional(makeBinary(NE, + unbox(make.Ident(dollar_s), syms.intType), + makeLit(syms.intType, nullValue)), + unbox(make.Ident(dollar_s), syms.intType), + makeLit(syms.intType, replacementValue)) + .setType(syms.intType); + JCExpression nullCheck = + make.Conditional(makeBinary(NE, make.Ident(dollar_s), makeNull()), + nullValueReplacement, + makeLit(syms.intType, nullValue)) + .setType(syms.intType); + newSelector = make.LetExpr(List.of(var), nullCheck).setType(syms.intType); + } else { + newSelector = unbox(selector, syms.intType); + } + + if (tree.hasTag(SWITCH)) { + ((JCSwitch) tree).selector = newSelector; + } else { + ((JCSwitchExpression) tree).selector = newSelector; + } + + return tree; + } + @Override public void visitBreak(JCBreak tree) { result = tree; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MatchBindingsComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MatchBindingsComputer.java index 9b389443456..6bd44a31a65 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MatchBindingsComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MatchBindingsComputer.java @@ -29,6 +29,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.BindingSymbol; import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCGuardPattern; import com.sun.tools.javac.tree.JCTree.Tag; import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Context; @@ -96,13 +97,7 @@ public class MatchBindingsComputer extends TreeScanner { public MatchBindings binary(JCTree tree, MatchBindings lhsBindings, MatchBindings rhsBindings) { switch (tree.getTag()) { case AND: { - // e.T = union(x.T, y.T) - // e.F = intersection(x.F, y.F) (error recovery) - List bindingsWhenTrue = - union(tree.pos(), lhsBindings.bindingsWhenTrue, rhsBindings.bindingsWhenTrue); - List bindingsWhenFalse = //error recovery - intersection(tree.pos(), lhsBindings.bindingsWhenFalse, rhsBindings.bindingsWhenFalse); - return new MatchBindings(bindingsWhenTrue, bindingsWhenFalse); + return andOperation(tree.pos(), lhsBindings, rhsBindings); } case OR: { // e.T = intersection(x.T, y.T) (error recovery) @@ -117,9 +112,40 @@ public class MatchBindingsComputer extends TreeScanner { return EMPTY; } + public MatchBindings guardedPattern(JCGuardPattern tree, MatchBindings patternBindings, MatchBindings guardBindings) { + return andOperation(tree.pos(), patternBindings, guardBindings); + } + + public MatchBindings andOperation(DiagnosticPosition pos, MatchBindings lhsBindings, MatchBindings rhsBindings) { + // e.T = union(x.T, y.T) + // e.F = intersection(x.F, y.F) (error recovery) + List bindingsWhenTrue = + union(pos, lhsBindings.bindingsWhenTrue, rhsBindings.bindingsWhenTrue); + List bindingsWhenFalse = //error recovery + intersection(pos, lhsBindings.bindingsWhenFalse, rhsBindings.bindingsWhenFalse); + return new MatchBindings(bindingsWhenTrue, bindingsWhenFalse); + } + + public MatchBindings switchCase(JCTree tree, MatchBindings prevBindings, MatchBindings currentBindings) { + if (prevBindings == null) + return currentBindings; + if (!prevBindings.bindingsWhenTrue.isEmpty() && !currentBindings.bindingsWhenTrue.isEmpty()) { + log.error(tree.pos(), Errors.FlowsThroughToPattern); + } + if (prevBindings.nullPattern) { + return currentBindings; + } + if (currentBindings.nullPattern) { + return prevBindings; + } + return new MatchBindings(intersection(tree.pos(), prevBindings.bindingsWhenTrue, currentBindings.bindingsWhenTrue), + intersection(tree.pos(), prevBindings.bindingsWhenFalse, currentBindings.bindingsWhenFalse)); + } + public MatchBindings finishBindings(JCTree tree, MatchBindings matchBindings) { switch (tree.getTag()) { case NOT: case AND: case OR: case BINDINGPATTERN: + case PARENTHESIZEDPATTERN: case GUARDPATTERN: case PARENS: case TYPETEST: case CONDEXPR: //error recovery: return matchBindings; @@ -132,10 +158,16 @@ public class MatchBindingsComputer extends TreeScanner { public final List bindingsWhenTrue; public final List bindingsWhenFalse; + public final boolean nullPattern; public MatchBindings(List bindingsWhenTrue, List bindingsWhenFalse) { + this(bindingsWhenTrue, bindingsWhenFalse, false); + } + + public MatchBindings(List bindingsWhenTrue, List bindingsWhenFalse, boolean nullPattern) { this.bindingsWhenTrue = bindingsWhenTrue; this.bindingsWhenFalse = bindingsWhenFalse; + this.nullPattern = nullPattern; } } 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 2474c151151..b458b7fa2ce 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 @@ -25,11 +25,16 @@ package com.sun.tools.javac.comp; +import com.sun.source.tree.CaseTree; +import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Kinds.Kind; +import com.sun.tools.javac.code.Preview; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.BindingSymbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; @@ -44,6 +49,7 @@ import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCInstanceOf; import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCSwitch; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCBindingPattern; import com.sun.tools.javac.tree.JCTree.JCWhileLoop; @@ -52,24 +58,40 @@ import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.ListBuffer; -import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Options; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type.MethodType; +import com.sun.tools.javac.code.Type.WildcardType; +import com.sun.tools.javac.code.TypeTag; import static com.sun.tools.javac.code.TypeTag.BOT; +import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant; import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCBreak; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCCaseLabel; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCContinue; import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCGuardPattern; import com.sun.tools.javac.tree.JCTree.JCLambda; +import com.sun.tools.javac.tree.JCTree.JCParenthesizedPattern; +import com.sun.tools.javac.tree.JCTree.JCPattern; import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCSwitchExpression; import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.List; /** @@ -87,13 +109,15 @@ public class TransPatterns extends TreeTranslator { } private final Symtab syms; + private final Attr attr; + private final Resolve rs; private final Types types; private final Operators operators; - private final Log log; - private final ConstFold constFold; private final Names names; private final Target target; + private final Preview preview; private TreeMaker make; + private Env env; BindingContext bindingContext = new BindingContext() { @Override @@ -106,6 +130,11 @@ public class TransPatterns extends TreeTranslator { return null; } + @Override + List bindingVars(int diagPos) { + return List.nil(); + } + @Override JCStatement decorateStatement(JCStatement stat) { return stat; @@ -132,68 +161,399 @@ public class TransPatterns extends TreeTranslator { boolean debugTransPatterns; + private ClassSymbol currentClass = null; private MethodSymbol currentMethodSym = null; + private VarSymbol currentValue = null; protected TransPatterns(Context context) { context.put(transPatternsKey, this); syms = Symtab.instance(context); + attr = Attr.instance(context); + rs = Resolve.instance(context); make = TreeMaker.instance(context); types = Types.instance(context); operators = Operators.instance(context); - log = Log.instance(context); - constFold = ConstFold.instance(context); names = Names.instance(context); target = Target.instance(context); + preview = Preview.instance(context); debugTransPatterns = Options.instance(context).isSet("debug.patterns"); } @Override public void visitTypeTest(JCInstanceOf tree) { - if (tree.pattern.hasTag(Tag.BINDINGPATTERN)) { - //E instanceof T N + if (tree.pattern instanceof JCPattern) { + //E instanceof $pattern //=> - //(let T' N$temp = E; N$temp instanceof T && (N = (T) N$temp == (T) N$temp)) + //(let T' N$temp = E; N$temp instanceof typeof($pattern) && ) + //note the pattern desugaring performs binding variable assignments Symbol exprSym = TreeInfo.symbol(tree.expr); - JCBindingPattern patt = (JCBindingPattern)tree.pattern; - VarSymbol pattSym = patt.var.sym; Type tempType = tree.expr.type.hasTag(BOT) ? syms.objectType : tree.expr.type; - VarSymbol temp; - if (exprSym != null && - exprSym.kind == Kind.VAR && - exprSym.owner.kind.matches(Kinds.KindSelector.VAL_MTH)) { - temp = (VarSymbol) exprSym; - } else { - temp = new VarSymbol(pattSym.flags() | Flags.SYNTHETIC, - names.fromString(pattSym.name.toString() + target.syntheticNameChar() + "temp"), - tempType, - patt.var.sym.owner); - } - JCExpression translatedExpr = translate(tree.expr); - Type castTargetType = types.boxedTypeOrType(pattSym.erasure(types)); + VarSymbol prevCurrentValue = currentValue; + try { + if (exprSym != null && + exprSym.kind == Kind.VAR && + exprSym.owner.kind.matches(Kinds.KindSelector.VAL_MTH)) { + currentValue = (VarSymbol) exprSym; + } else { + currentValue = new VarSymbol(Flags.FINAL | Flags.SYNTHETIC, + names.fromString("patt" + tree.pos + target.syntheticNameChar() + "temp"), + tempType, + currentMethodSym); + } - result = makeTypeTest(make.Ident(temp), make.Type(castTargetType)); - - VarSymbol bindingVar = bindingContext.bindingDeclared((BindingSymbol) patt.var.sym); - if (bindingVar != null) { //TODO: cannot be null here? - JCAssign fakeInit = (JCAssign)make.at(tree.pos).Assign( - make.Ident(bindingVar), convert(make.Ident(temp), castTargetType)).setType(bindingVar.erasure(types)); - LetExpr nestedLE = make.LetExpr(List.of(make.Exec(fakeInit)), - make.Literal(true)); - nestedLE.needsCond = true; - nestedLE.setType(syms.booleanType); - result = makeBinary(Tag.AND, (JCExpression)result, nestedLE); - } - if (temp != exprSym) { - result = make.at(tree.pos).LetExpr(make.VarDef(temp, translatedExpr), (JCExpression)result).setType(syms.booleanType); - ((LetExpr) result).needsCond = true; + JCExpression translatedExpr = translate(tree.expr); + Type principalType = principalType((JCPattern) tree.pattern); + result = makeBinary(Tag.AND, + makeTypeTest(make.Ident(currentValue), make.Type(principalType)), + (JCExpression) this.translate(tree.pattern)); + if (currentValue != exprSym) { + result = make.at(tree.pos).LetExpr(make.VarDef(currentValue, translatedExpr), + (JCExpression)result).setType(syms.booleanType); + ((LetExpr) result).needsCond = true; + } + } finally { + currentValue = prevCurrentValue; } } else { super.visitTypeTest(tree); } } + @Override + public void visitBindingPattern(JCBindingPattern tree) { + //it is assumed the primary type has already been checked: + BindingSymbol binding = (BindingSymbol) tree.var.sym; + Type castTargetType = principalType(tree); + VarSymbol bindingVar = bindingContext.bindingDeclared(binding); + + if (bindingVar != null) { + JCAssign fakeInit = (JCAssign)make.at(TreeInfo.getStartPos(tree)).Assign( + make.Ident(bindingVar), convert(make.Ident(currentValue), castTargetType)).setType(bindingVar.erasure(types)); + LetExpr nestedLE = make.LetExpr(List.of(make.Exec(fakeInit)), + make.Literal(true)); + nestedLE.needsCond = true; + nestedLE.setType(syms.booleanType); + result = nestedLE; + } else { + result = make.Literal(true); + } + } + + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern tree) { + result = translate(tree.pattern); + } + + @Override + public void visitGuardPattern(JCGuardPattern tree) { + JCExpression pattern = (JCExpression) this.translate(tree.patt); + JCExpression guard = translate(tree.expr); + result = makeBinary(Tag.AND, pattern, guard); + } + + @Override + public void visitSwitch(JCSwitch tree) { + handleSwitch(tree, tree.selector, tree.cases, tree.hasTotalPattern, tree.patternSwitch); + } + + @Override + public void visitSwitchExpression(JCSwitchExpression tree) { + handleSwitch(tree, tree.selector, tree.cases, tree.hasTotalPattern, tree.patternSwitch); + } + + private void handleSwitch(JCTree tree, + JCExpression selector, + List cases, + boolean hasTotalPattern, + boolean patternSwitch) { + Type seltype = selector.type; + boolean enumSwitch = (seltype.tsym.flags() & Flags.ENUM) != 0; + + if (patternSwitch) { + Assert.check(preview.isEnabled()); + Assert.check(preview.usesPreview(env.toplevel.sourcefile)); + + //rewrite pattern matching switches: + //switch ($obj) { + // case $constant: $stats$ + // case $pattern1: $stats$ + // case $pattern2, null: $stats$ + // case $pattern3: $stats$ + //} + //=> + //int $idx = 0; + //$RESTART: switch (invokeDynamic typeSwitch($constant, typeof($pattern1), typeof($pattern2), typeof($pattern3))($obj, $idx)) { + // case 0: + // if (!()) { $idx = 1; continue $RESTART; } + // $stats$ + // case 1: + // if (!()) { $idx = 2; continue $RESTART; } + // $stats$ + // case 2, -1: + // if (!()) { $idx = 3; continue $RESTART; } + // $stats$ + // case 3: + // if (!()) { $idx = 4; continue $RESTART; } + // $stats$ + //} + //notes: + //-pattern desugaring performs assignment to the binding variables + //-the selector is evaluated only once and stored in a temporary variable + //-typeSwitch bootstrap method can restart matching at specified index. The bootstrap will + // categorize the input, and return the case index whose type or constant matches the input. + // The bootstrap does not evaluate guards, which are injected at the beginning of the case's + // statement list, and if the guard fails, the switch is "continued" and matching is + // restarted from the next index. + //-case null is always desugared to case -1, as the typeSwitch bootstrap method will + // return -1 when the input is null + // + //a special case for switches over enums with pattern case + //with only a single unguarded (type) pattern case, which is equivalent + //to a default with additional binding variable assignment: + //switch ($enum) { + // case $constant1: $stats$ + // case $constant2: $stats$ + // case typeof($enum) e: $stats$ + //} + //=> + //switch ($enum) { + // case $constant1: $stats$ + // case $constant2: $stats$ + // default: typeof($enum) e = $enum; $stats$ + //} + //constant labels in switches over enums with one or more pattern cases + //with guards are desugared into guards: + //case $constant1: $stats$ + //=> + //case typeof($enum) e && e == $constant1: $stats$ + //and handled as a normal pattern matching switch + // + //note the selector is evaluated only once and stored in a temporary variable + ListBuffer newCases = new ListBuffer<>(); + for (List c = cases; c.nonEmpty(); c = c.tail) { + if (c.head.stats.isEmpty() && c.tail.nonEmpty()) { + c.tail.head.labels = c.tail.head.labels.prependList(c.head.labels); + } else { + newCases.add(c.head); + } + } + if (enumSwitch && hasGuards(newCases)) { + for (JCCase c : newCases) { + for (List l = c.labels; l.nonEmpty(); l = l.tail) { + if (l.head.isExpression() && !TreeInfo.isNull(l.head)) { + BindingSymbol temp = new BindingSymbol(Flags.SYNTHETIC, + names.fromString("enumGuard" + c.pos + + target.syntheticNameChar() + "temp"), + seltype, + currentMethodSym); + JCBindingPattern binding = + make.at(l.head.pos()).BindingPattern(make.VarDef(temp, null)); + binding.setType(seltype); + l.head = make.GuardPattern(binding, + makeBinary(Tag.EQ, + make.Ident(temp), + (JCExpression) l.head)); + } + } + } + enumSwitch = false; + } + cases = newCases.toList(); + ListBuffer statements = new ListBuffer<>(); + VarSymbol temp = new VarSymbol(Flags.SYNTHETIC, + names.fromString("selector" + tree.pos + target.syntheticNameChar() + "temp"), + seltype, + currentMethodSym); + boolean hasNullCase = cases.stream() + .flatMap(c -> c.labels.stream()) + .anyMatch(p -> p.isExpression() && + TreeInfo.isNull((JCExpression) p)); + + JCCase lastCase = cases.last(); + + if (hasTotalPattern && !hasNullCase) { + JCCase last = lastCase; + if (last.labels.stream().noneMatch(l -> l.hasTag(Tag.DEFAULTCASELABEL))) { + last.labels = last.labels.prepend(makeLit(syms.botType, null)); + hasNullCase = true; + } + } + statements.append(make.at(tree.pos).VarDef(temp, !hasNullCase ? attr.makeNullCheck(selector) + : selector)); + VarSymbol index = new VarSymbol(Flags.SYNTHETIC, + names.fromString(tree.pos + target.syntheticNameChar() + "index"), + syms.intType, + currentMethodSym); + statements.append(make.at(tree.pos).VarDef(index, makeLit(syms.intType, 0))); + + if (enumSwitch) { + selector = make.Ident(temp); + } else { + List staticArgTypes = List.of(syms.methodHandleLookupType, + syms.stringType, + syms.methodTypeType, + types.makeArrayType(new ClassType(syms.classType.getEnclosingType(), + List.of(new WildcardType(syms.objectType, BoundKind.UNBOUND, + syms.boundClass)), + syms.classType.tsym))); + LoadableConstant[] staticArgValues = + cases.stream() + .flatMap(c -> c.labels.stream()) + .map(l -> toLoadableConstant(l)) + .filter(c -> c != null) + .toArray(s -> new LoadableConstant[s]); + + Symbol bsm = rs.resolveInternalMethod(tree.pos(), env, syms.switchBootstrapsType, + names.fromString("typeSwitch"), staticArgTypes, List.nil()); + + MethodType indyType = new MethodType( + List.of(syms.objectType, syms.intType), + syms.intType, + List.nil(), + syms.methodClass + ); + DynamicMethodSymbol dynSym = new DynamicMethodSymbol(names.fromString("typeSwitch"), + syms.noSymbol, + ((MethodSymbol)bsm).asHandle(), + indyType, + staticArgValues); + + JCFieldAccess qualifier = make.Select(make.QualIdent(bsm.owner), dynSym.name); + qualifier.sym = dynSym; + qualifier.type = syms.intType; + selector = make.Apply(List.nil(), + qualifier, + List.of(make.Ident(temp), make.Ident(index))) + .setType(syms.intType); + } + + int i = 0; + boolean previousCompletesNormally = false; + + for (var c : cases) { + List clearedPatterns = c.labels; + boolean hasJoinedNull = + c.labels.size() > 1 && c.labels.stream().anyMatch(l -> l.isNullPattern()); + if (hasJoinedNull) { + clearedPatterns = c.labels.stream() + .filter(l -> !l.isNullPattern()) + .collect(List.collector()); + } + if (clearedPatterns.size() == 1 && clearedPatterns.head.isPattern() && !previousCompletesNormally) { + JCCaseLabel p = clearedPatterns.head; + bindingContext = new BasicBindingContext(); + VarSymbol prevCurrentValue = currentValue; + try { + currentValue = temp; + JCExpression test = (JCExpression) this.translate(p); + c.stats = translate(c.stats); + JCContinue continueSwitch = make.at(clearedPatterns.head.pos()).Continue(null); + continueSwitch.target = tree; + c.stats = c.stats.prepend(make.If(makeUnary(Tag.NOT, test).setType(syms.booleanType), + make.Block(0, List.of(make.Exec(make.Assign(make.Ident(index), + makeLit(syms.intType, i + 1)) + .setType(syms.intType)), + continueSwitch)), + null)); + c.stats = c.stats.prependList(bindingContext.bindingVars(c.pos)); + } finally { + currentValue = prevCurrentValue; + bindingContext.pop(); + } + } + if (enumSwitch) { + var labels = c.labels; + + while (labels.nonEmpty()) { + if (labels.head.isPattern()) { + labels.head = make.DefaultCaseLabel(); + } + labels = labels.tail; + } + } else { + ListBuffer translatedLabels = new ListBuffer<>(); + for (var p : c.labels) { + if (p.hasTag(Tag.DEFAULTCASELABEL)) { + translatedLabels.add(p); + } else if (hasTotalPattern && c == lastCase && p.isPattern()) { + //If the switch has total pattern, the last case will contain it. + //Convert the total pattern to default: + translatedLabels.add(make.DefaultCaseLabel()); + } else { + int value; + if (p.isNullPattern()) { + value = -1; + } else { + value = i++; + } + translatedLabels.add(make.Literal(value)); + } + } + c.labels = translatedLabels.toList(); + } + if (c.caseKind == CaseTree.CaseKind.STATEMENT) { + previousCompletesNormally = c.completesNormally; + } else { + previousCompletesNormally = false; + JCBreak brk = make.at(TreeInfo.endPos(c.stats.last())).Break(null); + brk.target = tree; + c.stats = c.stats.append(brk); + } + } + + if (tree.hasTag(Tag.SWITCH)) { + ((JCSwitch) tree).selector = selector; + ((JCSwitch) tree).cases = cases; + statements.append((JCSwitch) tree); + result = make.Block(0, statements.toList()); + } else { + ((JCSwitchExpression) tree).selector = selector; + ((JCSwitchExpression) tree).cases = cases; + LetExpr r = (LetExpr) make.LetExpr(statements.toList(), (JCSwitchExpression) tree) + .setType(tree.type); + + r.needsCond = true; + result = r; + } + return ; + } + if (tree.hasTag(Tag.SWITCH)) { + super.visitSwitch((JCSwitch) tree); + } else { + super.visitSwitchExpression((JCSwitchExpression) tree); + } + } + + private boolean hasGuards(Collection cases) { + return cases.stream() + .flatMap(c -> c.labels.stream()) + .filter(JCCaseLabel::isPattern) + .anyMatch(l -> !TreeInfo.primaryPatternType((JCPattern) l).unconditional()); + } + + private Type principalType(JCPattern p) { + return types.boxedTypeOrType(types.erasure(TreeInfo.primaryPatternType(p).type())); + } + + private LoadableConstant toLoadableConstant(JCCaseLabel l) { + if (l.isPattern()) { + return (LoadableConstant) principalType((JCPattern) l); + } else if (l.isExpression() && !TreeInfo.isNull((JCExpression) l)) { + Assert.checkNonNull(l.type.constValue()); + + return switch (l.type.getTag()) { + case BYTE, CHAR, + SHORT, INT -> LoadableConstant.Int((Integer) l.type.constValue()); + case CLASS -> LoadableConstant.String((String) l.type.constValue()); + default -> throw new AssertionError(); + }; + } else { + return null; + } + } + @Override public void visitBinary(JCBinary tree) { bindingContext = new BasicBindingContext(); @@ -308,7 +668,15 @@ public class TransPatterns extends TreeTranslator { return true; } }; + MethodSymbol oldMethodSym = currentMethodSym; try { + if (currentMethodSym == null) { + // Block is a static or instance initializer. + currentMethodSym = + new MethodSymbol(tree.flags | Flags.BLOCK, + names.empty, null, + currentClass); + } for (List l = tree.stats; l.nonEmpty(); l = l.tail) { statements.append(translate(l.head)); } @@ -316,6 +684,7 @@ public class TransPatterns extends TreeTranslator { tree.stats = statements.toList(); result = tree; } finally { + currentMethodSym = oldMethodSym; bindingContext.pop(); } } @@ -331,13 +700,45 @@ public class TransPatterns extends TreeTranslator { } } + @Override + public void visitClassDef(JCClassDecl tree) { + ClassSymbol prevCurrentClass = currentClass; + try { + currentClass = tree.sym; + super.visitClassDef(tree); + } finally { + currentClass = prevCurrentClass; + } + } + + public void visitVarDef(JCVariableDecl tree) { + MethodSymbol prevMethodSym = currentMethodSym; + try { + tree.mods = translate(tree.mods); + tree.vartype = translate(tree.vartype); + if (currentMethodSym == null) { + // A class or instance field initializer. + currentMethodSym = + new MethodSymbol((tree.mods.flags&Flags.STATIC) | Flags.BLOCK, + names.empty, null, + currentClass); + } + if (tree.init != null) tree.init = translate(tree.init); + result = tree; + } finally { + currentMethodSym = prevMethodSym; + } + } + public JCTree translateTopLevelClass(Env env, JCTree cdef, TreeMaker make) { try { this.make = make; + this.env = env; translate(cdef); } finally { // note that recursive invocations of this method fail hard this.make = null; + this.env = null; } return cdef; @@ -366,6 +767,17 @@ public class TransPatterns extends TreeTranslator { return tree; } + /** Make an attributed unary expression. + * @param optag The operators tree tag. + * @param arg The operator's argument. + */ + JCTree.JCUnary makeUnary(JCTree.Tag optag, JCExpression arg) { + JCTree.JCUnary tree = make.Unary(optag, arg); + tree.operator = operators.resolveUnary(tree, optag, arg.type); + tree.type = tree.operator.type.getReturnType(); + return tree; + } + JCExpression convert(JCExpression expr, Type target) { JCExpression result = make.at(expr.pos()).TypeCast(make.Type(target), expr); result.type = target; @@ -375,6 +787,7 @@ public class TransPatterns extends TreeTranslator { abstract class BindingContext { abstract VarSymbol bindingDeclared(BindingSymbol varSymbol); abstract VarSymbol getBindingFor(BindingSymbol varSymbol); + abstract List bindingVars(int diagPos); abstract JCStatement decorateStatement(JCStatement stat); abstract JCExpression decorateExpression(JCExpression expr); abstract BindingContext pop(); @@ -413,9 +826,22 @@ public class TransPatterns extends TreeTranslator { .map(e -> e.getValue()).orElse(null); } + @Override + List bindingVars(int diagPos) { + if (hoistedVarMap.isEmpty()) return List.nil(); + ListBuffer stats = new ListBuffer<>(); + for (Entry e : hoistedVarMap.entrySet()) { + JCVariableDecl decl = makeHoistedVarDecl(diagPos, e.getValue()); + if (!e.getKey().isPreserved() || + !parent.tryPrepend(e.getKey(), decl)) { + stats.add(decl); + } + } + return stats.toList(); + } + @Override JCStatement decorateStatement(JCStatement stat) { - if (hoistedVarMap.isEmpty()) return stat; //if (E instanceof T N) { // //use N //} @@ -426,17 +852,9 @@ public class TransPatterns extends TreeTranslator { // //use N // } //} - ListBuffer stats = new ListBuffer<>(); - for (Entry e : hoistedVarMap.entrySet()) { - JCVariableDecl decl = makeHoistedVarDecl(stat.pos, e.getValue()); - if (!e.getKey().isPreserved() || - !parent.tryPrepend(e.getKey(), decl)) { - stats.add(decl); - } - } + List stats = bindingVars(stat.pos); if (stats.nonEmpty()) { - stats.add(stat); - stat = make.at(stat.pos).Block(0, stats.toList()); + stat = make.at(stat.pos).Block(0, stats.append(stat)); } return stat; } @@ -475,4 +893,14 @@ public class TransPatterns extends TreeTranslator { } } + + /** Make an attributed tree representing a literal. This will be an + * Ident node in the case of boolean literals, a Literal node in all + * other cases. + * @param type The literal's type. + * @param value The literal's value. + */ + JCExpression makeLit(Type type, Object value) { + return make.Literal(type.getTag(), value).setType(type.constType(value)); + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java index cea7c3f0ae1..018efd59b9f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java @@ -25,15 +25,11 @@ package com.sun.tools.javac.comp; -import java.util.*; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.code.Symbol.*; -import com.sun.tools.javac.code.Type.IntersectionClassType; -import com.sun.tools.javac.code.Types.FunctionDescriptorLookupError; -import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.tree.*; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind; @@ -562,7 +558,7 @@ public class TransTypes extends TreeTranslator { } public void visitCase(JCCase tree) { - tree.pats = translate(tree.pats, null); + tree.labels = translate(tree.labels, null); tree.stats = translate(tree.stats); result = tree; } @@ -583,6 +579,19 @@ public class TransTypes extends TreeTranslator { result = retype(tree, tree.type, pt); } + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern tree) { + tree.pattern = translate(tree.pattern, null); + result = tree; + } + + @Override + public void visitGuardPattern(JCGuardPattern tree) { + tree.patt = translate(tree.patt, null); + tree.expr = translate(tree.expr, syms.booleanType); + result = tree; + } + public void visitSynchronized(JCSynchronized tree) { tree.lock = translate(tree.lock, erasure(tree.lock.type)); tree.body = translate(tree.body); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java index 971abe63a42..52efdcb2333 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java @@ -45,6 +45,7 @@ import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCConditional; import com.sun.tools.javac.tree.JCTree.JCContinue; +import com.sun.tools.javac.tree.JCTree.JCDefaultCaseLabel; import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; import com.sun.tools.javac.tree.JCTree.JCErroneous; @@ -284,7 +285,12 @@ public class TreeDiffer extends TreeScanner { @Override public void visitCase(JCCase tree) { JCCase that = (JCCase) parameter; - result = scan(tree.pats, that.pats) && scan(tree.stats, that.stats); + result = scan(tree.labels, that.labels) && scan(tree.stats, that.stats); + } + + @Override + public void visitDefaultCaseLabel(JCDefaultCaseLabel tree) { + result = true; } @Override diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/CRTable.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/CRTable.java index 1e9fa5218c3..561c010a728 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/CRTable.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/CRTable.java @@ -322,11 +322,16 @@ implements CRTFlags { public void visitCase(JCCase tree) { SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); - sr.mergeWith(csp(tree.pats)); + sr.mergeWith(csp(tree.labels)); sr.mergeWith(csp(tree.stats)); result = sr; } + @Override + public void visitDefaultCaseLabel(JCTree.JCDefaultCaseLabel that) { + result = null; + } + public void visitSynchronized(JCSynchronized tree) { SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); sr.mergeWith(csp(tree.lock)); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index 942a7dabea3..833410b8a04 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -1190,7 +1190,7 @@ public class Gen extends JCTree.Visitor { } public void visitSwitch(JCSwitch tree) { - handleSwitch(tree, tree.selector, tree.cases); + handleSwitch(tree, tree.selector, tree.cases, tree.patternSwitch); } @Override @@ -1235,7 +1235,7 @@ public class Gen extends JCTree.Visitor { } int prevLetExprStart = code.setLetExprStackPos(code.state.stacksize); try { - handleSwitch(tree, tree.selector, tree.cases); + handleSwitch(tree, tree.selector, tree.cases, tree.patternSwitch); } finally { code.setLetExprStackPos(prevLetExprStart); } @@ -1265,9 +1265,11 @@ public class Gen extends JCTree.Visitor { return hasTry[0]; } - private void handleSwitch(JCTree swtch, JCExpression selector, List cases) { + private void handleSwitch(JCTree swtch, JCExpression selector, List cases, + boolean patternSwitch) { int limit = code.nextreg; Assert.check(!selector.type.hasTag(CLASS)); + int switchStart = patternSwitch ? code.entryPoint() : -1; int startpcCrt = genCrt ? code.curCP() : 0; Assert.check(code.isStatementStart()); Item sel = genExpr(selector, syms.intType); @@ -1297,9 +1299,9 @@ public class Gen extends JCTree.Visitor { List l = cases; for (int i = 0; i < labels.length; i++) { - if (l.head.pats.nonEmpty()) { - Assert.check(l.head.pats.size() == 1); - int val = ((Number)l.head.pats.head.type.constValue()).intValue(); + if (l.head.labels.head.isExpression()) { + Assert.check(l.head.labels.size() == 1); + int val = ((Number)((JCExpression) l.head.labels.head).type.constValue()).intValue(); labels[i] = val; if (val < lo) lo = val; if (hi < val) hi = val; @@ -1371,6 +1373,12 @@ public class Gen extends JCTree.Visitor { genStats(c.stats, switchEnv, CRT_FLOW_TARGET); } + if (switchEnv.info.cont != null) { + Assert.check(patternSwitch); + code.resolve(switchEnv.info.cont); + code.resolve(code.branch(goto_), switchStart); + } + // Resolve all breaks. Chain exit = switchEnv.info.exit; if (exit != null) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index f6bf08f4c75..0158a060775 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -61,6 +61,7 @@ import static com.sun.tools.javac.tree.JCTree.Tag.*; import static com.sun.tools.javac.resources.CompilerProperties.Fragments.ImplicitAndExplicitNotAllowed; import static com.sun.tools.javac.resources.CompilerProperties.Fragments.VarAndExplicitNotAllowed; import static com.sun.tools.javac.resources.CompilerProperties.Fragments.VarAndImplicitNotAllowed; +import java.util.function.BiFunction; /** * The parser maps a token sequence into an abstract syntax tree. @@ -758,6 +759,33 @@ public class JavacParser implements Parser { return term(EXPR); } + + /** parses patterns. + */ + + public JCPattern parsePattern(int pos, JCModifiers mods, JCExpression parsedType, boolean inInstanceOf) { + if (token.kind == LPAREN && parsedType == null) { + int startPos = token.pos; + accept(LPAREN); + JCPattern p = parsePattern(token.pos, null, null, false); + accept(RPAREN); + return toP(F.at(startPos).ParenthesizedPattern(p)); + } else { + JCPattern pattern; + JCExpression e = parsedType == null ? term(EXPR | TYPE | NOLAMBDA) : parsedType; + mods = mods != null ? mods : F.at(token.pos).Modifiers(0); + JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), e, null)); + pattern = toP(F.at(pos).BindingPattern(var)); + if (!inInstanceOf && token.kind == AMPAMP) { + checkSourceLevel(Feature.PATTERN_SWITCH); + nextToken(); + JCExpression guard = term(EXPR | NOLAMBDA); + pattern = F.at(pos).GuardPattern(pattern, guard); + } + return pattern; + } + } + /** * parses (optional) type annotations followed by a type. If the * annotations are present before the type and are not consumed during array @@ -936,31 +964,35 @@ public class JavacParser implements Parser { if (token.kind == INSTANCEOF) { int pos = token.pos; nextToken(); - int patternPos = token.pos; - JCModifiers mods = optFinal(0); - int typePos = token.pos; - JCExpression type = unannotatedType(false); JCTree pattern; - if (token.kind == IDENTIFIER) { - checkSourceLevel(token.pos, Feature.PATTERN_MATCHING_IN_INSTANCEOF); - JCVariableDecl var = toP(F.at(token.pos).VarDef(mods, ident(), type, null)); - pattern = toP(F.at(patternPos).BindingPattern(var)); + if (token.kind == LPAREN) { + checkSourceLevel(token.pos, Feature.PATTERN_SWITCH); + pattern = parsePattern(token.pos, null, null, true); } else { - checkNoMods(typePos, mods.flags & ~Flags.DEPRECATED); - if (mods.annotations.nonEmpty()) { - checkSourceLevel(mods.annotations.head.pos, Feature.TYPE_ANNOTATIONS); - List typeAnnos = - mods.annotations - .map(decl -> { - JCAnnotation typeAnno = F.at(decl.pos) - .TypeAnnotation(decl.annotationType, - decl.args); - endPosTable.replaceTree(decl, typeAnno); - return typeAnno; - }); - type = insertAnnotationsToMostInner(type, typeAnnos, false); + int patternPos = token.pos; + JCModifiers mods = optFinal(0); + int typePos = token.pos; + JCExpression type = unannotatedType(false); + if (token.kind == IDENTIFIER) { + checkSourceLevel(token.pos, Feature.PATTERN_MATCHING_IN_INSTANCEOF); + pattern = parsePattern(patternPos, mods, type, true); + } else { + checkNoMods(typePos, mods.flags & ~Flags.DEPRECATED); + if (mods.annotations.nonEmpty()) { + checkSourceLevel(mods.annotations.head.pos, Feature.TYPE_ANNOTATIONS); + List typeAnnos = + mods.annotations + .map(decl -> { + JCAnnotation typeAnno = F.at(decl.pos) + .TypeAnnotation(decl.annotationType, + decl.args); + endPosTable.replaceTree(decl, typeAnno); + return typeAnno; + }); + type = insertAnnotationsToMostInner(type, typeAnnos, false); + } + pattern = type; } - pattern = type; } odStack[top] = F.at(pos).TypeTest(odStack[top], pattern); } else { @@ -1461,14 +1493,16 @@ public class JavacParser implements Parser { private List switchExpressionStatementGroup() { ListBuffer caseExprs = new ListBuffer<>(); int casePos = token.pos; - ListBuffer pats = new ListBuffer<>(); + ListBuffer pats = new ListBuffer<>(); if (token.kind == DEFAULT) { nextToken(); + pats.append(toP(F.at(casePos).DefaultCaseLabel())); } else { accept(CASE); while (true) { - pats.append(term(EXPR | NOLAMBDA)); + JCCaseLabel label = parseCaseLabel(); + pats.append(label); if (token.kind != COMMA) break; checkSourceLevel(Feature.SWITCH_MULTIPLE_CASE_LABELS); nextToken(); @@ -2958,9 +2992,9 @@ public class JavacParser implements Parser { switch (token.kind) { case CASE: { nextToken(); - ListBuffer pats = new ListBuffer<>(); + ListBuffer pats = new ListBuffer<>(); while (true) { - pats.append(term(EXPR | NOLAMBDA)); + pats.append(parseCaseLabel()); if (token.kind != COMMA) break; nextToken(); checkSourceLevel(Feature.SWITCH_MULTIPLE_CASE_LABELS); @@ -2991,6 +3025,7 @@ public class JavacParser implements Parser { nextToken(); CaseTree.CaseKind caseKind; JCTree body = null; + int patternPos = token.pos; if (token.kind == ARROW) { checkSourceLevel(Feature.SWITCH_RULE); accept(ARROW); @@ -3006,7 +3041,8 @@ public class JavacParser implements Parser { caseKind = JCCase.STATEMENT; stats = blockStatements(); } - c = F.at(pos).Case(caseKind, List.nil(), stats, body); + JCCaseLabel defaultPattern = toP(F.at(patternPos).DefaultCaseLabel()); + c = F.at(pos).Case(caseKind, List.of(defaultPattern), stats, body); if (stats.isEmpty()) storeEnd(c, S.prevToken().endPos); return cases.append(c).toList(); @@ -3015,6 +3051,46 @@ public class JavacParser implements Parser { throw new AssertionError("should not reach here"); } + private JCCaseLabel parseCaseLabel() { + int patternPos = token.pos; + JCCaseLabel label; + + if (token.kind == DEFAULT) { + checkSourceLevel(token.pos, Feature.PATTERN_SWITCH); + nextToken(); + label = toP(F.at(patternPos).DefaultCaseLabel()); + } else { + if (token.kind == LPAREN) { + int lookahead = 0; + Token ahead; + while ((ahead = S.token(lookahead)).kind != EOF && ahead.kind != RPAREN && ahead.kind != AMPAMP) { + lookahead++; + } + Token twoBack; + boolean pattern = S.token(lookahead - 1).kind == IDENTIFIER && + ((twoBack = S.token(lookahead - 2)).kind == IDENTIFIER || + twoBack.kind == GT || twoBack.kind == GTGT || twoBack.kind == GTGTGT); + if (pattern) { + return parsePattern(token.pos, null, null, false); + } else { + return term(EXPR | TYPE | NOLAMBDA); + } + } else { + JCModifiers mods = optFinal(0); + JCExpression e = term(EXPR | TYPE | NOLAMBDA); + + if (token.kind == IDENTIFIER || mods.flags != 0 || mods.annotations.nonEmpty()) { + checkSourceLevel(token.pos, Feature.PATTERN_SWITCH); + return parsePattern(patternPos, null, e, false); + } else { + return e; + } + } + } + + return label; + } + /** MoreStatementExpressions = { COMMA StatementExpression } */ > T moreStatementExpressions(int pos, 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 9798e6b6803..05b5fc13d32 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 @@ -351,6 +351,9 @@ compiler.misc.lambda=\ compiler.misc.inner.cls=\ an inner class +compiler.misc.guard=\ + a guard + # 0: type compiler.err.cant.deref=\ {0} cannot be dereferenced @@ -494,9 +497,28 @@ compiler.err.same.binary.name=\ compiler.err.duplicate.case.label=\ duplicate case label +compiler.err.pattern.dominated=\ + this case label is dominated by a preceding case label + compiler.err.duplicate.default.label=\ duplicate default label +compiler.err.duplicate.total.pattern=\ + duplicate total pattern + +compiler.err.total.pattern.and.default=\ + switch has both a total pattern and a default label + +# 0: type, 1: type +compiler.err.constant.label.not.compatible=\ + constant label of type {0} is not compatible with switch selector type {1} + +compiler.err.flows.through.to.pattern=\ + illegal fall-through to a pattern + +compiler.err.flows.through.from.pattern=\ + illegal fall-through from a pattern + compiler.err.else.without.if=\ ''else'' without ''if'' @@ -1357,6 +1379,9 @@ compiler.err.unreachable.stmt=\ compiler.err.not.exhaustive=\ the switch expression does not cover all possible input values +compiler.err.not.exhaustive.statement=\ + the switch statement does not cover all possible input values + compiler.err.initializer.must.be.able.to.complete.normally=\ initializer must be able to complete normally @@ -2958,6 +2983,12 @@ compiler.misc.feature.records=\ compiler.misc.feature.sealed.classes=\ sealed classes +compiler.misc.feature.case.null=\ + null in switch cases + +compiler.misc.feature.pattern.switch=\ + patterns in switch statements + compiler.warn.underscore.as.identifier=\ as of release 9, ''_'' is a keyword, and may not be used as an identifier @@ -3479,9 +3510,6 @@ compiler.err.illegal.argument.for.option=\ compiler.err.match.binding.exists=\ illegal attempt to redefine an existing match binding -compiler.err.switch.null.not.allowed=\ - null label in case is not allowed - compiler.err.switch.case.unexpected.statement=\ unexpected statement in case, expected is an expression, a block or a throw statement diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index d22049ea8b8..bf94f34bb9a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -240,6 +240,9 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { /** Patterns. */ BINDINGPATTERN, + DEFAULTCASELABEL, + GUARDPATTERN, + PARENTHESIZEDPATTERN, /** Indexed array expressions, of type Indexed. */ @@ -699,7 +702,15 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { } } - public static abstract class JCExpression extends JCTree implements ExpressionTree { + public static abstract class JCCaseLabel extends JCTree implements CaseLabelTree { + public abstract boolean isExpression(); + public boolean isNullPattern() { + return isExpression() && TreeInfo.isNull((JCExpression) this); + } + public abstract boolean isPattern(); + } + + public static abstract class JCExpression extends JCCaseLabel implements ExpressionTree { @Override public JCExpression setType(Type type) { super.setType(type); @@ -713,6 +724,16 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public boolean isPoly() { return false; } public boolean isStandalone() { return true; } + + @Override + public boolean isExpression() { + return true; + } + + @Override + public boolean isPattern() { + return false; + } } /** @@ -1264,6 +1285,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public List cases; /** Position of closing brace, optional. */ public int endpos = Position.NOPOS; + public boolean hasTotalPattern; + public boolean patternSwitch; protected JCSwitch(JCExpression selector, List cases) { this.selector = selector; this.cases = cases; @@ -1296,16 +1319,16 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public static final CaseKind STATEMENT = CaseKind.STATEMENT; public static final CaseKind RULE = CaseKind.RULE; public final CaseKind caseKind; - public List pats; + public List labels; public List stats; public JCTree body; public boolean completesNormally; - protected JCCase(CaseKind caseKind, List pats, + protected JCCase(CaseKind caseKind, List labels, List stats, JCTree body) { - Assert.checkNonNull(pats); - Assert.check(pats.isEmpty() || pats.head != null); + Assert.checkNonNull(labels); + Assert.check(labels.isEmpty() || labels.head != null); this.caseKind = caseKind; - this.pats = pats; + this.labels = labels; this.stats = stats; this.body = body; } @@ -1315,9 +1338,11 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { @Override @DefinedBy(Api.COMPILER_TREE) public Kind getKind() { return Kind.CASE; } @Override @Deprecated @DefinedBy(Api.COMPILER_TREE) - public JCExpression getExpression() { return pats.head; } + public JCExpression getExpression() { return getExpressions().head; } @Override @DefinedBy(Api.COMPILER_TREE) - public List getExpressions() { return pats; } + public List getExpressions() { return labels.stream().filter(p -> p instanceof JCExpression).map(p -> (JCExpression) p).collect(List.collector()); } + @Override @DefinedBy(Api.COMPILER_TREE) + public List getLabels() { return labels; } @Override @DefinedBy(Api.COMPILER_TREE) public List getStatements() { return caseKind == CaseKind.STATEMENT ? stats : null; @@ -1346,6 +1371,8 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public List cases; /** Position of closing brace, optional. */ public int endpos = Position.NOPOS; + public boolean hasTotalPattern; + public boolean patternSwitch; protected JCSwitchExpression(JCExpression selector, List cases) { this.selector = selector; this.cases = cases; @@ -2212,8 +2239,18 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { /** * Pattern matching forms. */ - public static abstract class JCPattern extends JCTree + public static abstract class JCPattern extends JCCaseLabel implements PatternTree { + + @Override + public boolean isExpression() { + return false; + } + + @Override + public boolean isPattern() { + return true; + } } public static class JCBindingPattern extends JCPattern @@ -2251,6 +2288,121 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { } } + public static class JCDefaultCaseLabel extends JCCaseLabel + implements DefaultCaseLabelTree { + + protected JCDefaultCaseLabel() { + } + + @Override + public void accept(Visitor v) { + v.visitDefaultCaseLabel(this); + } + + @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { + return Kind.DEFAULT_CASE_LABEL; + } + + @Override + @DefinedBy(Api.COMPILER_TREE) + public R accept(TreeVisitor v, D d) { + return v.visitDefaultCaseLabel(this, d); + } + + @Override + public Tag getTag() { + return DEFAULTCASELABEL; + } + + @Override + public boolean isExpression() { + return false; + } + + @Override + public boolean isPattern() { + return false; + } + } + + public static class JCParenthesizedPattern extends JCPattern + implements ParenthesizedPatternTree { + public JCPattern pattern; + + public JCParenthesizedPattern(JCPattern pattern) { + this.pattern = pattern; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public PatternTree getPattern() { + return pattern; + } + + @Override + public void accept(Visitor v) { + v.visitParenthesizedPattern(this); + } + + @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { + return Kind.PARENTHESIZED_PATTERN; + } + + @Override + @DefinedBy(Api.COMPILER_TREE) + public R accept(TreeVisitor v, D d) { + return v.visitParenthesizedPattern(this, d); + } + + @Override + public Tag getTag() { + return PARENTHESIZEDPATTERN; + } + } + + public static class JCGuardPattern extends JCPattern + implements GuardedPatternTree { + public JCPattern patt; + public JCExpression expr; + + public JCGuardPattern(JCPattern patt, JCExpression expr) { + this.patt = patt; + this.expr = expr; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public PatternTree getPattern() { + return patt; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public ExpressionTree getExpression() { + return expr; + } + + @Override + public void accept(Visitor v) { + v.visitGuardPattern(this); + } + + @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { + return Kind.GUARDED_PATTERN; + } + + @Override + @DefinedBy(Api.COMPILER_TREE) + public R accept(TreeVisitor v, D d) { + return v.visitGuardedPattern(this, d); + } + + @Override + public Tag getTag() { + return Tag.GUARDPATTERN; + } + } + /** * An array selection */ @@ -3187,7 +3339,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { JCLabeledStatement Labelled(Name label, JCStatement body); JCSwitch Switch(JCExpression selector, List cases); JCSwitchExpression SwitchExpression(JCExpression selector, List cases); - JCCase Case(CaseTree.CaseKind caseKind, List pat, + JCCase Case(CaseTree.CaseKind caseKind, List labels, List stats, JCTree body); JCSynchronized Synchronized(JCExpression lock, JCBlock body); JCTry Try(JCBlock body, List catchers, JCBlock finalizer); @@ -3291,6 +3443,9 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public void visitTypeCast(JCTypeCast that) { visitTree(that); } public void visitTypeTest(JCInstanceOf that) { visitTree(that); } public void visitBindingPattern(JCBindingPattern that) { visitTree(that); } + public void visitDefaultCaseLabel(JCDefaultCaseLabel that) { visitTree(that); } + public void visitParenthesizedPattern(JCParenthesizedPattern that) { visitTree(that); } + public void visitGuardPattern(JCGuardPattern that) { visitTree(that); } public void visitIndexed(JCArrayAccess that) { visitTree(that); } public void visitSelect(JCFieldAccess that) { visitTree(that); } public void visitReference(JCMemberReference that) { visitTree(that); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java index 71677486213..acccd4d574c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java @@ -852,11 +852,11 @@ public class Pretty extends JCTree.Visitor { public void visitCase(JCCase tree) { try { - if (tree.pats.isEmpty()) { + if (tree.labels.size() == 1 && tree.labels.get(0).hasTag(DEFAULTCASELABEL)) { print("default"); } else { print("case "); - printExprs(tree.pats); + printExprs(tree.labels); } if (tree.caseKind == JCCase.STATEMENT) { print(":"); @@ -867,13 +867,26 @@ public class Pretty extends JCTree.Visitor { align(); } else { print(" -> "); - printStat(tree.stats.head); + if (tree.stats.size() == 1) { + printStat(tree.stats.head); + } else { + printBlock(tree.stats); + } } } catch (IOException e) { throw new UncheckedIOException(e); } } + @Override + public void visitDefaultCaseLabel(JCTree.JCDefaultCaseLabel that) { + try { + print("default"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public void visitSwitchExpression(JCSwitchExpression tree) { try { print("switch "); @@ -902,6 +915,28 @@ public class Pretty extends JCTree.Visitor { } } + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern patt) { + try { + print("("); + printExpr(patt.pattern); + print(")"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void visitGuardPattern(JCGuardPattern patt) { + try { + printExpr(patt.patt); + print(" && "); + printExpr(patt.expr); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public void visitSynchronized(JCSynchronized tree) { try { print("synchronized "); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java index c4f90e00e12..2e223a3430e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java @@ -152,7 +152,7 @@ public class TreeCopier

implements TreeVisitor { @DefinedBy(Api.COMPILER_TREE) public JCTree visitCase(CaseTree node, P p) { JCCase t = (JCCase) node; - List pats = copy(t.pats, p); + List labels = copy(t.labels, p); List stats = copy(t.stats, p); JCTree body; if (node.getCaseKind() == CaseTree.CaseKind.RULE) { @@ -161,7 +161,7 @@ public class TreeCopier

implements TreeVisitor { } else { body = null; } - return M.at(t.pos).Case(t.caseKind, pats, stats, body); + return M.at(t.pos).Case(t.caseKind, labels, stats, body); } @DefinedBy(Api.COMPILER_TREE) @@ -497,6 +497,27 @@ public class TreeCopier

implements TreeVisitor { return M.at(t.pos).BindingPattern(var); } + @DefinedBy(Api.COMPILER_TREE) + public JCTree visitGuardedPattern(GuardedPatternTree node, P p) { + JCGuardPattern t = (JCGuardPattern) node; + JCPattern patt = copy(t.patt, p); + JCExpression expr = copy(t.expr, p); + return M.at(t.pos).GuardPattern(patt, expr); + } + + @DefinedBy(Api.COMPILER_TREE) + public JCTree visitParenthesizedPattern(ParenthesizedPatternTree node, P p) { + JCParenthesizedPattern t = (JCParenthesizedPattern) node; + JCPattern pattern = copy(t.pattern, p); + return M.at(t.pos).ParenthesizedPattern(pattern); + } + + @DefinedBy(Api.COMPILER_TREE) + public JCTree visitDefaultCaseLabel(DefaultCaseLabelTree node, P p) { + JCDefaultCaseLabel t = (JCDefaultCaseLabel) node; + return M.at(t.pos).DefaultCaseLabel(); + } + @DefinedBy(Api.COMPILER_TREE) public JCTree visitUnary(UnaryTree node, P p) { JCUnary t = (JCUnary) node; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java index d19a0b7bbaf..faa690e6d77 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java @@ -40,6 +40,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import static com.sun.tools.javac.code.Flags.*; import static com.sun.tools.javac.code.Kinds.Kind.*; import com.sun.tools.javac.code.Symbol.VarSymbol; +import static com.sun.tools.javac.code.TypeTag.BOOLEAN; import static com.sun.tools.javac.code.TypeTag.BOT; import static com.sun.tools.javac.tree.JCTree.Tag.*; import static com.sun.tools.javac.tree.JCTree.Tag.BLOCK; @@ -541,6 +542,10 @@ public class TreeInfo { JCBindingPattern node = (JCBindingPattern)tree; return getStartPos(node.var); } + case GUARDPATTERN: { + JCGuardPattern node = (JCGuardPattern) tree; + return getStartPos(node.patt); + } case ERRONEOUS: { JCErroneous node = (JCErroneous)tree; if (node.errs != null && node.errs.nonEmpty()) @@ -630,6 +635,14 @@ public class TreeInfo { return getEndPos(((JCWhileLoop) tree).body, endPosTable); case ANNOTATED_TYPE: return getEndPos(((JCAnnotatedType) tree).underlyingType, endPosTable); + case PARENTHESIZEDPATTERN: { + JCParenthesizedPattern node = (JCParenthesizedPattern) tree; + return getEndPos(node.pattern, endPosTable); + } + case GUARDPATTERN: { + JCGuardPattern node = (JCGuardPattern) tree; + return getEndPos(node.expr, endPosTable); + } case ERRONEOUS: { JCErroneous node = (JCErroneous)tree; if (node.errs != null && node.errs.nonEmpty()) @@ -1329,7 +1342,31 @@ public class TreeInfo { public static boolean isErrorEnumSwitch(JCExpression selector, List cases) { return selector.type.tsym.kind == Kinds.Kind.ERR && - cases.stream().flatMap(c -> c.pats.stream()) + cases.stream().flatMap(c -> c.labels.stream()) .allMatch(p -> p.hasTag(IDENT)); } + + public static PatternPrimaryType primaryPatternType(JCPattern pat) { + return switch (pat.getTag()) { + case BINDINGPATTERN -> new PatternPrimaryType(((JCBindingPattern) pat).type, true); + case GUARDPATTERN -> { + JCGuardPattern guarded = (JCGuardPattern) pat; + PatternPrimaryType nested = primaryPatternType(guarded.patt); + boolean unconditional = nested.unconditional(); + if (guarded.expr.type.hasTag(BOOLEAN) && unconditional) { + unconditional = false; + var constValue = guarded.expr.type.constValue(); + if (constValue != null && ((int) constValue) == 1) { + unconditional = true; + } + } + yield new PatternPrimaryType(nested.type(), unconditional); + } + case PARENTHESIZEDPATTERN -> primaryPatternType(((JCParenthesizedPattern) pat).pattern); + default -> throw new AssertionError(); + }; + } + + public record PatternPrimaryType(Type type, boolean unconditional) {} + } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java index 20c0be43924..5d03dc8e8b3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java @@ -291,9 +291,9 @@ public class TreeMaker implements JCTree.Factory { return tree; } - public JCCase Case(CaseTree.CaseKind caseKind, List pats, + public JCCase Case(CaseTree.CaseKind caseKind, List labels, List stats, JCTree body) { - JCCase tree = new JCCase(caseKind, pats, stats, body); + JCCase tree = new JCCase(caseKind, labels, stats, body); tree.pos = pos; return tree; } @@ -488,6 +488,24 @@ public class TreeMaker implements JCTree.Factory { return tree; } + public JCDefaultCaseLabel DefaultCaseLabel() { + JCDefaultCaseLabel tree = new JCDefaultCaseLabel(); + tree.pos = pos; + return tree; + } + + public JCParenthesizedPattern ParenthesizedPattern(JCPattern pattern) { + JCParenthesizedPattern tree = new JCParenthesizedPattern(pattern); + tree.pos = pos; + return tree; + } + + public JCGuardPattern GuardPattern(JCPattern guardedPattern, JCExpression expr) { + JCGuardPattern tree = new JCGuardPattern(guardedPattern, expr); + tree.pos = pos; + return tree; + } + public JCArrayAccess Indexed(JCExpression indexed, JCExpression index) { JCArrayAccess tree = new JCArrayAccess(indexed, index); tree.pos = pos; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java index 3d64c1fc15e..ffdb2eaa135 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java @@ -177,7 +177,7 @@ public class TreeScanner extends Visitor { } public void visitCase(JCCase tree) { - scan(tree.pats); + scan(tree.labels); scan(tree.stats); } @@ -307,6 +307,21 @@ public class TreeScanner extends Visitor { scan(tree.var); } + @Override + public void visitDefaultCaseLabel(JCDefaultCaseLabel tree) { + } + + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern that) { + scan(that.pattern); + } + + @Override + public void visitGuardPattern(JCGuardPattern that) { + scan(that.patt); + scan(that.expr); + } + public void visitIndexed(JCArrayAccess tree) { scan(tree.indexed); scan(tree.index); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java index b6a5d6d447b..1796b4d754a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java @@ -207,7 +207,7 @@ public class TreeTranslator extends JCTree.Visitor { } public void visitCase(JCCase tree) { - tree.pats = translate(tree.pats); + tree.labels = translate(tree.labels); tree.stats = translate(tree.stats); result = tree; } @@ -363,6 +363,24 @@ public class TreeTranslator extends JCTree.Visitor { result = tree; } + @Override + public void visitDefaultCaseLabel(JCDefaultCaseLabel tree) { + result = tree; + } + + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern tree) { + tree.pattern = translate(tree.pattern); + result = tree; + } + + @Override + public void visitGuardPattern(JCGuardPattern tree) { + tree.patt = translate(tree.patt); + tree.expr = translate(tree.expr); + result = tree; + } + public void visitIndexed(JCArrayAccess tree) { tree.indexed = translate(tree.indexed); tree.index = translate(tree.index); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java index a45229a419a..24906467c63 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java @@ -3196,11 +3196,12 @@ public class Utils { flags.add(ElementFlag.DEPRECATED); } - if (!previewLanguageFeaturesUsed(el).isEmpty() || - configuration.workArounds.isPreviewAPI(el) || - !previewAPIs.previewAPI.isEmpty() || - !previewAPIs.reflectivePreviewAPI.isEmpty() || - !previewAPIs.declaredUsingPreviewFeature.isEmpty()) { + if ((!previewLanguageFeaturesUsed(el).isEmpty() || + configuration.workArounds.isPreviewAPI(el) || + !previewAPIs.previewAPI.isEmpty() || + !previewAPIs.reflectivePreviewAPI.isEmpty() || + !previewAPIs.declaredUsingPreviewFeature.isEmpty()) && + !hasNoProviewAnnotation(el)) { flags.add(ElementFlag.PREVIEW); } @@ -3216,4 +3217,9 @@ public class Utils { PREVIEW } + private boolean hasNoProviewAnnotation(Element el) { + return el.getAnnotationMirrors() + .stream() + .anyMatch(am -> "jdk.internal.javac.NoPreview".equals(getQualifiedTypeName(am.getAnnotationType()))); + } } diff --git a/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java b/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java new file mode 100644 index 00000000000..31bd3ae8645 --- /dev/null +++ b/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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. + */ + +import java.io.Serializable; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.runtime.SwitchBootstraps; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +/** + * @test + * @compile --enable-preview -source ${jdk.version} SwitchBootstrapsTest.java + * @run testng/othervm --enable-preview SwitchBootstrapsTest + */ +@Test +public class SwitchBootstrapsTest { + + public static final MethodHandle BSM_TYPE_SWITCH; + + static { + try { + BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class)); + } + catch (ReflectiveOperationException e) { + throw new AssertionError("Should not happen", e); + } + } + + private void testType(Object target, int start, int result, Object... labels) throws Throwable { + MethodType switchType = MethodType.methodType(int.class, Object.class, int.class); + MethodHandle indy = ((CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker(); + assertEquals((int) indy.invoke(target, start), result); + assertEquals(-1, (int) indy.invoke(null, start)); + } + + public enum E1 { + A; + } + + public enum E2 { + C; + } + + public void testTypes() throws Throwable { + testType("", 0, 0, String.class, Object.class); + testType("", 0, 0, Object.class); + testType("", 0, 1, Integer.class); + testType("", 0, 1, Integer.class, Serializable.class); + testType(E1.A, 0, 0, E1.class, Object.class); + testType(E2.C, 0, 1, E1.class, Object.class); + testType(new Serializable() { }, 0, 1, Comparable.class, Serializable.class); + testType("", 0, 0, "", String.class); + testType("", 1, 1, "", String.class); + testType("a", 0, 1, "", String.class); + testType(1, 0, 0, 1, Integer.class); + testType(2, 0, 1, 1, Integer.class); + testType(Byte.valueOf((byte) 1), 0, 0, 1, Integer.class); + 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("", 0, 0, String.class, String.class, String.class); + testType("", 1, 1, String.class, String.class, String.class); + testType("", 2, 2, String.class, String.class, String.class); + } + + 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) { + try { + BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType); + fail("Didn't get the expected exception."); + } catch (IllegalArgumentException ex) { + //OK, expected + } + } + } + + public void testNullLabels() throws Throwable { + MethodType switchType = MethodType.methodType(int.class, Object.class, int.class); + try { + BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, (Object[]) null); + fail("Didn't get the expected exception."); + } catch (NullPointerException ex) { + //OK + } + try { + BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, + new Object[] {1, null, String.class}); + fail("Didn't get the expected exception."); + } catch (IllegalArgumentException ex) { + //OK + } + } +} diff --git a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java index 93e9c80d966..d0023a53dbb 100644 --- a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java +++ b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/LineNumberTestBase.java @@ -65,7 +65,7 @@ public class LineNumberTestBase extends TestBase { try { writeToFileIfEnabled(Paths.get(testCase.getName() + ".java"), testCase.src); Set coveredLines = new HashSet<>(); - for (JavaFileObject file : compile(testCase.src).getClasses().values()) { + for (JavaFileObject file : compile(testCase.extraCompilerOptions, testCase.src).getClasses().values()) { ClassFile classFile = ClassFile.read(file.openInputStream()); for (Method m : classFile.methods) { Code_attribute code_attribute = (Code_attribute) m.attributes.get(Code); @@ -84,10 +84,17 @@ public class LineNumberTestBase extends TestBase { .collect(toList())); } } - assertTrue(coveredLines.containsAll(testCase.expectedLines), - format("All significant lines are not covered.%n" + - "Covered: %s%n" + - "Expected: %s%n", coveredLines, testCase.expectedLines)); + if (testCase.exactLines) { + assertTrue(coveredLines.equals(testCase.expectedLines), + format("Incorrect covered lines.%n" + + "Covered: %s%n" + + "Expected: %s%n", coveredLines, testCase.expectedLines)); + } else { + assertTrue(coveredLines.containsAll(testCase.expectedLines), + format("All significant lines are not covered.%n" + + "Covered: %s%n" + + "Expected: %s%n", coveredLines, testCase.expectedLines)); + } } catch (AssertionFailedException | CompilationException ex) { System.err.printf("# %-20s#%n", testCase.getName()); int l = 0; diff --git a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/RuleSwitchBreaks.java b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/RuleSwitchBreaks.java new file mode 100644 index 00000000000..0d888b3830a --- /dev/null +++ b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/RuleSwitchBreaks.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Verify correct LineNumberTable for rule switches. + * @library /tools/lib /tools/javac/lib ../lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * jdk.jdeps/com.sun.tools.classfile + * @build toolbox.ToolBox InMemoryFileManager TestBase + * @build LineNumberTestBase TestCase + * @run main RuleSwitchBreaks + */ + +import java.util.List; + +public class RuleSwitchBreaks extends LineNumberTestBase { + public static void main(String[] args) throws Exception { + new RuleSwitchBreaks().test(); + } + + public void test() throws Exception { + test(List.of(TEST_CASE)); + } + + private static final TestCase[] TEST_CASE = new TestCase[] { + new TestCase(""" + public class Test { // 1 + private void test(int i) { // 2 + switch (i) { // 3 + case 0 -> // 4 + System.out.println("a"); // 5 + case 1 -> // 6 + System.out.println("a"); // 7 + default -> // 8 + System.out.println("default"); // 9 + } //10 + } //11 + } //12 + """, + List.of(1, 3, 5, 7, 9, 11), + true, + List.of(), + "Test"), + new TestCase(""" + public class TestGuards { // 1 + private void test(Object o) { // 2 + switch (o) { // 3 + case String s && s.isEmpty() -> // 4 + System.out.println("a"); // 5 + case String s -> // 6 + System.out.println("a"); // 7 + default -> // 8 + System.out.println("default"); // 9 + } //10 + } //11 + } //12 + """, + List.of(1, 3, 4, 5, 6, 7, 9, 11), + true, + List.of("--enable-preview", "-source", System.getProperty("java.specification.version")), + "TestGuards") + }; + +} diff --git a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java index ce80a629f4f..dfb5a778825 100644 --- a/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java +++ b/test/langtools/tools/javac/classfiles/attributes/LineNumberTable/TestCase.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -32,6 +33,8 @@ import java.util.Set; public class TestCase { public final String src; public final Set expectedLines; + public final boolean exactLines; + public final List extraCompilerOptions; private final String name; @@ -41,8 +44,16 @@ public class TestCase { } public TestCase(String src, Collection expectedLines, String name) { + this(src, expectedLines, false, List.of(), name); + } + + public TestCase(String src, Collection expectedLines, + boolean exactLines, List extraCompilerOptions, + String name) { this.src = src; this.expectedLines = new HashSet<>(expectedLines); + this.exactLines = exactLines; + this.extraCompilerOptions = extraCompilerOptions; this.name = name; } } diff --git a/test/langtools/tools/javac/diags/examples/CantRefNonEffectivelyFinalVar.java b/test/langtools/tools/javac/diags/examples/CantRefNonEffectivelyFinalVar.java index 421d1f0a768..6d084b6e61c 100644 --- a/test/langtools/tools/javac/diags/examples/CantRefNonEffectivelyFinalVar.java +++ b/test/langtools/tools/javac/diags/examples/CantRefNonEffectivelyFinalVar.java @@ -24,6 +24,10 @@ // key: compiler.err.cant.ref.non.effectively.final.var // key: compiler.misc.inner.cls // key: compiler.misc.lambda +// key: compiler.misc.guard +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview class CantRefNonEffectivelyFinalVar { void test() { @@ -41,4 +45,11 @@ class CantRefNonEffectivelyFinalVar { SAM s = ()-> { int j = i; }; i++; } + + void test3(Object o, int i) { + switch (o) { + case String s && s.length() == i++: break; + default: break; + } + } } diff --git a/test/langtools/tools/javac/diags/examples/SwitchNullNotAllowed.java b/test/langtools/tools/javac/diags/examples/CaseNull.java similarity index 76% rename from test/langtools/tools/javac/diags/examples/SwitchNullNotAllowed.java rename to test/langtools/tools/javac/diags/examples/CaseNull.java index 9e0680a403d..b49b299e53f 100644 --- a/test/langtools/tools/javac/diags/examples/SwitchNullNotAllowed.java +++ b/test/langtools/tools/javac/diags/examples/CaseNull.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,14 +21,15 @@ * questions. */ -// key: compiler.err.switch.null.not.allowed +// key: compiler.misc.feature.case.null +// key: compiler.warn.preview.feature.use +// options: --enable-preview -source ${jdk.version} -Xlint:preview -class SwitchNullNotAllowed { - - void test(Integer i) { - switch (i) { +class CaseNull { + private void doSwitch(String s) { + switch (s) { case null: break; - case 0: break; + default: break; } } } diff --git a/test/langtools/tools/javac/diags/examples/ConstantLabelNotCompatible.java b/test/langtools/tools/javac/diags/examples/ConstantLabelNotCompatible.java new file mode 100644 index 00000000000..1d3fbfea22c --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ConstantLabelNotCompatible.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.constant.label.not.compatible +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class ConstantLabelNotCompatible { + private void doSwitch(Object o) { + switch (o) { + case 0: + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/DuplicateTotalPattern.java b/test/langtools/tools/javac/diags/examples/DuplicateTotalPattern.java new file mode 100644 index 00000000000..5d0976f9f7a --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/DuplicateTotalPattern.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.duplicate.total.pattern +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class DuplicateTotalPattern { + private void doSwitch(Object o) { + switch (o) { + case Object obj: break; + case Object obj: break; + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/FlowsThroughFromPattern.java b/test/langtools/tools/javac/diags/examples/FlowsThroughFromPattern.java new file mode 100644 index 00000000000..22c35404dcd --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/FlowsThroughFromPattern.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.flows.through.from.pattern +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class FlowsThroughToPattern { + private void doSwitch(Object o) { + switch (o) { + case String str: + default: break; + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/FlowsThroughToPattern.java b/test/langtools/tools/javac/diags/examples/FlowsThroughToPattern.java new file mode 100644 index 00000000000..1ab22992f03 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/FlowsThroughToPattern.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.flows.through.to.pattern +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class FlowsThroughToPattern { + private void doSwitch(Object o) { + switch (o) { + case String str: + case Object obj: break; + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/NotExhaustiveStatement.java b/test/langtools/tools/javac/diags/examples/NotExhaustiveStatement.java new file mode 100644 index 00000000000..0d151dbe6a8 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/NotExhaustiveStatement.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.not.exhaustive.statement +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview --source ${jdk.version} + +class NotExhaustive { + void t(Object o) { + switch (o) { + case String s -> System.err.println("String of length: " + s.length()); + }; + } +} diff --git a/test/langtools/tools/javac/diags/examples/PatternDominated.java b/test/langtools/tools/javac/diags/examples/PatternDominated.java new file mode 100644 index 00000000000..f1e813da1df --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDominated.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.pattern.dominated +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class PatternDominated { + private void doSwitch(Object o) { + switch (o) { + case CharSequence cs: break; + case String str: break; + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/PatternSwitch.java b/test/langtools/tools/javac/diags/examples/PatternSwitch.java new file mode 100644 index 00000000000..a563ad3d749 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternSwitch.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class PatternSwitch { + private void doSwitch(Object o) { + switch (o) { + case String str: break; + default: break; + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/TotalPatternAndDefault.java b/test/langtools/tools/javac/diags/examples/TotalPatternAndDefault.java new file mode 100644 index 00000000000..778e6afffa1 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/TotalPatternAndDefault.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.total.pattern.and.default +// key: compiler.misc.feature.pattern.switch +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +class TotalPatternAndDefault { + private void doSwitch(Object o) { + switch (o) { + case Object obj: break; + default: break; + } + } +} diff --git a/test/langtools/tools/javac/lib/DPrinter.java b/test/langtools/tools/javac/lib/DPrinter.java index 9e54919d44e..e2301138644 100644 --- a/test/langtools/tools/javac/lib/DPrinter.java +++ b/test/langtools/tools/javac/lib/DPrinter.java @@ -738,7 +738,7 @@ public class DPrinter { @Override public void visitCase(JCCase tree) { - printList("pat", tree.pats); + printList("labels", tree.labels); printList("stats", tree.stats); } diff --git a/test/langtools/tools/javac/patterns/CaseDefault.java b/test/langtools/tools/javac/patterns/CaseDefault.java new file mode 100644 index 00000000000..0664118a742 --- /dev/null +++ b/test/langtools/tools/javac/patterns/CaseDefault.java @@ -0,0 +1,37 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8262891 + * @summary Check null handling for non-pattern switches. + * @compile/fail/ref=CaseDefault.out -source 16 -Xlint:-options -XDrawDiagnostics CaseDefault.java + * @compile --enable-preview -source ${jdk.version} CaseDefault.java + * @run main/othervm --enable-preview CaseDefault + */ + +public class CaseDefault { + + public static void main(String[] args) { + new CaseDefault().run(); + } + + void run() { + String str = "other"; + switch (str) { + case "a": throw new AssertionError("Wrong branch."); + case default: break; //OK + } + switch (str) { + case "a" -> throw new AssertionError("Wrong branch."); + case default -> {} //OK + } + int i; + i = switch (str) { + case "a": throw new AssertionError("Wrong branch."); + case default: yield 0; //OK + }; + i = switch (str) { + case "a" -> throw new AssertionError("Wrong branch."); + case default -> 0; //OK + }; + } + +} diff --git a/test/langtools/tools/javac/patterns/CaseDefault.out b/test/langtools/tools/javac/patterns/CaseDefault.out new file mode 100644 index 00000000000..ffea67361fb --- /dev/null +++ b/test/langtools/tools/javac/patterns/CaseDefault.out @@ -0,0 +1,2 @@ +CaseDefault.java:20:18: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.pattern.switch) +1 error diff --git a/test/langtools/tools/javac/patterns/DisambiguateParenthesizedPattern.java b/test/langtools/tools/javac/patterns/DisambiguateParenthesizedPattern.java new file mode 100644 index 00000000000..a71a2b5a39b --- /dev/null +++ b/test/langtools/tools/javac/patterns/DisambiguateParenthesizedPattern.java @@ -0,0 +1,88 @@ +/** + * @test + * @modules jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.parser + * jdk.compiler/com.sun.tools.javac.tree + * jdk.compiler/com.sun.tools.javac.util + * @compile --enable-preview -source ${jdk.version} DisambiguateParenthesizedPattern.java + * @run main/othervm --enable-preview DisambiguateParenthesizedPattern + */ + +import com.sun.source.tree.CaseLabelTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.PatternTree; +import com.sun.source.tree.SwitchTree; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.parser.JavacParser; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.util.Options; +import java.nio.charset.Charset; + +public class DisambiguateParenthesizedPattern { + + public static void main(String... args) throws Throwable { + DisambiguateParenthesizedPattern test = new DisambiguateParenthesizedPattern(); + test.disambiguationTest("String s", + ExpressionType.PATTERN); + test.disambiguationTest("String s && s.isEmpty()", + ExpressionType.PATTERN); + test.disambiguationTest("(String s)", + ExpressionType.PATTERN); + test.disambiguationTest("((String s))", + ExpressionType.PATTERN); + test.disambiguationTest("(String) s", + ExpressionType.EXPRESSION); + test.disambiguationTest("((String) s)", + ExpressionType.EXPRESSION); + test.disambiguationTest("((0x1))", + ExpressionType.EXPRESSION); + } + + private final ParserFactory factory; + + public DisambiguateParenthesizedPattern() { + Context context = new Context(); + JavacFileManager jfm = new JavacFileManager(context, true, Charset.defaultCharset()); + Options.instance(context).put(Option.PREVIEW, ""); + factory = ParserFactory.instance(context); + } + + void disambiguationTest(String snippet, ExpressionType expectedType) { + String code = """ + public class Test { + private void test() { + switch (null) { + case SNIPPET -> {} + } + } + } + """.replace("SNIPPET", snippet); + JavacParser parser = factory.newParser(code, false, false, false); + CompilationUnitTree result = parser.parseCompilationUnit(); + ClassTree clazz = (ClassTree) result.getTypeDecls().get(0); + MethodTree method = (MethodTree) clazz.getMembers().get(0); + SwitchTree st = (SwitchTree) method.getBody().getStatements().get(0); + CaseLabelTree label = st.getCases().get(0).getLabels().get(0); + ExpressionType actualType = switch (label) { + case ExpressionTree et -> ExpressionType.EXPRESSION; + case PatternTree pt -> ExpressionType.PATTERN; + default -> throw new AssertionError("Unexpected result: " + result); + }; + if (expectedType != actualType) { + throw new AssertionError("Expected: " + expectedType + ", actual: " + actualType + + ", for: " + code + ", parsed: " + result); + } + } + + enum ExpressionType { + PATTERN, + EXPRESSION; + } + +} diff --git a/test/langtools/tools/javac/patterns/Domination.java b/test/langtools/tools/javac/patterns/Domination.java new file mode 100644 index 00000000000..b5283fe31a7 --- /dev/null +++ b/test/langtools/tools/javac/patterns/Domination.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Check the pattern domination error are reported correctly. + * @compile/fail/ref=Domination.out -XDrawDiagnostics --enable-preview -source ${jdk.version} Domination.java + */ +public class Domination { + + int testDominatesError1(Object o) { + switch (o) { + case CharSequence cs: return 0; + case String s: return 1; + case Object x: return -1; + } + } + + int testDominatesError2(Object o) { + switch (o) { + case CharSequence cs: return 0; + case String s && s.isEmpty(): return 1; + case Object x: return -1; + } + } + + int testDominatesError3(Object o) { + switch (o) { + case CharSequence cs && true: return 0; + case String s && s.isEmpty(): return 1; + case Object x: return -1; + } + } + + int testNotDominates1(Object o) { + switch (o) { + case CharSequence cs && cs.length() == 0: return 0; + case String s: return 1; + case Object x: return -1; + } + } + + int testDominatesStringConstant(String str) { + switch (str) { + case String s: return 1; + case "": return -1; + } + } + + int testDominatesIntegerConstant(Integer i) { + switch (i) { + case Integer j: return 1; + case 0: return -1; + } + } + + int testDominatesEnumConstant() { + enum E { + A, B; + } + E e = E.A; + switch (e) { + case E d: return 1; + case A: return -1; + } + } + +} diff --git a/test/langtools/tools/javac/patterns/Domination.out b/test/langtools/tools/javac/patterns/Domination.out new file mode 100644 index 00000000000..3d1aa46bab0 --- /dev/null +++ b/test/langtools/tools/javac/patterns/Domination.out @@ -0,0 +1,9 @@ +Domination.java:35:18: compiler.err.pattern.dominated +Domination.java:43:18: compiler.err.pattern.dominated +Domination.java:51:18: compiler.err.pattern.dominated +Domination.java:67:18: compiler.err.pattern.dominated +Domination.java:74:18: compiler.err.pattern.dominated +Domination.java:85:18: compiler.err.pattern.dominated +- compiler.note.preview.filename: Domination.java, DEFAULT +- compiler.note.preview.recompile +6 errors diff --git a/test/langtools/tools/javac/patterns/Exhaustiveness.java b/test/langtools/tools/javac/patterns/Exhaustiveness.java new file mode 100644 index 00000000000..fc3e217b55f --- /dev/null +++ b/test/langtools/tools/javac/patterns/Exhaustiveness.java @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8262891 + * @summary Check exhaustiveness of switches over sealed types. + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox toolbox.JavacTask + * @run main Exhaustiveness +*/ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import toolbox.TestRunner; +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.ToolBox; + +public class Exhaustiveness extends TestRunner { + + private static final String JAVA_VERSION = System.getProperty("java.specification.version"); + + ToolBox tb; + + public static void main(String... args) throws Exception { + new Exhaustiveness().runTests(); + } + + Exhaustiveness() { + super(System.err); + tb = new ToolBox(); + } + + public void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testExhaustiveSealedClasses(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case B b -> 1; + }; + } + } + """); + } + + @Test + public void testNonExhaustiveSealedClasses(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + }; + } + } + """, + "Test.java:5:16: compiler.err.not.exhaustive", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + } + + @Test + public void testAbstractSealedClasses(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed abstract class S permits A, B {} + """, + """ + package lib; + public final class A extends S {} + """, + """ + package lib; + public final class B extends S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case B b -> 1; + }; + } + } + """); + } + + @Test + public void testConcreteSealedClasses(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed class S permits A, B {} + """, + """ + package lib; + public final class A extends S {} + """, + """ + package lib; + public final class B extends S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case B b -> 1; + }; + } + } + """, + "Test.java:5:16: compiler.err.not.exhaustive", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + } + + @Test + public void testGuards1(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a && a.toString().isEmpty() -> 0; + case B b -> 1; + }; + } + } + """, + "Test.java:5:16: compiler.err.not.exhaustive", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + } + + @Test + public void testGuards2(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private static final boolean TEST = true; + private int test(S obj) { + return switch (obj) { + case A a && !(!(TEST)) -> 0; + case B b -> 1; + }; + } + } + """); + } + + @Test + public void testGuards3(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a && false -> 0; + case B b -> 1; + }; + } + } + """, + "Test.java:5:16: compiler.err.not.exhaustive", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + } + + @Test + public void testCoversType1(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case S s -> 1; + }; + } + } + """); + } + + @Test + public void testCoversType2(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public interface S {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case S s -> 1; + }; + } + } + """); + } + + @Test + public void testCoversType3(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public interface S {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case S s -> 1; + }; + } + } + """); + } + + private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException { + Path current = base.resolve("."); + Path libSrc = current.resolve("lib-src"); + for (String code : libraryCode) { + tb.writeJavaFiles(libSrc, code); + } + + Path libClasses = current.resolve("libClasses"); + + Files.createDirectories(libClasses); + + new JavacTask(tb) + .options("--enable-preview", + "-source", JAVA_VERSION) + .outdir(libClasses) + .files(tb.findJavaFiles(libSrc)) + .run(); + + Path src = current.resolve("src"); + tb.writeJavaFiles(src, testCode); + + Path classes = current.resolve("libClasses"); + + Files.createDirectories(libClasses); + + var log = + new JavacTask(tb) + .options("--enable-preview", + "-source", JAVA_VERSION, + "-XDrawDiagnostics", + "-Xlint:-preview", + "--class-path", libClasses.toString()) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(expectedErrors.length > 0 ? Task.Expect.FAIL : Task.Expect.SUCCESS) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + if (expectedErrors.length > 0 && !List.of(expectedErrors).equals(log)) { + throw new AssertionError("Incorrect errors, expected: " + List.of(expectedErrors) + + ", actual: " + log); + } + } + + @Test + public void testInaccessiblePermitted(Path base) throws IOException { + Path current = base.resolve("."); + Path libSrc = current.resolve("lib-src"); + + tb.writeJavaFiles(libSrc, + """ + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + final class B implements S {} + """); + + Path libClasses = current.resolve("libClasses"); + + Files.createDirectories(libClasses); + + new JavacTask(tb) + .options("--enable-preview", + "-source", JAVA_VERSION) + .outdir(libClasses) + .files(tb.findJavaFiles(libSrc)) + .run(); + + Path src = current.resolve("src"); + tb.writeJavaFiles(src, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + }; + } + } + """); + + Path classes = current.resolve("libClasses"); + + Files.createDirectories(libClasses); + + var log = + new JavacTask(tb) + .options("--enable-preview", + "-source", JAVA_VERSION, + "-XDrawDiagnostics", + "-Xlint:-preview", + "--class-path", libClasses.toString()) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + List expectedErrors = List.of( + "Test.java:5:16: compiler.err.not.exhaustive", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + + if (!expectedErrors.equals(log)) { + throw new AssertionError("Incorrect errors, expected: " + expectedErrors + + ", actual: " + log); + } + + Path bClass = libClasses.resolve("lib").resolve("B.class"); + + Files.delete(bClass); + + var log2 = + new JavacTask(tb) + .options("--enable-preview", + "-source", JAVA_VERSION, + "-XDrawDiagnostics", + "-Xlint:-preview", + "--class-path", libClasses.toString()) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + if (!expectedErrors.equals(log2)) { + throw new AssertionError("Incorrect errors, expected: " + expectedErrors + + ", actual: " + log2); + } + + } + + @Test + public void testExhaustiveStatement1(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public interface Lib {} + """}, + """ + package test; + public class Test { + private int test(Object obj) { + switch (obj) { + case Object o: return 0; + } + } + } + """); + } + + @Test + public void testExhaustiveStatement2(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public interface Lib {} + """}, + """ + package test; + public class Test { + private void test(Object obj) { + switch (obj) { + case String s: return; + }; + } + } + """, + "Test.java:4:9: compiler.err.not.exhaustive.statement", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + } + + @Test + public void testExhaustiveStatement3(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case S s -> 1; + }; + } + } + """); + } + + @Test + public void testExhaustiveStatement4(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + }; + } + } + """, + "Test.java:5:16: compiler.err.not.exhaustive", + "- compiler.note.preview.filename: Test.java, DEFAULT", + "- compiler.note.preview.recompile", + "1 error"); + } + + @Test + public void testExhaustiveStatement5(Path base) throws Exception { + doTest(base, + new String[]{""" + package lib; + public sealed interface S permits A, B {} + """, + """ + package lib; + public final class A implements S {} + """, + """ + package lib; + public final class B implements S {} + """}, + """ + package test; + import lib.*; + public class Test { + private int test(S obj) { + return switch (obj) { + case A a -> 0; + case B b -> 0; + }; + } + } + """); + } + +} diff --git a/test/langtools/tools/javac/patterns/Guards.java b/test/langtools/tools/javac/patterns/Guards.java new file mode 100644 index 00000000000..be37651f7e5 --- /dev/null +++ b/test/langtools/tools/javac/patterns/Guards.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Check guards implementation. + * @compile --enable-preview -source ${jdk.version} Guards.java + * @run main/othervm --enable-preview Guards + */ + +import java.util.Objects; +import java.util.function.Function; + +public class Guards { + public static void main(String... args) { + new Guards().run(); + } + + void run() { + run(this::typeTestPatternSwitchTest); + run(this::typeTestPatternSwitchExpressionTest); + run(this::testBooleanSwitchExpression); + assertEquals("a", testPatternInGuard("a")); + assertEquals(null, testPatternInGuard(1)); + } + + void run(Function convert) { + assertEquals("zero", convert.apply(0)); + assertEquals("one", convert.apply(1)); + assertEquals("other", convert.apply(-1)); + assertEquals("any", convert.apply("")); + } + + String typeTestPatternSwitchTest(Object o) { + switch (o) { + case Integer i && i == 0: return "zero"; + case Integer i && i == 1: return "one"; + case Integer i: return "other"; + case Object x: return "any"; + } + } + + String typeTestPatternSwitchExpressionTest(Object o) { + return switch (o) { + case Integer i && i == 0 -> "zero"; + case Integer i && i == 1 -> { yield "one"; } + case Integer i -> "other"; + case Object x -> "any"; + }; + } + + String testBooleanSwitchExpression(Object o) { + String x; + if (switch (o) { + case Integer i && i == 0 -> (x = "zero") != null; + case Integer i && i == 1 -> { x = "one"; yield true; } + case Integer i -> { x = "other"; yield true; } + case Object other -> (x = "any") != null; + }) { + return x; + } else { + throw new IllegalStateException("TODO - needed?"); + } + } + + String testPatternInGuard(Object o) { + if (o instanceof (CharSequence cs && cs instanceof String s)) { + return s; + } + return null; + } + + void assertEquals(String expected, String actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/GuardsErrors.java b/test/langtools/tools/javac/patterns/GuardsErrors.java new file mode 100644 index 00000000000..0211dedb8e6 --- /dev/null +++ b/test/langtools/tools/javac/patterns/GuardsErrors.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Check errors reported for guarded patterns. + * @compile/fail/ref=GuardsErrors.out -XDrawDiagnostics --enable-preview -source ${jdk.version} GuardsErrors.java + */ + +public class GuardsErrors { + + void typeTestPatternSwitchTest(Object o, int check) { + switch (o) { + case Integer i && i == check -> System.err.println(); //error: check is not effectivelly final + default -> {} + } + check = 0; + + } + +} diff --git a/test/langtools/tools/javac/patterns/GuardsErrors.out b/test/langtools/tools/javac/patterns/GuardsErrors.out new file mode 100644 index 00000000000..5fc6734db7f --- /dev/null +++ b/test/langtools/tools/javac/patterns/GuardsErrors.out @@ -0,0 +1,4 @@ +GuardsErrors.java:35:36: compiler.err.cant.ref.non.effectively.final.var: check, (compiler.misc.guard) +- compiler.note.preview.filename: GuardsErrors.java, DEFAULT +- compiler.note.preview.recompile +1 error diff --git a/test/langtools/tools/javac/patterns/NullSwitch.java b/test/langtools/tools/javac/patterns/NullSwitch.java new file mode 100644 index 00000000000..3932c9014b7 --- /dev/null +++ b/test/langtools/tools/javac/patterns/NullSwitch.java @@ -0,0 +1,168 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8262891 + * @summary Check null handling for non-pattern switches. + * @compile --enable-preview -source ${jdk.version} NullSwitch.java + * @run main/othervm --enable-preview NullSwitch + */ + +public class NullSwitch { + + public static void main(String[] args) { + new NullSwitch().switchTest(); + } + + void switchTest() { + assertEquals(0, matchingSwitch1("")); + assertEquals(1, matchingSwitch1("a")); + assertEquals(100, matchingSwitch1(0)); + assertEquals(-1, matchingSwitch1(null)); + assertEquals(-2, matchingSwitch1(0.0)); + assertEquals(0, matchingSwitch2("")); + assertEquals(1, matchingSwitch2(null)); + assertEquals(1, matchingSwitch2(0.0)); + assertEquals(0, matchingSwitch3("")); + assertEquals(1, matchingSwitch3("a")); + assertEquals(100, matchingSwitch3(0)); + assertEquals(-1, matchingSwitch3(null)); + assertEquals(-2, matchingSwitch3(0.0)); + assertEquals(0, matchingSwitch4("")); + assertEquals(1, matchingSwitch4(null)); + assertEquals(1, matchingSwitch4(0.0)); + assertEquals(0, matchingSwitch5("")); + assertEquals(1, matchingSwitch5("a")); + assertEquals(100, matchingSwitch5(0)); + assertEquals(-1, matchingSwitch5(null)); + assertEquals(-2, matchingSwitch5(0.0)); + assertEquals(0, matchingSwitch6("")); + assertEquals(1, matchingSwitch6(null)); + assertEquals(1, matchingSwitch6(0.0)); + assertEquals(0, matchingSwitch7("")); + assertEquals(1, matchingSwitch7("a")); + assertEquals(100, matchingSwitch7(0)); + assertEquals(-1, matchingSwitch7(null)); + assertEquals(-2, matchingSwitch7(0.0)); + assertEquals(0, matchingSwitch8("")); + assertEquals(1, matchingSwitch8(null)); + assertEquals(1, matchingSwitch8(0.0)); + assertEquals(0, matchingSwitch9("")); + assertEquals(1, matchingSwitch9(null)); + assertEquals(1, matchingSwitch9(0.0)); + assertEquals(0, matchingSwitch10("")); + assertEquals(1, matchingSwitch10(null)); + assertEquals(1, matchingSwitch10(0.0)); + assertEquals(0, matchingSwitch11("")); + assertEquals(2, matchingSwitch11(null)); + assertEquals(1, matchingSwitch11(0.0)); + assertEquals(0, matchingSwitch12("")); + assertEquals(2, matchingSwitch12(null)); + assertEquals(1, matchingSwitch12(0.0)); + } + + private int matchingSwitch1(Object obj) { + return switch (obj) { + case String s -> s.length(); + case null, Integer i -> i == null ? -1 : 100 + i; + default -> -2; + }; + } + + private int matchingSwitch2(Object obj) { + return switch (obj) { + case String s -> 0; + case null, default -> 1; + }; + } + + private int matchingSwitch3(Object obj) { + return switch (obj) { + case String s -> s.length(); + case Integer i, null -> i == null ? -1 : 100 + i; + default -> -2; + }; + } + + private int matchingSwitch4(Object obj) { + return switch (obj) { + case String s -> 0; + case default, null -> 1; + }; + } + + private int matchingSwitch5(Object obj) { + return switch (obj) { + case String s: yield s.length(); + case null: + case Integer i: yield i == null ? -1 : 100 + i; + default: yield -2; + }; + } + + private int matchingSwitch6(Object obj) { + return switch (obj) { + case String s: yield 0; + case null: + default: yield 1; + }; + } + + private int matchingSwitch7(Object obj) { + return switch (obj) { + case String s: yield s.length(); + case Integer i: + case null: yield i == null ? -1 : 100 + i; + default: yield -2; + }; + } + + private int matchingSwitch8(Object obj) { + return switch (obj) { + case String s: yield 0; + default: + case null: yield 1; + }; + } + + private int matchingSwitch9(Object obj) { + return switch (obj) { + case String s: yield 0; + case Object o: yield 1; + }; + } + + private int matchingSwitch10(Object obj) { + switch (obj) { + case String s: return 0; + case Object o: return 1; + } + } + + private int matchingSwitch11(Object obj) { + try { + return switch (obj) { + case String s: yield 0; + default: yield 1; + }; + } catch (NullPointerException ex) { + return 2; + } + } + + private int matchingSwitch12(Object obj) { + try { + switch (obj) { + case String s: return 0; + default: return 1; + } + } catch (NullPointerException ex) { + return 2; + } + } + + static void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } + +} diff --git a/test/langtools/tools/javac/patterns/Parenthesized.java b/test/langtools/tools/javac/patterns/Parenthesized.java new file mode 100644 index 00000000000..f54d9f66af9 --- /dev/null +++ b/test/langtools/tools/javac/patterns/Parenthesized.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Test parenthesized pattern + * @compile --enable-preview -source ${jdk.version} Parenthesized.java + * @run main/othervm --enable-preview Parenthesized + */ +public class Parenthesized { + public static void main(String... args) { + new Parenthesized().run(); + } + + void run() { + Object o = ""; + switch (o) { + case (String s && s.isEmpty()) -> System.err.println("OK: " + s); + default -> throw new AssertionError(); + } + System.err.println(switch (o) { + case (String s && s.isEmpty()) -> "OK: " + s; + default -> throw new AssertionError(); + }); + if (o instanceof (String s && s.isEmpty())) { + System.err.println("OK: " + s); + } + } + +} diff --git a/test/langtools/tools/javac/patterns/SealedTypeChanges.java b/test/langtools/tools/javac/patterns/SealedTypeChanges.java new file mode 100644 index 00000000000..d22c7ac3fb8 --- /dev/null +++ b/test/langtools/tools/javac/patterns/SealedTypeChanges.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Verify pattern switches work properly when the set of sealed types changes. + * @compile --enable-preview -source ${jdk.version} SealedTypeChanges.java + * @compile --enable-preview -source ${jdk.version} SealedTypeChanges2.java + * @run main/othervm --enable-preview SealedTypeChanges + */ + +import java.util.function.Consumer; + +public class SealedTypeChanges { + + public static void main(String... args) throws Exception { + new SealedTypeChanges().run(); + } + + void run() throws Exception { + doRun(this::expressionIntf, this::validateIncompatibleClassChangeError); + doRun(this::statementIntf, this::validateIncompatibleClassChangeError); + doRun(this::expressionCls, this::validateIncompatibleClassChangeError); + doRun(this::statementCls, this::validateIncompatibleClassChangeError); + doRun(this::expressionCoveredIntf, this::validateTestException); + doRun(this::statementCoveredIntf, this::validateTestException); + doRun(this::expressionCoveredCls, this::validateTestException); + doRun(this::statementCoveredCls, this::validateTestException); + } + + void doRun(Consumer t, Consumer validateException) throws Exception { + t.accept((T) new A()); + try { + t.accept((T) Class.forName("SealedTypeChangesClass").newInstance()); + throw new AssertionError("Expected an exception, but none thrown."); + } catch (Throwable ex) { + validateException.accept(ex); + } + } + + void validateIncompatibleClassChangeError(Throwable t) { + if (!(t instanceof IncompatibleClassChangeError)) { + throw new AssertionError("Unexpected exception", t); + } + } + + void validateTestException(Throwable t) { + if (!(t instanceof TestException)) { + throw new AssertionError("Unexpected exception", t); + } + } + + void statementIntf(SealedTypeChangesIntf obj) { + switch (obj) { + case A a -> System.err.println(1); + } + } + + int expressionIntf(SealedTypeChangesIntf obj) { + return switch (obj) { + case A a -> 0; + }; + } + + void statementCls(SealedTypeChangesCls obj) { + switch (obj) { + case A a -> System.err.println(1); + } + } + + int expressionCls(SealedTypeChangesCls obj) { + return switch (obj) { + case A a -> 0; + }; + } + + void statementCoveredIntf(SealedTypeChangesIntf obj) { + switch (obj) { + case A a -> System.err.println(1); + case SealedTypeChangesIntf o -> throw new TestException(); + } + } + + int expressionCoveredIntf(SealedTypeChangesIntf obj) { + return switch (obj) { + case A a -> 0; + case SealedTypeChangesIntf o -> throw new TestException(); + }; + } + + void statementCoveredCls(SealedTypeChangesCls obj) { + switch (obj) { + case A a -> System.err.println(1); + case SealedTypeChangesCls o -> throw new TestException(); + } + } + + int expressionCoveredCls(SealedTypeChangesCls obj) { + return switch (obj) { + case A a -> 0; + case SealedTypeChangesCls o -> throw new TestException(); + }; + } + + final static class A extends SealedTypeChangesCls implements SealedTypeChangesIntf {} + class TestException extends RuntimeException {} +} + +sealed interface SealedTypeChangesIntf permits SealedTypeChanges.A {} +sealed abstract class SealedTypeChangesCls permits SealedTypeChanges.A {} diff --git a/test/langtools/tools/javac/patterns/SealedTypeChanges2.java b/test/langtools/tools/javac/patterns/SealedTypeChanges2.java new file mode 100644 index 00000000000..8e6daeef258 --- /dev/null +++ b/test/langtools/tools/javac/patterns/SealedTypeChanges2.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +sealed interface SealedTypeChangesIntf permits SealedTypeChanges.A, SealedTypeChangesClass {} +sealed abstract class SealedTypeChangesCls permits SealedTypeChanges.A, SealedTypeChangesClass {} +final class SealedTypeChangesClass extends SealedTypeChangesCls implements SealedTypeChangesIntf {} diff --git a/test/langtools/tools/javac/patterns/SimpleAndGuardPattern.java b/test/langtools/tools/javac/patterns/SimpleAndGuardPattern.java new file mode 100644 index 00000000000..46a312df0cf --- /dev/null +++ b/test/langtools/tools/javac/patterns/SimpleAndGuardPattern.java @@ -0,0 +1,31 @@ +/** + * @test + * @compile --enable-preview -source ${jdk.version} -doe SimpleAndGuardPattern.java + * @run main/othervm --enable-preview SimpleAndGuardPattern + */ + +import java.util.List; +import java.util.Objects; + +public class SimpleAndGuardPattern { + + public static void main(String... args) throws Throwable { + if (!Objects.equals(4, simple("test"))) { + throw new IllegalStateException(); + } + if (!Objects.equals(4, simple("TEST"))) { + throw new IllegalStateException(); + } + if (!Objects.equals(-1, simple("other"))) { + throw new IllegalStateException(); + } + } + + private static int simple(Object o) throws Throwable { + return switch (o) { + case String s && s.equalsIgnoreCase("test") -> s.length(); + default -> -1; + }; + } + +} diff --git a/test/langtools/tools/javac/patterns/SwitchErrors.java b/test/langtools/tools/javac/patterns/SwitchErrors.java new file mode 100644 index 00000000000..e082ab6619e --- /dev/null +++ b/test/langtools/tools/javac/patterns/SwitchErrors.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Verify errors related to pattern switches. + * @compile/fail/ref=SwitchErrors.out --enable-preview -source ${jdk.version} -XDrawDiagnostics -XDshould-stop.at=FLOW SwitchErrors.java + */ +public class SwitchErrors { + void incompatibleSelectorObjectString(Object o) { + switch (o) { + case "A": break; + case CharSequence cs: break; + } + } + void incompatibleSelectorObjectInteger(Object o) { + switch (o) { + case 1: break; + case CharSequence cs: break; + } + } + void incompatibleSelectorIntegerString(Integer i) { + switch (i) { + case "A": break; + case CharSequence cs: break; + } + } + void incompatibleSelectorPrimitive(int i) { + switch (i) { + case null: break; + case "A": break; + case CharSequence cs: break; + } + } + void totalAndDefault1(Object o) { + switch (o) { + case Object obj: break; + default: break; + } + } + void totalAndDefault2(Object o) { + switch (o) { + case Object obj: break; + case null, default: break; + } + } + void totalAndDefault3(Object o) { + switch (o) { + default: break; + case Object obj: break; + } + } + void duplicatedTotal(Object o) { + switch (o) { + case Object obj: break; + case Object obj: break; + } + } + void duplicatedDefault1(Object o) { + switch (o) { + case null, default: break; + default: break; + } + } + void duplicatedDefault2(Object o) { + switch (o) { + case default: break; + default: break; + } + } + void duplicatedDefault3(Object o) { + switch (o) { + case default, default: break; + } + } + void duplicatedNullCase1(Object o) { + switch (o) { + case null: break; + case null: break; + } + } + void duplicatedNullCase2(Object o) { + switch (o) { + case null, null: break; + } + } + void duplicatedTypePatterns1(Object o) { + switch (o) { + case String s, Integer i: break; + } + } + void duplicatedTypePatterns2(Object o) { + switch (o) { + case String s: + case Integer i: break; + } + } + void duplicatedTypePatterns3(Object o) { + switch (o) { + case String s: + System.err.println(1); + case Integer i: break; + } + } + void flowIntoTypePatterns(Object o) { + switch (o) { + case null: + System.err.println(1); + case Integer i: break; + } + } + void incompatible1(String str) { + switch (str) { + case Integer i: break; + default: break; + } + } + void incompatible2(java.util.List l) { + switch (l) { + case java.util.List l2: break; + } + } + void erroneous(Object o) { + switch (o) { + case String s: break; + case Undefined u: break; + case Integer i: break; + 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; + } + } + void patternAndDefault2(Object o) { + switch (o) { + case String s: + case default: break; + } + } + void patternAndDefault3(Object o) { + switch (o) { + case default, String s: break; + } + } + void patternAndDefault4(Object o) { + switch (o) { + case default: + case String s: break; + } + } + void nullAfterTotal(Object o) { + switch (o) { + case Object obj: break; + case null: break; + } + } + void sealedNonAbstract(SealedNonAbstract obj) { + switch (obj) {//does not cover SealedNonAbstract + case A a -> {} + } + } + sealed class SealedNonAbstract permits A {} + final class A extends SealedNonAbstract {} +} diff --git a/test/langtools/tools/javac/patterns/SwitchErrors.out b/test/langtools/tools/javac/patterns/SwitchErrors.out new file mode 100644 index 00000000000..d694b4c6800 --- /dev/null +++ b/test/langtools/tools/javac/patterns/SwitchErrors.out @@ -0,0 +1,44 @@ +SwitchErrors.java:33:18: compiler.err.constant.label.not.compatible: java.lang.String, java.lang.Object +SwitchErrors.java:39:18: compiler.err.constant.label.not.compatible: int, java.lang.Object +SwitchErrors.java:45:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Integer) +SwitchErrors.java:46:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Integer, java.lang.CharSequence) +SwitchErrors.java:51:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: compiler.misc.type.null, int) +SwitchErrors.java:52:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, int) +SwitchErrors.java:53:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.CharSequence) +SwitchErrors.java:59:20: compiler.err.total.pattern.and.default +SwitchErrors.java:65:13: compiler.err.pattern.dominated +SwitchErrors.java:65:24: compiler.err.total.pattern.and.default +SwitchErrors.java:71:18: compiler.err.total.pattern.and.default +SwitchErrors.java:77:18: compiler.err.duplicate.total.pattern +SwitchErrors.java:83:20: compiler.err.duplicate.default.label +SwitchErrors.java:89:20: compiler.err.duplicate.default.label +SwitchErrors.java:94:27: compiler.err.duplicate.default.label +SwitchErrors.java:100:13: compiler.err.duplicate.case.label +SwitchErrors.java:105:13: compiler.err.duplicate.case.label +SwitchErrors.java:110:28: compiler.err.flows.through.to.pattern +SwitchErrors.java:116:18: compiler.err.flows.through.to.pattern +SwitchErrors.java:123:18: compiler.err.flows.through.to.pattern +SwitchErrors.java:130:18: compiler.err.flows.through.to.pattern +SwitchErrors.java:135:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Integer) +SwitchErrors.java:141:18: compiler.err.instanceof.reifiable.not.safe: java.util.List, java.util.List +SwitchErrors.java:147:18: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.class, SwitchErrors, null) +SwitchErrors.java:154:18: compiler.err.type.found.req: int, (compiler.misc.type.req.class.array) +SwitchErrors.java:160:28: compiler.err.flows.through.from.pattern +SwitchErrors.java:166:18: compiler.err.flows.through.from.pattern +SwitchErrors.java:171:27: compiler.err.flows.through.to.pattern +SwitchErrors.java:177:18: compiler.err.flows.through.to.pattern +SwitchErrors.java:183:13: compiler.err.pattern.dominated +SwitchErrors.java:32:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:38:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:44:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:50:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:98:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:104:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:109:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:114:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:120:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:127:9: compiler.err.not.exhaustive.statement +SwitchErrors.java:187:9: compiler.err.not.exhaustive.statement +- compiler.note.preview.filename: SwitchErrors.java, DEFAULT +- compiler.note.preview.recompile +41 errors diff --git a/test/langtools/tools/javac/patterns/Switches.java b/test/langtools/tools/javac/patterns/Switches.java new file mode 100644 index 00000000000..c928a63a1a0 --- /dev/null +++ b/test/langtools/tools/javac/patterns/Switches.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +/* + * @test + * @bug 8262891 + * @summary Check behavior of pattern switches. + * @compile --enable-preview -source ${jdk.version} Switches.java + * @run main/othervm --enable-preview Switches + */ +public class Switches { + + public static void main(String... args) { + new Switches().run(); + } + + void run() { + run(this::typeTestPatternSwitchTest); + run(this::typeTestPatternSwitchExpressionTest); + run(this::testBooleanSwitchExpression); + assertFalse(testNullSwitch(null)); + assertTrue(testNullSwitch("")); + runArrayTypeTest(this::testArrayTypeStatement); + runArrayTypeTest(this::testArrayTypeExpression); + runEnumTest(this::testEnumExpression1); + runEnumTest(this::testEnumExpression2); + runEnumTest(this::testEnumWithGuards1); + runEnumTest(this::testEnumWithGuards2); + runEnumTest(this::testEnumWithGuardsExpression1); + runEnumTest(this::testEnumWithGuardsExpression2); + runEnumTest(this::testStringWithGuards1); + runEnumTest(this::testStringWithGuardsExpression1); + runEnumTest(this::testIntegerWithGuards1); + runEnumTest(this::testIntegerWithGuardsExpression1); + runStringWithConstant(this::testStringWithConstant); + runStringWithConstant(this::testStringWithConstantExpression); + npeTest(this::npeTestStatement); + npeTest(this::npeTestExpression); + exhaustiveStatementSane(""); + exhaustiveStatementSane(null); + } + + void run(Function mapper) { + assertEquals(2, mapper.apply("2")); + assertEquals(3, mapper.apply("3")); + assertEquals(8, mapper.apply(new StringBuilder("4"))); + assertEquals(2, mapper.apply(2)); + assertEquals(3, mapper.apply(3)); + assertEquals(-1, mapper.apply(2.0)); + assertEquals(-1, mapper.apply(new Object())); + } + + void runArrayTypeTest(Function mapper) { + assertEquals("arr0", mapper.apply(new int[0])); + assertEquals("str6", mapper.apply("string")); + assertEquals("i1", mapper.apply(1)); + assertEquals("", mapper.apply(1.0)); + } + + void runEnumTest(Function mapper) { + assertEquals("a", mapper.apply(E.A)); + assertEquals("b", mapper.apply(E.B)); + assertEquals("C", mapper.apply(E.C)); + assertEquals("null", mapper.apply(null)); + } + + void runStringWithConstant(Function mapper) { + assertEquals(1, mapper.apply("A")); + assertEquals(2, mapper.apply("AA")); + assertEquals(0, mapper.apply("")); + assertEquals(-1, mapper.apply(null)); + } + + void npeTest(Consumer testCase) { + try { + testCase.accept(null); + throw new AssertionError("Expected a NullPointerException, but got nothing."); + } catch (NullPointerException ex) { + //OK + } + } + + int typeTestPatternSwitchTest(Object o) { + switch (o) { + case String s: return Integer.parseInt(s.toString()); + case CharSequence s: return 2 * Integer.parseInt(s.toString()); + case Integer i: return i; + case Object x: return -1; + } + } + + int typeTestPatternSwitchExpressionTest(Object o) { + return switch (o) { + case String s -> Integer.parseInt(s.toString()); + case @Deprecated CharSequence s -> { yield 2 * Integer.parseInt(s.toString()); } + case final Integer i -> i; + case Object x -> -1; + }; + } + + int testBooleanSwitchExpression(Object o) { + Object x; + if (switch (o) { + default -> false; + }) { + return -3; + } else if (switch (o) { + case String s -> (x = s) != null; + default -> false; + }) { + return Integer.parseInt(x.toString()); + } else if (switch (o) { + case CharSequence s -> { + x = s; + yield true; + } + default -> false; + }) { + return 2 * Integer.parseInt(x.toString()); + } + return typeTestPatternSwitchTest(o); + } + + boolean testNullSwitch(Object o) { + return switch (o) { + case null -> false; + default -> true; + }; + } + + String testArrayTypeStatement(Object o) { + String res; + switch (o) { + case Integer i -> res = "i" + i; + case int[] arr -> res = "arr" + arr.length; + case String str -> res = "str" + str.length(); + default -> res = ""; + } + return res; + } + + String testArrayTypeExpression(Object o) { + return switch (o) { + case Integer i -> "i" + i; + case int[] arr -> "arr" + arr.length; + case String str -> "str" + str.length(); + default -> ""; + }; + } + + int testStringWithConstant(String str) { + switch (str) { + case "A": return 1; + case null: return -1; + case String s: return s.length(); + } + } + + int testStringWithConstantExpression(String str) { + return switch (str) { + case "A" -> 1; + case null -> -1; + case String s -> s.length(); + }; + } + + String testEnumExpression1(E e) { + return switch (e) { + case A -> "a"; + case B -> "b"; + case null, E x -> String.valueOf(x); + }; + } + + String testEnumExpression2(E e) { + return switch (e) { + case A -> "a"; + case B -> "b"; + case E x, null -> String.valueOf(x); + }; + } + + String testEnumWithGuards1(E e) { + switch (e) { + case A: return "a"; + case B: return "b"; + case E x && "A".equals(x.name()): return "broken"; + case C: return String.valueOf(e); + case null, E x: return String.valueOf(x); + } + } + + String testEnumWithGuardsExpression1(E e) { + return switch (e) { + case A -> "a"; + case B -> "b"; + case E x && "A".equals(x.name()) -> "broken"; + case C -> String.valueOf(e); + case null, E x -> String.valueOf(x); + }; + } + + String testEnumWithGuards2(E e) { + switch (e) { + case A: return "a"; + case B: return "b"; + case E x && "C".equals(x.name()): return "C"; + case C: return "broken"; + case null, E x: return String.valueOf(x); + } + } + + String testEnumWithGuardsExpression2(E e) { + return switch (e) { + case A -> "a"; + case B -> "b"; + case E x && "C".equals(x.name()) -> "C"; + case C -> "broken"; + case null, E x -> String.valueOf(x); + }; + } + + String testStringWithGuards1(E e) { + switch (e != null ? e.name() : null) { + case "A": return "a"; + case "B": return "b"; + case String x && "C".equals(x): return "C"; + case "C": return "broken"; + case null, String x: return String.valueOf(x); + } + } + + String testStringWithGuardsExpression1(E e) { + return switch (e != null ? e.name() : null) { + case "A" -> "a"; + case "B" -> "b"; + case String x && "C".equals(x) -> "C"; + case "C" -> "broken"; + case null, String x -> String.valueOf(x); + }; + } + + String testIntegerWithGuards1(E e) { + switch (e != null ? e.ordinal() : null) { + case 0: return "a"; + case 1: return "b"; + case Integer x && x.equals(2): return "C"; + case 2: return "broken"; + case null, Integer x: return String.valueOf(x); + } + } + + String testIntegerWithGuardsExpression1(E e) { + return switch (e != null ? e.ordinal() : null) { + case 0 -> "a"; + case 1 -> "b"; + case Integer x && x.equals(2) -> "C"; + case 2 -> "broken"; + case null, Integer x -> String.valueOf(x); + }; + } + + void npeTestStatement(I i) { + switch (i) { + case A a -> {} + case B b -> {} + } + } + + void npeTestExpression(I i) { + int j = switch (i) { + case A a -> 0; + case B b -> 1; + }; + } + + void exhaustiveStatementSane(Object o) { + switch (o) { + case Object obj:; //no break intentionally - should not fall through to any possible default + } + switch (o) { + case null, Object obj:; //no break intentionally - should not fall through to any possible default + } + switch (o) { + case Object obj, null:; //no break intentionally - should not fall through to any possible default + } + } + + sealed interface I {} + final class A implements I {} + final class B implements I {} + + void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } + + void assertEquals(String expected, String actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } + + void assertTrue(boolean actual) { + if (!actual) { + throw new AssertionError("Expected: true, but got false"); + } + } + + void assertFalse(boolean actual) { + if (actual) { + throw new AssertionError("Expected: false, but got true"); + } + } + + public enum E { + A, B, C; + } +} diff --git a/test/langtools/tools/javac/stackmap/OrdinarySwitchStackMapTest.java b/test/langtools/tools/javac/stackmap/OrdinarySwitchStackMapTest.java new file mode 100644 index 00000000000..4a2041d955a --- /dev/null +++ b/test/langtools/tools/javac/stackmap/OrdinarySwitchStackMapTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Verify StackMapTable is sensible for simple ordinary switches + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.javap + * @build toolbox.ToolBox toolbox.JavapTask + * @run compile OrdinarySwitchStackMapTest.java + * @run main OrdinarySwitchStackMapTest + */ + +import java.nio.file.Path; +import java.nio.file.Paths; + +import toolbox.JavapTask; +import toolbox.Task; +import toolbox.ToolBox; + +public class OrdinarySwitchStackMapTest { + + class Test { + void method0(int i) throws Exception { + switch (i) { + case 0: System.err.println(0); break; + case 1: System.err.println(1); break; + default: System.err.println(2); break; + } + } + } + + public static void main(String args[]) throws Exception { + ToolBox tb = new ToolBox(); + Path pathToClass = Paths.get(ToolBox.testClasses, "OrdinarySwitchStackMapTest$Test.class"); + String javapOut = new JavapTask(tb) + .options("-v") + .classes(pathToClass.toString()) + .run() + .getOutput(Task.OutputKind.DIRECT); + + if (!javapOut.contains("StackMapTable: number_of_entries = 4")) + throw new AssertionError("The number of entries of the stack map " + + "table should be equal to 4"); + } + +} diff --git a/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out b/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out index ad9fd6b30d5..0a0aadc2519 100644 --- a/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out +++ b/test/langtools/tools/javac/switchextra/SwitchNoExtraTypes.out @@ -1,5 +1,6 @@ -SwitchNoExtraTypes.java:11:16: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: boolean, int) -SwitchNoExtraTypes.java:17:16: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: long, int) -SwitchNoExtraTypes.java:23:16: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: float, int) -SwitchNoExtraTypes.java:29:16: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: double, int) -4 errors +SwitchNoExtraTypes.java:11:16: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.pattern.switch) +SwitchNoExtraTypes.java:12:18: compiler.err.constant.label.not.compatible: boolean, boolean +SwitchNoExtraTypes.java:18:18: compiler.err.constant.label.not.compatible: int, long +SwitchNoExtraTypes.java:24:18: compiler.err.constant.label.not.compatible: int, float +SwitchNoExtraTypes.java:30:18: compiler.err.constant.label.not.compatible: int, double +5 errors diff --git a/test/langtools/tools/javac/switchextra/SwitchObject.out b/test/langtools/tools/javac/switchextra/SwitchObject.out index cdd8757e115..9fa56d0cde5 100644 --- a/test/langtools/tools/javac/switchextra/SwitchObject.out +++ b/test/langtools/tools/javac/switchextra/SwitchObject.out @@ -1,2 +1,4 @@ -SwitchObject.java:10:16: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Object, int) -1 error +SwitchObject.java:10:16: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.pattern.switch) +SwitchObject.java:11:18: compiler.err.constant.label.not.compatible: int, java.lang.Object +SwitchObject.java:12:18: compiler.err.constant.label.not.compatible: java.lang.String, java.lang.Object +3 errors diff --git a/test/langtools/tools/javac/switchnull/SwitchNull.java b/test/langtools/tools/javac/switchnull/SwitchNull.java new file mode 100644 index 00000000000..8268c1890b4 --- /dev/null +++ b/test/langtools/tools/javac/switchnull/SwitchNull.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8262891 + * @summary Verify "case null" behavior. + * @compile --enable-preview -source ${jdk.version} SwitchNull.java + * @run main/othervm --enable-preview SwitchNull + */ + +public class SwitchNull { + public static void main(String... args) { + SwitchNull instance = new SwitchNull(); + + instance.run(); + } + + private void run() { + assertEquals(0, switchIntegerBox(Integer.MIN_VALUE)); + assertEquals(1, switchIntegerBox(-2)); + assertEquals(2, switchIntegerBox(-1)); + assertEquals(3, switchIntegerBox(0)); + assertEquals(4, switchIntegerBox(1)); + assertEquals(5, switchIntegerBox(2)); + assertEquals(6, switchIntegerBox(Integer.MAX_VALUE)); + assertEquals(-1, switchIntegerBox(null)); + assertEquals(-2, switchIntegerBox(3)); + assertEquals(0, switchString("")); + assertEquals(1, switchString("a")); + assertEquals(2, switchString("A")); + assertEquals(-1, switchString(null)); + assertEquals(-2, switchString("c")); + assertEquals(0, switchEnum(E.A)); + assertEquals(1, switchEnum(E.B)); + assertEquals(2, switchEnum(E.C)); + assertEquals(-1, switchEnum(null)); + assertEquals(0, switchEnumWithDefault(E.A)); + assertEquals(1, switchEnumWithDefault(E.B)); + assertEquals(1, switchEnumWithDefault(E.C)); + assertEquals(-1, switchEnumWithDefault(null)); + testSwitchIntegerBoxExhaustive(); + } + + private int switchIntegerBox(Integer i) { + switch (i) { + case Integer.MIN_VALUE: return 0; + case -2: return 1; + case -1: return 2; + case 0: return 3; + case 1: return 4; + case 2: return 5; + case Integer.MAX_VALUE: return 6; + case null: return -1; + default: return -2; + } + } + + private int switchString(String s) { + switch (s) { + case "": return 0; + case "a": return 1; + case "A": return 2; + case null: return -1; + default: return -2; + } + } + + private int switchEnum(E e) { + switch (e) { + case A: return 0; + case B: return 1; + case C: return 2; + case null: return -1; + } + throw new AssertionError(String.valueOf(e)); + } + + private int switchEnumWithDefault(E e) { + switch (e) { + case A: return 0; + default: return 1; + case null: return -1; + } + } + + private void testSwitchIntegerBoxExhaustive() { + int i = Integer.MIN_VALUE; + + do { + int result = switchIntegerBoxExhaustive(i); + int expected = i == 0 ? 0 : 1; + assertEquals(expected, result); + } while (i++ < Integer.MAX_VALUE); + + int result = switchIntegerBoxExhaustive(null); + assertEquals(-1, result); + } + + private int switchIntegerBoxExhaustive(Integer i) { + return switch (i) { + case null -> -1; + case 0 -> 0; + default -> 1; + }; + } + + enum E { + A, + B, + C; + } + + private void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/switchnull/SwitchNullDisabled-preview.out b/test/langtools/tools/javac/switchnull/SwitchNullDisabled-preview.out new file mode 100644 index 00000000000..d8bf355a56c --- /dev/null +++ b/test/langtools/tools/javac/switchnull/SwitchNullDisabled-preview.out @@ -0,0 +1,2 @@ +SwitchNullDisabled.java:13:18: compiler.err.preview.feature.disabled: (compiler.misc.feature.case.null) +1 error diff --git a/test/langtools/tools/javac/switchnull/SwitchNullDisabled.java b/test/langtools/tools/javac/switchnull/SwitchNullDisabled.java index de4d34a539d..069cf2f0ab6 100644 --- a/test/langtools/tools/javac/switchnull/SwitchNullDisabled.java +++ b/test/langtools/tools/javac/switchnull/SwitchNullDisabled.java @@ -1,8 +1,10 @@ /* * @test /nodynamiccopyright/ * @bug 8206986 - * @summary Verify "case null" is not allowed. - * @compile/fail/ref=SwitchNullDisabled.out -XDrawDiagnostics SwitchNullDisabled.java + * @summary Verify "case null" is not allowed for -source 16 + * @compile/fail/ref=SwitchNullDisabled.out -XDrawDiagnostics -source 16 -Xlint:-options SwitchNullDisabled.java + * @compile/fail/ref=SwitchNullDisabled-preview.out -XDrawDiagnostics SwitchNullDisabled.java + * @compile --enable-preview -source ${jdk.version} SwitchNullDisabled.java */ public class SwitchNullDisabled { diff --git a/test/langtools/tools/javac/switchnull/SwitchNullDisabled.out b/test/langtools/tools/javac/switchnull/SwitchNullDisabled.out index 082253da41d..d8bf355a56c 100644 --- a/test/langtools/tools/javac/switchnull/SwitchNullDisabled.out +++ b/test/langtools/tools/javac/switchnull/SwitchNullDisabled.out @@ -1,2 +1,2 @@ -SwitchNullDisabled.java:11:18: compiler.err.switch.null.not.allowed +SwitchNullDisabled.java:13:18: compiler.err.preview.feature.disabled: (compiler.misc.feature.case.null) 1 error diff --git a/test/langtools/tools/javac/tree/SourceTreeScannerTest.java b/test/langtools/tools/javac/tree/SourceTreeScannerTest.java index bf50f298871..fc5c9b75150 100644 --- a/test/langtools/tools/javac/tree/SourceTreeScannerTest.java +++ b/test/langtools/tools/javac/tree/SourceTreeScannerTest.java @@ -95,6 +95,8 @@ public class SourceTreeScannerTest extends AbstractTreeScannerTest { private class ScanTester extends TreeScanner { /** Main entry method for the class. */ int test(JCCompilationUnit tree) { + if (!tree.sourcefile.toString().contains("EmptyBreak.java")) + return 0; sourcefile = tree.sourcefile; found = new HashSet(); scan(tree, null); @@ -143,28 +145,30 @@ public class SourceTreeScannerTest extends AbstractTreeScannerTest { if (o instanceof JCTree) { JCTree tree = (JCTree) o; //System.err.println("EXPECT: " + tree.getKind() + " " + trim(tree, 64)); - expect.add(tree); - for (Field f: getFields(tree)) { - if (TypeBoundKind.class.isAssignableFrom(f.getType())) { - // not part of public API - continue; - } - try { - //System.err.println("FIELD: " + f.getName()); - if (tree instanceof JCModuleDecl && f.getName().equals("mods")) { - // The modifiers will not found by TreeScanner, - // but the embedded annotations will be. - reflectiveScan(((JCModuleDecl) tree).mods.annotations); - } else if (tree instanceof JCCase && - ((JCCase) tree).getCaseKind() == CaseKind.RULE && - f.getName().equals("stats")) { - //value case, visit value: - reflectiveScan(((JCCase) tree).getBody()); - } else { - reflectiveScan(f.get(tree)); + if (!tree.hasTag(JCTree.Tag.DEFAULTCASELABEL)) { + expect.add(tree); + for (Field f: getFields(tree)) { + if (TypeBoundKind.class.isAssignableFrom(f.getType())) { + // not part of public API + continue; + } + try { + //System.err.println("FIELD: " + f.getName()); + if (tree instanceof JCModuleDecl && f.getName().equals("mods")) { + // The modifiers will not found by TreeScanner, + // but the embedded annotations will be. + reflectiveScan(((JCModuleDecl) tree).mods.annotations); + } else if (tree instanceof JCCase && + ((JCCase) tree).getCaseKind() == CaseKind.RULE && + f.getName().equals("stats")) { + //value case, visit value: + reflectiveScan(((JCCase) tree).getBody()); + } else { + reflectiveScan(f.get(tree)); + } + } catch (IllegalAccessException e) { + error(e.toString()); } - } catch (IllegalAccessException e) { - error(e.toString()); } } } else if (o instanceof List) { diff --git a/test/langtools/tools/jdeps/listdeps/ListModuleDeps.java b/test/langtools/tools/jdeps/listdeps/ListModuleDeps.java index b82baa41d66..0f014f4bc36 100644 --- a/test/langtools/tools/jdeps/listdeps/ListModuleDeps.java +++ b/test/langtools/tools/jdeps/listdeps/ListModuleDeps.java @@ -92,6 +92,7 @@ public class ListModuleDeps { public Object[][] jdkModules() { return new Object[][]{ {"jdk.compiler", new String[]{ + "java.base/jdk.internal.javac", "java.base/jdk.internal.jmod", "java.base/jdk.internal.misc", "java.base/sun.reflect.annotation",