Gradually prohibit comparison of incompatible enums
#KT-22043 Fixed
This commit is contained in:
@@ -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);
|
||||
|
||||
+1
@@ -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
|
||||
}
|
||||
+3
-3
@@ -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,
|
||||
|
||||
+1
-3
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user