[K2] Avoid shortening duplicated PSI elements
The existing K2 reference shortener collects all the PSI elements to
shorten. As a result, it possibly shortens duplicated PSI elements. For
example,
```
// FILE: main.kt
package a.b.c
fun test(n: Int) {
return if (<expr>x.y.z.Outer.Inner.VALUE0 > x.y.z.Outer.Inner.VALUE1</expr>) 1
else n
}
// FILE: values.kt
package x.y.z
class Outer {
object Inner {
val VALUE0 = 13
val VALUE1 = 17
}
}
```
for the above code, the existing K2 reference shortener tried to shorten
- x.y.z.Outer.Inner -> Inner
- x.y.z.Outer.Inner.VALUE0 -> VALUE0
- x.y.z.Outer.Inner -> Inner
- x.y.z.Outer.Inner.VALUE1 -> VALUE1
`x.y.z.Outer.Inner` is included in the list to shorten twice.
When it actually shortens the PSI elements, it shortens only
- x.y.z.Outer.Inner.VALUE0 -> VALUE0
- x.y.z.Outer.Inner.VALUE1 -> VALUE1
but it imports all of
- x.y.z.Outer.Inner
- x.y.z.Outer.Inner.VALUE0
- x.y.z.Outer.Inner.VALUE1
As a result, it has unnecessary additional import directives.
This commit fixes the issue by avoiding duplicated shortening for a
single PSI element.
This commit is contained in:
+68
-27
@@ -78,7 +78,11 @@ internal class KtFirReferenceShortener(
|
|||||||
?: file.withDeclarationsResolvedToBodyResolve()
|
?: file.withDeclarationsResolvedToBodyResolve()
|
||||||
|
|
||||||
val firDeclaration = declarationToVisit.getOrBuildFir(firResolveSession) as? FirDeclaration ?: return ShortenCommandImpl(
|
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 =
|
val towerContext =
|
||||||
@@ -98,10 +102,10 @@ internal class KtFirReferenceShortener(
|
|||||||
|
|
||||||
return ShortenCommandImpl(
|
return ShortenCommandImpl(
|
||||||
file.createSmartPointer(),
|
file.createSmartPointer(),
|
||||||
collector.namesToImport.distinct(),
|
collector.getNamesToImport(starImport = false).toList(),
|
||||||
collector.namesToImportWithStar.distinct(),
|
collector.getNamesToImport(starImport = true).toList(),
|
||||||
collector.typesToShorten.distinct().map { it.createSmartPointer() },
|
collector.typesToShorten.map { it.element }.distinct().map { it.createSmartPointer() },
|
||||||
collector.qualifiersToShorten.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(
|
fun findScopesAtPosition(
|
||||||
position: KtElement,
|
position: KtElement,
|
||||||
newImports: List<FqName>,
|
newImports: Sequence<FqName>,
|
||||||
towerContextProvider: FirTowerContextProvider
|
towerContextProvider: FirTowerContextProvider
|
||||||
): List<FirScope>? {
|
): List<FirScope>? {
|
||||||
val towerDataContext = towerContextProvider.getClosestAvailableParentContext(position) ?: return null
|
val towerDataContext = towerContextProvider.getClosestAvailableParentContext(position) ?: return null
|
||||||
@@ -248,8 +252,8 @@ private class FirShorteningContext(val analysisSession: KtFirAnalysisSession) {
|
|||||||
return result.asReversed()
|
return result.asReversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFakeImportingScope(newImports: List<FqName>): FirScope? {
|
private fun createFakeImportingScope(newImports: Sequence<FqName>): FirScope? {
|
||||||
val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) }
|
val resolvedNewImports = newImports.mapNotNull { createFakeResolvedImport(it) }.toList()
|
||||||
if (resolvedNewImports.isEmpty()) return null
|
if (resolvedNewImports.isEmpty()) return null
|
||||||
|
|
||||||
return FirExplicitSimpleImportingScope(
|
return FirExplicitSimpleImportingScope(
|
||||||
@@ -311,10 +315,8 @@ private class ElementsToShortenCollector(
|
|||||||
private val firResolveSession: LLFirResolveSession,
|
private val firResolveSession: LLFirResolveSession,
|
||||||
) :
|
) :
|
||||||
FirVisitorVoid() {
|
FirVisitorVoid() {
|
||||||
val namesToImport: MutableList<FqName> = mutableListOf()
|
val typesToShorten: MutableList<ShortenType> = mutableListOf()
|
||||||
val namesToImportWithStar: MutableList<FqName> = mutableListOf()
|
val qualifiersToShorten: MutableList<ShortenQualifier> = mutableListOf()
|
||||||
val typesToShorten: MutableList<KtUserType> = mutableListOf()
|
|
||||||
val qualifiersToShorten: MutableList<KtDotQualifiedExpression> = mutableListOf()
|
|
||||||
private val visitedProperty = mutableSetOf<FirProperty>()
|
private val visitedProperty = mutableSetOf<FirProperty>()
|
||||||
|
|
||||||
override fun visitValueParameter(valueParameter: FirValueParameter) {
|
override fun visitValueParameter(valueParameter: FirValueParameter) {
|
||||||
@@ -392,8 +394,14 @@ private class ElementsToShortenCollector(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getNamesToImport(starImport: Boolean = false): Sequence<FqName> = sequence {
|
||||||
|
yieldAll(typesToShorten)
|
||||||
|
yieldAll(qualifiersToShorten)
|
||||||
|
}.filter { starImport == it.importAllInParent }.mapNotNull { it.nameToImport }.distinct()
|
||||||
|
|
||||||
private fun findTypeToShorten(wholeClassifierId: ClassId, wholeTypeElement: KtUserType): ElementToShorten? {
|
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 allClassIds = wholeClassifierId.outerClassesWithSelf
|
||||||
val allQualifiedTypeElements = wholeTypeElement.qualifiedTypesWithSelf
|
val allQualifiedTypeElements = wholeTypeElement.qualifiedTypesWithSelf
|
||||||
return findClassifierElementsToShorten(
|
return findClassifierElementsToShorten(
|
||||||
@@ -429,7 +437,7 @@ private class ElementsToShortenCollector(
|
|||||||
wholeQualifierElement: KtDotQualifiedExpression
|
wholeQualifierElement: KtDotQualifiedExpression
|
||||||
): ElementToShorten? {
|
): ElementToShorten? {
|
||||||
val positionScopes: List<FirScope> =
|
val positionScopes: List<FirScope> =
|
||||||
shorteningContext.findScopesAtPosition(wholeQualifierElement, namesToImport, towerContextProvider) ?: return null
|
shorteningContext.findScopesAtPosition(wholeQualifierElement, getNamesToImport(), towerContextProvider) ?: return null
|
||||||
val allClassIds: Sequence<ClassId> = wholeClassQualifier.outerClassesWithSelf
|
val allClassIds: Sequence<ClassId> = wholeClassQualifier.outerClassesWithSelf
|
||||||
val allQualifiers: Sequence<KtDotQualifiedExpression> = wholeQualifierElement.qualifiedExpressionsWithSelf
|
val allQualifiers: Sequence<KtDotQualifiedExpression> = wholeQualifierElement.qualifiedExpressionsWithSelf
|
||||||
return findClassifierElementsToShorten(
|
return findClassifierElementsToShorten(
|
||||||
@@ -862,7 +870,7 @@ private class ElementsToShortenCollector(
|
|||||||
val option = callableShortenOption(callableSymbol)
|
val option = callableShortenOption(callableSymbol)
|
||||||
if (option == ShortenOption.DO_NOT_SHORTEN) return
|
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 availableCallables = shorteningContext.findPropertiesInScopes(scopes, callableSymbol.name)
|
||||||
|
|
||||||
val firPropertyAccess = qualifiedProperty.getOrBuildFir(firResolveSession) as? FirQualifiedAccessExpression ?: return
|
val firPropertyAccess = qualifiedProperty.getOrBuildFir(firResolveSession) as? FirQualifiedAccessExpression ?: return
|
||||||
@@ -893,7 +901,7 @@ private class ElementsToShortenCollector(
|
|||||||
val option = callableShortenOption(calledSymbol)
|
val option = callableShortenOption(calledSymbol)
|
||||||
if (option == ShortenOption.DO_NOT_SHORTEN) return
|
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)
|
val availableCallables = shorteningContext.findFunctionsInScopes(scopes, calledSymbol.name)
|
||||||
if (availableCallables.isNotEmpty() && shortenIfAlreadyImported(functionCall, calledSymbol, callExpression)) {
|
if (availableCallables.isNotEmpty() && shortenIfAlreadyImported(functionCall, calledSymbol, callExpression)) {
|
||||||
addElementToShorten(ShortenQualifier(qualifiedCallExpression))
|
addElementToShorten(ShortenQualifier(qualifiedCallExpression))
|
||||||
@@ -986,22 +994,49 @@ private class ElementsToShortenCollector(
|
|||||||
return if (deepestQualifier.hasFakeRootPrefix()) ShortenQualifier(deepestQualifier) else null
|
return if (deepestQualifier.hasFakeRootPrefix()) ShortenQualifier(deepestQualifier) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addElementToShorten(element: ElementToShorten) {
|
private fun KtElement.isInsideOf(another: KtElement): Boolean = another.textRange.contains(textRange)
|
||||||
if (element.importAllInParent && element.nameToImport?.parentOrNull()?.isRoot == false) {
|
|
||||||
namesToImportWithStar.addIfNotNull(element.nameToImport?.parent())
|
/**
|
||||||
} else {
|
* Remove entries from [typesToShorten] and [qualifiersToShorten] if their qualifiers will be shortened
|
||||||
namesToImport.addIfNotNull(element.nameToImport)
|
* 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)
|
private fun addElementToShorten(element: KtElement, nameToImport: FqName?, isImportWithStar: Boolean) {
|
||||||
}
|
val qualifier = element.getQualifier() ?: return
|
||||||
is ShortenQualifier -> {
|
if (!qualifier.isAlreadyCollected()) {
|
||||||
qualifiersToShorten.add(element.element)
|
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<ClassId>
|
private val ClassId.outerClassesWithSelf: Sequence<ClassId>
|
||||||
get() = generateSequence(this) { it.outerClassId }
|
get() = generateSequence(this) { it.outerClassId }
|
||||||
|
|
||||||
@@ -1076,4 +1111,10 @@ internal fun KtElement.getDotQualifiedExpressionForSelector(): KtDotQualifiedExp
|
|||||||
private fun KtDotQualifiedExpression.deleteQualifier(): KtExpression? {
|
private fun KtDotQualifiedExpression.deleteQualifier(): KtExpression? {
|
||||||
val selectorExpression = selectorExpression ?: return null
|
val selectorExpression = selectorExpression ?: return null
|
||||||
return this.replace(selectorExpression) as KtExpression
|
return this.replace(selectorExpression) as KtExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtElement.getQualifier(): KtElement? = when (this) {
|
||||||
|
is KtUserType -> qualifier
|
||||||
|
is KtDotQualifiedExpression -> receiverExpression
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
+6
@@ -184,6 +184,12 @@ public class FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated ext
|
|||||||
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumEntryInitUsesCompanion2.kt");
|
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
|
@Test
|
||||||
@TestMetadata("nestedClass.kt")
|
@TestMetadata("nestedClass.kt")
|
||||||
public void testNestedClass() throws Exception {
|
public void testNestedClass() throws Exception {
|
||||||
|
|||||||
+6
@@ -184,6 +184,12 @@ public class FirStandaloneNormalAnalysisSourceModuleReferenceShortenerTestGenera
|
|||||||
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/enumEntryInitUsesCompanion2.kt");
|
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
|
@Test
|
||||||
@TestMetadata("nestedClass.kt")
|
@TestMetadata("nestedClass.kt")
|
||||||
public void testNestedClass() throws Exception {
|
public void testNestedClass() throws Exception {
|
||||||
|
|||||||
Vendored
-3
@@ -1,11 +1,8 @@
|
|||||||
Before shortening: Child.Foo.check()
|
Before shortening: Child.Foo.check()
|
||||||
with DO_NOT_SHORTEN:
|
with DO_NOT_SHORTEN:
|
||||||
with SHORTEN_IF_ALREADY_IMPORTED:
|
with SHORTEN_IF_ALREADY_IMPORTED:
|
||||||
[qualifier] Child.Foo
|
|
||||||
[qualifier] Child.Foo.check()
|
[qualifier] Child.Foo.check()
|
||||||
with SHORTEN_AND_IMPORT:
|
with SHORTEN_AND_IMPORT:
|
||||||
[qualifier] Child.Foo
|
|
||||||
[qualifier] Child.Foo.check()
|
[qualifier] Child.Foo.check()
|
||||||
with SHORTEN_AND_STAR_IMPORT:
|
with SHORTEN_AND_STAR_IMPORT:
|
||||||
[qualifier] Child.Foo
|
|
||||||
[qualifier] Child.Foo.check()
|
[qualifier] Child.Foo.check()
|
||||||
|
|||||||
-3
@@ -1,11 +1,8 @@
|
|||||||
Before shortening: a.b.c.MyEnum.Companion.foo()
|
Before shortening: a.b.c.MyEnum.Companion.foo()
|
||||||
with DO_NOT_SHORTEN:
|
with DO_NOT_SHORTEN:
|
||||||
with SHORTEN_IF_ALREADY_IMPORTED:
|
with SHORTEN_IF_ALREADY_IMPORTED:
|
||||||
[qualifier] a.b.c.MyEnum
|
|
||||||
[qualifier] a.b.c.MyEnum.Companion.foo()
|
[qualifier] a.b.c.MyEnum.Companion.foo()
|
||||||
with SHORTEN_AND_IMPORT:
|
with SHORTEN_AND_IMPORT:
|
||||||
[qualifier] a.b.c.MyEnum.Companion
|
|
||||||
[qualifier] a.b.c.MyEnum.Companion.foo()
|
[qualifier] a.b.c.MyEnum.Companion.foo()
|
||||||
with SHORTEN_AND_STAR_IMPORT:
|
with SHORTEN_AND_STAR_IMPORT:
|
||||||
[qualifier] a.b.c.MyEnum.Companion
|
|
||||||
[qualifier] a.b.c.MyEnum.Companion.foo()
|
[qualifier] a.b.c.MyEnum.Companion.foo()
|
||||||
|
|||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// FILE: main.kt
|
||||||
|
package a.b.c
|
||||||
|
|
||||||
|
fun test(n: Int) {
|
||||||
|
return if (<expr>x.y.z.Outer.Inner.VALUE0 > x.y.z.Outer.Inner.VALUE1</expr>) 1
|
||||||
|
else n
|
||||||
|
}
|
||||||
|
// FILE: values.kt
|
||||||
|
package x.y.z
|
||||||
|
|
||||||
|
class Outer {
|
||||||
|
object Inner {
|
||||||
|
val VALUE0 = 13
|
||||||
|
val VALUE1 = 17
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+9
@@ -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
|
||||||
-3
@@ -1,11 +1,8 @@
|
|||||||
Before shortening: Outer.Inner.foo()
|
Before shortening: Outer.Inner.foo()
|
||||||
with DO_NOT_SHORTEN:
|
with DO_NOT_SHORTEN:
|
||||||
with SHORTEN_IF_ALREADY_IMPORTED:
|
with SHORTEN_IF_ALREADY_IMPORTED:
|
||||||
[qualifier] Outer.Inner
|
|
||||||
[qualifier] Outer.Inner.foo()
|
[qualifier] Outer.Inner.foo()
|
||||||
with SHORTEN_AND_IMPORT:
|
with SHORTEN_AND_IMPORT:
|
||||||
[qualifier] Outer.Inner
|
|
||||||
[qualifier] Outer.Inner.foo()
|
[qualifier] Outer.Inner.foo()
|
||||||
with SHORTEN_AND_STAR_IMPORT:
|
with SHORTEN_AND_STAR_IMPORT:
|
||||||
[qualifier] Outer.Inner
|
|
||||||
[qualifier] Outer.Inner.foo()
|
[qualifier] Outer.Inner.foo()
|
||||||
|
|||||||
Reference in New Issue
Block a user