Use header->impl mapping in HeaderImplDeclarationChecker.areCompatibleTypes
Construct a special TypeCheckingContext that is aware of the magic behind the "impl typealias" that implements a "header class" #KT-16986 Fixed
This commit is contained in:
+60
-15
@@ -40,9 +40,11 @@ import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.TypeConstructor
|
||||||
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
|
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
|
||||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
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.types.typeUtil.asTypeProjection
|
||||||
import org.jetbrains.kotlin.utils.SmartList
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
import org.jetbrains.kotlin.utils.keysToMap
|
import org.jetbrains.kotlin.utils.keysToMap
|
||||||
@@ -293,11 +295,13 @@ object HeaderImplDeclarationChecker : DeclarationChecker {
|
|||||||
val bTypeParams = b.typeParameters
|
val bTypeParams = b.typeParameters
|
||||||
if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount
|
if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount
|
||||||
|
|
||||||
|
val platformModule = b.module
|
||||||
val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor)
|
val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor)
|
||||||
|
|
||||||
if (!areCompatibleTypeLists(aParams.map { substitutor(it.type) }, bParams.map { it.type }) ||
|
if (!areCompatibleTypeLists(aParams.map { substitutor(it.type) }, bParams.map { it.type }, platformModule) ||
|
||||||
!areCompatibleTypes(aExtensionReceiver?.type?.let(substitutor), bExtensionReceiver?.type)) return Incompatible.ParameterTypes
|
!areCompatibleTypes(aExtensionReceiver?.type?.let(substitutor), bExtensionReceiver?.type, platformModule))
|
||||||
if (!areCompatibleTypes(substitutor(a.returnType), b.returnType)) return Incompatible.ReturnType
|
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 (b.hasStableParameterNames() && !equalsBy(aParams, bParams, ValueParameterDescriptor::getName)) return Incompatible.ParameterNames
|
||||||
if (!equalsBy(aTypeParams, bTypeParams, TypeParameterDescriptor::getName)) return Incompatible.TypeParameterNames
|
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.modality != b.modality) return Incompatible.Modality
|
||||||
if (a.visibility != b.visibility) return Incompatible.Visibility
|
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, ValueParameterDescriptor::declaresDefaultValue)) return Incompatible.ValueParameterHasDefault
|
||||||
if (!equalsBy(aParams, bParams, { p -> listOf(p.varargElementType != null, p.isCrossinline, p.isNoinline) })) return Incompatible.ValueParameterModifiers
|
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
|
return Compatible
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun areCompatibleTypes(a: KotlinType?, b: KotlinType?): Boolean {
|
private fun areCompatibleTypes(a: KotlinType?, b: KotlinType?, platformModule: ModuleDescriptor): Boolean {
|
||||||
return if (a != null) b != null && TypeUtils.equalTypes(a, b) else b == null
|
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<KotlinType?>, b: List<KotlinType?>): 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<A, B> = FooImpl<A, B>", see DeclarationsChecker#checkImplTypeAlias
|
||||||
|
(classifier as? TypeAliasDescriptor)?.classDescriptor == impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun areCompatibleTypeLists(a: List<KotlinType?>, b: List<KotlinType?>, platformModule: ModuleDescriptor): Boolean {
|
||||||
for (i in a.indices) {
|
for (i in a.indices) {
|
||||||
if (!areCompatibleTypes(a[i], b[i])) return false
|
if (!areCompatibleTypes(a[i], b[i], platformModule)) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun areCompatibleTypeParameters(a: List<TypeParameterDescriptor>, b: List<TypeParameterDescriptor>, substitutor: Substitutor): Compatibility {
|
private fun areCompatibleTypeParameters(
|
||||||
if (!areCompatibleTypeLists(a.map { substitutor(it.defaultType) }, b.map { it.defaultType })) return Incompatible.TypeParameterUpperBounds
|
a: List<TypeParameterDescriptor>,
|
||||||
|
b: List<TypeParameterDescriptor>,
|
||||||
|
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::getVariance)) return Incompatible.TypeParameterVariance
|
||||||
if (!equalsBy(a, b, TypeParameterDescriptor::isReified)) return Incompatible.TypeParameterReified
|
if (!equalsBy(a, b, TypeParameterDescriptor::isReified)) return Incompatible.TypeParameterReified
|
||||||
|
|
||||||
@@ -370,8 +414,8 @@ object HeaderImplDeclarationChecker : DeclarationChecker {
|
|||||||
is TypeAliasDescriptor -> {
|
is TypeAliasDescriptor -> {
|
||||||
val classDescriptor = other.classDescriptor ?: return Compatible // do not report extra error on erroneous typealias
|
val classDescriptor = other.classDescriptor ?: return Compatible // do not report extra error on erroneous typealias
|
||||||
implTypealias = true
|
implTypealias = true
|
||||||
// If a platform class test.C is implemented by a typealias test.C = test.CImpl, we must now state that any occurrence
|
// 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 platform class scope should be replaced with "test.CImpl".
|
// 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.
|
// 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
|
// For this, we construct an additional substitutor with a single mapping test.C -> test.CImpl
|
||||||
// TODO: this looks like a dirty hack
|
// TODO: this looks like a dirty hack
|
||||||
@@ -395,15 +439,16 @@ object HeaderImplDeclarationChecker : DeclarationChecker {
|
|||||||
|
|
||||||
if (a.visibility != b.visibility) return Incompatible.Visibility
|
if (a.visibility != b.visibility) return Incompatible.Visibility
|
||||||
|
|
||||||
|
val platformModule = b.module
|
||||||
val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor)
|
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,
|
// 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
|
// and not added if an explicit supertype _is_ specified
|
||||||
val aSupertypes = a.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny)
|
val aSupertypes = a.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny)
|
||||||
val bSupertypes = b.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny)
|
val bSupertypes = b.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny)
|
||||||
if (aSupertypes.map(substitutor).any { aSupertype ->
|
if (aSupertypes.map(substitutor).any { aSupertype ->
|
||||||
bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype) }
|
bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype, platformModule) }
|
||||||
}) return Incompatible.Supertypes
|
}) return Incompatible.Supertypes
|
||||||
|
|
||||||
areCompatibleClassScopes(a, b, checkImpl && !implTypealias, substitutor).let { if (it != Compatible) return it }
|
areCompatibleClassScopes(a, b, checkImpl && !implTypealias, substitutor).let { if (it != Compatible) return it }
|
||||||
|
|||||||
@@ -78,9 +78,10 @@ object ErrorTypesAreEqualToAnything : KotlinTypeChecker {
|
|||||||
|
|
||||||
object NewKotlinTypeChecker : KotlinTypeChecker {
|
object NewKotlinTypeChecker : KotlinTypeChecker {
|
||||||
override fun isSubtypeOf(subtype: KotlinType, supertype: KotlinType): Boolean =
|
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 =
|
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 {
|
fun TypeCheckerContext.equalTypes(a: UnwrappedType, b: UnwrappedType): Boolean {
|
||||||
if (a === b) return true
|
if (a === b) return true
|
||||||
@@ -247,7 +248,7 @@ object NewKotlinTypeChecker : KotlinTypeChecker {
|
|||||||
anySupertype(baseType, { false }) {
|
anySupertype(baseType, { false }) {
|
||||||
val current = captureFromArguments(it, CaptureStatus.FOR_SUBTYPING)
|
val current = captureFromArguments(it, CaptureStatus.FOR_SUBTYPING)
|
||||||
|
|
||||||
if (current.constructor == constructor) {
|
if (areEqualTypeConstructors(current.constructor, constructor)) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = SmartList()
|
result = SmartList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ open class TypeCheckerContext(val errorTypeEqualsToAnything: Boolean) {
|
|||||||
|
|
||||||
open fun addSubtypeConstraint(subType: UnwrappedType, superType: UnwrappedType): Boolean? = null
|
open fun addSubtypeConstraint(subType: UnwrappedType, superType: UnwrappedType): Boolean? = null
|
||||||
|
|
||||||
inline fun <T> runWithArgumentsSettings(subArgument: UnwrappedType, f: TypeCheckerContext.() -> T): T {
|
open fun areEqualTypeConstructors(a: TypeConstructor, b: TypeConstructor): Boolean {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inline fun <T> runWithArgumentsSettings(subArgument: UnwrappedType, f: TypeCheckerContext.() -> T): T {
|
||||||
if (argumentsDepth > 100) {
|
if (argumentsDepth > 100) {
|
||||||
error("Arguments depth is too high. Some related argument: $subArgument")
|
error("Arguments depth is too high. Some related argument: $subArgument")
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
header class Simple
|
||||||
|
header fun createSimple(): Simple
|
||||||
|
|
||||||
|
header class Generic<A, B>
|
||||||
|
header fun <A, B> createGeneric(a: A, b: B): Generic<A, B>
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
class SimpleImpl
|
||||||
|
impl typealias Simple = SimpleImpl
|
||||||
|
impl fun createSimple(): Simple = SimpleImpl()
|
||||||
|
|
||||||
|
class GenericImpl<A, B>
|
||||||
|
impl typealias Generic<A, B> = GenericImpl<A, B>
|
||||||
|
impl fun <A, B> createGeneric(a: A, b: B): Generic<A, B> = GenericImpl()
|
||||||
Vendored
+11
@@ -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<String, List<Double>>)
|
||||||
|
|
||||||
|
header fun <A, B> genericPair(p: Pair<A, B>)
|
||||||
Vendored
+11
@@ -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<String, List<Double>>) {}
|
||||||
|
|
||||||
|
impl fun <A, B> genericPair(p: Pair<A, B>) {}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user