Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Herrmann2021-08-24 20:17:37 +0000
committerStephan Herrmann2021-08-26 09:29:32 +0000
commit331697d97d55cb9367bd18055a735bb6685f07b8 (patch)
tree91c314930c3bd65a0bc5d8a0215cf247f53e788d
parent017341e01a5cb516e8b0cad50533a1fab05cdb4f (diff)
downloadeclipse.jdt.core-331697d97d55cb9367bd18055a735bb6685f07b8.tar.gz
eclipse.jdt.core-331697d97d55cb9367bd18055a735bb6685f07b8.tar.xz
eclipse.jdt.core-331697d97d55cb9367bd18055a735bb6685f07b8.zip
Bug 575593 - [17][switch pattern] Integrate pattern switch with nullY20210826-0800P20210827-0040
analysis / annotations Change-Id: I3f4ec51a4a27dd26b25c84ecfe78b82cd8a872ad Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de> Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/184384 Tested-by: JDT Bot <jdt-bot@eclipse.org>
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java2
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests17.java473
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java1
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java4
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java6
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java15
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypePattern.java13
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java4
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties1
9 files changed, 516 insertions, 3 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java
index 5be9ae2376..5b129ef0aa 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java
@@ -1104,6 +1104,7 @@ public void test011_problem_categories() {
expectedProblemAttributes.put("UnnecessaryElse", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE));
expectedProblemAttributes.put("UnnecessaryInstanceof", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE));
expectedProblemAttributes.put("UnnecessaryNLSTag", new ProblemAttributes(CategorizedProblem.CAT_NLS));
+ expectedProblemAttributes.put("UnnecessaryNullCaseInSwitchOverNonNull", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM));
expectedProblemAttributes.put("UnqualifiedFieldAccess", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE));
expectedProblemAttributes.put("UnreachableCatch", new ProblemAttributes(CategorizedProblem.CAT_TYPE));
expectedProblemAttributes.put("UnresolvedVariable", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
@@ -2180,6 +2181,7 @@ public void test012_compiler_problems_tuning() {
expectedProblemAttributes.put("UnnecessaryElse", new ProblemAttributes(JavaCore.COMPILER_PB_UNNECESSARY_ELSE));
expectedProblemAttributes.put("UnnecessaryInstanceof", new ProblemAttributes(JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK));
expectedProblemAttributes.put("UnnecessaryNLSTag", new ProblemAttributes(JavaCore.COMPILER_PB_NON_NLS_STRING_LITERAL));
+ expectedProblemAttributes.put("UnnecessaryNullCaseInSwitchOverNonNull", new ProblemAttributes(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK));
expectedProblemAttributes.put("UnqualifiedFieldAccess", new ProblemAttributes(JavaCore.COMPILER_PB_UNQUALIFIED_FIELD_ACCESS));
expectedProblemAttributes.put("UnreachableCatch", SKIP);
expectedProblemAttributes.put("UnresolvedVariable", SKIP);
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests17.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests17.java
new file mode 100644
index 0000000000..d4a98a27db
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests17.java
@@ -0,0 +1,473 @@
+/*******************************************************************************
+ * Copyright (c) 2021 GK Software SE, and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * This is an implementation of an early-draft specification developed under the Java
+ * Community Process (JCP) and is made available for testing and evaluation purposes
+ * only. The code is not compatible with any specification of the JCP.
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.compiler.regression;
+
+import static org.eclipse.jdt.core.tests.util.Util.createJar;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jdt.core.tests.util.Util;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+
+import junit.framework.Test;
+
+public class NullAnnotationTests17 extends AbstractNullAnnotationTest {
+
+ public NullAnnotationTests17(String name) {
+ super(name);
+ }
+
+ static {
+// TESTS_NAMES = new String[] { "test_totalTypePatternNonNullExpression" };
+// TESTS_NUMBERS = new int[] { 001 };
+// TESTS_RANGE = new int[] { 1, 12 };
+ }
+
+ public static Test suite() {
+ return buildMinimalComplianceTestSuite(testClass(), F_17);
+ }
+
+ public static Class<?> testClass() {
+ return NullAnnotationTests17.class;
+ }
+
+ @Deprecated // super method is deprecated
+ @Override
+ protected void setUpAnnotationLib() throws IOException {
+ if (this.LIBS == null) {
+ String[] defaultLibs = getDefaultClassPaths();
+ int len = defaultLibs.length;
+ this.LIBS = new String[len+1];
+ System.arraycopy(defaultLibs, 0, this.LIBS, 0, len);
+ this.LIBS[len] = createAnnotation_2_2_jar(Util.getOutputDirectory() + File.separator, null);
+ }
+ }
+
+ public static String createAnnotation_2_2_jar(String dirName, String jcl17Path) throws IOException {
+ // role our own annotation library as long as o.e.j.annotation is still at BREE 1.8:
+ String jarFileName = dirName + "org.eclipse.jdt.annotation_2.2.0.jar";
+ createJar(new String[] {
+ "module-info.java",
+ "module org.eclipse.jdt.annotation {\n" +
+ " exports org.eclipse.jdt.annotation;\n" +
+ "}\n",
+
+ "org/eclipse/jdt/annotation/DefaultLocation.java",
+ "package org.eclipse.jdt.annotation;\n" +
+ "\n" +
+ "public enum DefaultLocation {\n" +
+ " \n" +
+ " PARAMETER, RETURN_TYPE, FIELD, TYPE_PARAMETER, TYPE_BOUND, TYPE_ARGUMENT, ARRAY_CONTENTS\n" +
+ "}\n",
+
+ "org/eclipse/jdt/annotation/NonNullByDefault.java",
+ "package org.eclipse.jdt.annotation;\n" +
+ "\n" +
+ "import java.lang.annotation.ElementType;\n" +
+ "import static org.eclipse.jdt.annotation.DefaultLocation.*;\n" +
+ "\n" +
+ "import java.lang.annotation.*;\n" +
+ " \n" +
+ "@Documented\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({ ElementType.MODULE, ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE })\n" +
+ "public @interface NonNullByDefault {\n" +
+ " DefaultLocation[] value() default { PARAMETER, RETURN_TYPE, FIELD, TYPE_BOUND, TYPE_ARGUMENT };\n" +
+ "}",
+
+ "org/eclipse/jdt/annotation/NonNull.java",
+ "package org.eclipse.jdt.annotation;\n" +
+ "import static java.lang.annotation.ElementType.TYPE_USE;\n" +
+ "\n" +
+ "import java.lang.annotation.*;\n" +
+ " \n" +
+ "@Documented\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({ TYPE_USE })\n" +
+ "public @interface NonNull {\n" +
+ " // marker annotation with no members\n" +
+ "}\n",
+
+ "org/eclipse/jdt/annotation/Nullable.java",
+ "package org.eclipse.jdt.annotation;\n" +
+ "\n" +
+ "import static java.lang.annotation.ElementType.TYPE_USE;\n" +
+ "\n" +
+ "import java.lang.annotation.*;\n" +
+ " \n" +
+ "@Documented\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({ TYPE_USE })\n" +
+ "public @interface Nullable {\n" +
+ " // marker annotation with no members\n" +
+ "}\n"
+ },
+ null,
+ jarFileName,
+ jcl17Path != null ? new String[] { jcl17Path } : null,
+ "17");
+ return jarFileName;
+ }
+
+ // -------- helper ------------
+
+ private Runner getDefaultRunner() {
+ Runner runner = new Runner();
+ runner.classLibraries = this.LIBS;
+ Map<String,String> opts = getCompilerOptions();
+ opts.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_17);
+ opts.put(CompilerOptions.OPTION_EnablePreviews, CompilerOptions.ENABLED);
+ runner.customOptions = opts;
+ runner.vmArguments = new String[] {"--enable-preview"};
+ runner.javacTestOptions =
+ JavacTestOptions.Excuse.EclipseWarningConfiguredAsError;
+ return runner;
+ }
+
+ // --------- tests start -----------
+
+ public void test_typePatternIsNN() {
+ Runner runner = getDefaultRunner();
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(Object o) {\n" +
+ " switch (o) {\n" +
+ " case Integer i -> consumeInt(i);\n" +
+ " default -> System.out.println(\"default\");\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeInt(@NonNull Integer i) {\n" +
+ " System.out.print(i);\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(3);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog = "";
+ runner.expectedOutputString = "3";
+ runner.runConformTest();
+ }
+
+ public void test_totalTypePatternAdmitsNull() {
+ Runner runner = getDefaultRunner();
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(Number n) {\n" +
+ " try {\n" +
+ " switch (n) {\n" +
+ " case Integer i -> consumeInt(i);\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " }\n" +
+ " } catch (NullPointerException npe) {\n" +
+ " // ignoring the unchecked warning, and expecting the NPE:\n" +
+ " System.out.print(npe.getMessage());\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeInt(@NonNull Integer i) {\n" +
+ " System.out.print(i);\n" +
+ " }\n" +
+ " void consumeNumber(@NonNull Number n) {\n" +
+ " System.out.print(n.toString());\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog =
+ "----------\n" +
+ "1. WARNING in X.java (at line 7)\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " ^^\n" +
+ "Null type safety (type annotations): The expression of type \'Number\' needs unchecked conversion to conform to \'@NonNull Number\'\n" +
+ "----------\n";
+ runner.expectedOutputString = "Cannot invoke \"Object.toString()\" because \"n\" is null";
+ runner.runConformTest();
+ }
+
+ public void test_totalTypePatternNonNullExpression() {
+ Runner runner = getDefaultRunner();
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(Number n) {\n" +
+ " if (n == null) return;\n" + // this prevents the NPE -> no need to warn
+ " switch (n) {\n" +
+ " case Integer i -> System.out.print(i);\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeNumber(@NonNull Number n) {\n" +
+ " System.out.print(n.toString());\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog = "";
+ runner.expectedOutputString = "";
+ runner.runConformTest();
+ }
+
+ public void test_totalTypePatternNonNullExpression_swExpr() {
+ Runner runner = getDefaultRunner();
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " int foo(Number n) {\n" +
+ " if (n == null) return -1;\n" + // this prevents the NPE -> no need to warn
+ " return switch (n) {\n" +
+ " case Integer i -> i;\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " };\n" +
+ " }\n" +
+ " int consumeNumber(@NonNull Number n) {\n" +
+ " return Integer.valueOf(n.toString());\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog = "";
+ runner.expectedOutputString = "";
+ runner.runConformTest();
+ }
+
+ public void test_totalTypePatternPlusNullPattern() {
+ Runner runner = getDefaultRunner();
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(Number n) {\n" +
+ " switch (n) {\n" +
+ " case null -> System.out.print(\"null\");\n" + // this prevents the NPE
+ " case Integer i -> System.out.print(i);\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeNumber(@NonNull Number n) {\n" +
+ " System.out.print(n.toString());\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog = "";
+ runner.expectedOutputString = "null";
+ runner.runConformTest();
+ }
+
+ public void test_totalTypePatternNullableExpression() {
+ Runner runner = getDefaultRunner();
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@Nullable Number n) {\n" + // @Nullable here turns "unchecked" into "null type mismatch"
+ " switch (n) {\n" +
+ " case Integer i -> System.out.print(i);\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeNumber(@NonNull Number n) {\n" +
+ " System.out.print(n.toString());\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog =
+ "----------\n" +
+ "1. ERROR in X.java (at line 6)\n" +
+ " case Number n0 -> consumeNumber(n0);\n" +
+ " ^^\n" +
+ "Null type mismatch: required \'@NonNull Number\' but the provided value is inferred as @Nullable\n" +
+ "----------\n";
+ runner.runNegativeTest();
+ }
+
+ public void test_switchOverNNValueWithNullCase() {
+ Runner runner = getDefaultRunner();
+ runner.customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.WARNING);
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@NonNull Object o) {\n" +
+ " switch (o) {\n" +
+ " case Integer i -> consumeInt(i);\n" +
+ " case null -> System.out.print(\"null\");\n" +
+ " default -> System.out.println(\"default\");\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeInt(@NonNull Integer i) {\n" +
+ " System.out.print(i);\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(3);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog =
+ "----------\n" +
+ "1. WARNING in X.java (at line 6)\n" +
+ " case null -> System.out.print(\"null\");\n" +
+ " ^^^^^^^^^\n" +
+ "Unnecessary \'null\' pattern, the switch selector expression cannot be null\n" +
+ "----------\n";
+ runner.expectedOutputString = "3";
+ runner.runConformTest();
+ }
+
+ public void test_switchNullInSameCase() {
+ Runner runner = getDefaultRunner();
+ runner.customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.WARNING);
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(Object o) {\n" +
+ " switch (o) {\n" +
+ " case null, Integer i -> consumeInt(i);\n" +
+ " default -> System.out.println(\"default\");\n" +
+ " }\n" +
+ " }\n" +
+ " void consumeInt(@NonNull Integer i) {\n" +
+ " System.out.print(i);\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(3);\n" +
+ " }\n" +
+ "}\n"
+ };
+ // demonstrate that null case cannot leak into a type pattern:
+ runner.expectedCompilerLog =
+ "----------\n" +
+ "1. ERROR in X.java (at line 5)\n" +
+ " case null, Integer i -> consumeInt(i);\n" +
+ " ^^^^^^^^^\n" +
+ "Illegal fall-through to a pattern\n" +
+ "----------\n";
+ runner.runNegativeTest();
+ }
+
+ public void test_switchOverNNValueWithNullCase_swExpr() {
+ Runner runner = getDefaultRunner();
+ runner.customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.WARNING);
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " int foo(@NonNull Object o) {\n" +
+ " return switch (o) {\n" +
+ " case Integer i -> consumeInt(i);\n" +
+ " case null -> 0;\n" +
+ " default -> -1;\n" +
+ " };\n" +
+ " }\n" +
+ " int consumeInt(@NonNull Integer i) {\n" +
+ " return i;\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " System.out.print(new X().foo(3));\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog =
+ "----------\n" +
+ "1. WARNING in X.java (at line 6)\n" +
+ " case null -> 0;\n" +
+ " ^^^^^^^^^\n" +
+ "Unnecessary \'null\' pattern, the switch selector expression cannot be null\n" +
+ "----------\n";
+ runner.expectedOutputString = "3";
+ runner.runConformTest();
+ }
+
+ public void test_nullHostileSwitch() {
+ Runner runner = getDefaultRunner();
+ runner.customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.WARNING);
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@Nullable Object o) {\n" +
+ " switch (o) {\n" +
+ " case Integer i -> consumeInt(i);\n" +
+ " default -> System.out.println(o);\n" +
+ " };\n" +
+ " }\n" +
+ " void consumeInt(@NonNull Integer i) {\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog =
+ "----------\n" +
+ "1. ERROR in X.java (at line 4)\n" +
+ " switch (o) {\n" +
+ " ^\n" +
+ "Potential null pointer access: this expression has a \'@Nullable\' type\n" +
+ "----------\n";
+ runner.runNegativeTest();
+ }
+
+ // disabled because our implementation has no straight-forward representation of the scope
+ // where 'o' would be known to be non-null after 'default'.
+ // currently this would bogusly report "Potential null pointer access" against o.toString()
+ public void _test_defaultDoesNotApplyToNull() {
+ Runner runner = getDefaultRunner();
+ runner.customOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.WARNING);
+ runner.testFiles = new String[] {
+ "X.java",
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class X {\n" +
+ " void foo(@Nullable Object o) {\n" +
+ " switch (o) {\n" +
+ " case Integer i -> consumeInt(i);\n" +
+ " default -> System.out.println(o.toString());\n" +
+ " };\n" +
+ " }\n" +
+ " void consumeInt(@NonNull Integer i) {\n" +
+ " }\n" +
+ " public static void main(String... args) {\n" +
+ " new X().foo(null);\n" +
+ " }\n" +
+ "}\n"
+ };
+ runner.expectedCompilerLog = "";
+ runner.expectedOutputString = "3";
+ runner.runConformTest();
+ }
+}
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java
index bbd5cadba3..5bd066fe94 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java
@@ -222,6 +222,7 @@ public static Test suite() {
since_17.add(SealedTypesTests.class);
since_17.add(SwitchPatternTest.class);
since_17.add(InstanceofPrimaryPatternTest.class);
+ since_17.add(NullAnnotationTests17.class);
// Build final test suite
TestSuite all = new TestSuite(TestAll.class.getName());
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
index a786cc91e6..dab1a6eac8 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
@@ -2501,4 +2501,8 @@ void setSourceStart(int sourceStart);
/** @since 3.27 BETA_JAVA17
* @noreference preview feature error */
int DuplicateTotalPattern = PreviewRelated + 1909;
+
+ /** @since 3.27 BETA_JAVA17
+ * @noreference preview feature error */
+ int UnnecessaryNullCaseInSwitchOverNonNull = 1910;
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java
index 325d28c53f..029ff7d6ac 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java
@@ -99,6 +99,12 @@ private void analyseConstantExpression(
&& (e instanceof NullLiteral || e instanceof FakeDefaultLiteral);
if (!caseNullorDefaultAllowed)
currentScope.problemReporter().caseExpressionMustBeConstant(e);
+ if (e instanceof NullLiteral && flowContext.associatedNode instanceof SwitchStatement) {
+ Expression switchValue = ((SwitchStatement) flowContext.associatedNode).expression;
+ if (switchValue != null && switchValue.nullStatus(flowInfo, flowContext) == FlowInfo.NON_NULL) {
+ currentScope.problemReporter().unnecessaryNullCaseInSwitchOverNonNull(this);
+ }
+ }
}
e.analyseCode(currentScope, flowContext, flowInfo);
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java
index dd14a13576..a9a0b96a50 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java
@@ -151,9 +151,7 @@ public class SwitchStatement extends Expression {
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
try {
flowInfo = this.expression.analyseCode(currentScope, flowContext, flowInfo);
- if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0
- || (this.expression.resolvedType != null
- && (this.expression.resolvedType.id == T_JavaLangString || this.expression.resolvedType.isEnum()))) {
+ if (isNullHostile()) {
this.expression.checkNPE(currentScope, flowContext, flowInfo, 1);
}
SwitchFlowContext switchContext =
@@ -243,6 +241,17 @@ public class SwitchStatement extends Expression {
if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block
}
}
+ private boolean isNullHostile() {
+ if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0) {
+ return true;
+ } else if (this.expression.resolvedType != null
+ && (this.expression.resolvedType.id == T_JavaLangString || this.expression.resolvedType.isEnum())) {
+ return true;
+ } else if ((this.switchBits & (LabeledRules|NullCase)) == LabeledRules && this.totalPattern == null) {
+ return true;
+ }
+ return false;
+ }
/**
* Switch on String code generation
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypePattern.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypePattern.java
index 2e7d39f34c..84b73189d5 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypePattern.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypePattern.java
@@ -60,6 +60,19 @@ public class TypePattern extends Pattern {
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
flowInfo.markAsDefinitelyAssigned(this.local.binding);
+ if (!this.isTotalTypeNode) {
+ // non-total type patterns create a nonnull local:
+ flowInfo.markAsDefinitelyNonNull(this.local.binding);
+ } else {
+ // total type patterns inherit the nullness of the value being switched over, unless ...
+ if (flowContext.associatedNode instanceof SwitchStatement) {
+ SwitchStatement swStmt = (SwitchStatement) flowContext.associatedNode;
+ int nullStatus = swStmt.containsNull
+ ? FlowInfo.NON_NULL // ... null is handled in a separate case
+ : swStmt.expression.nullStatus(flowInfo, flowContext);
+ flowInfo.markNullStatus(this.local.binding, nullStatus);
+ }
+ }
return flowInfo;
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
index 70c4316b69..6f46230ad8 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
@@ -467,6 +467,7 @@ public static int getIrritant(int problemID) {
case IProblem.RedundantNullCheckOnConstNonNullField:
case IProblem.ConstNonNullFieldComparisonYieldsFalse:
case IProblem.FieldComparisonYieldsFalse:
+ case IProblem.UnnecessaryNullCaseInSwitchOverNonNull:
return CompilerOptions.RedundantNullCheck;
case IProblem.RequiredNonNullButProvidedNull:
@@ -6341,6 +6342,9 @@ public boolean expressionNonNullComparison(Expression expr, boolean checkForNull
this.handle(problemId, arguments, arguments, start, end);
return true;
}
+public void unnecessaryNullCaseInSwitchOverNonNull(CaseStatement caseStmt) {
+ this.handle(IProblem.UnnecessaryNullCaseInSwitchOverNonNull, NoArgument, NoArgument, caseStmt.sourceStart, caseStmt.sourceEnd);
+}
public void nullAnnotationUnsupportedLocation(Annotation annotation) {
String[] arguments = new String[] {
String.valueOf(annotation.resolvedType.readableName())
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
index f2b7478645..a5a70a73aa 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
@@ -1126,6 +1126,7 @@
1907 = Switch case cannot have both a total pattern and default label
1908 = An enhanced switch statement should be exhaustive; a default label expected
1909 = The switch statement cannot have more than one total pattern
+1910 = Unnecessary 'null' pattern, the switch selector expression cannot be null
### ELABORATIONS
## Access restrictions

Back to the top