Allow impl declarations to have flexible types

Types of the corresponding parameters (or type parameter bounds, types
in supertypes, etc) are now compatible not only if they're equal, but
also if values of those types are mutually assignable (if "a" is subtype
of "b" and "b" is subtype of "a")

 #KT-17005 Fixed
This commit is contained in:
Alexander Udalov
2017-03-23 15:08:54 +03:00
parent 116380a826
commit b971ac9312
6 changed files with 145 additions and 5 deletions
@@ -42,6 +42,7 @@ import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.keysToMap
@@ -266,9 +267,9 @@ class HeaderImplDeclarationChecker(val moduleToCheck: ModuleDescriptor? = null)
val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor)
if (aParams.map { substitutor(it.type) } != bParams.map { it.type } ||
aExtensionReceiver?.type?.let(substitutor) != bExtensionReceiver?.type) return Incompatible.ParameterTypes
if (substitutor(a.returnType) != b.returnType) return Incompatible.ReturnType
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 (b.hasStableParameterNames() && !equalsBy(aParams, bParams, ValueParameterDescriptor::getName)) return Incompatible.ParameterNames
if (!equalsBy(aTypeParams, bTypeParams, TypeParameterDescriptor::getName)) return Incompatible.TypeParameterNames
@@ -292,8 +293,19 @@ class HeaderImplDeclarationChecker(val moduleToCheck: ModuleDescriptor? = null)
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 areCompatibleTypeLists(a: List<KotlinType?>, b: List<KotlinType?>): Boolean {
for (i in a.indices) {
if (!areCompatibleTypes(a[i], b[i])) return false
}
return true
}
private fun areCompatibleTypeParameters(a: List<TypeParameterDescriptor>, b: List<TypeParameterDescriptor>, substitutor: Substitutor): Compatibility {
if (a.map { substitutor(it.defaultType) } != b.map { it.defaultType }) return Incompatible.TypeParameterUpperBounds
if (!areCompatibleTypeLists(a.map { substitutor(it.defaultType) }, b.map { it.defaultType })) return Incompatible.TypeParameterUpperBounds
if (!equalsBy(a, b, TypeParameterDescriptor::getVariance)) return Incompatible.TypeParameterVariance
if (!equalsBy(a, b, TypeParameterDescriptor::isReified)) return Incompatible.TypeParameterReified
@@ -362,7 +374,9 @@ class HeaderImplDeclarationChecker(val moduleToCheck: ModuleDescriptor? = null)
// 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 (!bSupertypes.containsAll(aSupertypes.map(substitutor))) return Incompatible.Supertypes
if (aSupertypes.map(substitutor).any { aSupertype ->
bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype) }
}) return Incompatible.Supertypes
areCompatibleClassScopes(a, b, checkImpl && !implTypealias, substitutor).let { if (it != Compatible) return it }
@@ -0,0 +1,29 @@
// !LANGUAGE: +MultiPlatformProjects
// !DIAGNOSTICS: -UNSUPPORTED
// MODULE: m1-common
// FILE: common.kt
header class Foo {
constructor(p: Any)
fun f1(s: String): Int
fun f2(s: List<String>?): MutableMap<Boolean?, Foo>
fun <T : Set<Number>> f3(t: T): T?
}
// MODULE: m2-js(m1-common)
// FILE: js.kt
// TODO: do not suppress UNSUPPORTED once JS files in multi-platform tests are analyzed with JS analyzer facade
impl class Foo {
impl constructor(p: dynamic) {}
impl fun f1(s: dynamic): dynamic = null!!
impl fun f2(s: dynamic): MutableMap<Boolean?, Foo> = null!!
impl fun <T : Set<Number>> f3(t: T): dynamic = null!!
}
@@ -0,0 +1,26 @@
// -- Module: <m1-common> --
package
public final header class Foo {
public constructor Foo(/*0*/ p: kotlin.Any)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final header fun f1(/*0*/ s: kotlin.String): kotlin.Int
public final header fun f2(/*0*/ s: kotlin.collections.List<kotlin.String>?): kotlin.collections.MutableMap<kotlin.Boolean?, Foo>
public final header fun </*0*/ T : kotlin.collections.Set<kotlin.Number>> f3(/*0*/ t: T): T?
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
// -- Module: <m2-js> --
package
public final impl class Foo {
public constructor Foo(/*0*/ p: dynamic)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final impl fun f1(/*0*/ s: dynamic): dynamic
public final impl fun f2(/*0*/ s: dynamic): kotlin.collections.MutableMap<kotlin.Boolean?, Foo>
public final impl fun </*0*/ T : kotlin.collections.Set<kotlin.Number>> f3(/*0*/ t: T): dynamic
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,32 @@
// !LANGUAGE: +MultiPlatformProjects
// MODULE: m1-common
// FILE: common.kt
header class Foo {
constructor(p: Any)
fun f1(s: String): Int
fun f2(s: List<String>?): MutableMap<Boolean?, Foo>
fun <T : Set<Number>> f3(t: T): T?
}
// MODULE: m2-jvm(m1-common)
// FILE: FooImpl.java
import java.util.*;
public class FooImpl {
public FooImpl(Object p) {}
public final int f1(String s) { return 0; }
public final Map<Boolean, FooImpl> f2(List<String> s) { return null; }
public final <T extends Set<Number>> T f3(T t) { return null; }
}
// FILE: jvm.kt
impl typealias Foo = FooImpl
@@ -0,0 +1,27 @@
// -- Module: <m1-common> --
package
public final header class Foo {
public constructor Foo(/*0*/ p: kotlin.Any)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final header fun f1(/*0*/ s: kotlin.String): kotlin.Int
public final header fun f2(/*0*/ s: kotlin.collections.List<kotlin.String>?): kotlin.collections.MutableMap<kotlin.Boolean?, Foo>
public final header fun </*0*/ T : kotlin.collections.Set<kotlin.Number>> f3(/*0*/ t: T): T?
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
// -- Module: <m2-jvm> --
package
public open class FooImpl {
public constructor FooImpl(/*0*/ p: kotlin.Any!)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun f1(/*0*/ s: kotlin.String!): kotlin.Int
public final fun f2(/*0*/ s: kotlin.collections.(Mutable)List<kotlin.String!>!): kotlin.collections.(Mutable)Map<kotlin.Boolean!, FooImpl!>!
public final fun </*0*/ T : kotlin.collections.(Mutable)Set<kotlin.Number!>!> f3(/*0*/ t: T!): T!
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public typealias Foo = FooImpl
@@ -13102,6 +13102,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("implDynamic.kt")
public void testImplDynamic() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/multiplatform/implDynamic.kt");
doTest(fileName);
}
@TestMetadata("implFakeOverride.kt")
public void testImplFakeOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/multiplatform/implFakeOverride.kt");
@@ -13245,6 +13251,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/java"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("flexibleTypes.kt")
public void testFlexibleTypes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt");
doTest(fileName);
}
@TestMetadata("parameterNames.kt")
public void testParameterNames() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/multiplatform/java/parameterNames.kt");