diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index b8de945f29c..0280cff57ba 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -1202,6 +1202,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) { builder.bindLabel(doneLabel) mergeValues(branches, expression) + WhenChecker.checkDuplicatedLabels(expression, trace) } override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt index 33e7948d9fd..fb6b5c878ef 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt @@ -37,6 +37,8 @@ import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant +import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator import java.util.* interface WhenMissingCase { @@ -333,6 +335,46 @@ object WhenChecker { fun containsNullCase(expression: KtWhenExpression, context: BindingContext) = WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, true).isEmpty() + fun checkDuplicatedLabels(expression: KtWhenExpression, trace: BindingTrace) { + if (expression.subjectExpression == null) return + + val checkedTypes = HashSet>() + val checkedConstants = HashSet>() + for (entry in expression.entries) { + if (entry.isElse) continue + + conditions@ for (condition in entry.conditions) { + when (condition) { + is KtWhenConditionWithExpression -> { + val constantExpression = condition.expression ?: continue@conditions + val constant = ConstantExpressionEvaluator.getConstant( + constantExpression, trace.bindingContext) ?: continue@conditions + if (checkedConstants.contains(constant)) { + trace.report(Errors.DUPLICATE_LABEL_IN_WHEN.on(constantExpression)) + } + else { + checkedConstants.add(constant) + } + + } + is KtWhenConditionIsPattern -> { + val typeReference = condition.typeReference ?: continue@conditions + val type = trace.get(BindingContext.TYPE, typeReference) ?: continue@conditions + val typeWithIsNegation = type to condition.isNegated + if (checkedTypes.contains(typeWithIsNegation)) { + trace.report(Errors.DUPLICATE_LABEL_IN_WHEN.on(typeReference)) + } + else { + checkedTypes.add(typeWithIsNegation) + } + } + else -> {} + } + } + } + + } + fun checkDeprecatedWhenSyntax(trace: BindingTrace, expression: KtWhenExpression) { if (expression.subjectExpression != null) return diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 67a204f9c33..5ce5701c3b4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -728,6 +728,7 @@ public interface Errors { DiagnosticFactory1> NO_ELSE_IN_WHEN = DiagnosticFactory1.create(ERROR, WHEN_EXPRESSION); DiagnosticFactory1> NON_EXHAUSTIVE_WHEN = DiagnosticFactory1.create(WARNING, WHEN_EXPRESSION); DiagnosticFactory0 COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 DUPLICATE_LABEL_IN_WHEN = DiagnosticFactory0.create(WARNING); // Type mismatch diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index f769c629113..f066433affe 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -430,6 +430,7 @@ public class DefaultErrorMessages { MAP.put(ELSE_MISPLACED_IN_WHEN, "'else' entry must be the last one in a when-expression"); MAP.put(COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, "Deprecated syntax. Use '||' instead of commas in when-condition for 'when' without argument"); + MAP.put(DUPLICATE_LABEL_IN_WHEN, "Duplicate label in when"); MAP.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", RENDER_WHEN_MISSING_CASES); MAP.put(NON_EXHAUSTIVE_WHEN, "''when'' expression on enum is recommended to be exhaustive, add {0}", RENDER_WHEN_MISSING_CASES); diff --git a/compiler/testData/diagnostics/tests/generics/RawTypeInIsPattern.kt b/compiler/testData/diagnostics/tests/generics/RawTypeInIsPattern.kt index 6decc0f4180..e62b26b7934 100644 --- a/compiler/testData/diagnostics/tests/generics/RawTypeInIsPattern.kt +++ b/compiler/testData/diagnostics/tests/generics/RawTypeInIsPattern.kt @@ -3,7 +3,7 @@ public fun foo(a: Any, b: -> {} is Map -> {} is Map -> {} - is Map<*, *> -> {} + is Map<*, *> -> {} is Map<> -> {} is List<Map> -> {} is List -> {} diff --git a/compiler/testData/diagnostics/tests/when/DuplicatedLabels.kt b/compiler/testData/diagnostics/tests/when/DuplicatedLabels.kt new file mode 100644 index 00000000000..4661ede0dea --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/DuplicatedLabels.kt @@ -0,0 +1,51 @@ +package test + +const val four = 4 + +fun first(arg: Int) = when (arg) { + 1 -> 2 + 2 -> 3 + 1 -> 4 + 4 -> 5 + 1 -> 6 + 2 -> 7 + // Error should be here: see KT-11971 + four -> 8 + else -> 0 +} + +fun second(arg: String): Int { + when (arg) { + "ABC" -> return 0 + "DEF" -> return 1 + "ABC" -> return -1 + "DEF" -> return -2 + } + return 42 +} + +fun third(arg: Any?): Int { + when (arg) { + null -> return -1 + is String -> return 0 + is Double -> return 1 + is Double -> return 2 + null -> return 3 + else -> return 5 + } +} + +enum class Color { RED, GREEN, BLUE } + +fun fourth(arg: Color) = when (arg) { + Color.RED -> "RED" + Color.GREEN -> "GREEN" + Color.RED -> "BLUE" + Color.BLUE -> "BLUE" +} + +fun fifth(arg: Any?) = when (arg) { + is Any -> "Any" + else -> "" + else -> null +} diff --git a/compiler/testData/diagnostics/tests/when/DuplicatedLabels.txt b/compiler/testData/diagnostics/tests/when/DuplicatedLabels.txt new file mode 100644 index 00000000000..2a678a62287 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/DuplicatedLabels.txt @@ -0,0 +1,31 @@ +package + +package test { + public const val four: kotlin.Int = 4 + public fun fifth(/*0*/ arg: kotlin.Any?): kotlin.String? + public fun first(/*0*/ arg: kotlin.Int): kotlin.Int + public fun fourth(/*0*/ arg: test.Color): kotlin.String + public fun second(/*0*/ arg: kotlin.String): kotlin.Int + public fun third(/*0*/ arg: kotlin.Any?): kotlin.Int + + public final enum class Color : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.Color): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): test.Color + public final /*synthesized*/ fun values(): kotlin.Array + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/dynamicCastTarget.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/dynamicCastTarget.kt index 07aaa59629a..13530d6ab61 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/dynamicCastTarget.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/dynamicCastTarget.kt @@ -15,9 +15,9 @@ fun test(d: Any, dl: Collection) { when (d) { is dynamic -> {} - is dynamic? -> {} + is dynamic? -> {} !is dynamic -> {} - !is dynamic? -> {} + !is dynamic? -> {} } dl as List diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 4438f8570f9..15a93dc638e 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -19260,6 +19260,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("DuplicatedLabels.kt") + public void testDuplicatedLabels() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/DuplicatedLabels.kt"); + doTest(fileName); + } + @TestMetadata("ElseOnNullableEnum.kt") public void testElseOnNullableEnum() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ElseOnNullableEnum.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/CompileTimeConstant.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/CompileTimeConstant.kt index ac2f8655718..3f0d0c55f95 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/CompileTimeConstant.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/CompileTimeConstant.kt @@ -56,6 +56,21 @@ class TypedCompileTimeConstant( val type: KotlinType = constantValue.type override fun toConstantValue(expectedType: KotlinType): ConstantValue = constantValue + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TypedCompileTimeConstant<*>) return false + if (isError) return other.isError + if (other.isError) return false + return constantValue.value == other.constantValue.value && type == other.type + } + + override fun hashCode(): Int { + if (isError) return 13 + var result = constantValue.value?.hashCode() ?: 0 + result = 31 * result + type.hashCode() + return result + } } class IntegerValueTypeConstant( @@ -92,4 +107,8 @@ class IntegerValueTypeConstant( fun getType(expectedType: KotlinType): KotlinType = TypeUtils.getPrimitiveNumberType(typeConstructor, expectedType) override fun toString() = typeConstructor.toString() + + override fun equals(other: Any?) = other is IntegerValueTypeConstant && value == other.value + + override fun hashCode() = value.hashCode() }