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");