[FE 1.0] Use the new type intersection emptiness check

Also introduced compatibility mode to preserve compatibility for is/as/equality checks

^KT-29316 Related
This commit is contained in:
Victor Petukhov
2022-06-09 09:59:55 +02:00
committed by teamcity
parent bc1c2adae1
commit 8227c4b603
15 changed files with 90 additions and 24 deletions
@@ -426,6 +426,8 @@ interface ConeInferenceContext : TypeSystemInferenceExtensionContext, ConeTypeCo
return withAttributes(attributes.remove(CompilerConeAttributes.Exact))
}
override fun KotlinTypeMarker.canHaveSubtypes(typeChecker: AbstractTypeChecker): Boolean? = null
override fun TypeConstructorMarker.toErrorType(): SimpleTypeMarker {
if (this is ErrorTypeConstructor) return createErrorType(reason)
if (this is ConeClassLikeLookupTag) return createErrorType("Not found classifier: $classId")
@@ -64,6 +64,7 @@ import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
import org.jetbrains.kotlin.types.error.ErrorTypeKind;
import org.jetbrains.kotlin.types.error.ErrorUtils;
import org.jetbrains.kotlin.types.expressions.*;
import org.jetbrains.kotlin.types.model.KotlinTypeMarker;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import java.util.*;
@@ -619,7 +620,8 @@ public class DescriptorResolver {
@NotNull TypeParameterDescriptor parameter,
@NotNull KtTypeParameter typeParameter
) {
if (KotlinBuiltIns.isNothing(TypeIntersector.getUpperBoundsAsType(parameter))) {
List<KotlinType> bounds = parameter.getUpperBounds();
if (TypeUtilsKt.isEmptyIntersectionTypeCompatible(bounds.toArray(new KotlinTypeMarker[0]))) {
trace.report(CONFLICTING_UPPER_BOUNDS.on(typeParameter, parameter));
}
}
@@ -16,12 +16,12 @@ import org.jetbrains.kotlin.resolve.calls.inference.BuilderInferenceSession
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeIntersector
import org.jetbrains.kotlin.types.checkEnumsForCompatibility
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker.checkSenselessComparisonWithNull
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isEmptyIntersectionTypeCompatible
import org.jetbrains.kotlin.util.OperatorNameConventions
object EqualityCallChecker : CallChecker {
@@ -90,7 +90,7 @@ object EqualityCallChecker : CallChecker {
val leftType = context.trace.getType(left) ?: return
val rightType = context.trace.getType(right) ?: return
if (TypeIntersector.isIntersectionEmpty(leftType, rightType)) {
if (isEmptyIntersectionTypeCompatible(leftType, rightType)) {
val isProperEqualityChecksEnabled =
context.languageVersionSettings.supportsFeature(LanguageFeature.ProperEqualityChecksInBuilderInferenceCalls)
val shouldReportWarnings = !isProperEqualityChecksEnabled
@@ -39,8 +39,6 @@ import org.jetbrains.kotlin.resolve.calls.model.KotlinCallArgument
import org.jetbrains.kotlin.resolve.calls.model.SimpleKotlinCallArgument
import org.jetbrains.kotlin.resolve.calls.results.SimpleConstraintSystem
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeIntersector
import org.jetbrains.kotlin.types.checker.KotlinTypeRefiner
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -104,10 +102,6 @@ class KotlinResolutionStatelessCallbacksImpl(
return org.jetbrains.kotlin.resolve.calls.inference.isApplicableCallForBuilderInference(descriptor, languageVersionSettings)
}
override fun isOldIntersectionIsEmpty(types: Collection<KotlinType>): Boolean {
return TypeIntersector.intersectTypes(types) == null
}
override fun createConstraintSystemForOverloadResolution(
constraintInjector: ConstraintInjector, builtIns: KotlinBuiltIns
): SimpleConstraintSystem {
@@ -29,8 +29,10 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure
import org.jetbrains.kotlin.types.checker.intersectTypes
import org.jetbrains.kotlin.types.expressions.DataFlowAnalyzer
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
import org.jetbrains.kotlin.types.typeUtil.isEmptyIntersectionTypeCompatible
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
object CastDiagnosticsUtil {
@@ -223,7 +225,11 @@ object CastDiagnosticsUtil {
targetType: KotlinType,
shouldCheckForExactType: Boolean
): Boolean {
val intersectedType = TypeIntersector.intersectTypes(possibleTypes.map { it.upperIfFlexible() }) ?: return false
val types = possibleTypes.map { it.upperIfFlexible() }
if (isEmptyIntersectionTypeCompatible(*types.toTypedArray())) return false
val intersectedType = intersectTypes(possibleTypes.map { it.upperIfFlexible() })
return if (shouldCheckForExactType)
isExactTypeCast(intersectedType, targetType)
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.types.checker.IntersectionTypeKt;
import org.jetbrains.kotlin.types.error.ErrorScopeKind;
import org.jetbrains.kotlin.types.error.ErrorTypeKind;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
@@ -344,7 +345,7 @@ public class CommonSupertypes {
}
if (ins != null) {
assert !ins.isEmpty() : "In projections is empty for parameter " + parameterDescriptor + ", type projections " + typeProjections;
KotlinType intersection = TypeIntersector.intersectTypes(ins);
KotlinType intersection = IntersectionTypeKt.intersectWrappedTypes(ins);
if (intersection == null) {
return TypeUtils.makeStarProjection(parameterDescriptor);
}
@@ -78,6 +78,7 @@ import org.jetbrains.kotlin.types.expressions.unqualifiedSuper.UnqualifiedSuperK
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -1147,7 +1148,12 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
boolean isBuilderInferenceContext = context.inferenceSession instanceof BuilderInferenceSession;
if (leftType != null && rightType != null && !TypeIntersector.isIntersectionEmpty(leftType, rightType) && isBuilderInferenceContext) {
if (
leftType != null &&
rightType != null &&
!TypeUtilsKt.isEmptyIntersectionTypeCompatible(leftType, rightType) &&
isBuilderInferenceContext
) {
context.trace.record(MARKED_EQUALIY_CALL_PROPER_IN_BUILDER_INFERENCE, expression);
}
@@ -46,6 +46,7 @@ import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.noTypeInfo
import org.jetbrains.kotlin.types.typeUtil.containsError
import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
import org.jetbrains.kotlin.types.typeUtil.isEmptyIntersectionTypeCompatible
import java.util.*
class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTypingInternals) : ExpressionTypingVisitor(facade) {
@@ -700,7 +701,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
reportErrorOn: KtElement
): Boolean {
// TODO : Take smart casts into account?
if (TypeIntersector.isIntersectionEmpty(type, subjectType)) {
if (isEmptyIntersectionTypeCompatible(type, subjectType)) {
context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType))
return false
}
@@ -48,8 +48,6 @@ interface KotlinResolutionStatelessCallbacks {
fun isBuilderInferenceCall(argument: KotlinCallArgument, parameter: ValueParameterDescriptor): Boolean
fun isApplicableCallForBuilderInference(descriptor: CallableDescriptor, languageVersionSettings: LanguageVersionSettings): Boolean
fun isOldIntersectionIsEmpty(types: Collection<KotlinType>): Boolean
fun createConstraintSystemForOverloadResolution(
constraintInjector: ConstraintInjector, builtIns: KotlinBuiltIns
): SimpleConstraintSystem
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.compactIfPossible
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -885,8 +884,8 @@ internal object CheckIncompatibleTypeVariableUpperBounds : ResolutionPart() {
* Check if the candidate was already discriminated by `CompatibilityOfTypeVariableAsIntersectionTypePart` resolution part
* If it's true we shouldn't mark the candidate with warning, but should mark with error, to repeat the existing proper behaviour
*/
private fun ResolutionCandidate.wasPreviouslyDiscriminated(upperTypes: List<KotlinTypeMarker>) =
callComponents.statelessCallbacks.isOldIntersectionIsEmpty(upperTypes.cast())
private fun wasPreviouslyDiscriminated(upperTypes: List<KotlinTypeMarker>) =
isEmptyIntersectionTypeCompatible(*upperTypes.toTypedArray())
override fun ResolutionCandidate.process(workIndex: Int) = with(getSystem().asConstraintSystemCompleterContext()) {
val constraintSystem = getSystem()
@@ -11,10 +11,11 @@ import org.jetbrains.kotlin.types.isDefinitelyEmpty
import org.jetbrains.kotlin.types.isPossiblyEmpty
import org.jetbrains.kotlin.types.model.*
internal object EmptyIntersectionTypeChecker {
object EmptyIntersectionTypeChecker {
fun computeEmptyIntersectionEmptiness(
context: TypeSystemInferenceExtensionContext,
types: Collection<KotlinTypeMarker>
types: Collection<KotlinTypeMarker>,
compatibilityModeEnabled: Boolean = false
): EmptyIntersectionTypeInfo? = with(context) {
if (types.isEmpty()) return null
@@ -34,6 +35,11 @@ internal object EmptyIntersectionTypeChecker {
if (!mayCauseEmptyIntersection(secondType)) continue
if (compatibilityModeEnabled) {
val compatibleKind = computeEmptyIntersectionEmptinessCompatible(firstType, secondType)
if (compatibleKind != null) return compatibleKind else continue
}
val secondSubstitutedType = secondType.eraseContainingTypeParameters()
if (!mayCauseEmptyIntersection(secondSubstitutedType) && !mayCauseEmptyIntersection(firstSubstitutedType)) continue
@@ -286,6 +292,31 @@ internal object EmptyIntersectionTypeChecker {
val type = argument.getType()
return if (type is CapturedTypeMarker) type.typeConstructorProjection() else argument
}
private fun TypeSystemInferenceExtensionContext.computeEmptyIntersectionEmptinessCompatible(
firstType: KotlinTypeMarker,
secondType: KotlinTypeMarker
): EmptyIntersectionTypeInfo? {
@Suppress("NAME_SHADOWING")
val firstType = firstType.withNullability(false)
@Suppress("NAME_SHADOWING")
val secondType = secondType.withNullability(false)
val firstErasedType by lazy { firstType.eraseContainingTypeParameters() }
val secondErasedType by lazy { secondType.eraseContainingTypeParameters() }
if (AbstractTypeChecker.areRelatedBySubtyping(this, firstErasedType, secondErasedType))
return null
val canBothHaveSubtypes = firstType.withNullability(false).canHaveSubtypes(AbstractTypeChecker) == true
&& secondType.withNullability(false).canHaveSubtypes(AbstractTypeChecker) == true
if (canBothHaveSubtypes)
return null
return EmptyIntersectionTypeInfo(EmptyIntersectionTypeKind.COMPATIBLE, firstErasedType, secondErasedType)
}
}
class EmptyIntersectionTypeInfo(val kind: EmptyIntersectionTypeKind, vararg val casingTypes: KotlinTypeMarker)
class EmptyIntersectionTypeInfo(val kind: EmptyIntersectionTypeKind, vararg val casingTypes: KotlinTypeMarker)
@@ -9,7 +9,11 @@ enum class EmptyIntersectionTypeKind(val description: String) {
MULTIPLE_CLASSES("multiple incompatible classes"),
INCOMPATIBLE_SUPERTYPES("incompatible supertypes"),
INCOMPATIBLE_TYPE_ARGUMENTS("incompatible type arguments"),
SINGLE_FINAL_CLASS("final class and interface")
SINGLE_FINAL_CLASS("final class and interface"),
// This kind corresponds to migrated logic from old TypeIntersector
// Now it's used for determining types compatibility for is/as/equality checks
COMPATIBLE("")
}
fun EmptyIntersectionTypeKind.isDefinitelyEmpty(): Boolean =
@@ -179,6 +179,8 @@ interface TypeSystemInferenceExtensionContext : TypeSystemContext, TypeSystemBui
fun KotlinTypeMarker.removeAnnotations(): KotlinTypeMarker
fun KotlinTypeMarker.removeExactAnnotation(): KotlinTypeMarker
fun KotlinTypeMarker.canHaveSubtypes(typeChecker: AbstractTypeChecker): Boolean?
fun SimpleTypeMarker.replaceArguments(newArguments: List<TypeArgumentMarker>): SimpleTypeMarker
fun SimpleTypeMarker.replaceArguments(replacement: (TypeArgumentMarker) -> TypeArgumentMarker): SimpleTypeMarker
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.inference.isCaptured
import org.jetbrains.kotlin.resolve.checkers.EmptyIntersectionTypeChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.*
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.types.model.TypeArgumentMarker
import org.jetbrains.kotlin.types.model.TypeVariableTypeConstructorMarker
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -395,3 +397,8 @@ fun isUnresolvedType(type: KotlinType): Boolean {
}
return type is ErrorType && type.kind.isUnresolved
}
fun isEmptyIntersectionTypeCompatible(vararg types: KotlinTypeMarker): Boolean =
EmptyIntersectionTypeChecker.computeEmptyIntersectionEmptiness(
SimpleClassicTypeSystemContext, types.toList(), compatibilityModeEnabled = true
)?.kind == EmptyIntersectionTypeKind.COMPATIBLE
@@ -425,11 +425,10 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
return when (this) {
is SimpleTypeMarker -> this.withNullability(nullable)
is FlexibleTypeMarker -> createFlexibleType(lowerBound().withNullability(nullable), upperBound().withNullability(nullable))
else -> error("sealed")
else -> asSimpleType()?.withNullability(nullable) ?: error("Unwrapped type should be simple")
}
}
override fun newTypeCheckerState(
errorTypesEqualToAnything: Boolean,
stubTypesEqualToAnything: Boolean
@@ -478,6 +477,20 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
return this.replaceAnnotations(Annotations.create(annotationsWithoutExact))
}
override fun KotlinTypeMarker.canHaveSubtypes(typeChecker: AbstractTypeChecker): Boolean? {
require(this is KotlinType, this::errorMessage)
val kotlinTypeChecker = object : KotlinTypeChecker {
override fun isSubtypeOf(subtype: KotlinType, supertype: KotlinType): Boolean =
typeChecker.isSubtypeOf(this@ClassicTypeSystemContext, subtype, supertype)
override fun equalTypes(a: KotlinType, b: KotlinType): Boolean =
typeChecker.equalTypes(this@ClassicTypeSystemContext, a, b)
}
return TypeUtils.canHaveSubtypes(kotlinTypeChecker, this)
}
override fun KotlinTypeMarker.hasExactAnnotation(): Boolean {
require(this is UnwrappedType, this::errorMessage)
return hasExactInternal(this)