Gradually prohibit comparison of incompatible enums

#KT-22043 Fixed
This commit is contained in:
Mikhail Zarechenskiy
2019-02-28 15:30:34 +03:00
parent 82c8289666
commit c4b69b65bc
14 changed files with 479 additions and 12 deletions
@@ -800,6 +800,8 @@ public interface Errors {
DiagnosticFactory2<KtElement, KotlinType, KotlinType> INCOMPATIBLE_ENUM_COMPARISON =
DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtElement, KotlinType, KotlinType> INCOMPATIBLE_ENUM_COMPARISON_ERROR =
DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<KtExpression, KotlinType> HAS_NEXT_MISSING = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<KtExpression, KotlinType> HAS_NEXT_FUNCTION_AMBIGUITY = DiagnosticFactory1.create(ERROR);
@@ -741,6 +741,7 @@ public class DefaultErrorMessages {
}, RENDER_TYPE, RENDER_TYPE);
MAP.put(INCOMPATIBLE_ENUM_COMPARISON, "Comparison of incompatible enums ''{0}'' and ''{1}'' is always unsuccessful", RENDER_TYPE, RENDER_TYPE);
MAP.put(INCOMPATIBLE_ENUM_COMPARISON_ERROR, "Comparison of incompatible enums ''{0}'' and ''{1}'' is always unsuccessful", RENDER_TYPE, RENDER_TYPE);
MAP.put(SENSELESS_COMPARISON, "Condition ''{0}'' is always ''{1}''", ELEMENT_TEXT, TO_STRING);
MAP.put(SENSELESS_NULL_IN_WHEN, "Expression under 'when' is never equal to null");
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.resolve.calls.inference.CallHandle;
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystem;
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl;
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import java.util.*;
@@ -41,11 +40,6 @@ public class TypeIntersector {
return intersectTypes(new LinkedHashSet<>(Arrays.asList(typeA, typeB))) == null;
}
public static boolean isIncompatibleEnums(@NotNull KotlinType typeA, @NotNull KotlinType typeB) {
if (!TypeUtilsKt.isEnum(typeA) || !TypeUtilsKt.isEnum(typeB)) return false;
return !typeA.getConstructor().equals(typeB.getConstructor());
}
@Nullable
public static KotlinType intersectTypes(@NotNull Collection<KotlinType> types) {
assert !types.isEmpty() : "Attempting to intersect empty collection of types, this case should be dealt with on the call site.";
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.types
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
import org.jetbrains.kotlin.types.typeUtil.*
fun checkEnumsForCompatibility(context: ExpressionTypingContext, reportOn: KtElement, typeA: KotlinType, typeB: KotlinType) {
if (isIncompatibleEnums(typeA, typeB)) {
val diagnostic = if (context.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitComparisonOfIncompatibleEnums)) {
Errors.INCOMPATIBLE_ENUM_COMPARISON_ERROR
} else {
Errors.INCOMPATIBLE_ENUM_COMPARISON
}
context.trace.report(diagnostic.on(reportOn, typeA, typeB))
}
}
private fun isIncompatibleEnums(typeA: KotlinType, typeB: KotlinType): Boolean {
if (!typeA.isEnum() && !typeB.isEnum()) return false
if (TypeUtils.isNullableType(typeA) && TypeUtils.isNullableType(typeB)) return false
// TODO: remove this line once KT-30266 will be fixed
// For now, this check is needed as isSubClass contains bug wrt Nothing
if (typeA.isNothingOrNullableNothing() || typeB.isNothingOrNullableNothing()) return false
val representativeTypeA = typeA.representativeTypeForTypeParameter()
val representativeTypeB = typeB.representativeTypeForTypeParameter()
val classA = representativeTypeA.constructor.declarationDescriptor as? ClassDescriptor ?: return false
val classB = representativeTypeB.constructor.declarationDescriptor as? ClassDescriptor ?: return false
return !DescriptorUtils.isSubclass(classA, classB) && !DescriptorUtils.isSubclass(classB, classA)
}
private fun KotlinType.representativeTypeForTypeParameter(): KotlinType {
val descriptor = constructor.declarationDescriptor
return if (descriptor is TypeParameterDescriptor) descriptor.representativeUpperBound else this
}
@@ -1430,10 +1430,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
if (rightType != null) {
if (TypeIntersector.isIntersectionEmpty(leftType, rightType)) {
context.trace.report(EQUALITY_NOT_APPLICABLE.on(expression, expression.getOperationReference(), leftType, rightType));
} else {
EnumCompatibilityCheckerKt.checkEnumsForCompatibility(context, expression, leftType, rightType);
}
else if (TypeIntersector.isIncompatibleEnums(leftType, rightType)) {
context.trace.report(INCOMPATIBLE_ENUM_COMPARISON.on(expression, leftType, rightType));
}
SenselessComparisonChecker.checkSenselessComparisonWithNull(
expression, left, right, context,
@@ -684,9 +684,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
return
}
if (TypeIntersector.isIncompatibleEnums(type, subjectType)) {
context.trace.report(INCOMPATIBLE_ENUM_COMPARISON.on(reportErrorOn, subjectType, type))
}
checkEnumsForCompatibility(context, reportErrorOn, type, subjectType)
// check if the pattern is essentially a 'null' expression
if (KotlinBuiltIns.isNullableNothing(type) && !TypeUtils.isNullableType(subjectType)) {