diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt index db6d9a7c76e..32cdb617387 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt @@ -40,9 +40,11 @@ import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeConstructor import org.jetbrains.kotlin.types.TypeConstructorSubstitution import org.jetbrains.kotlin.types.TypeSubstitutor -import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.checker.NewKotlinTypeChecker +import org.jetbrains.kotlin.types.checker.TypeCheckerContext import org.jetbrains.kotlin.types.typeUtil.asTypeProjection import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.keysToMap @@ -293,11 +295,13 @@ object HeaderImplDeclarationChecker : DeclarationChecker { val bTypeParams = b.typeParameters if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount + val platformModule = b.module val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor) - if (!areCompatibleTypeLists(aParams.map { substitutor(it.type) }, bParams.map { it.type }) || - !areCompatibleTypes(aExtensionReceiver?.type?.let(substitutor), bExtensionReceiver?.type)) return Incompatible.ParameterTypes - if (!areCompatibleTypes(substitutor(a.returnType), b.returnType)) return Incompatible.ReturnType + if (!areCompatibleTypeLists(aParams.map { substitutor(it.type) }, bParams.map { it.type }, platformModule) || + !areCompatibleTypes(aExtensionReceiver?.type?.let(substitutor), bExtensionReceiver?.type, platformModule)) + return Incompatible.ParameterTypes + if (!areCompatibleTypes(substitutor(a.returnType), b.returnType, platformModule)) return Incompatible.ReturnType if (b.hasStableParameterNames() && !equalsBy(aParams, bParams, ValueParameterDescriptor::getName)) return Incompatible.ParameterNames if (!equalsBy(aTypeParams, bTypeParams, TypeParameterDescriptor::getName)) return Incompatible.TypeParameterNames @@ -305,7 +309,7 @@ object HeaderImplDeclarationChecker : DeclarationChecker { if (a.modality != b.modality) return Incompatible.Modality if (a.visibility != b.visibility) return Incompatible.Visibility - areCompatibleTypeParameters(aTypeParams, bTypeParams, substitutor).let { if (it != Compatible) return it } + areCompatibleTypeParameters(aTypeParams, bTypeParams, platformModule, substitutor).let { if (it != Compatible) return it } if (!equalsBy(aParams, bParams, ValueParameterDescriptor::declaresDefaultValue)) return Incompatible.ValueParameterHasDefault if (!equalsBy(aParams, bParams, { p -> listOf(p.varargElementType != null, p.isCrossinline, p.isNoinline) })) return Incompatible.ValueParameterModifiers @@ -321,19 +325,59 @@ object HeaderImplDeclarationChecker : DeclarationChecker { return Compatible } - private fun areCompatibleTypes(a: KotlinType?, b: KotlinType?): Boolean { - return if (a != null) b != null && TypeUtils.equalTypes(a, b) else b == null + private fun areCompatibleTypes(a: KotlinType?, b: KotlinType?, platformModule: ModuleDescriptor): Boolean { + if (a == null) return b == null + if (b == null) return false + + with(NewKotlinTypeChecker) { + val context = object : TypeCheckerContext(false) { + override fun areEqualTypeConstructors(a: TypeConstructor, b: TypeConstructor): Boolean { + return isHeaderClassAndImplTypeAlias(a, b, platformModule) || + isHeaderClassAndImplTypeAlias(b, a, platformModule) || + super.areEqualTypeConstructors(a, b) + } + } + return context.equalTypes(a.unwrap(), b.unwrap()) + } } - private fun areCompatibleTypeLists(a: List, b: List): Boolean { + // For example, headerTypeConstructor may be the header class kotlin.text.StringBuilder, while implTypeConstructor + // is java.lang.StringBuilder. For the purposes of type compatibility checking, we must consider these types equal here. + // Note that the case of an "impl class" works as expected though, because the impl class by definition has the same FQ name + // as the corresponding header class, so their type constructors are equal as per AbstractClassTypeConstructor#equals + private fun isHeaderClassAndImplTypeAlias( + headerTypeConstructor: TypeConstructor, + implTypeConstructor: TypeConstructor, + platformModule: ModuleDescriptor + ): Boolean { + val header = headerTypeConstructor.declarationDescriptor + val impl = implTypeConstructor.declarationDescriptor + return header is ClassifierDescriptorWithTypeParameters && + header.isHeader && + impl is ClassifierDescriptorWithTypeParameters && + header.findClassifiersFromModule(platformModule).any { classifier -> + // Note that it's fine to only check that this "impl typealias" expands to the expected class, without checking + // whether the type arguments in the expansion are in the correct order or have the correct variance, because we only + // allow simple cases like "impl typealias Foo = FooImpl", see DeclarationsChecker#checkImplTypeAlias + (classifier as? TypeAliasDescriptor)?.classDescriptor == impl + } + } + + private fun areCompatibleTypeLists(a: List, b: List, platformModule: ModuleDescriptor): Boolean { for (i in a.indices) { - if (!areCompatibleTypes(a[i], b[i])) return false + if (!areCompatibleTypes(a[i], b[i], platformModule)) return false } return true } - private fun areCompatibleTypeParameters(a: List, b: List, substitutor: Substitutor): Compatibility { - if (!areCompatibleTypeLists(a.map { substitutor(it.defaultType) }, b.map { it.defaultType })) return Incompatible.TypeParameterUpperBounds + private fun areCompatibleTypeParameters( + a: List, + b: List, + platformModule: ModuleDescriptor, + substitutor: Substitutor + ): Compatibility { + if (!areCompatibleTypeLists(a.map { substitutor(it.defaultType) }, b.map { it.defaultType }, platformModule)) + return Incompatible.TypeParameterUpperBounds if (!equalsBy(a, b, TypeParameterDescriptor::getVariance)) return Incompatible.TypeParameterVariance if (!equalsBy(a, b, TypeParameterDescriptor::isReified)) return Incompatible.TypeParameterReified @@ -370,8 +414,8 @@ object HeaderImplDeclarationChecker : DeclarationChecker { is TypeAliasDescriptor -> { val classDescriptor = other.classDescriptor ?: return Compatible // do not report extra error on erroneous typealias implTypealias = true - // If a platform class test.C is implemented by a typealias test.C = test.CImpl, we must now state that any occurrence - // of the type "test.C" in the platform class scope should be replaced with "test.CImpl". + // If a header class test.C is implemented by a typealias test.C = test.CImpl, we must now state that any occurrence + // of the type "test.C" in the header class scope should be replaced with "test.CImpl". // Otherwise the types would not be equal and e.g. test.C's constructor is not going to be found in test.CImpl's scope. // For this, we construct an additional substitutor with a single mapping test.C -> test.CImpl // TODO: this looks like a dirty hack @@ -395,15 +439,16 @@ object HeaderImplDeclarationChecker : DeclarationChecker { if (a.visibility != b.visibility) return Incompatible.Visibility + val platformModule = b.module val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor) - areCompatibleTypeParameters(aTypeParams, bTypeParams, substitutor).let { if (it != Compatible) return it } + areCompatibleTypeParameters(aTypeParams, bTypeParams, platformModule, substitutor).let { if (it != Compatible) return it } // Subtract kotlin.Any from supertypes because it's implicitly added if no explicit supertype is specified, // and not added if an explicit supertype _is_ specified val aSupertypes = a.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny) val bSupertypes = b.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny) if (aSupertypes.map(substitutor).any { aSupertype -> - bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype) } + bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype, platformModule) } }) return Incompatible.Supertypes areCompatibleClassScopes(a, b, checkImpl && !implTypealias, substitutor).let { if (it != Compatible) return it } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/checker/NewKotlinTypeChecker.kt b/core/descriptors/src/org/jetbrains/kotlin/types/checker/NewKotlinTypeChecker.kt index 74cd928d7dc..5366a48202e 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/checker/NewKotlinTypeChecker.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/checker/NewKotlinTypeChecker.kt @@ -78,9 +78,10 @@ object ErrorTypesAreEqualToAnything : KotlinTypeChecker { object NewKotlinTypeChecker : KotlinTypeChecker { override fun isSubtypeOf(subtype: KotlinType, supertype: KotlinType): Boolean = - TypeCheckerContext(true).run { isSubtypeOf(subtype.unwrap(), supertype.unwrap()) } // todo fix flag errorTypeEqualsToAnything + TypeCheckerContext(true).isSubtypeOf(subtype.unwrap(), supertype.unwrap()) // todo fix flag errorTypeEqualsToAnything + override fun equalTypes(a: KotlinType, b: KotlinType): Boolean = - TypeCheckerContext(false).run { equalTypes(a.unwrap(), b.unwrap()) } + TypeCheckerContext(false).equalTypes(a.unwrap(), b.unwrap()) fun TypeCheckerContext.equalTypes(a: UnwrappedType, b: UnwrappedType): Boolean { if (a === b) return true @@ -247,7 +248,7 @@ object NewKotlinTypeChecker : KotlinTypeChecker { anySupertype(baseType, { false }) { val current = captureFromArguments(it, CaptureStatus.FOR_SUBTYPING) - if (current.constructor == constructor) { + if (areEqualTypeConstructors(current.constructor, constructor)) { if (result == null) { result = SmartList() } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckerContext.kt b/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckerContext.kt index 607c1651dcf..c33e482a3b1 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckerContext.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckerContext.kt @@ -29,7 +29,11 @@ open class TypeCheckerContext(val errorTypeEqualsToAnything: Boolean) { open fun addSubtypeConstraint(subType: UnwrappedType, superType: UnwrappedType): Boolean? = null - inline fun runWithArgumentsSettings(subArgument: UnwrappedType, f: TypeCheckerContext.() -> T): T { + open fun areEqualTypeConstructors(a: TypeConstructor, b: TypeConstructor): Boolean { + return a == b + } + + internal inline fun runWithArgumentsSettings(subArgument: UnwrappedType, f: TypeCheckerContext.() -> T): T { if (argumentsDepth > 100) { error("Arguments depth is too high. Some related argument: $subArgument") } diff --git a/idea/testData/multiModuleHighlighting/multiplatform/headerClassImplTypealias/common/common.kt b/idea/testData/multiModuleHighlighting/multiplatform/headerClassImplTypealias/common/common.kt new file mode 100644 index 00000000000..eae393da3ea --- /dev/null +++ b/idea/testData/multiModuleHighlighting/multiplatform/headerClassImplTypealias/common/common.kt @@ -0,0 +1,7 @@ +package test + +header class Simple +header fun createSimple(): Simple + +header class Generic +header fun createGeneric(a: A, b: B): Generic diff --git a/idea/testData/multiModuleHighlighting/multiplatform/headerClassImplTypealias/jvm/jvm.kt b/idea/testData/multiModuleHighlighting/multiplatform/headerClassImplTypealias/jvm/jvm.kt new file mode 100644 index 00000000000..2572c97fbfe --- /dev/null +++ b/idea/testData/multiModuleHighlighting/multiplatform/headerClassImplTypealias/jvm/jvm.kt @@ -0,0 +1,9 @@ +package test + +class SimpleImpl +impl typealias Simple = SimpleImpl +impl fun createSimple(): Simple = SimpleImpl() + +class GenericImpl +impl typealias Generic = GenericImpl +impl fun createGeneric(a: A, b: B): Generic = GenericImpl() diff --git a/idea/testData/multiModuleHighlighting/multiplatform/headerFunUsesStdlibInSignature/common/common.kt b/idea/testData/multiModuleHighlighting/multiplatform/headerFunUsesStdlibInSignature/common/common.kt new file mode 100644 index 00000000000..678068289aa --- /dev/null +++ b/idea/testData/multiModuleHighlighting/multiplatform/headerFunUsesStdlibInSignature/common/common.kt @@ -0,0 +1,11 @@ +package test + +header fun stringBuilder(sb: StringBuilder) + +header fun kotlinVersion(kv: KotlinVersion) + +header fun regex(r: Regex) + +header fun pair(p: Pair>) + +header fun genericPair(p: Pair) diff --git a/idea/testData/multiModuleHighlighting/multiplatform/headerFunUsesStdlibInSignature/jvm/jvm.kt b/idea/testData/multiModuleHighlighting/multiplatform/headerFunUsesStdlibInSignature/jvm/jvm.kt new file mode 100644 index 00000000000..40f295d60c1 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/multiplatform/headerFunUsesStdlibInSignature/jvm/jvm.kt @@ -0,0 +1,11 @@ +package test + +impl fun stringBuilder(sb: StringBuilder) {} + +impl fun kotlinVersion(kv: KotlinVersion) {} + +impl fun regex(r: Regex) {} + +impl fun pair(p: Pair>) {} + +impl fun genericPair(p: Pair) {} diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt index 9f614753ec4..282fab71b7d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt @@ -102,5 +102,17 @@ class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { } }) } + + fun testHeaderClassImplTypealias() { + doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) + } + + fun testHeaderFunUsesStdlibInSignature() { + doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], withStdlibCommon = true, configureModule = { module, platform -> + if (platform is TargetPlatformKind.Jvm) { + module.addLibrary(ForTestCompileRuntime.runtimeJarForTests()) + } + }) + } } }