diff --git a/compiler/testData/diagnostics/tests/enum/commonSupertype.kt b/compiler/testData/diagnostics/tests/enum/commonSupertype.kt new file mode 100644 index 00000000000..3719b7e97cc --- /dev/null +++ b/compiler/testData/diagnostics/tests/enum/commonSupertype.kt @@ -0,0 +1,6 @@ +// KT-5943 StackOverflowError from commonSupertype of two enums + +enum class A { A } +enum class B { B } + +val x = if (true) A.A else B.B \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/enum/commonSupertype.txt b/compiler/testData/diagnostics/tests/enum/commonSupertype.txt new file mode 100644 index 00000000000..5f4c0841b77 --- /dev/null +++ b/compiler/testData/diagnostics/tests/enum/commonSupertype.txt @@ -0,0 +1,65 @@ +package + +internal val x: kotlin.Enum>>>> + +internal final enum class A : kotlin.Enum { + private 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 final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public enum entry A : A { + private 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 final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public class object : A.A { + private 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 final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): A + public final /*synthesized*/ fun values(): kotlin.Array +} + +internal final enum class B : kotlin.Enum { + private constructor B() + 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 final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public enum entry B : B { + private constructor B() + 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 final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public class object : B.B { + private 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 final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): B + public final /*synthesized*/ fun values(): kotlin.Array +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/generics/recursive.kt b/compiler/testData/diagnostics/tests/generics/recursive.kt new file mode 100644 index 00000000000..c2dc9fd7fd6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/recursive.kt @@ -0,0 +1,11 @@ +// !DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE + +trait Rec +class A : Rec +class B : Rec + +fun test(a: A, b: B, c: Boolean) { + var ab = if (c) a else b + ab = a + ab = b +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/recursiveGeneric.kt b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/recursiveGeneric.kt new file mode 100644 index 00000000000..fd370e7547e --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/commonSupertype/recursiveGeneric.kt @@ -0,0 +1,35 @@ +// !DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE + +// FILE: p/Rec.java + +package p; + +public interface Rec { + +} + +// FILE: p/A.java + +package p; + +public interface A extends Rec { + +} + +// FILE: p/B.java + +package p; + +public interface B extends Rec { + +} + +// FILE: k.kt + +import p.* + +fun test(a: A, b: B, c: Boolean) { + var ab = if (c) a else b + ab = a + ab = b +} \ No newline at end of file diff --git a/compiler/testData/type-checker-test.kt b/compiler/testData/type-checker-test.kt index b578f212933..a5b335c478e 100644 --- a/compiler/testData/type-checker-test.kt +++ b/compiler/testData/type-checker-test.kt @@ -40,4 +40,8 @@ fun f(a : Float) : T {a} trait Parent trait A: Parent -trait B: Parent \ No newline at end of file +trait B: Parent + +trait Rec +class ARec : Rec +class BRec : Rec \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index ba7ae5dd6fc..2bbc8e178ab 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -3763,6 +3763,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("commonSupertype.kt") + public void testCommonSupertype() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/enum/commonSupertype.kt"); + doTest(fileName); + } + @TestMetadata("dontCreatePackageTypeForEnumEntry.kt") public void testDontCreatePackageTypeForEnumEntry() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/enum/dontCreatePackageTypeForEnumEntry.kt"); @@ -4437,6 +4443,11 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("recursive.kt") + public void testRecursive() throws Exception { + doTest("compiler/testData/diagnostics/tests/generics/recursive.kt"); + } + @TestMetadata("RecursiveUpperBoundCheck.kt") public void testRecursiveUpperBoundCheck() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/RecursiveUpperBoundCheck.kt"); @@ -7763,6 +7774,11 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("recursiveGeneric.kt") + public void testRecursiveGeneric() throws Exception { + doTest("compiler/testData/diagnostics/tests/platformTypes/commonSupertype/recursiveGeneric.kt"); + } + @TestMetadata("stringOrNull.kt") public void testStringOrNull() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/commonSupertype/stringOrNull.kt"); diff --git a/compiler/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java b/compiler/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java index 268ca44b7a2..f4d7e4874cb 100644 --- a/compiler/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java +++ b/compiler/tests/org/jetbrains/jet/types/JetTypeCheckerTest.java @@ -204,6 +204,10 @@ public class JetTypeCheckerTest extends JetLiteFixture { assertCommonSupertype("Base_T", "Base_T", "Base_T"); } + public void testCommonSupertypesForRecursive() throws Exception { + assertCommonSupertype("Rec>>>>", "ARec", "BRec"); + } + public void testIntersect() throws Exception { assertIntersection("Long", "Long?", "Number"); assertIntersection("Long", "Number", "Long?"); 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 427f4baa5c1..48da04a18f9 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/CommonSupertypes.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/CommonSupertypes.java @@ -16,6 +16,8 @@ package org.jetbrains.jet.lang.types; +import kotlin.Function1; +import kotlin.KotlinPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.ClassDescriptor; @@ -47,6 +49,34 @@ public class CommonSupertypes { @NotNull public static JetType commonSupertype(@NotNull Collection types) { + // Recursion should not be significantly deeper than the deepest type in question + // It can be slightly deeper, though: e.g. when initial types are simple, but their supertypes are complex + return findCommonSupertype(types, 0, maxDepth(types) + 3); + } + + private static int maxDepth(@NotNull Collection types) { + int max = 0; + for (JetType type : types) { + int depth = depth(type); + if (max < depth) { + max = depth; + } + } + return max; + } + + private static int depth(@NotNull JetType type) { + return 1 + maxDepth(KotlinPackage.map(type.getArguments(), new Function1() { + @Override + public JetType invoke(TypeProjection projection) { + return projection.getType(); + } + })); + } + + @NotNull + private static JetType findCommonSupertype(@NotNull Collection types, int recursionDepth, int maxDepth) { + assert recursionDepth <= maxDepth : "Recursion depth exceeded: " + recursionDepth + " > " + maxDepth + " for types " + types; boolean hasFlexible = false; List upper = new ArrayList(types.size()); List lower = new ArrayList(types.size()); @@ -63,12 +93,15 @@ public class CommonSupertypes { } } - if (!hasFlexible) return commonSuperTypeForInflexible(types); - return DelegatingFlexibleType.OBJECT$.create(commonSuperTypeForInflexible(lower), commonSuperTypeForInflexible(upper)); + if (!hasFlexible) return commonSuperTypeForInflexible(types, recursionDepth, maxDepth); + return DelegatingFlexibleType.OBJECT$.create( + commonSuperTypeForInflexible(lower, recursionDepth, maxDepth), + commonSuperTypeForInflexible(upper, recursionDepth, maxDepth) + ); } @NotNull - private static JetType commonSuperTypeForInflexible(@NotNull Collection types) { + 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(); @@ -114,7 +147,7 @@ public class CommonSupertypes { Map.Entry> entry = commonSupertypes.entrySet().iterator().next(); // Reconstructing type arguments if possible - JetType result = computeSupertypeProjections(entry.getKey(), entry.getValue()); + JetType result = computeSupertypeProjections(entry.getKey(), entry.getValue(), recursionDepth, maxDepth); return TypeUtils.makeNullableIfNeeded(result, nullable); } @@ -160,7 +193,7 @@ public class CommonSupertypes { // constructor - type constructor of a supertype to be instantiated // types - instantiations of constructor occurring as supertypes of classes we are trying to intersect @NotNull - private static JetType computeSupertypeProjections(@NotNull TypeConstructor constructor, @NotNull Set types) { + private static JetType computeSupertypeProjections(@NotNull TypeConstructor constructor, @NotNull Set types, int recursionDepth, int maxDepth) { // we assume that all the given types are applications of the same type constructor assert !types.isEmpty(); @@ -177,7 +210,7 @@ public class CommonSupertypes { for (JetType type : types) { typeProjections.add(type.getArguments().get(i)); } - newProjections.add(computeSupertypeProjection(parameterDescriptor, typeProjections)); + newProjections.add(computeSupertypeProjection(parameterDescriptor, typeProjections, recursionDepth, maxDepth)); } boolean nullable = false; @@ -195,11 +228,21 @@ public class CommonSupertypes { } @NotNull - private static TypeProjection computeSupertypeProjection(@NotNull TypeParameterDescriptor parameterDescriptor, @NotNull Set typeProjections) { + private static TypeProjection computeSupertypeProjection( + @NotNull TypeParameterDescriptor parameterDescriptor, + @NotNull Set typeProjections, + int recursionDepth, int maxDepth + ) { if (typeProjections.size() == 1) { return typeProjections.iterator().next(); } + if (recursionDepth >= maxDepth) { + // If recursion is too deep, we cut it by taking as an ultimate supertype argument + // Example: class A : Base; class B : Base, commonSuperType(A, B) = Base + return new TypeProjectionImpl(OUT_VARIANCE, KotlinBuiltIns.getInstance().getNullableAnyType()); + } + Set ins = new HashSet(); Set outs = new HashSet(); @@ -239,19 +282,19 @@ public class CommonSupertypes { if (outs != null) { Variance projectionKind = variance == OUT_VARIANCE ? Variance.INVARIANT : OUT_VARIANCE; - return new TypeProjectionImpl(projectionKind, commonSupertype(outs)); + return new TypeProjectionImpl(projectionKind, findCommonSupertype(outs, recursionDepth + 1, maxDepth)); } if (ins != null) { JetType intersection = TypeUtils.intersect(JetTypeChecker.DEFAULT, ins); if (intersection == null) { - return new TypeProjectionImpl(OUT_VARIANCE, commonSupertype(parameterDescriptor.getUpperBounds())); + return new TypeProjectionImpl(OUT_VARIANCE, findCommonSupertype(parameterDescriptor.getUpperBounds(), recursionDepth + 1, maxDepth)); } Variance projectionKind = variance == IN_VARIANCE ? Variance.INVARIANT : IN_VARIANCE; return new TypeProjectionImpl(projectionKind, intersection); } else { Variance projectionKind = variance == OUT_VARIANCE ? Variance.INVARIANT : OUT_VARIANCE; - return new TypeProjectionImpl(projectionKind, commonSupertype(parameterDescriptor.getUpperBounds())); + return new TypeProjectionImpl(projectionKind, findCommonSupertype(parameterDescriptor.getUpperBounds(), recursionDepth + 1, maxDepth)); } }