diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/StandardClassIds.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/StandardClassIds.kt index 3e6a7f66f4c..6b66168a79a 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/StandardClassIds.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/symbols/StandardClassIds.kt @@ -14,10 +14,12 @@ object StandardClassIds { val BASE_KOTLIN_PACKAGE = FqName("kotlin") val BASE_REFLECT_PACKAGE = BASE_KOTLIN_PACKAGE.child(Name.identifier("reflect")) + val BASE_COLLECTIONS_PACKAGE = BASE_KOTLIN_PACKAGE.child(Name.identifier("collections")) private fun String.baseId() = ClassId(BASE_KOTLIN_PACKAGE, Name.identifier(this)) private fun ClassId.unsignedId() = ClassId(BASE_KOTLIN_PACKAGE, Name.identifier("U" + shortClassName.identifier)) private fun String.reflectId() = ClassId(BASE_REFLECT_PACKAGE, Name.identifier(this)) private fun Name.primitiveArrayId() = ClassId(Array.packageFqName, Name.identifier(identifier + Array.shortClassName.identifier)) + private fun String.collectionsId() = ClassId(BASE_COLLECTIONS_PACKAGE, Name.identifier(this)) val Nothing = "Nothing".baseId() val Unit = "Unit".baseId() @@ -85,6 +87,25 @@ object StandardClassIds { return "Function$n".baseId() } + val Iterator = "Iterator".collectionsId() + val Iterable = "Iterable".collectionsId() + val Collection = "Collection".collectionsId() + val List = "List".collectionsId() + val ListIterator = "ListIterator".collectionsId() + val Set = "Set".collectionsId() + val Map = "Map".collectionsId() + val MutableIterator = "MutableIterator".collectionsId() + + val MutableIterable = "MutableIterable".collectionsId() + val MutableCollection = "MutableCollection".collectionsId() + val MutableList = "MutableList".collectionsId() + val MutableListIterator = "MutableListIterator".collectionsId() + val MutableSet = "MutableSet".collectionsId() + val MutableMap = "MutableMap".collectionsId() + + val MapEntry = Map.createNestedClassId(Name.identifier("Entry")) + val MutableMapEntry = MutableMap.createNestedClassId(Name.identifier("MutableEntry")) + val Suppress = "Suppress".baseId() } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 5b3b04c16a3..ed7d56fdac6 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -143,7 +143,7 @@ abstract class LogicSystem(protected val context: ConeInferenceCont if (types.any { it.isEmpty() }) return mutableSetOf() val intersectedTypes = types.map { if (it.size > 1) { - context.intersectTypes(it.toList()) as ConeKotlinType + context.intersectTypes(it.toList()) } else { assert(it.size == 1) { "We've already checked each set of types is not empty." } it.single() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeFlexibleTypeBoundsChecker.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeFlexibleTypeBoundsChecker.kt new file mode 100644 index 00000000000..675bd2f8cf0 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeFlexibleTypeBoundsChecker.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.fir.types + +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.fir.symbols.StandardClassIds +import org.jetbrains.kotlin.name.ClassId + +object ConeFlexibleTypeBoundsChecker { + private val fqNames = StandardNames.FqNames + private val baseTypesToMutableEquivalent = mapOf( + StandardClassIds.Iterable to StandardClassIds.MutableIterable, + StandardClassIds.Iterator to StandardClassIds.MutableIterator, + StandardClassIds.ListIterator to StandardClassIds.MutableListIterator, + StandardClassIds.List to StandardClassIds.MutableList, + StandardClassIds.Collection to StandardClassIds.MutableCollection, + StandardClassIds.Set to StandardClassIds.MutableSet, + StandardClassIds.Map to StandardClassIds.MutableMap, + StandardClassIds.MapEntry to StandardClassIds.MutableMapEntry + ) + private val mutableToBaseMap = baseTypesToMutableEquivalent.entries.associateBy({ it.value }) { it.key } + + fun areTypesMayBeLowerAndUpperBoundsOfSameFlexibleTypeByMutability(a: ConeKotlinType, b: ConeKotlinType): Boolean { + val classId = a.classId ?: return false + val possiblePairBound = (baseTypesToMutableEquivalent[classId] ?: mutableToBaseMap[classId]) ?: return false + + return possiblePairBound == b.classId + } + + // We consider base bounds as not mutable collections + fun getBaseBoundFqNameByMutability(a: ConeKotlinType): ClassId? { + val classId = a.classId ?: return null + + if (classId in baseTypesToMutableEquivalent) return classId + + return mutableToBaseMap[classId] + } +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt index 4c2c0a9c5c3..b70a0b5a4a5 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeInferenceContext.kt @@ -340,10 +340,6 @@ interface ConeInferenceContext : TypeSystemInferenceExtensionContext, ConeTypeCo return false } - override fun captureFromExpression(type: KotlinTypeMarker): KotlinTypeMarker? { - return type - } - override fun createErrorType(debugName: String): ConeClassErrorType { return ConeClassErrorType(ConeIntermediateDiagnostic(debugName)) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt index e424d75d3ab..4fec0ecf1af 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt @@ -228,9 +228,8 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty private fun TypeConstructorMarker.toClassLikeSymbol(): FirClassLikeSymbol<*>? = (this as? ConeClassLikeLookupTag)?.toSymbol(session) - override fun TypeConstructorMarker.supertypes(): Collection { + override fun TypeConstructorMarker.supertypes(): Collection { if (this is ErrorTypeConstructor) return emptyList() - //require(this is ConeSymbol) return when (this) { is ConeTypeVariableTypeConstructor -> emptyList() is ConeTypeParameterLookupTag -> symbol.fir.bounds.map { it.coneType } @@ -307,56 +306,14 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty fir.classKind != ClassKind.ANNOTATION_CLASS } + override fun captureFromExpression(type: KotlinTypeMarker): KotlinTypeMarker? { + require(type is ConeKotlinType) + return captureFromExpressionInternal(type) + } + override fun captureFromArguments(type: SimpleTypeMarker, status: CaptureStatus): SimpleTypeMarker? { require(type is ConeKotlinType) - val argumentsCount = type.typeArguments.size - if (argumentsCount == 0) return null - - val typeConstructor = type.typeConstructor() - if (argumentsCount != typeConstructor.parametersCount()) return null - - if (type.typeArguments.all { it !is ConeStarProjection && it.kind == ProjectionKind.INVARIANT }) return null - - val newArguments = Array(argumentsCount) { index -> - val argument = type.typeArguments[index] - if (argument !is ConeStarProjection && argument.kind == ProjectionKind.INVARIANT) return@Array argument - - val lowerType = if (argument !is ConeStarProjection && argument.getVariance() == TypeVariance.IN) { - (argument as ConeKotlinTypeProjection).type - } else { - null - } - - ConeCapturedType(status, lowerType, argument, typeConstructor.getParameter(index)) - } - - val substitutor = substitutorByMap((0 until argumentsCount).map { index -> - (typeConstructor.getParameter(index) as ConeTypeParameterLookupTag).symbol to (newArguments[index] as ConeKotlinType) - }.toMap()) - - for (index in 0 until argumentsCount) { - val oldArgument = type.typeArguments[index] - val newArgument = newArguments[index] - - if (oldArgument !is ConeStarProjection && oldArgument.kind == ProjectionKind.INVARIANT) continue - - val parameter = typeConstructor.getParameter(index) - val upperBounds = (0 until parameter.upperBoundCount()).mapTo(mutableListOf()) { paramIndex -> - substitutor.safeSubstitute( - this as TypeSystemInferenceExtensionContext, parameter.getUpperBound(paramIndex) - ) - } - - if (!oldArgument.isStarProjection() && oldArgument.getVariance() == TypeVariance.OUT) { - upperBounds += oldArgument.getType() - } - - require(newArgument is ConeCapturedType) - @Suppress("UNCHECKED_CAST") - newArgument.constructor.supertypes = upperBounds as List - } - - return type.withArguments(newArguments) + return captureFromArgumentsInternal(type, status) as SimpleTypeMarker? } override fun SimpleTypeMarker.asArgumentList(): TypeArgumentListMarker { @@ -392,10 +349,6 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty typeConstructor is ConeTypeParameterLookupTag } - override fun captureFromExpression(type: KotlinTypeMarker): KotlinTypeMarker? { - TODO("not implemented") - } - override fun SimpleTypeMarker.isPrimitiveType(): Boolean { if (this is ConeClassLikeType) { return StandardClassIds.primitiveTypes.contains(this.lookupTag.classId) @@ -417,7 +370,7 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty return ConeTypeIntersector.intersectTypes(this as ConeInferenceContext, types as List) as SimpleTypeMarker } - override fun intersectTypes(types: List): KotlinTypeMarker { + override fun intersectTypes(types: List): ConeKotlinType { @Suppress("UNCHECKED_CAST") return ConeTypeIntersector.intersectTypes(this as ConeInferenceContext, types as List) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt index b43f0ee261d..3a18b53a1f4 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt @@ -11,8 +11,10 @@ import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.classId import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLookupTagWithFixedSymbol import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef @@ -22,6 +24,7 @@ import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator import org.jetbrains.kotlin.types.AbstractStrictEqualityTypeChecker import org.jetbrains.kotlin.types.AbstractTypeApproximator import org.jetbrains.kotlin.types.TypeApproximatorConfiguration +import org.jetbrains.kotlin.types.model.* fun ConeInferenceContext.commonSuperTypeOrNull(types: List): ConeKotlinType? { return when (types.size) { @@ -322,3 +325,159 @@ private fun FirTypeRef.hideLocalTypeIfNeeded( return this } +fun ConeTypeContext.captureFromArgumentsInternal(type: ConeKotlinType, status: CaptureStatus): ConeKotlinType? { + val capturedArguments = captureArguments(type, status) ?: return null + return if (type is ConeFlexibleType) { + ConeFlexibleType( + type.lowerBound.withArguments(capturedArguments), + type.upperBound.withArguments(capturedArguments), + ) + } else { + type.withArguments(capturedArguments) + } +} + +private fun ConeTypeContext.captureArguments(type: ConeKotlinType, status: CaptureStatus): Array? { + val argumentsCount = type.typeArguments.size + if (argumentsCount == 0) return null + + val typeConstructor = type.typeConstructor() + if (argumentsCount != typeConstructor.parametersCount()) return null + + if (type.typeArguments.all { it !is ConeStarProjection && it.kind == ProjectionKind.INVARIANT }) return null + + val newArguments = Array(argumentsCount) { index -> + val argument = type.typeArguments[index] + if (argument !is ConeStarProjection && argument.kind == ProjectionKind.INVARIANT) return@Array argument + + val lowerType = if (argument !is ConeStarProjection && argument.getVariance() == TypeVariance.IN) { + (argument as ConeKotlinTypeProjection).type + } else { + null + } + + ConeCapturedType(status, lowerType, argument, typeConstructor.getParameter(index)) + } + + val substitutor = substitutorByMap((0 until argumentsCount).map { index -> + (typeConstructor.getParameter(index) as ConeTypeParameterLookupTag).symbol to (newArguments[index] as ConeKotlinType) + }.toMap()) + + for (index in 0 until argumentsCount) { + val oldArgument = type.typeArguments[index] + val newArgument = newArguments[index] + + if (oldArgument !is ConeStarProjection && oldArgument.kind == ProjectionKind.INVARIANT) continue + + val parameter = typeConstructor.getParameter(index) + val upperBounds = (0 until parameter.upperBoundCount()).mapTo(mutableListOf()) { paramIndex -> + substitutor.safeSubstitute( + this as TypeSystemInferenceExtensionContext, parameter.getUpperBound(paramIndex) + ) + } + + if (!oldArgument.isStarProjection() && oldArgument.getVariance() == TypeVariance.OUT) { + upperBounds += oldArgument.getType() + } + + require(newArgument is ConeCapturedType) + @Suppress("UNCHECKED_CAST") + newArgument.constructor.supertypes = upperBounds as List + } + return newArguments +} + +fun ConeTypeContext.captureFromExpressionInternal(type: ConeKotlinType): ConeKotlinType? { + if (type !is ConeIntersectionType && type !is ConeFlexibleType) { + return captureFromArgumentsInternal(type, CaptureStatus.FROM_EXPRESSION) + } + /* + * We capture arguments in the intersection types in specific way: + * 1) Firstly, we create captured arguments for all type arguments grouped by a type constructor* and a type argument's type. + * It means, that we create only one captured argument for two types `Foo<*>` and `Foo<*>?` within a flexible type, for instance. + * * In addition to grouping by type constructors, we look at possibility locating of two types in different bounds of the same flexible type. + * This is necessary in order to create the same captured arguments, + * for example, for `MutableList` in the lower bound of the flexible type and for `List` in the upper one. + * Example: MutableList<*>..List<*>? -> MutableList..List?, Captured1(*) and Captured2(*) are the same. + * 2) Secondly, we replace type arguments with captured arguments by given a type constructor and type arguments. + */ + val capturedArgumentsByComponents = captureArgumentsForIntersectionType(type) ?: return null + + // We reuse `TypeToCapture` for some types, suitability to reuse defines by `isSuitableForType` + fun findCorrespondingCapturedArgumentsForType(type: ConeKotlinType) = + capturedArgumentsByComponents.find { typeToCapture -> typeToCapture.isSuitableForType(type, this) }?.capturedArguments + + fun replaceArgumentsWithCapturedArgumentsByIntersectionComponents(typeToReplace: ConeKotlinType): List { + return if (typeToReplace is ConeIntersectionType) { + typeToReplace.intersectedTypes.map { componentType -> + val capturedArguments = findCorrespondingCapturedArgumentsForType(componentType) + ?: return@map componentType + componentType.withArguments(capturedArguments) + } + } else { + val capturedArguments = findCorrespondingCapturedArgumentsForType(typeToReplace) + ?: return listOf(typeToReplace) + listOf(typeToReplace.withArguments(capturedArguments)) + } + } + + return if (type is ConeFlexibleType) { + val lowerIntersectedType = intersectTypes(replaceArgumentsWithCapturedArgumentsByIntersectionComponents(type.lowerBound)) + .withNullability(type.lowerBound.isMarkedNullable) as ConeKotlinType + val upperIntersectedType = intersectTypes(replaceArgumentsWithCapturedArgumentsByIntersectionComponents(type.upperBound)) + .withNullability(type.upperBound.isMarkedNullable) as ConeKotlinType + + ConeFlexibleType(lowerIntersectedType, upperIntersectedType) + } else { + intersectTypes(replaceArgumentsWithCapturedArgumentsByIntersectionComponents(type)).withNullability(type.isMarkedNullable) as ConeKotlinType + } +} + +private fun ConeTypeContext.captureArgumentsForIntersectionType(type: ConeKotlinType): List? { + // It's possible to have one of the bounds as non-intersection type + fun getTypesToCapture(type: ConeKotlinType) = + if (type is ConeIntersectionType) type.intersectedTypes else listOf(type) + + val filteredTypesToCapture = + when (type) { + is ConeFlexibleType -> { + val typesToCapture = getTypesToCapture(type.lowerBound) + getTypesToCapture(type.upperBound) + typesToCapture.distinctBy { + (ConeFlexibleTypeBoundsChecker.getBaseBoundFqNameByMutability(it) ?: it.typeConstructor(this)) to it.typeArguments + } + } + is ConeIntersectionType -> type.intersectedTypes + else -> error("Should not be here") + } + + var changed = false + + val capturedArgumentsByTypes = filteredTypesToCapture.mapNotNull { typeToCapture -> + val capturedArguments = captureArguments(typeToCapture, CaptureStatus.FROM_EXPRESSION) + ?: return@mapNotNull null + changed = true + CapturedArguments(capturedArguments, originalType = typeToCapture) + } + + if (!changed) return null + + return capturedArgumentsByTypes +} + +private class CapturedArguments(val capturedArguments: Array, private val originalType: ConeKotlinType) { + fun isSuitableForType(type: ConeKotlinType, context: ConeTypeContext): Boolean { + val areArgumentsMatched = type.typeArguments.withIndex().all { (i, typeArgumentsType) -> + originalType.typeArguments.size > i && typeArgumentsType == originalType.typeArguments[i] + } + + if (!areArgumentsMatched) return false + + val areConstructorsMatched = originalType.typeConstructor(context) == type.typeConstructor(context) + || ConeFlexibleTypeBoundsChecker.areTypesMayBeLowerAndUpperBoundsOfSameFlexibleTypeByMutability(originalType, type) + + if (!areConstructorsMatched) return false + + return true + } +} + diff --git a/compiler/testData/diagnostics/tests/resolve/overloadConflicts/withVariance.fir.kt b/compiler/testData/diagnostics/tests/resolve/overloadConflicts/withVariance.fir.kt index 9a0af8f0569..b02cbaad936 100644 --- a/compiler/testData/diagnostics/tests/resolve/overloadConflicts/withVariance.fir.kt +++ b/compiler/testData/diagnostics/tests/resolve/overloadConflicts/withVariance.fir.kt @@ -7,4 +7,4 @@ class A fun A.foo() = X1 fun A.foo() = X2 -fun A.test() = foo() // TODO fix constraint system +fun A.test() = foo() // TODO fix constraint system diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/13.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/13.fir.kt index 48459ac32c3..23c7c7c13e9 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/13.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/13.fir.kt @@ -10,29 +10,29 @@ fun case_1(x: T) { x x.equals(null) x.propT - x.propAny + x.propAny x.propNullableT x.propNullableAny x.funT() - x.funAny() + x.funAny() x.funNullableT() x.funNullableAny() x.apply { equals(null) } x.apply { propT } - x.apply { propAny } + x.apply { propAny } x.apply { propNullableT } x.apply { propNullableAny } x.apply { funT() } - x.apply { funAny() } + x.apply { funAny() } x.apply { funNullableT() } x.apply { funNullableAny(); x.equals(null) } x.also { it.equals(null) } x.also { it.propT } - x.also { it.propAny } + x.also { it.propAny } x.also { it.propNullableT } x.also { it.propNullableAny } x.also { it.funT() } - x.also { it.funAny() } + x.also { it.funAny() } x.also { it.funNullableT() } x.also { it.funNullableAny() } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/18.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/18.fir.kt index 9d52ae3f312..e7c9655bddf 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/18.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/18.fir.kt @@ -246,11 +246,11 @@ fun case_18(x: T, f: Boolean) { x x.equals(null) x.propT - x.propAny + x.propAny x.propNullableT x.propNullableAny x.funT() - x.funAny() + x.funAny() x.funNullableT() x.funNullableAny() } @@ -263,21 +263,21 @@ fun case_19(map: MutableMap, y: Nothing?) { k k.equals(null) k.propT - k.propAny + k.propAny k.propNullableT k.propNullableAny k.funT() - k.funAny() + k.funAny() k.funNullableT() k.funNullableAny() v v.equals(null) v.propT - v.propAny + v.propAny v.propNullableT v.propNullableAny v.funT() - v.funAny() + v.funAny() v.funNullableT() v.funNullableAny() }