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:
Alexander Udalov
2017-04-19 18:08:46 +03:00
parent 1ce4612f74
commit ea727ff3f0
8 changed files with 119 additions and 19 deletions
@@ -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")
} }
@@ -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>
@@ -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()
@@ -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>)
@@ -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())
}
})
}
} }
} }