diff --git a/compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.bounds b/compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.bounds
new file mode 100644
index 00000000000..b3a8ecb359c
--- /dev/null
+++ b/compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.bounds
@@ -0,0 +1,26 @@
+VARIABLES T P E
+
+SUBTYPE T Producer
+SUBTYPE P Producer
+SUBTYPE E Producer
+
+type parameter bounds:
+T <: Producer
*, <: Producer>*, <: Producer>>*
+P <: Producer*
+E <: Producer*
+
+status:
+-hasCannotCaptureTypesError: false
+-hasConflictingConstraints: false
+-hasContradiction: false
+-hasErrorInConstrainingTypes: false
+-hasTypeConstructorMismatch: false
+-hasTypeInferenceIncorporationError: false
+-hasUnknownParameters: true
+-hasViolatedUpperBound: false
+-isSuccessful: false
+
+result:
+T=???
+P=???
+E=???
diff --git a/compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.constraints b/compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.constraints
new file mode 100644
index 00000000000..da4f2840fc4
--- /dev/null
+++ b/compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.constraints
@@ -0,0 +1,5 @@
+VARIABLES T P E
+
+SUBTYPE T Producer
+SUBTYPE P Producer
+SUBTYPE E Producer
diff --git a/compiler/tests/org/jetbrains/kotlin/resolve/constraintSystem/ConstraintSystemTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/resolve/constraintSystem/ConstraintSystemTestGenerated.java
index 8186137e300..1557eceb704 100644
--- a/compiler/tests/org/jetbrains/kotlin/resolve/constraintSystem/ConstraintSystemTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/resolve/constraintSystem/ConstraintSystemTestGenerated.java
@@ -441,6 +441,12 @@ public class ConstraintSystemTestGenerated extends AbstractConstraintSystemTest
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/constraintSystem/severalVariables/recursive"), Pattern.compile("^(.+)\\.constraints$"), true);
}
+ @TestMetadata("implicitlyRecursive.constraints")
+ public void testImplicitlyRecursive() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("compiler/testData/constraintSystem/severalVariables/recursive/implicitlyRecursive.constraints");
+ doTest(fileName);
+ }
+
@TestMetadata("mutuallyRecursive.constraints")
public void testMutuallyRecursive() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/constraintSystem/severalVariables/recursive/mutuallyRecursive.constraints");
diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/ConstraintSystemImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/ConstraintSystemImpl.kt
index bb55bb1093a..d424a67d23b 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/ConstraintSystemImpl.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/ConstraintSystemImpl.kt
@@ -346,9 +346,10 @@ public class ConstraintSystemImpl : ConstraintSystem {
typeVariable: TypeParameterDescriptor,
constrainingType: JetType,
kind: TypeBounds.BoundKind,
- position: ConstraintPosition
+ position: ConstraintPosition,
+ derivedFrom: Set = emptySet()
) {
- val bound = Bound(typeVariable, constrainingType, kind, position, constrainingType.isProper())
+ val bound = Bound(typeVariable, constrainingType, kind, position, constrainingType.isProper(), derivedFrom)
val typeBounds = getTypeBounds(typeVariable)
if (typeBounds.bounds.contains(bound)) return
diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBounds.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBounds.kt
index bee0e0ab739..78d7963dd8f 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBounds.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBounds.kt
@@ -49,7 +49,9 @@ public trait TypeBounds {
public val constrainingType: JetType,
public val kind: BoundKind,
public val position: ConstraintPosition,
- public val isProper: Boolean = true
+ public val isProper: Boolean,
+ // to prevent infinite recursion in incorporation we store the variables that was substituted to derive this bound
+ public val derivedFrom: Set
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBoundsImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBoundsImpl.kt
index cd640e14e55..cc84ca284fb 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBoundsImpl.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/TypeBoundsImpl.kt
@@ -194,7 +194,8 @@ fun Collection.substitute(substituteTypeVariable: (TypeParameterDescripto
it.constrainingType
}
substitutedType?.let { type ->
- Bound(substituteTypeVariable(it.typeVariable) ?: it.typeVariable, type, it.kind, it.position, it.isProper)
+ Bound(substituteTypeVariable(it.typeVariable) ?: it.typeVariable, type, it.kind, it.position, it.isProper,
+ it.derivedFrom.map { substituteTypeVariable(it) ?: it }.toSet())
}
}.filterNotNull()
}
diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/constraintIncorporation.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/constraintIncorporation.kt
index e39eab674cd..8066ab7ea18 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/constraintIncorporation.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/calls/inference/constraintIncorporation.kt
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.Variance.INVARIANT
import org.jetbrains.kotlin.types.typeUtil.getNestedTypeArguments
import org.jetbrains.kotlin.types.typesApproximation.approximateCapturedTypes
+import java.util.*
fun ConstraintSystemImpl.incorporateBound(newBound: Bound) {
val typeVariable = newBound.typeVariable
@@ -92,9 +93,14 @@ private fun ConstraintSystemImpl.generateNewBound(bound: Bound, substitution: Bo
fun addNewBound(newConstrainingType: JetType, newBoundKind: BoundKind) {
// We don't generate new recursive constraints
val nestedTypeVariables = newConstrainingType.getNestedTypeVariables()
- if (nestedTypeVariables.contains(bound.typeVariable) || nestedTypeVariables.contains(substitution.typeVariable)) return
+ if (nestedTypeVariables.contains(bound.typeVariable)) return
- addBound(bound.typeVariable, newConstrainingType, newBoundKind, position)
+ // We don't generate constraint if a type variable was substituted twice
+ val derivedFrom = HashSet(bound.derivedFrom + substitution.derivedFrom)
+ if (derivedFrom.contains(substitution.typeVariable)) return
+
+ derivedFrom.add(substitution.typeVariable)
+ addBound(bound.typeVariable, newConstrainingType, newBoundKind, position, derivedFrom)
}
if (substitution.kind == EXACT_BOUND) {