diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java index 234007215ea..d149d0921ba 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java @@ -4019,7 +4019,7 @@ private int addBootStrapTypeSwitchEntry(int localContentsOffset, SwitchStatement for (CaseStatement.ResolvedCase c : constants) { if (c.isPattern()) { int typeOrDynIndex; - if ((switchStatement.switchBits & SwitchStatement.Primitive) != 0) { + if (c.e.resolvedType.isPrimitiveType()) { // Dynamic for Class.getPrimitiveClass(Z) or such typeOrDynIndex = this.constantPool.literalIndexForDynamic(c.primitivesBootstrapIdx, c.t.signature(), diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java index 7b33fd14412..ee9735a3c76 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java @@ -381,28 +381,24 @@ private Constant resolveCasePattern(BlockScope scope, TypeBinding caseType, Type } } } else if (type.isValidBinding()) { - PrimitiveConversionRoute route = PrimitiveConversionRoute.NO_CONVERSION_ROUTE; // if not a valid binding, an error has already been reported for unresolved type - if (type.isPrimitiveType()) { - route = Pattern.findPrimitiveConversionRoute(type, expressionType, scope); - if (route == PrimitiveConversionRoute.NO_CONVERSION_ROUTE) { + if (Pattern.findPrimitiveConversionRoute(type, expressionType, scope) == PrimitiveConversionRoute.NO_CONVERSION_ROUTE) { + if (type.isPrimitiveType() && !JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(scope.compilerOptions())) { scope.problemReporter().unexpectedTypeinSwitchPattern(type, e); return Constant.NotAConstant; + } else if (!e.checkCastTypesCompatibility(scope, type, expressionType, null, false)) { + scope.problemReporter().typeMismatchError(expressionType, type, e, null); + return Constant.NotAConstant; } } - if ((type.isBaseType() && route == PrimitiveConversionRoute.NO_CONVERSION_ROUTE) - || !e.checkCastTypesCompatibility(scope, type, expressionType, null, false)) { - scope.problemReporter().typeMismatchError(expressionType, type, e, null); - return Constant.NotAConstant; - } } - if (e.coversType(expressionType)) { + if (e.coversType(expressionType, scope)) { if ((switchStatement.switchBits & SwitchStatement.TotalPattern) != 0) { scope.problemReporter().duplicateTotalPattern(e); return IntConstant.fromValue(-1); } switchStatement.switchBits |= SwitchStatement.Exhaustive; - if (e.isUnconditional(expressionType)) { + if (e.isUnconditional(expressionType, scope)) { switchStatement.switchBits |= SwitchStatement.TotalPattern; if (switchStatement.defaultCase != null && !(e instanceof RecordPattern)) scope.problemReporter().illegalTotalPatternWithDefault(this); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java index 00fb53a7bcd..9d4e81fe9b0 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; +import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; public class EitherOrMultiPattern extends Pattern { @@ -109,11 +110,11 @@ public boolean dominates(Pattern p) { } @Override - public boolean coversType(TypeBinding type) { + public boolean coversType(TypeBinding type, Scope scope) { if (!isUnguarded()) return false; for (Pattern p : this.patterns) { - if (p.coversType(type)) + if (p.coversType(type, scope)) return true; } return false; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java index 3cddebb7227..a6424343045 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; +import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; @@ -64,8 +65,8 @@ public boolean matchFailurePossible() { } @Override - public boolean isUnconditional(TypeBinding t) { - return isUnguarded() && this.primaryPattern.isUnconditional(t); + public boolean isUnconditional(TypeBinding t, Scope scope) { + return isUnguarded() && this.primaryPattern.isUnconditional(t, scope); } @Override @@ -80,8 +81,8 @@ public void setIsEitherOrPattern() { } @Override - public boolean coversType(TypeBinding type) { - return isUnguarded() && this.primaryPattern.coversType(type); + public boolean coversType(TypeBinding type, Scope scope) { + return isUnguarded() && this.primaryPattern.coversType(type, scope); } @Override diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java index c8510caa12b..d72ccb4e901 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java @@ -26,6 +26,7 @@ import org.eclipse.jdt.internal.compiler.lookup.NullTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.lookup.VoidTypeBinding; public abstract class Pattern extends Expression { @@ -76,12 +77,29 @@ public boolean isUnnamed() { * * @return whether pattern covers the given type or not */ - public boolean coversType(TypeBinding type) { + public boolean coversType(TypeBinding type, Scope scope) { if (!isUnguarded()) return false; if (type == null || this.resolvedType == null) return false; - return (type.isSubtypeOf(this.resolvedType, false)); + if (type.isPrimitiveOrBoxedPrimitiveType()) { + PrimitiveConversionRoute route = Pattern.findPrimitiveConversionRoute(this.resolvedType, type, scope); + switch (route) { + case IDENTITY_CONVERSION: + case BOXING_CONVERSION: + case BOXING_CONVERSION_AND_WIDENING_REFERENCE_CONVERSION: + return true; + case WIDENING_PRIMITIVE_CONVERSION: + return BaseTypeBinding.isExactWidening(this.resolvedType.id, type.id); + case UNBOXING_AND_WIDENING_PRIMITIVE_CONVERSION: + return BaseTypeBinding.isExactWidening(this.resolvedType.id, TypeIds.box2primitive(type.id)); + default: + break; + } + } + if (type.isSubtypeOf(this.resolvedType, false)) + return true; + return false; } // Given a non-null instance of same type, would the pattern always match ? @@ -89,8 +107,8 @@ public boolean matchFailurePossible() { return false; } - public boolean isUnconditional(TypeBinding t) { - return isUnguarded() && coversType(t); + public boolean isUnconditional(TypeBinding t, Scope scope) { + return isUnguarded() && coversType(t, scope); } public abstract void generateCode(BlockScope currentScope, CodeStream codeStream, BranchLabel patternMatchLabel, BranchLabel matchFailLabel); @@ -179,12 +197,9 @@ public static boolean isBoxing(TypeBinding left, TypeBinding right) { } return false; } - public static PrimitiveConversionRoute findPrimitiveConversionRoute(TypeBinding destinationType, TypeBinding expressionType, BlockScope scope) { - if (!(JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported( - scope.compilerOptions().sourceLevel, - scope.compilerOptions().enablePreviewFeatures))) { + public static PrimitiveConversionRoute findPrimitiveConversionRoute(TypeBinding destinationType, TypeBinding expressionType, Scope scope) { + if (!JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(scope.compilerOptions())) return PrimitiveConversionRoute.NO_CONVERSION_ROUTE; - } if (destinationType == null || expressionType == null) return PrimitiveConversionRoute.NO_CONVERSION_ROUTE; boolean destinationIsBaseType = destinationType.isBaseType(); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java index 3bac82d4746..814fdde799f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java @@ -29,6 +29,7 @@ import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.RecordComponentBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; public class RecordPattern extends Pattern { @@ -72,7 +73,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } @Override - public boolean coversType(TypeBinding t) { + public boolean coversType(TypeBinding t, Scope scope) { if (!isUnguarded()) return false; @@ -89,7 +90,7 @@ public boolean coversType(TypeBinding t) { for (int i = 0; i < components.length; i++) { Pattern p = this.patterns[i]; RecordComponentBinding componentBinding = components[i]; - if (!p.coversType(componentBinding.type)) { + if (!p.coversType(componentBinding.type, scope)) { return false; } } @@ -149,7 +150,7 @@ public TypeBinding resolveType(BlockScope scope) { return this.resolvedType; } - this.isTotalTypeNode = super.coversType(this.resolvedType); + this.isTotalTypeNode = super.coversType(this.resolvedType, scope); RecordComponentBinding[] components = this.resolvedType.capture(scope, this.sourceStart, this.sourceEnd).components(); for (int i = 0; i < components.length; i++) { Pattern p1 = this.patterns[i]; @@ -162,7 +163,7 @@ public TypeBinding resolveType(BlockScope scope) { } TypeBinding expressionType = componentBinding.type; if (p1.isApplicable(expressionType, scope)) { - p1.isTotalTypeNode = p1.coversType(componentBinding.type); + p1.isTotalTypeNode = p1.coversType(componentBinding.type, scope); MethodBinding[] methods = this.resolvedType.getMethods(componentBinding.name); if (methods != null && methods.length > 0) { p1.accessorMethod = methods[0]; @@ -189,7 +190,7 @@ public boolean matchFailurePossible() { } @Override - public boolean isUnconditional(TypeBinding t) { + public boolean isUnconditional(TypeBinding t, Scope scope) { return false; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java index 570cdcbfc50..8f025cc1bad 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java @@ -107,7 +107,6 @@ public static record SingletonBootstrap(String id, char[] selector, char[] signa public final static int TotalPattern = ASTNode.Bit3; public final static int Exhaustive = ASTNode.Bit4; public final static int QualifiedEnum = ASTNode.Bit5; - public final static int Primitive = ASTNode.Bit6; // for switch on strings private static final char[] SecretStringVariableName = " switchDispatchString".toCharArray(); //$NON-NLS-1$ @@ -881,41 +880,27 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { statementGenerateCode(currentScope, codeStream, statement); } } - - boolean isEnumSwitchWithoutDefaultCase = this.defaultCase == null && resolvedType1.isEnum() && (this instanceof SwitchExpression || this.containsNull); - CompilerOptions compilerOptions = this.scope != null ? this.scope.compilerOptions() : null; - boolean isPatternSwitchSealedWithoutDefaultCase = this.defaultCase == null - && compilerOptions != null - && this.containsPatterns - && JavaFeature.SEALED_CLASSES.isSupported(compilerOptions) - && JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) - && this.expression.resolvedType instanceof ReferenceBinding - && ((ReferenceBinding) this.expression.resolvedType).isSealed(); - - boolean isRecordPatternSwitchWithoutDefault = this.defaultCase == null - && compilerOptions != null - && this.containsPatterns - && JavaFeature.RECORD_PATTERNS.isSupported(compilerOptions) - && JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) - && this.expression.resolvedType instanceof ReferenceBinding - && this.expression.resolvedType.isRecord(); - if (isEnumSwitchWithoutDefaultCase - || isPatternSwitchSealedWithoutDefaultCase - || isRecordPatternSwitchWithoutDefault) { + boolean needsThrowingDefault = false; + if (this.defaultCase == null) { + // enum: + needsThrowingDefault = resolvedType1.isEnum() && (this instanceof SwitchExpression || this.containsNull); + // pattern switches: + needsThrowingDefault |= isExhaustive(); + } + if (needsThrowingDefault) { // we want to force an line number entry to get an end position after the switch statement if (this.preSwitchInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); } defaultLabel.place(); - /* a default case is not needed for enum if all enum values are used in the switch expression - * we need to handle the default case to throw an error (IncompatibleClassChangeError) in order - * to make the stack map consistent. All cases will return a value on the stack except the missing default - * case. - * There is no returned value for the default case so we handle it with an exception thrown. An - * IllegalClassChangeError seems legitimate as this would mean the enum type has been recompiled with more - * enum constants and the class that is using the switch on the enum has not been recompiled + /* a default case is not needed for an exhaustive switch expression + * we need to handle the default case to throw an error in order to make the stack map consistent. + * All cases will return a value on the stack except the missing default case. + * There is no returned value for the default case so we handle it with an exception thrown. */ + CompilerOptions compilerOptions = this.scope != null ? this.scope.compilerOptions() : null; if (compilerOptions.complianceLevel >= ClassFileConstants.JDK19) { + // since 19 we have MatchException for this if (codeStream.lastAbruptCompletion != codeStream.position) { codeStream.goto_(this.breakLabel); // hop, skip and jump over match exception throw. } @@ -926,6 +911,7 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { codeStream.invokeJavaLangMatchExceptionConstructor(); codeStream.athrow(); } else { + // old style using IncompatibleClassChangeError: codeStream.newJavaLangIncompatibleClassChangeError(); codeStream.dup(); codeStream.invokeJavaLangIncompatibleClassChangeErrorDefaultConstructor(); @@ -943,9 +929,7 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { } // place the trailing labels (for break and default case) this.breakLabel.place(); - if (this.defaultCase == null && !(isEnumSwitchWithoutDefaultCase - || isPatternSwitchSealedWithoutDefaultCase - || isRecordPatternSwitchWithoutDefault)) { + if (this.defaultCase == null && !needsThrowingDefault) { // we want to force an line number entry to get an end position after the switch statement codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd, true); defaultLabel.place(); @@ -977,7 +961,7 @@ private void generateCodeSwitchPatternEpilogue(CodeStream codeStream) { private void generateCodeSwitchPatternPrologue(BlockScope currentScope, CodeStream codeStream) { this.expression.generateCode(currentScope, codeStream, true); - if ((this.switchBits & NullCase) == 0 && (this.switchBits & Primitive) == 0) { + if ((this.switchBits & NullCase) == 0 && !this.expression.resolvedType.isPrimitiveType()) { codeStream.dup(); codeStream.invokeJavaUtilObjectsrequireNonNull(); codeStream.pop(); @@ -1007,7 +991,7 @@ private void generateCodeSwitchPatternPrologue(BlockScope currentScope, CodeStre if (hasQualifiedEnums) { c.index = i; } - if ((this.switchBits & Primitive) != 0) { + if (c.t.isPrimitiveType()) { SingletonBootstrap descriptor = null; if (c.isPattern()) { descriptor = PRIMITIVE_CLASS__BOOTSTRAP; @@ -1043,7 +1027,7 @@ char[] typeSwitchSignature(TypeBinding exprType) { case TypeIds.T_JavaLangLong, TypeIds.T_JavaLangFloat, TypeIds.T_JavaLangDouble, TypeIds.T_JavaLangBoolean -> exprType.signature(); default -> - (this.switchBits & Primitive) != 0 + exprType.isPrimitiveType() ? exprType.signature() : "Ljava/lang/Object;".toCharArray(); //$NON-NLS-1$ }; @@ -1126,6 +1110,7 @@ public void resolve(BlockScope upperScope) { try { boolean isEnumSwitch = false; boolean isStringSwitch = false; + boolean isPrimitiveSwitch = false; TypeBinding expressionType = this.expression.resolveType(upperScope); CompilerOptions compilerOptions = upperScope.compilerOptions(); boolean isEnhanced = checkAndSetEnhanced(upperScope, expressionType); @@ -1137,7 +1122,7 @@ public void resolve(BlockScope upperScope) { break checkType; } else if (expressionType.isBaseType()) { if (JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(compilerOptions)) { - this.switchBits |= Primitive; + isPrimitiveSwitch = true; } if (this.expression.isConstantValueOfTypeAssignableToType(expressionType, TypeBinding.INT)) break checkType; @@ -1162,7 +1147,7 @@ public void resolve(BlockScope upperScope) { break checkType; } if (!JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) || (expressionType.isBaseType() && expressionType.id != T_null && expressionType.id != T_void)) { - if ((this.switchBits & Primitive) == 0) { // when Primitive is set it is approved above + if (!isPrimitiveSwitch) { // when isPrimitiveSwitch is set it is approved above upperScope.problemReporter().incorrectSwitchType(this.expression, expressionType); expressionType = null; // fault-tolerance: ignore type mismatch from constants from hereon } @@ -1212,7 +1197,7 @@ public void resolve(BlockScope upperScope) { if (con == Constant.NotAConstant) continue; this.otherConstants[counter] = c; - final int c1 = this.containsPatterns ? (c.intValue() == -1 ? -1 : counter) : c.intValue(); + final int c1 = this.containsPatterns ? (c.intValue() == -1 ? -1 : counter) : c.intValue(); this.constants[counter] = c1; if (counter == 0 && defaultFound) { if (c.isPattern() || isCaseStmtNullOnly(caseStmt)) @@ -1232,6 +1217,8 @@ public void resolve(BlockScope upperScope) { return id == otherId; // 'null' shares IntConstant(-1) if (con.equals(c2)) return true; + if (id == TypeIds.T_boolean) + this.switchBits |= Exhaustive; // 2 different boolean constants => exhaustive :) return this.constants[idx] == c1; } }; @@ -1253,7 +1240,7 @@ public void resolve(BlockScope upperScope) { if (type.isBaseType()) { type = this.scope.environment().computeBoxingType(type); } - if (p1.coversType(type)) + if (p1.coversType(type, this.scope)) this.scope.problemReporter().patternDominatedByAnother(c.e); } } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java index bc90d8e7aaa..3083496c320 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java @@ -160,6 +160,7 @@ public static final boolean isExactWidening(int left, int right) { case TypeIds.Short2Long: case TypeIds.Short2Float: case TypeIds.Short2Double: + case TypeIds.Char2Int: case TypeIds.Char2Long: case TypeIds.Char2Float: case TypeIds.Char2Double: diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java index f26f4e2f35e..e0c4a672010 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java @@ -297,4 +297,18 @@ public interface TypeIds { public static int getCategory(int typeId) { return typeId == TypeIds.T_double || typeId == TypeIds.T_long ? 2 : 1; } + + public static int box2primitive(int id) { + return switch (id) { + case T_JavaLangBoolean -> T_boolean; + case T_JavaLangByte -> T_byte; + case T_JavaLangCharacter -> T_char; + case T_JavaLangShort -> T_short; + case T_JavaLangInteger -> T_int; + case T_JavaLangLong -> T_long; + case T_JavaLangFloat -> T_float; + case T_JavaLangDouble -> T_double; + default -> -1; + }; + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java index 7fc14e9801f..5fc947c1e00 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java @@ -1423,6 +1423,193 @@ public static void main(String... args) { ---------- """); } + public void testBooleanSwitchExhaustive_OK() { + runConformTest(new String[] { + "X.java", + """ + public class X { + static int m1(boolean b) { + return switch (b) { + case true -> 1; + case false -> 0; + }; + } + public static void main(String... args) { + System.out.print(m1(true)); + System.out.print(m1(false)); + } + } + """ + }, + "10"); + } + public void testBooleanSwitchExhaustive_NOK() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + static int m1(boolean b) { + return switch (b) { + case true -> 1; + }; + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 3) + return switch (b) { + ^ + A switch expression should have a default case + ---------- + """); + } + + // exhaustiveness with identity conversion is already cover testNarrowingInSwitchFrom() + + public void testShortSwitchExhaustive_int_Number_Comparable() { + runConformTest(new String[] { + "X.java", + """ + public class X { + static int m1(short s) { + return switch (s) { + case 1 -> 0; + case int v -> v*2; + }; + } + static int m2(short s) { + return switch (s) { + case 1 -> 0; + case Number v -> v.intValue()*2; + }; + } + static int m3(short s) { + return switch (s) { + case 1 -> 0; + case Comparable v -> humbug(v); + }; + } + static int humbug(Comparable v) { + return 8; + } + public static void main(String... args) { + System.out.print(m1((short) 1)); + System.out.print(m1((short) 4)); + System.out.print(m2((short) 1)); + System.out.print(m2((short) 4)); + System.out.print(m3((short) 1)); + System.out.print(m3((short) 4)); + } + } + """ + }, + "080808"); + } + + public void testIntSwitchExhaustive_NOK() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + static float m1(Integer i) { + return switch(i) { + case 1 -> 1.0f; + case float f -> f; + }; + } + static float m2(int i) { + return switch(i) { + case 1 -> 1; + case float f -> f; + }; + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 3) + return switch(i) { + ^ + A switch expression should have a default case + ---------- + 2. ERROR in X.java (at line 9) + return switch(i) { + ^ + A switch expression should have a default case + ---------- + """); + } + + public void testIntSwitchExhaustive_OK() { + runConformTest(new String[] { + "X.java", + """ + public class X { + static double m1(Integer i) { + return switch(i) { + case 1 -> 1.0f; + case double d -> d; + }; + } + static double m2(int i) { + return switch(i) { + case 1 -> 1; + case double d -> d; + }; + } + public static void main(String... args) { + System.out.print(m1(1)); + System.out.print('|'); + System.out.print(m1(3)); + System.out.print('|'); + System.out.print(m2(1)); + System.out.print('|'); + System.out.print(m2(3)); + } + } + """ + }, + "1.0|3.0|1.0|3.0"); + } + + public void testLongSwitchExhaustive_NOK() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + static float m1(Long l) { + return switch(l) { + case 1L -> 1.0f; + case float f -> f; + }; + } + static double m2(long l) { + return switch(l) { + case 1L -> 1.0d; + case double d -> d; + }; + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 3) + return switch(l) { + ^ + A switch expression should have a default case + ---------- + 2. ERROR in X.java (at line 9) + return switch(l) { + ^ + A switch expression should have a default case + ---------- + """); + } + // test from spec public void _testSpec001() { runConformTest(new String[] {