diff --git a/compiler/testData/diagnostics/tests/j+k/contravariantIterable.kt b/compiler/testData/diagnostics/tests/j+k/contravariantIterable.kt new file mode 100644 index 00000000000..6a78efd5e2e --- /dev/null +++ b/compiler/testData/diagnostics/tests/j+k/contravariantIterable.kt @@ -0,0 +1,20 @@ +// FILE: A.java +import java.util.*; +public class A { + public static void foo(Iterable x) {} + public static void bar(Iterator x) {} +} + +// FILE: main.kt + +fun test(x: List, y: List<*>, z: MutableList<*>, w: MutableList) { + A.foo(x) + A.foo(y) + A.foo(z) + A.foo(w) + + A.bar(x.iterator()) + A.bar(y.iterator()) + A.bar(z.iterator()) + A.bar(w.iterator()) +} diff --git a/compiler/testData/diagnostics/tests/j+k/contravariantIterable.txt b/compiler/testData/diagnostics/tests/j+k/contravariantIterable.txt new file mode 100644 index 00000000000..a1e900e1233 --- /dev/null +++ b/compiler/testData/diagnostics/tests/j+k/contravariantIterable.txt @@ -0,0 +1,14 @@ +package + +public fun test(/*0*/ x: kotlin.collections.List, /*1*/ y: kotlin.collections.List<*>, /*2*/ z: kotlin.collections.MutableList<*>, /*3*/ w: kotlin.collections.MutableList): kotlin.Unit + +public open class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public open fun bar(/*0*/ x: kotlin.collections.(Mutable)Iterator<*>!): kotlin.Unit + public open fun foo(/*0*/ x: kotlin.collections.(Mutable)Iterable<*>!): kotlin.Unit +} diff --git a/compiler/testData/diagnostics/tests/j+k/matchers.kt b/compiler/testData/diagnostics/tests/j+k/matchers.kt new file mode 100644 index 00000000000..6d65c7863e9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/j+k/matchers.kt @@ -0,0 +1,18 @@ +// FILE: Assert.java + +public class Assert { + public static void assertThat(T actual, Matcher matcher) { + } +} + +// FILE: Matcher.java +public class Matcher { + public static Matcher> hasItem(T item) { + return null; + } +} + +// FILE: main.kt +fun test(x: List) { + Assert.assertThat(x, Matcher.hasItem("abc")) +} diff --git a/compiler/testData/diagnostics/tests/j+k/matchers.txt b/compiler/testData/diagnostics/tests/j+k/matchers.txt new file mode 100644 index 00000000000..8b326bb1c0b --- /dev/null +++ b/compiler/testData/diagnostics/tests/j+k/matchers.txt @@ -0,0 +1,23 @@ +package + +public fun test(/*0*/ x: kotlin.collections.List): kotlin.Unit + +public open class Assert { + public constructor Assert() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public open fun assertThat(/*0*/ actual: T!, /*1*/ matcher: Matcher!): kotlin.Unit +} + +public open class Matcher { + public constructor Matcher() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public open fun hasItem(/*0*/ item: T!): Matcher!>! +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 5255320d8cd..905402411b0 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -10005,6 +10005,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("contravariantIterable.kt") + public void testContravariantIterable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/j+k/contravariantIterable.kt"); + doTest(fileName); + } + @TestMetadata("differentFilename.kt") public void testDifferentFilename() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/j+k/differentFilename.kt"); @@ -10179,6 +10185,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("matchers.kt") + public void testMatchers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/j+k/matchers.kt"); + doTest(fileName); + } + @TestMetadata("mutableIterator.kt") public void testMutableIterator() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/j+k/mutableIterator.kt"); diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/LazyJavaTypeResolver.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/LazyJavaTypeResolver.kt index b602ba37137..47dfa196bbb 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/LazyJavaTypeResolver.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/LazyJavaTypeResolver.kt @@ -148,10 +148,8 @@ class LazyJavaTypeResolver( if (javaToKotlin.isReadOnly(kotlinDescriptor)) { if (howThisTypeIsUsedEffectively == MEMBER_SIGNATURE_COVARIANT - || howThisTypeIsUsedEffectively == SUPERTYPE - // Convert (Mutable)List to MutableList - // Same for (Mutable)Map - || javaType.typeArguments.lastOrNull().isSuperWildcard()) { + || howThisTypeIsUsedEffectively == SUPERTYPE + || javaType.argumentsMakeSenseOnlyForMutableContainer(readOnlyContainer = kotlinDescriptor)) { return javaToKotlin.convertReadOnlyToMutable(kotlinDescriptor) } } @@ -159,6 +157,20 @@ class LazyJavaTypeResolver( return kotlinDescriptor } + // Returns true for covariant read-only container that has mutable pair with invariant parameter + // List does not make sense, but MutableList does + // Same for Map + // But both Iterable, MutableIterable don't make sense as they are covariant, so return false + private fun JavaClassifierType.argumentsMakeSenseOnlyForMutableContainer( + readOnlyContainer: ClassDescriptor + ): Boolean { + if (!typeArguments.lastOrNull().isSuperWildcard()) return false + val mutableLastParameterVariance = JavaToKotlinClassMap.INSTANCE.convertReadOnlyToMutable(readOnlyContainer) + .typeConstructor.parameters.lastOrNull()?.variance ?: return false + + return mutableLastParameterVariance != OUT_VARIANCE + } + private fun JavaType?.isSuperWildcard(): Boolean = (this as? JavaWildcardType)?.let { it.bound != null && !it.isExtends } ?: false private fun isRaw(): Boolean {