diff options
author | Stephan Herrmann | 2021-08-24 20:17:37 +0000 |
---|---|---|
committer | Stephan Herrmann | 2021-08-26 09:29:32 +0000 |
commit | 331697d97d55cb9367bd18055a735bb6685f07b8 (patch) | |
tree | 91c314930c3bd65a0bc5d8a0215cf247f53e788d | |
parent | 017341e01a5cb516e8b0cad50533a1fab05cdb4f (diff) | |
download | eclipse.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>
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 |