diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.kt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.kt new file mode 100644 index 00000000000..a6f5329d0f4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.kt @@ -0,0 +1,12 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +// !CHECK_TYPE + +import java.util.ArrayList + +fun foo(handlers: Array?>) { + val v = handlers[0] ?: ArrayList() + handlers[0] = v + val js: MutableList = v + v checkType { it : _>} + v checkType { it : _>} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.txt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.txt new file mode 100644 index 00000000000..1ee2d59f6da --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.txt @@ -0,0 +1,11 @@ +package + +internal fun foo(/*0*/ handlers: kotlin.Array?>): kotlin.Unit +internal fun T.checkType(/*0*/ f: (_) -> kotlin.Unit): (_) -> kotlin.Unit + +internal final class _ { + public constructor _() + 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 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.kt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.kt new file mode 100644 index 00000000000..14f87fdfc59 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.kt @@ -0,0 +1,20 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +// !CHECK_TYPE +// FILE: p/J.java + +package p; + +public class J { + public static J j() { return null; } +} + +// FILE: k.kt + +import p.* + +fun foo(j: J) { + val v = if (true) j else J.j() + val js: J = v + v checkType { it : _>} + v checkType { it : _>} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.txt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.txt new file mode 100644 index 00000000000..9f4886438da --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.txt @@ -0,0 +1,11 @@ +package + +internal fun foo(/*0*/ j: p.J): kotlin.Unit +internal fun T.checkType(/*0*/ f: (_) -> kotlin.Unit): (_) -> kotlin.Unit + +internal final class _ { + public constructor _() + 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 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.kt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.kt new file mode 100644 index 00000000000..71fcbb0ea12 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.kt @@ -0,0 +1,24 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +// !CHECK_TYPE +// FILE: p/J.java + +package p; + +public class J { + public static J j() { return null; } +} + +// FILE: k.kt + +import p.* + +trait Out + +fun f(a: Out, b: Out, c: Out): T = null!! +fun out(t: T): Out> = null!! + +fun test(a: Out, b: Out>) { + val v = f(a, b, out(J.j())) + v checkType { it : _>} + v checkType { it : _>} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.txt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.txt new file mode 100644 index 00000000000..69d16eef60f --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.txt @@ -0,0 +1,19 @@ +package + +internal fun f(/*0*/ a: Out, /*1*/ b: Out, /*2*/ c: Out): T +internal fun out(/*0*/ t: T): Out> +internal fun test(/*0*/ a: Out, /*1*/ b: Out>): kotlin.Unit +internal fun T.checkType(/*0*/ f: (_) -> kotlin.Unit): (_) -> kotlin.Unit + +internal trait Out { + 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 +} + +internal final class _ { + public constructor _() + 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 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.kt b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.kt new file mode 100644 index 00000000000..084ea2b60ca --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.kt @@ -0,0 +1,21 @@ +import java.util.HashMap + +trait ModuleDescriptorImpl +trait ModuleInfo +trait ResolverForModule +trait ResolverForProject + +class ResolverForProjectImpl( + descriptorByModule: Map, + delegateResolver: ResolverForProject +) : ResolverForProject + +fun foo(delegateResolver: ResolverForProject): ResolverForProject { + val descriptorByModule = HashMap() + return ResolverForProjectImpl(descriptorByModule, delegateResolver) +} + +// M = M2 +// HashMap :< Map => M = M2! +// R = A +// RFP :< RFP \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.txt b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.txt new file mode 100644 index 00000000000..916ef884014 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.txt @@ -0,0 +1,34 @@ +package + +internal fun foo(/*0*/ delegateResolver: ResolverForProject): ResolverForProject + +internal trait ModuleDescriptorImpl { + 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 +} + +internal trait ModuleInfo { + 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 +} + +internal trait ResolverForModule { + 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 +} + +internal trait ResolverForProject { + 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 +} + +internal final class ResolverForProjectImpl : ResolverForProject { + public constructor ResolverForProjectImpl(/*0*/ descriptorByModule: kotlin.Map, /*1*/ delegateResolver: ResolverForProject) + 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 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.kt b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.kt new file mode 100644 index 00000000000..2ad7f1cb09b --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.kt @@ -0,0 +1,26 @@ +import java.util.HashMap + +trait ResolverForProject { + val exposeM: M1 get() = null!! +} + +class ResolverForProjectImpl( + descriptorByModule: Map, + delegateResolver: ResolverForProject +) : ResolverForProject + +trait WithFoo { + fun foo() +} + +fun foo(delegateResolver: ResolverForProject): ResolverForProject { + val descriptorByModule = HashMap() + val result = ResolverForProjectImpl(descriptorByModule, delegateResolver) + result.exposeM.foo() // M is not M2? + result.exposeM?.foo() // no warning, M is not M2, hense M is M2! + + return ResolverForProjectImpl(descriptorByModule, delegateResolver) // another bound check +} + +// HashMap :< Map => M = M2! +// RFP :< RFP => M = M2? \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.txt b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.txt new file mode 100644 index 00000000000..9fee42620c3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.txt @@ -0,0 +1,25 @@ +package + +internal fun foo(/*0*/ delegateResolver: ResolverForProject): ResolverForProject + +internal trait ResolverForProject { + internal open val exposeM: M1 + 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 +} + +internal final class ResolverForProjectImpl : ResolverForProject { + public constructor ResolverForProjectImpl(/*0*/ descriptorByModule: kotlin.Map, /*1*/ delegateResolver: ResolverForProject) + internal open override /*1*/ /*fake_override*/ val exposeM: M + 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 +} + +internal trait WithFoo { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + internal abstract fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index 33bdbdc3df0..e732cf35eda 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -7793,6 +7793,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("mixedElvis.kt") + public void testMixedElvis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedElvis.kt"); + doTest(fileName); + } + + @TestMetadata("mixedIf.kt") + public void testMixedIf() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/commonSupertype/mixedIf.kt"); + doTest(fileName); + } + @TestMetadata("recursiveGeneric.kt") public void testRecursiveGeneric() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/commonSupertype/recursiveGeneric.kt"); @@ -7811,6 +7823,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("withNothing.kt") + public void testWithNothing() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/commonSupertype/withNothing.kt"); + doTest(fileName); + } + } @TestMetadata("compiler/testData/diagnostics/tests/platformTypes/methodCall") @@ -7869,6 +7887,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("multipleExactBounds.kt") + public void testMultipleExactBounds() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBounds.kt"); + doTest(fileName); + } + + @TestMetadata("multipleExactBoundsNullable.kt") + public void testMultipleExactBoundsNullable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/methodCall/multipleExactBoundsNullable.kt"); + doTest(fileName); + } + @TestMetadata("objectArray.kt") public void testObjectArray() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/methodCall/objectArray.kt"); diff --git a/core/descriptors/src/org/jetbrains/jet/lang/resolve/calls/inference/TypeBoundsImpl.java b/core/descriptors/src/org/jetbrains/jet/lang/resolve/calls/inference/TypeBoundsImpl.java index 33894abd35e..57a3580048a 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/resolve/calls/inference/TypeBoundsImpl.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/resolve/calls/inference/TypeBoundsImpl.java @@ -156,10 +156,10 @@ public class TypeBoundsImpl implements TypeBounds { } Set exactBounds = filterBounds(bounds, BoundKind.EXACT_BOUND, values); - if (exactBounds.size() == 1) { - JetType exactBound = exactBounds.iterator().next(); - if (tryPossibleAnswer(exactBound)) { - return Collections.singleton(exactBound); + JetType bestFit = TypesPackage.singleBestRepresentative(exactBounds); + if (bestFit != null) { + if (tryPossibleAnswer(bestFit)) { + return Collections.singleton(bestFit); } } values.addAll(exactBounds); diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/CommonSupertypes.java b/core/descriptors/src/org/jetbrains/jet/lang/types/CommonSupertypes.java index 5b7e7cbcfde..d0208d8c735 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/CommonSupertypes.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/CommonSupertypes.java @@ -103,7 +103,9 @@ public class CommonSupertypes { private static JetType commonSuperTypeForInflexible(@NotNull Collection types, int recursionDepth, int maxDepth) { assert !types.isEmpty(); Collection typeSet = new HashSet(types); - if (typeSet.size() == 1) return typeSet.iterator().next(); + + JetType bestFit = TypesPackage.singleBestRepresentative(typeSet); + if (bestFit != null) return bestFit; // If any of the types is nullable, the result must be nullable // This also removed Nothing and Nothing? because they are subtypes of everything else @@ -232,8 +234,9 @@ public class CommonSupertypes { @NotNull Set typeProjections, int recursionDepth, int maxDepth ) { - if (typeProjections.size() == 1) { - return typeProjections.iterator().next(); + TypeProjection singleBestProjection = TypesPackage.singleBestRepresentative(typeProjections); + if (singleBestProjection != null) { + return singleBestProjection; } if (recursionDepth >= maxDepth) { diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/flexibleTypes.kt b/core/descriptors/src/org/jetbrains/jet/lang/types/flexibleTypes.kt index b218a9cdb6c..93d90326a0e 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/flexibleTypes.kt +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/flexibleTypes.kt @@ -18,6 +18,7 @@ package org.jetbrains.jet.lang.types import org.jetbrains.jet.lang.types.checker.JetTypeChecker import org.jetbrains.jet.lang.types.Approximation.DataFlowExtras +import org.jetbrains.kotlin.util.printAndReturn public trait Flexibility : TypeCapability { // lowerBound is a subtype of upperBound @@ -29,6 +30,38 @@ public trait Flexibility : TypeCapability { public fun JetType.isFlexible(): Boolean = this.getCapability(javaClass()) != null public fun JetType.flexibility(): Flexibility = this.getCapability(javaClass())!! +// This function is intended primarily for sets: since JetType.equals() represents _syntactical_ equality of types, +// whereas JetTypeChecker.DEFAULT.equalsTypes() represents semantical equality +// A set of types (e.g. exact bounds etc) may contain, for example, X, X? and X! +// These are not equal syntactically (by JetType.equals()), but X! is _compatible_ with others as exact bounds, +// moreover, X! is a better fit. +// +// So, we are looking for a type among this set such that it is equal to all others semantically +// (by JetTypeChecker.DEFAULT.equalsTypes()), and fits at least as well as they do. +fun Collection.singleBestRepresentative(): JetType? { + if (this.size() == 1) return this.first() + + return this.firstOrNull { + candidate -> + this.all { + other -> + candidate == other || JetTypeChecker.DEFAULT.equalTypes(candidate, other) + } + } +} + +fun Collection.singleBestRepresentative(): TypeProjection? { + if (this.size() == 1) return this.first() + + val projectionKinds = this.map { it.getProjectionKind() }.toSet() + if (projectionKinds.size() != 1) return null + + val bestType = this.map { it.getType() }.singleBestRepresentative() + if (bestType == null) return null + + return TypeProjectionImpl(projectionKinds.single(), bestType) +} + public fun JetType.lowerIfFlexible(): JetType = if (this.isFlexible()) this.flexibility().getLowerBound() else this public fun JetType.upperIfFlexible(): JetType = if (this.isFlexible()) this.flexibility().getUpperBound() else this