Bug 575241 - [17] switch block covering a type T

Change-Id: If2bd7343d1c8c6562f00b4695edd54c7aff86a71
Signed-off-by: Manoj Palat <manpalat@in.ibm.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/183890
Tested-by: JDT Bot <jdt-bot@eclipse.org>
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java
index fff3ce0..8435308 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java
@@ -1635,6 +1635,11 @@
 			"----------\n" +
 			"1. ERROR in X.java (at line 5)\n" +
 			"	case 1, Integer i  -> System.out.println(o);\n" +
+			"	     ^\n" +
+			"The constant case label element is not compatible with switch expression type Integer\n" +
+			"----------\n" +
+			"2. ERROR in X.java (at line 5)\n" +
+			"	case 1, Integer i  -> System.out.println(o);\n" +
 			"	        ^^^^^^^^^\n" +
 			"Constant case label elements and pattern case label elements cannot be present in a switch label\n" +
 			"----------\n");
@@ -1956,10 +1961,15 @@
 			"----------\n" +
 			"1. ERROR in X.java (at line 7)\n" +
 			"	case  10, null, var k  -> System.out.println(0);\n" +
+			"	      ^^\n" +
+			"The constant case label element is not compatible with switch expression type Integer\n" +
+			"----------\n" +
+			"2. ERROR in X.java (at line 7)\n" +
+			"	case  10, null, var k  -> System.out.println(0);\n" +
 			"	                ^^^\n" +
 			"\'var\' is not allowed here\n" +
 			"----------\n" +
-			"2. ERROR in X.java (at line 7)\n" +
+			"3. ERROR in X.java (at line 7)\n" +
 			"	case  10, null, var k  -> System.out.println(0);\n" +
 			"	                ^^^^^\n" +
 			"Constant case label elements and pattern case label elements cannot be present in a switch label\n" +
@@ -2055,20 +2065,25 @@
 			"----------\n" +
 			"1. ERROR in X.java (at line 7)\n" +
 			"	case  default, 1, var k  -> System.out.println(0);\n" +
-			"	                  ^^^\n" +
-			"\'var\' is not allowed here\n" +
+			"	               ^\n" +
+			"The constant case label element is not compatible with switch expression type Integer\n" +
 			"----------\n" +
 			"2. ERROR in X.java (at line 7)\n" +
 			"	case  default, 1, var k  -> System.out.println(0);\n" +
-			"	                  ^^^^^\n" +
-			"A switch label may not have both a pattern case label element and a default case label element.\n" +
+			"	                  ^^^\n" +
+			"\'var\' is not allowed here\n" +
 			"----------\n" +
 			"3. ERROR in X.java (at line 7)\n" +
 			"	case  default, 1, var k  -> System.out.println(0);\n" +
 			"	                  ^^^^^\n" +
+			"A switch label may not have both a pattern case label element and a default case label element.\n" +
+			"----------\n" +
+			"4. ERROR in X.java (at line 7)\n" +
+			"	case  default, 1, var k  -> System.out.println(0);\n" +
+			"	                  ^^^^^\n" +
 			"Constant case label elements and pattern case label elements cannot be present in a switch label\n" +
 			"----------\n" +
-			"4. ERROR in X.java (at line 8)\n" +
+			"5. ERROR in X.java (at line 8)\n" +
 			"	default -> System.out.println(o);\n" +
 			"	^^^^^^^\n" +
 			"The default case is already defined\n" +
@@ -2905,4 +2920,157 @@
 			},
 			"0");
 	}
-}
+	public void testBug575241_01() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static int foo(Integer i) {\n"+
+				"   return switch (i) {\n"+
+				"     case Integer i1 -> 0;\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   System.out.println(foo(1));\n"+
+				" }\n"+
+				"}",
+			},
+			"0");
+	}
+	public void testBug575241_02() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static int foo(Integer i) {\n"+
+				"   return switch (i) {\n"+
+				"     case Object o -> 0;\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   System.out.println(foo(1));\n"+
+				" }\n"+
+				"}",
+			},
+			"0");
+	}
+	public void testBug575241_03() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static int foo(Object myVar) {\n"+
+				"   return switch (myVar) {\n"+
+				"     case null  -> 0;\n"+
+				"     case Integer o -> 1;\n"+
+				"     case Object obj ->2;\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   System.out.println(foo(Integer.valueOf(0)));\n"+
+				"   System.out.println(foo(null));\n"+
+				" }\n"+
+				"}",
+			},
+			"1\n" +
+			"0");
+	}
+	public void testBug575241_04() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static int foo(Object myVar) {\n"+
+				"   return switch (myVar) {\n"+
+				"     case Integer o -> 1;\n"+
+				"     case Object obj ->2;\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   System.out.println(foo(Integer.valueOf(0)));\n"+
+				"   System.out.println(foo(null));\n"+
+				" }\n"+
+				"}",
+			},
+			"1\n" +
+			"2");
+	}
+	public void testBug575241_05() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static void foo(Integer myVar) {\n"+
+				"    switch (myVar) {\n"+
+				"     case  null  -> System.out.println(100);\n"+
+				"     case Integer o -> System.out.println(o);\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   foo(Integer.valueOf(0));\n"+
+				"   foo(null);\n"+
+				" }\n"+
+				"}",
+			},
+			"0\n" +
+			"100");
+	}
+	public void testBug575241_06() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static void foo(Integer myVar) {\n"+
+				"    switch (myVar) {\n"+
+				"     case Integer o -> System.out.println(o);\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   foo(Integer.valueOf(0));\n"+
+				"   foo(null);\n"+
+				" }\n"+
+				"}",
+			},
+			"0\n" +
+			"null");
+	}
+	public void testBug575241_07() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static void foo(String myVar) {\n"+
+				"    switch (myVar) {\n"+
+				"     case  null  -> System.out.println(100);\n"+
+				"     case String o -> System.out.println(o);\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   foo(\"Hello\");\n"+
+				"   foo(null);\n"+
+				" }\n"+
+				"}",
+			},
+			"Hello\n" +
+			"100");
+	}
+	public void testBug575241_08() {
+		runConformTest(
+			new String[] {
+				"X.java",
+				"public class X {\n"+
+				" static void foo(String myVar) {\n"+
+				"    switch (myVar) {\n"+
+				"     case String o -> System.out.println(o);\n"+
+				"   };\n"+
+				" }\n"+
+				" public static void main(String[] args) {\n"+
+				"   foo(\"Hello\");\n"+
+				"   foo(null);\n"+
+				" }\n"+
+				"}",
+			},
+			"Hello\n" +
+			"null");
+	}
+}
\ No newline at end of file
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 24895e5..12dd6a5 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
@@ -419,9 +419,13 @@
 				}
 			}
 			if (e.isTotalForType(expressionType)) {
-				switchStatement.switchBits |= SwitchStatement.TotalPattern;
+				switchStatement.switchBits |= (SwitchStatement.TotalPattern | SwitchStatement.Covered);
 				if (switchStatement.defaultCase != null)
 					scope.problemReporter().illegalTotalPatternWithDefault(this);
+				switchStatement.totalPattern = e;
+				e.isTotalTypeNode = true;
+				if (switchStatement.nullCase == null)
+					constant = IntConstant.fromValue(-1);
 			}
 		}
 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Pattern.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Pattern.java
index ac4b862..0218755 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Pattern.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Pattern.java
@@ -25,6 +25,8 @@
 
 public abstract class Pattern extends Expression {
 
+	/* package */ boolean isTotalTypeNode = false;
+
 	public boolean isTotalForType(TypeBinding type) {
 		return false;
 	}
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 b1bf615..ad8f599 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
@@ -71,6 +71,7 @@
 
 	public boolean containsPatterns;
 	private BranchLabel switchPatternRestartTarget;
+	/* package */ Pattern totalPattern;
 
 	// fallthrough
 	public final static int CASE = 0;
@@ -82,6 +83,7 @@
 	public final static int LabeledRules = ASTNode.Bit1;
 	public final static int NullCase = ASTNode.Bit2;
 	public final static int TotalPattern = ASTNode.Bit3;
+	public final static int Covered = ASTNode.Bit4;
 
 	// for switch on strings
 	private static final char[] SecretStringVariableName = " switchDispatchString".toCharArray(); //$NON-NLS-1$
@@ -441,6 +443,8 @@
 				Expression e = stmt.constantExpressions[k];
 				if (e instanceof FakeDefaultLiteral) continue;
 				targetLabels[count++] = (caseLabels[j] = newLabel.apply(codeStream));
+				if (this.totalPattern != null &&  e.equals(this.totalPattern))
+					this.defaultCase = stmt;
 				caseLabels[j++].tagBits |= BranchLabel.USED;
 			}
 			System.arraycopy(targetLabels, 0, stmt.targetLabels = new BranchLabel[count], 0, count);
@@ -653,7 +657,7 @@
 	}
 	private void generateCodeSwitchPatternPrologue(BlockScope currentScope, CodeStream codeStream) {
 		this.expression.generateCode(currentScope, codeStream, true);
-		if ((this.switchBits & NullCase) == 0) {
+		if ((this.switchBits & NullCase) == 0 && this.totalPattern == null) {
 			codeStream.dup();
 			codeStream.invokeJavaUtilObjectsrequireNonNull();
 			codeStream.pop();
@@ -771,7 +775,7 @@
 							upperScope.problemReporter().incorrectSwitchType(this.expression, expressionType); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=360317
 						}
 						break checkType;
-					} else if (upperScope.isBoxingCompatibleWith(expressionType, TypeBinding.INT)) {
+					} else if (!this.containsPatterns && upperScope.isBoxingCompatibleWith(expressionType, TypeBinding.INT)) {
 						this.expression.computeConversion(upperScope, TypeBinding.INT, expressionType);
 						break checkType;
 					} else if (compilerOptions.complianceLevel >= ClassFileConstants.JDK1_7 && expressionType.id == TypeIds.T_JavaLangString) {
@@ -791,7 +795,7 @@
 					}
 				}
 			}
-			if (isStringSwitch) {
+ 			if (isStringSwitch) {
 				// the secret variable should be created before iterating over the switch's statements that could
 				// create more locals. This must be done to prevent overlapping of locals
 				// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=356002
@@ -892,19 +896,15 @@
 				}
 			}
 
-			boolean checkSealed = this.containsPatterns
-					&& JavaFeature.SEALED_CLASSES.isSupported(compilerOptions)
-					&& JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions)
-					&& this.expression.resolvedType instanceof ReferenceBinding
-					&& ((ReferenceBinding) this.expression.resolvedType).isSealed();
 			// check default case for all kinds of switch:
+			checkAndFlagDefaultSealed(upperScope, compilerOptions);
 			if (this.defaultCase == null) {
 				if (ignoreMissingDefaultCase(compilerOptions, isEnumSwitch)) {
 					if (isEnumSwitch) {
 						upperScope.methodScope().hasMissingSwitchDefault = true;
 					}
 				} else {
-					if (!checkSealed)
+					if (!isCovered())
 						upperScope.problemReporter().missingDefaultCase(this, isEnumSwitch, expressionType);
 				}
 			}
@@ -923,6 +923,7 @@
 									if ((enumConstant.id + 1) == this.constants[j]) // zero should not be returned see bug 141810
 										break findConstant;
 								}
+								this.switchBits &= ~(1 << SwitchStatement.Covered);
 								// enum constant did not get referenced from switch
 								boolean suppress = (this.defaultCase != null && (this.defaultCase.bits & DocumentedCasesOmitted) != 0);
 								if (!suppress) {
@@ -932,17 +933,27 @@
 						}
 					}
 				}
-			} else if (checkSealed) {
-				checkAndFlagDefaultSealed(upperScope);
 			}
 		} finally {
 			if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block
 		}
 	}
-	private void checkAndFlagDefaultSealed(BlockScope skope) {
-		if (this.defaultCase != null) return;
+	private boolean isCovered() {
+		return (this.switchBits & SwitchStatement.Covered) != 0;
+	}
+	private void checkAndFlagDefaultSealed(BlockScope skope, CompilerOptions compilerOptions) {
+		if (this.defaultCase != null) { // mark covered as a side effect (since covers is intro in 406)
+			this.switchBits |= SwitchStatement.Covered;
+			return;
+		}
+		boolean checkSealed = this.containsPatterns
+				&& JavaFeature.SEALED_CLASSES.isSupported(compilerOptions)
+				&& JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions)
+				&& this.expression.resolvedType instanceof ReferenceBinding
+				&& ((ReferenceBinding) this.expression.resolvedType).isSealed();
+		if (!checkSealed) return;
 		ReferenceBinding ref = (ReferenceBinding) this.expression.resolvedType;
-		assert ref.isSealed();
+		if (!ref.isSealed()) return;
 		List<TypeBinding> permittedTypes = Arrays.asList(ref.permittedTypes());
 		for (TypeBinding pt : permittedTypes) {
 			if (!this.caseLabelElementTypes.contains(pt)) {
@@ -950,11 +961,13 @@
 				return;
 			}
 		}
+		this.switchBits |= SwitchStatement.Covered;
 	}
 	private void addSecretPatternSwitchVariables(BlockScope upperScope) {
 		if (this.containsPatterns) {
 			this.scope = new BlockScope(upperScope);
-			this.dispatchPatternCopy  = new LocalVariableBinding(SecretPatternVariableName, upperScope.getJavaLangObject(), ClassFileConstants.AccDefault, false);
+			TypeBinding type = this.expression.resolvedType.clone(this.expression.resolvedType.enclosingType());
+			this.dispatchPatternCopy  = new LocalVariableBinding(SecretPatternVariableName, type, ClassFileConstants.AccDefault, false);
 			this.scope.addLocalVariable(this.dispatchPatternCopy);
 			this.dispatchPatternCopy.setConstant(Constant.NotAConstant);
 			this.dispatchPatternCopy.useFlag = LocalVariableBinding.USED;
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 def9ad4..2e7d39f 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
@@ -67,7 +67,8 @@
 	public void generateCode(BlockScope currentScope, CodeStream codeStream) {
 		if (this.local != null) {
 			LocalVariableBinding localBinding = this.local.binding;
-			codeStream.checkcast(localBinding.type);
+			if (!this.isTotalTypeNode)
+				codeStream.checkcast(localBinding.type);
 			this.local.generateCode(currentScope, codeStream);
 			codeStream.store(localBinding, false);
 			localBinding.recordInitializationStartPC(codeStream.position);