From b971ac931295e99a1d4a12e136d311361115f98a Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 23 Mar 2017 15:08:54 +0300 Subject: [PATCH] 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 --- .../checkers/HeaderImplDeclarationChecker.kt | 24 +++++++++++--- .../tests/multiplatform/implDynamic.kt | 29 +++++++++++++++++ .../tests/multiplatform/implDynamic.txt | 26 +++++++++++++++ .../tests/multiplatform/java/flexibleTypes.kt | 32 +++++++++++++++++++ .../multiplatform/java/flexibleTypes.txt | 27 ++++++++++++++++ .../checkers/DiagnosticsTestGenerated.java | 12 +++++++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/multiplatform/implDynamic.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/implDynamic.txt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.txt 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 770895ee9e4..0a401037451 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt @@ -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, b: List): Boolean { + for (i in a.indices) { + if (!areCompatibleTypes(a[i], b[i])) return false + } + return true + } + private fun areCompatibleTypeParameters(a: List, b: List, 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 } diff --git a/compiler/testData/diagnostics/tests/multiplatform/implDynamic.kt b/compiler/testData/diagnostics/tests/multiplatform/implDynamic.kt new file mode 100644 index 00000000000..c9f9a65c870 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/implDynamic.kt @@ -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?): MutableMap + + fun > 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 = null!! + + impl fun > f3(t: T): dynamic = null!! +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/implDynamic.txt b/compiler/testData/diagnostics/tests/multiplatform/implDynamic.txt new file mode 100644 index 00000000000..bcf299c0bc9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/implDynamic.txt @@ -0,0 +1,26 @@ +// -- Module: -- +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.collections.MutableMap + public final header fun > 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: -- +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 + public final impl fun > 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 +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt b/compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt new file mode 100644 index 00000000000..3cd9824048f --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt @@ -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?): MutableMap + + fun > 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 f2(List s) { return null; } + + public final > T f3(T t) { return null; } +} + +// FILE: jvm.kt + +impl typealias Foo = FooImpl diff --git a/compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.txt b/compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.txt new file mode 100644 index 00000000000..0056a64398c --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.txt @@ -0,0 +1,27 @@ +// -- Module: -- +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.collections.MutableMap + public final header fun > 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: -- +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.collections.(Mutable)Map! + public final fun !> 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 diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index d4ef5e4c8fd..f2a1878f8ba 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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");