diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirReferenceShortener.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirReferenceShortener.kt index 82049cb91ed..a65ee884f5a 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirReferenceShortener.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirReferenceShortener.kt @@ -78,7 +78,11 @@ internal class KtFirReferenceShortener( ?: file.withDeclarationsResolvedToBodyResolve() val firDeclaration = declarationToVisit.getOrBuildFir(firResolveSession) as? FirDeclaration ?: return ShortenCommandImpl( - file.createSmartPointer(), emptyList(), emptyList(), emptyList(), emptyList(), + file.createSmartPointer(), + importsToAdd = emptyList(), + starImportsToAdd = emptyList(), + typesToShorten = emptyList(), + qualifiersToShorten = emptyList(), ) val towerContext = @@ -98,10 +102,10 @@ internal class KtFirReferenceShortener( return ShortenCommandImpl( file.createSmartPointer(), - collector.namesToImport.distinct(), - collector.namesToImportWithStar.distinct(), - collector.typesToShorten.distinct().map { it.createSmartPointer() }, - collector.qualifiersToShorten.distinct().map { it.createSmartPointer() } + collector.getNamesToImport(starImport = false).toList(), + collector.getNamesToImport(starImport = true).toList(), + collector.typesToShorten.map { it.element }.distinct().map { it.createSmartPointer() }, + collector.qualifiersToShorten.map { it.element }.distinct().map { it.createSmartPointer() } ) } @@ -231,7 +235,7 @@ private class FirShorteningContext(val analysisSession: KtFirAnalysisSession) { fun findScopesAtPosition( position: KtElement, - newImports: List, + newImports: Sequence, towerContextProvider: FirTowerContextProvider ): List? { val towerDataContext = towerContextProvider.getClosestAvailableParentContext(position) ?: return null @@ -248,8 +252,8 @@ private class FirShorteningContext(val analysisSession: KtFirAnalysisSession) { return result.asReversed() } - private fun createFakeImportingScope(newImports: List): FirScope? { - val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) } + private fun createFakeImportingScope(newImports: Sequence): FirScope? { + val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) }.toList() if (resolvedNewImports.isEmpty()) return null return FirExplicitSimpleImportingScope( @@ -311,10 +315,8 @@ private class ElementsToShortenCollector( private val firResolveSession: LLFirResolveSession, ) : FirVisitorVoid() { - val namesToImport: MutableList = mutableListOf() - val namesToImportWithStar: MutableList = mutableListOf() - val typesToShorten: MutableList = mutableListOf() - val qualifiersToShorten: MutableList = mutableListOf() + val typesToShorten: MutableList = mutableListOf() + val qualifiersToShorten: MutableList = mutableListOf() private val visitedProperty = mutableSetOf() override fun visitValueParameter(valueParameter: FirValueParameter) { @@ -392,8 +394,14 @@ private class ElementsToShortenCollector( } } + fun getNamesToImport(starImport: Boolean = false): Sequence = sequence { + yieldAll(typesToShorten) + yieldAll(qualifiersToShorten) + }.filter { starImport == it.importAllInParent }.mapNotNull { it.nameToImport }.distinct() + private fun findTypeToShorten(wholeClassifierId: ClassId, wholeTypeElement: KtUserType): ElementToShorten? { - val positionScopes = shorteningContext.findScopesAtPosition(wholeTypeElement, namesToImport, towerContextProvider) ?: return null + val positionScopes = + shorteningContext.findScopesAtPosition(wholeTypeElement, getNamesToImport(), towerContextProvider) ?: return null val allClassIds = wholeClassifierId.outerClassesWithSelf val allQualifiedTypeElements = wholeTypeElement.qualifiedTypesWithSelf return findClassifierElementsToShorten( @@ -429,7 +437,7 @@ private class ElementsToShortenCollector( wholeQualifierElement: KtDotQualifiedExpression ): ElementToShorten? { val positionScopes: List = - shorteningContext.findScopesAtPosition(wholeQualifierElement, namesToImport, towerContextProvider) ?: return null + shorteningContext.findScopesAtPosition(wholeQualifierElement, getNamesToImport(), towerContextProvider) ?: return null val allClassIds: Sequence = wholeClassQualifier.outerClassesWithSelf val allQualifiers: Sequence = wholeQualifierElement.qualifiedExpressionsWithSelf return findClassifierElementsToShorten( @@ -862,7 +870,7 @@ private class ElementsToShortenCollector( val option = callableShortenOption(callableSymbol) if (option == ShortenOption.DO_NOT_SHORTEN) return - val scopes = shorteningContext.findScopesAtPosition(qualifiedProperty, namesToImport, towerContextProvider) ?: return + val scopes = shorteningContext.findScopesAtPosition(qualifiedProperty, getNamesToImport(), towerContextProvider) ?: return val availableCallables = shorteningContext.findPropertiesInScopes(scopes, callableSymbol.name) val firPropertyAccess = qualifiedProperty.getOrBuildFir(firResolveSession) as? FirQualifiedAccessExpression ?: return @@ -893,7 +901,7 @@ private class ElementsToShortenCollector( val option = callableShortenOption(calledSymbol) if (option == ShortenOption.DO_NOT_SHORTEN) return - val scopes = shorteningContext.findScopesAtPosition(callExpression, namesToImport, towerContextProvider) ?: return + val scopes = shorteningContext.findScopesAtPosition(callExpression, getNamesToImport(), towerContextProvider) ?: return val availableCallables = shorteningContext.findFunctionsInScopes(scopes, calledSymbol.name) if (availableCallables.isNotEmpty() && shortenIfAlreadyImported(functionCall, calledSymbol, callExpression)) { addElementToShorten(ShortenQualifier(qualifiedCallExpression)) @@ -986,22 +994,49 @@ private class ElementsToShortenCollector( return if (deepestQualifier.hasFakeRootPrefix()) ShortenQualifier(deepestQualifier) else null } - private fun addElementToShorten(element: ElementToShorten) { - if (element.importAllInParent && element.nameToImport?.parentOrNull()?.isRoot == false) { - namesToImportWithStar.addIfNotNull(element.nameToImport?.parent()) - } else { - namesToImport.addIfNotNull(element.nameToImport) + private fun KtElement.isInsideOf(another: KtElement): Boolean = another.textRange.contains(textRange) + + /** + * Remove entries from [typesToShorten] and [qualifiersToShorten] if their qualifiers will be shortened + * when we shorten [qualifier]. + */ + private fun removeRedundantElements(qualifier: KtElement) { + typesToShorten.removeAll { it.element.qualifier?.isInsideOf(qualifier) == true } + qualifiersToShorten.removeAll { it.element.receiverExpression.isInsideOf(qualifier) } + } + + private fun KtElement.isAlreadyCollected(): Boolean { + val thisElement = this + return typesToShorten.any { shortenType -> + shortenType.element.qualifier?.let { thisElement.isInsideOf(it) } == true + } || qualifiersToShorten.any { shortenQualifier -> + thisElement.isInsideOf(shortenQualifier.element.receiverExpression) } - when (element) { - is ShortenType -> { - typesToShorten.add(element.element) - } - is ShortenQualifier -> { - qualifiersToShorten.add(element.element) + } + + private fun addElementToShorten(element: KtElement, nameToImport: FqName?, isImportWithStar: Boolean) { + val qualifier = element.getQualifier() ?: return + if (!qualifier.isAlreadyCollected()) { + removeRedundantElements(qualifier) + when (element) { + is KtUserType -> typesToShorten.add(ShortenType(element, nameToImport, isImportWithStar)) + is KtDotQualifiedExpression -> qualifiersToShorten.add(ShortenQualifier(element, nameToImport, isImportWithStar)) } } } + private fun addElementToShorten(elementInfoToShorten: ElementToShorten) { + val (nameToImport, isImportWithStar) = if (elementInfoToShorten.importAllInParent && elementInfoToShorten.nameToImport?.parentOrNull()?.isRoot == false) { + elementInfoToShorten.nameToImport?.parent() to true + } else { + elementInfoToShorten.nameToImport to false + } + when (elementInfoToShorten) { + is ShortenType -> addElementToShorten(elementInfoToShorten.element, nameToImport, isImportWithStar) + is ShortenQualifier -> addElementToShorten(elementInfoToShorten.element, nameToImport, isImportWithStar) + } + } + private val ClassId.outerClassesWithSelf: Sequence get() = generateSequence(this) { it.outerClassId } @@ -1076,4 +1111,10 @@ internal fun KtElement.getDotQualifiedExpressionForSelector(): KtDotQualifiedExp private fun KtDotQualifiedExpression.deleteQualifier(): KtExpression? { val selectorExpression = selectorExpression ?: return null return this.replace(selectorExpression) as KtExpression +} + +private fun KtElement.getQualifier(): KtElement? = when (this) { + is KtUserType -> qualifier + is KtDotQualifiedExpression -> receiverExpression + else -> null } \ No newline at end of file diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java index 8c41aea6cba..80c1a6562f1 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java @@ -184,6 +184,12 @@ public class FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated ext runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumEntryInitUsesCompanion2.kt"); } + @Test + @TestMetadata("multipleImport.kt") + public void testMultipleImport() throws Exception { + runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.kt"); + } + @Test @TestMetadata("nestedClass.kt") public void testNestedClass() throws Exception { diff --git a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java index 8cf383a6aae..de2f13a3ce4 100644 --- a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java +++ b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceShortenerTestGenerated.java @@ -184,6 +184,12 @@ public class FirStandaloneNormalAnalysisSourceModuleReferenceShortenerTestGenera runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumEntryInitUsesCompanion2.kt"); } + @Test + @TestMetadata("multipleImport.kt") + public void testMultipleImport() throws Exception { + runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.kt"); + } + @Test @TestMetadata("nestedClass.kt") public void testNestedClass() throws Exception { diff --git a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/classScopes2.txt b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/classScopes2.txt index ca2e835b02d..4ef59b665b1 100644 --- a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/classScopes2.txt +++ b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/classScopes2.txt @@ -1,11 +1,8 @@ Before shortening: Child.Foo.check() with DO_NOT_SHORTEN: with SHORTEN_IF_ALREADY_IMPORTED: -[qualifier] Child.Foo [qualifier] Child.Foo.check() with SHORTEN_AND_IMPORT: -[qualifier] Child.Foo [qualifier] Child.Foo.check() with SHORTEN_AND_STAR_IMPORT: -[qualifier] Child.Foo [qualifier] Child.Foo.check() diff --git a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumClassCompanionAlreadyImported.txt b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumClassCompanionAlreadyImported.txt index 7b0784b6498..5ce590d2266 100644 --- a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumClassCompanionAlreadyImported.txt +++ b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumClassCompanionAlreadyImported.txt @@ -1,11 +1,8 @@ Before shortening: a.b.c.MyEnum.Companion.foo() with DO_NOT_SHORTEN: with SHORTEN_IF_ALREADY_IMPORTED: -[qualifier] a.b.c.MyEnum [qualifier] a.b.c.MyEnum.Companion.foo() with SHORTEN_AND_IMPORT: -[qualifier] a.b.c.MyEnum.Companion [qualifier] a.b.c.MyEnum.Companion.foo() with SHORTEN_AND_STAR_IMPORT: -[qualifier] a.b.c.MyEnum.Companion [qualifier] a.b.c.MyEnum.Companion.foo() diff --git a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.kt b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.kt new file mode 100644 index 00000000000..9d20fe7cbd2 --- /dev/null +++ b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.kt @@ -0,0 +1,16 @@ +// FILE: main.kt +package a.b.c + +fun test(n: Int) { + return if (x.y.z.Outer.Inner.VALUE0 > x.y.z.Outer.Inner.VALUE1) 1 + else n +} +// FILE: values.kt +package x.y.z + +class Outer { + object Inner { + val VALUE0 = 13 + val VALUE1 = 17 + } +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.txt b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.txt new file mode 100644 index 00000000000..9cac65eeab0 --- /dev/null +++ b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/multipleImport.txt @@ -0,0 +1,9 @@ +Before shortening: x.y.z.Outer.Inner.VALUE0 > x.y.z.Outer.Inner.VALUE1 +with DO_NOT_SHORTEN: +with SHORTEN_IF_ALREADY_IMPORTED: +with SHORTEN_AND_IMPORT: +[qualifier] x.y.z.Outer.Inner.VALUE0 +[qualifier] x.y.z.Outer.Inner.VALUE1 +with SHORTEN_AND_STAR_IMPORT: +[qualifier] x.y.z.Outer.Inner.VALUE0 +[qualifier] x.y.z.Outer.Inner.VALUE1 diff --git a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/shortenAlreadyImportedFunction2.txt b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/shortenAlreadyImportedFunction2.txt index cf9ca641e97..a216ed179be 100644 --- a/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/shortenAlreadyImportedFunction2.txt +++ b/analysis/analysis-api/testData/components/referenceShortener/referenceShortener/shortenAlreadyImportedFunction2.txt @@ -1,11 +1,8 @@ Before shortening: Outer.Inner.foo() with DO_NOT_SHORTEN: with SHORTEN_IF_ALREADY_IMPORTED: -[qualifier] Outer.Inner [qualifier] Outer.Inner.foo() with SHORTEN_AND_IMPORT: -[qualifier] Outer.Inner [qualifier] Outer.Inner.foo() with SHORTEN_AND_STAR_IMPORT: -[qualifier] Outer.Inner [qualifier] Outer.Inner.foo()