diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirPsiTypeProvider.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirPsiTypeProvider.kt index 678d2fc6bee..06f442a9f50 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirPsiTypeProvider.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirPsiTypeProvider.kt @@ -11,6 +11,7 @@ import com.intellij.psi.impl.cache.TypeInfo import com.intellij.psi.impl.compiled.ClsTypeElementImpl import com.intellij.psi.impl.compiled.SignatureParsing import com.intellij.psi.impl.compiled.StubBuildingVisitor +import java.text.StringCharacterIterator import org.jetbrains.kotlin.analysis.api.components.KtPsiTypeProvider import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.analysis.api.fir.types.KtFirType @@ -42,7 +43,6 @@ import org.jetbrains.kotlin.psi import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.types.model.SimpleTypeMarker -import java.text.StringCharacterIterator internal class KtFirPsiTypeProvider( override val analysisSession: KtFirAnalysisSession, @@ -238,7 +238,15 @@ private class AnonymousTypesSubstitutor( if (type !is ConeClassLikeType) return null val hasStableName = type.classId?.isLocal == true - if (!hasStableName) return null + if (!hasStableName) { + // Make sure we're not going to expand type argument over and over again. + // If so, i.e., if there is a recursive type argument, return the current, non-null [type] + // to prevent the following [substituteTypeOr*] from proceeding to its own (recursive) substitution. + if (type.hasRecursiveTypeArgument()) return type + // Return `null` means we will use [fir.resolve.substitution.Substitutors]'s [substituteRecursive] + // that literally substitutes type arguments recursively. + return null + } val firClassNode = type.lookupTag.toSymbol(session) as? FirClassSymbol if (firClassNode != null) { @@ -248,4 +256,22 @@ private class AnonymousTypesSubstitutor( return if (type.nullability.isNullable) session.builtinTypes.nullableAnyType.type else session.builtinTypes.anyType.type } + + private fun ConeKotlinType.hasRecursiveTypeArgument( + visited: MutableSet = mutableSetOf() + ): Boolean { + if (typeArguments.isEmpty()) return false + visited.add(this) + for (projection in typeArguments) { + // E.g., Test : Comparable + val type = (projection as? ConeKotlinTypeProjection)?.type ?: continue + // E.g., Comparable + val newType = substituteOrNull(type) ?: continue + if (newType in visited) return true + // Visit new type: e.g., Test, as a type argument, is substituted with Comparable, again. + if (newType.hasRecursiveTypeArgument(visited)) return true + } + return false + } + } diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java index b8bad01a42d..ecafad0d61f 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java @@ -64,6 +64,12 @@ public class FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProv runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/localClassWithUnresolvedSuperType.kt"); } + @Test + @TestMetadata("recursiveTypeParameter.kt") + public void testRecursiveTypeParameter() throws Exception { + runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.kt"); + } + @Test @TestMetadata("typeParamFlexibleUpperBound.kt") public void testTypeParamFlexibleUpperBound() throws Exception { diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java index e7026eb6263..64776992ff2 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/psiTypeProvider/FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java @@ -64,6 +64,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProvide runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/localClassWithUnresolvedSuperType.kt"); } + @Test + @TestMetadata("recursiveTypeParameter.kt") + public void testRecursiveTypeParameter() throws Exception { + runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.kt"); + } + @Test @TestMetadata("typeParamFlexibleUpperBound.kt") public void testTypeParamFlexibleUpperBound() throws Exception { diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/psiTypeProvider/AbstractAnalysisApiExpressionPsiTypeProviderTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/psiTypeProvider/AbstractAnalysisApiExpressionPsiTypeProviderTest.kt index 0d348a15c8e..44865d1ac24 100644 --- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/psiTypeProvider/AbstractAnalysisApiExpressionPsiTypeProviderTest.kt +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/psiTypeProvider/AbstractAnalysisApiExpressionPsiTypeProviderTest.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.analysis.utils.printer.parentOfType import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtValueArgument import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.assertions @@ -21,7 +22,11 @@ import org.jetbrains.kotlin.types.Variance abstract class AbstractAnalysisApiExpressionPsiTypeProviderTest : AbstractAnalysisApiSingleFileTest() { override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) { - val declarationAtCaret = testServices.expressionMarkerProvider.getSelectedElement(ktFile) as KtExpression + val declarationAtCaret = when (val element = testServices.expressionMarkerProvider.getSelectedElement(ktFile)) { + is KtExpression -> element + is KtValueArgument -> element.getArgumentExpression()!! + else -> error("Unexpected element: $element of ${element::class.java}") + } val containingDeclaration = declarationAtCaret.parentOfType() ?: error("Can't find containing declaration for $declarationAtCaret") val containingClass = getContainingKtLightClass(containingDeclaration, ktFile) diff --git a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/psiTypeProvider/FirStandaloneNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/psiTypeProvider/FirStandaloneNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java index f0fc22934b9..5b91aa324eb 100644 --- a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/psiTypeProvider/FirStandaloneNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java +++ b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/psiTypeProvider/FirStandaloneNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProviderTestGenerated.java @@ -64,6 +64,12 @@ public class FirStandaloneNormalAnalysisSourceModuleAnalysisApiExpressionPsiType runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/localClassWithUnresolvedSuperType.kt"); } + @Test + @TestMetadata("recursiveTypeParameter.kt") + public void testRecursiveTypeParameter() throws Exception { + runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.kt"); + } + @Test @TestMetadata("typeParamFlexibleUpperBound.kt") public void testTypeParamFlexibleUpperBound() throws Exception { diff --git a/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.kt b/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.kt new file mode 100644 index 00000000000..a00650b6aab --- /dev/null +++ b/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.kt @@ -0,0 +1,14 @@ +// WITH_STDLIB + +fun testNotTransitive() { + class Test(val v: Int): Comparable { + override fun compareTo(other: Test): Int { + return v.compareTo(-other.v) + } + } + val list = listOf(Test(1), Test(2), Test(3), Test(4)) + checkTransitiveComparator(list) +} + +fun > checkTransitiveComparator(list: List) { +} diff --git a/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.txt b/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.txt new file mode 100644 index 00000000000..8885140f23e --- /dev/null +++ b/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/recursiveTypeParameter.txt @@ -0,0 +1,2 @@ +KtType: kotlin.collections.List +PsiType: PsiType:List>