Skip to content

Commit

Permalink
[23] JEP 455 - Exhaustiveness of Switch eclipse-jdt#2869
Browse files Browse the repository at this point in the history
clarify code gen for BOXING_CONVERSION_AND_WIDENING_REFERENCE_CONVERSION
implement NARROWING_AND_UNBOXING_CONVERSION
+ InstanceOfExpression.generateTypeCheck()
+ TypePattern.generateTestingConversion()
specific correct error message for incompatible case constant
+ fix bogus expectation in existing tests
  • Loading branch information
stephan-herrmann committed Aug 31, 2024
1 parent 2c89e92 commit bd29a9f
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2690,4 +2690,10 @@ public interface IProblem {
*/
int WrongCaseType = PreviewRelated + 2100;

/**
* @since 3.39
* @noreference preview feature
*/
int IncompatibleCaseType = PreviewRelated + 2101;

}
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public Constant resolveConstantExpression(BlockScope scope,
throw new AssertionError("Unexpected control flow"); //$NON-NLS-1$
} else if (expression instanceof NullLiteral) {
if (!caseType.isCompatibleWith(switchType, scope)) {
scope.problemReporter().typeMismatchError(TypeBinding.NULL, switchType, expression, null);
scope.problemReporter().caseConstantIncompatible(TypeBinding.NULL, switchType, expression);
}
switchStatement.switchBits |= SwitchStatement.NullCase;
return IntConstant.fromValue(-1);
Expand All @@ -311,7 +311,7 @@ public Constant resolveConstantExpression(BlockScope scope,
} else {
if (switchStatement.isNonTraditional) {
if (switchType.isBaseType() && !expression.isConstantValueOfTypeAssignableToType(caseType, switchType)) {
scope.problemReporter().typeMismatchError(caseType, switchType, expression, null);
scope.problemReporter().caseConstantIncompatible(caseType, switchType, expression);
return Constant.NotAConstant;
}
}
Expand Down Expand Up @@ -354,7 +354,7 @@ public Constant resolveConstantExpression(BlockScope scope,
// constantExpression.computeConversion(scope, caseType, switchExpressionType); - do not report boxing/unboxing conversion
return expression.constant;
}
scope.problemReporter().typeMismatchError(expression.resolvedType, switchType, expression, switchStatement.expression);
scope.problemReporter().caseConstantIncompatible(expression.resolvedType, switchType, expression);
return Constant.NotAConstant;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ private void generateTypeCheck(BlockScope scope, CodeStream codeStream, BranchLa
break;
// TODO: case WIDENING_REFERENCE_AND_UNBOXING_COVERSION:
// TODO: case WIDENING_REFERENCE_AND_UNBOXING_COVERSION_AND_WIDENING_PRIMITIVE_CONVERSION:
// TODO: case NARROWING_AND_UNBOXING_CONVERSION:
case NARROWING_AND_UNBOXING_CONVERSION:
TypeBinding boxType = scope.environment().computeBoxingType(this.type.resolvedType);
codeStream.instance_of(this.type, boxType);
break;
case UNBOXING_CONVERSION:
case UNBOXING_AND_WIDENING_PRIMITIVE_CONVERSION:
codeStream.ifnull(internalFalseLabel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public enum PrimitiveConversionRoute {
}

protected TypeBinding outerExpressionType; // the expression type of the enclosing instanceof, switch or outer record pattern

record TestContextRecord(TypeBinding left, TypeBinding right, PrimitiveConversionRoute route) {}

public Pattern getEnclosingPattern() {
Expand Down Expand Up @@ -247,6 +247,10 @@ public static PrimitiveConversionRoute findPrimitiveConversionRoute(TypeBinding
return PrimitiveConversionRoute.WIDENING_REFERENCE_AND_UNBOXING_COVERSION;
if (BaseTypeBinding.isWidening(destinationType.id, exprPrimId))
return PrimitiveConversionRoute.WIDENING_REFERENCE_AND_UNBOXING_COVERSION_AND_WIDENING_PRIMITIVE_CONVERSION;
} else if (destinationIsBaseType) {
TypeBinding boxedDestinationType = scope.environment().computeBoxingType(destinationType);
if (boxedDestinationType.isCompatibleWith(expressionType))
return PrimitiveConversionRoute.NARROWING_AND_UNBOXING_CONVERSION;
}
}
return PrimitiveConversionRoute.NO_CONVERSION_ROUTE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,9 @@ public void generateTestingConversion(BlockScope scope, CodeStream codeStream) {
codeStream.generateImplicitConversion(this.implicitConversion);
break;
case BOXING_CONVERSION:
case BOXING_CONVERSION_AND_WIDENING_REFERENCE_CONVERSION: // widening needs no conversion :)
codeStream.generateBoxingConversion(provided.id);
break;
case BOXING_CONVERSION_AND_WIDENING_REFERENCE_CONVERSION:
int providedId = provided.id;
codeStream.generateBoxingConversion(providedId);
TypeBinding unboxedType = scope.environment().computeBoxingType(TypeBinding.wellKnownBaseType(providedId));
this.computeConversion(scope, lhs, unboxedType);
break;
case WIDENING_REFERENCE_AND_UNBOXING_COVERSION:
codeStream.generateUnboxingConversion(expected.id);
break;
Expand All @@ -155,7 +150,11 @@ public void generateTestingConversion(BlockScope scope, CodeStream codeStream) {
this.computeConversion(scope, TypeBinding.wellKnownBaseType(rhsUnboxed), expected);
codeStream.generateImplicitConversion(this.implicitConversion);
break;
// TODO: case NARROWING_AND_UNBOXING_CONVERSION:
case NARROWING_AND_UNBOXING_CONVERSION:
TypeBinding boxType = scope.environment().computeBoxingType(expected);
codeStream.checkcast(boxType);
codeStream.generateUnboxingConversion(expected.id);
break;
case UNBOXING_CONVERSION:
codeStream.generateUnboxingConversion(expected.id);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,14 @@ public void caseExpressionWrongType(Expression expression, TypeBinding switchBin
expression.sourceStart,
expression.sourceEnd);
}
public void caseConstantIncompatible(TypeBinding resolvedType, TypeBinding switchType, Expression expression) {
this.handle(
IProblem.IncompatibleCaseType,
new String[] {String.valueOf(resolvedType.readableName()), String.valueOf(switchType.readableName())},
new String[] {String.valueOf(resolvedType.shortReadableName()), String.valueOf(switchType.shortReadableName())},
expression.sourceStart,
expression.sourceEnd);
}
public void classExtendFinalClass(SourceTypeBinding type, TypeReference superclass, TypeBinding superTypeBinding) {
String name = new String(type.sourceName());
String superTypeFullName = new String(superTypeBinding.readableName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,7 @@

# JEP 455 Primitive Types in Patterns, instanceof, and switch (Preview)
2100 = Case constants in a switch on ''{0}'' must have type ''{1}''
2101 = Case constant of type {0} is incompatible with switch selector type {1}

### ELABORATIONS
## Access restrictions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2090,7 +2090,7 @@ public void test068() {
"6. ERROR in X.java (at line 9)\n" +
" case \'a\' : // case statement\n" +
" ^^^\n" +
"Type mismatch: cannot convert from char to Integer\n" +
"Case constant of type char is incompatible with switch selector type Integer\n" +
"----------\n");
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=480989
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4956,7 +4956,7 @@ public void test165() {
"4. ERROR in X.java (at line 8)\n" +
" case s:\n" +
" ^\n" +
"Type mismatch: cannot convert from short to Integer\n" +
"Case constant of type short is incompatible with switch selector type Integer\n" +
"----------\n" +
"5. WARNING in X.java (at line 12)\n" +
" Integer i2 = 10 ;\n" +
Expand All @@ -4976,7 +4976,7 @@ public void test165() {
"8. ERROR in X.java (at line 17)\n" +
" case b:\n" +
" ^\n" +
"Type mismatch: cannot convert from byte to Integer\n" +
"Case constant of type byte is incompatible with switch selector type Integer\n" +
"----------\n" +
"9. WARNING in X.java (at line 21)\n" +
" Integer i3 = 10 ;\n" +
Expand All @@ -4996,7 +4996,7 @@ public void test165() {
"12. ERROR in X.java (at line 26)\n" +
" case c:\n" +
" ^\n" +
"Type mismatch: cannot convert from char to Integer\n" +
"Case constant of type char is incompatible with switch selector type Integer\n" +
"----------\n");
}

Expand Down Expand Up @@ -5245,7 +5245,7 @@ public void test169() {
"7. ERROR in X.java (at line 7)\n" +
" case 1:\n" +
" ^\n" +
"Type mismatch: cannot convert from int to T\n" +
"Case constant of type int is incompatible with switch selector type T\n" +
"----------\n" +
"8. WARNING in X.java (at line 12)\n" +
" t = 5;\n" +
Expand Down Expand Up @@ -5287,7 +5287,7 @@ public void test169() {
"6. ERROR in X.java (at line 7)\n" +
" case 1:\n" +
" ^\n" +
"Type mismatch: cannot convert from int to T\n" +
"Case constant of type int is incompatible with switch selector type T\n" +
"----------\n" +
"7. WARNING in X.java (at line 12)\n" +
" t = 5;\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ class ProblemAttributes {
expectedProblemAttributes.put("ImportInternalNameProvided", DEPRECATED);
expectedProblemAttributes.put("ImportNotFound", new ProblemAttributes(CategorizedProblem.CAT_IMPORT));
expectedProblemAttributes.put("ImportNotVisible", DEPRECATED);
expectedProblemAttributes.put("IncompatibleCaseType", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED));
expectedProblemAttributes.put("IncompatibleExceptionInInheritedMethodThrowsClause", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("IncompatibleExceptionInThrowsClause", new ProblemAttributes(CategorizedProblem.CAT_MEMBER));
expectedProblemAttributes.put("IncompatibleExceptionInThrowsClauseForNonInheritedInterfaceMethod", new ProblemAttributes(CategorizedProblem.CAT_NAME_SHADOWING_CONFLICT));
Expand Down Expand Up @@ -1744,6 +1745,7 @@ class ProblemAttributes {
expectedProblemAttributes.put("ImportInternalNameProvided", SKIP);
expectedProblemAttributes.put("ImportNotFound", SKIP);
expectedProblemAttributes.put("ImportNotVisible", SKIP);
expectedProblemAttributes.put("IncompatibleCaseType", SKIP);
expectedProblemAttributes.put("IncompatibleExceptionInInheritedMethodThrowsClause", SKIP);
expectedProblemAttributes.put("IncompatibleExceptionInThrowsClause", SKIP);
expectedProblemAttributes.put("IncompatibleExceptionInThrowsClauseForNonInheritedInterfaceMethod", new ProblemAttributes(JavaCore.COMPILER_PB_INCOMPATIBLE_NON_INHERITED_INTERFACE_METHOD));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,24 @@ public void test003() {
}
public void test003a() {
Map<String, String> options = getCompilerOptions(true);
runNegativeTest(
String[] testFiles =
new String[] {
"X3.java",
"@SuppressWarnings(\"preview\")\n" +
"public class X3 {\n" +
" public void foo(Number num) {\n" +
" if (num instanceof int) {\n" +
" System.out.print(\"int\");\n" +
" }\n " +
" }\n" +
" public static void main(String... args) {\n" +
" new X3().foo(3);" +
" }\n" +
"}\n",
},
};
if (this.complianceLevel < ClassFileConstants.JDK23) {
runNegativeTest(
testFiles,
"----------\n" +
"1. ERROR in X3.java (at line 4)\n" +
" if (num instanceof int) {\n" +
Expand All @@ -219,6 +226,9 @@ public void test003a() {
null,
true,
options);
} else {
runConformTest(testFiles, "int", options, new String[] {"--enable-preview"}, JavacTestOptions.DEFAULT);
}
}
public void test004() {
Map<String, String> options = getCompilerOptions(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,117 @@ public static PRIM switchPRIM(BOX in) {
"false|false|49|-1|1|-|49|-1|49|-1|49|-1|49.0|-1.0|49.0|-1.0|");
}

public void testPrimitivePatternInSwitch_narrowConst_NOK() {
StringBuilder methods = new StringBuilder();
StringBuilder calls = new StringBuilder();
String methodTmpl =
"""
public static PRIM switchPRIM(Object in) {
return switch (in) {
case MAX -> NEGVAL;
default -> MAX;
};
}
""";
String callTmpl =
"""
BOX vBOX = VAL;
System.out.print(X.switchPRIM(vBOX));
System.out.print('|');
vBOX = MAX;
System.out.print(X.switchPRIM(vBOX));
System.out.print('|');
""";
// for all primitive types:
for (int i = 0; i < PRIMITIVES.length; i++) {
methods.append(fillIn(methodTmpl, i));
calls.append(fillIn(callTmpl, i));
}
StringBuilder classX = new StringBuilder("public class X {\n");
classX.append(methods.toString());
classX.append("public static void main(String[] args) {\n");
classX.append(calls);
classX.append("}}\n");
runNegativeTest(new String[] { "X.java", classX.toString() },
"""
----------
1. ERROR in X.java (at line 4)
case true -> false;
^^^^
Case constant of type boolean is incompatible with switch selector type Object
----------
2. ERROR in X.java (at line 10)
case Byte.MAX_VALUE -> -1;
^^^^^^^^^^^^^^
Case constant of type byte is incompatible with switch selector type Object
----------
3. ERROR in X.java (at line 16)
case 'z' -> '-';
^^^
Case constant of type char is incompatible with switch selector type Object
----------
4. ERROR in X.java (at line 22)
case Short.MAX_VALUE -> -1;
^^^^^^^^^^^^^^^
Case constant of type short is incompatible with switch selector type Object
----------
5. ERROR in X.java (at line 28)
case Integer.MAX_VALUE -> -1;
^^^^^^^^^^^^^^^^^
Case constant of type int is incompatible with switch selector type Object
----------
6. ERROR in X.java (at line 34)
case Long.MAX_VALUE -> -1L;
^^^^^^^^^^^^^^
Case constant of type long is incompatible with switch selector type Object
----------
7. ERROR in X.java (at line 40)
case Float.MAX_VALUE -> -1.0f;
^^^^^^^^^^^^^^^
Case constant of type float is incompatible with switch selector type Object
----------
8. ERROR in X.java (at line 46)
case Double.MAX_VALUE -> -1.0d;
^^^^^^^^^^^^^^^^
Case constant of type double is incompatible with switch selector type Object
----------
""");
}

public void testPrimitivePatternInSwitch_narrowUnbox() {
StringBuilder methods = new StringBuilder();
StringBuilder calls = new StringBuilder();
String methodTmpl =
"""
public static PRIM switchPRIM(Object in) {
return switch (in) {
case PRIM v -> v;
default -> NEGVAL;
};
}
""";
String callTmpl =
"""
BOX vBOX = VAL;
System.out.print(X.switchPRIM(vBOX));
System.out.print('|');
System.out.print(X.switchPRIM(new Object()));
System.out.print('|');
""";
// for all primitive types:
for (int i = 0; i < PRIMITIVES.length; i++) {
methods.append(fillIn(methodTmpl, i));
calls.append(fillIn(callTmpl, i));
}
StringBuilder classX = new StringBuilder("public class X {\n");
classX.append(methods.toString());
classX.append("public static void main(String[] args) {\n");
classX.append(calls);
classX.append("}}\n");
runConformTest(new String[] { "X.java", classX.toString() },
"true|false|49|-1|1|-|49|-1|49|-1|49|-1|49.0|-1.0|49.0|-1.0|");
}

private void testPrimitivePatternInSwitch_from_unboxAndNarrow_NOK(String from, int idx, String expectedError) {
assert from.equals(BOXES[idx]) : "mismatching from vs idx";
StringBuilder methods = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public void testBug544073_004() {
"1. ERROR in X.java (at line 12)\n" +
" case \"hello\" -> throw new java.io.IOException(\"hello\");\n" +
" ^^^^^^^\n" +
"Type mismatch: cannot convert from String to int\n" +
"Case constant of type String is incompatible with switch selector type int\n" +
"----------\n");
}
public void testBug544073_005() {
Expand Down Expand Up @@ -273,7 +273,7 @@ public void testBug544073_006() {
"2. ERROR in X.java (at line 13)\n" +
" case \"hello\" -> throw new IOException(\"hello\");\n" +
" ^^^^^^^\n" +
"Type mismatch: cannot convert from String to int\n" +
"Case constant of type String is incompatible with switch selector type int\n" +
"----------\n");
}
/*
Expand Down Expand Up @@ -929,7 +929,7 @@ public void testBug544073_027() {
"1. ERROR in X.java (at line 9)\n" +
" case \"2\": \n" +
" ^^^\n" +
"Type mismatch: cannot convert from String to int\n" +
"Case constant of type String is incompatible with switch selector type int\n" +
"----------\n";
this.runNegativeTest(
testFiles,
Expand Down Expand Up @@ -7677,7 +7677,7 @@ public static void main(String[] args) {
+ "1. ERROR in X.java (at line 8)\n"
+ " case 1.0 -> System.out.println(d);\n"
+ " ^^^\n"
+ "Type mismatch: cannot convert from double to void\n"
+ "Case constant of type double is incompatible with switch selector type void\n"
+ "----------\n");
}
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2382
Expand Down Expand Up @@ -7801,7 +7801,7 @@ public static void main(String[] args) {
"2. ERROR in X.java (at line 8)\n" +
" case null -> System.out.println(d);\n" +
" ^^^^\n" +
"Type mismatch: cannot convert from null to void\n" +
"Case constant of type null is incompatible with switch selector type void\n" +
"----------\n");
}
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2387
Expand Down
Loading

0 comments on commit bd29a9f

Please sign in to comment.