KTIJ-26103 [Analysis API] Ignore implicit dispatch receivers in Reference Shortener

Calls to extension functions imported from objects have implicit
dispatch receivers, but those receivers are not present in the code
and should not be analyzed by the shortener

^KTIJ-26103 Fixed
This commit is contained in:
Roman Golyshev
2023-07-03 15:57:49 +02:00
committed by teamcity
parent 8d2ac41454
commit c5ded1ba48
7 changed files with 57 additions and 2 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.analysis.api.fir package org.jetbrains.kotlin.analysis.api.fir
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationInfo import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationInfo
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo
import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue
@@ -17,6 +18,7 @@ import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.expressions.arguments import org.jetbrains.kotlin.fir.expressions.arguments
import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
@@ -94,3 +96,21 @@ internal fun FirAnnotation.toKtAnnotationInfo(
isCallWithArguments = this is FirAnnotationCall && arguments.isNotEmpty(), isCallWithArguments = this is FirAnnotationCall && arguments.isNotEmpty(),
index = index, index = index,
) )
/**
* Implicit dispatch receiver is present when an extension function declared in object
* is imported somewhere else and used without directly referencing the object instance
* itself:
*
* ```kt
* import Foo.bar
*
* object Foo { fun String.bar() {} }
*
* fun usage() {
* "hello".bar() // this call has implicit 'Foo' dispatch receiver
* }
* ```
*/
internal val FirResolvedQualifier.isImplicitDispatchReceiver: Boolean
get() = source?.kind == KtFakeSourceElementKind.ImplicitReceiver
@@ -5,11 +5,11 @@
package org.jetbrains.kotlin.analysis.api.fir.components package org.jetbrains.kotlin.analysis.api.fir.components
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtRealSourceElementKind import org.jetbrains.kotlin.KtRealSourceElementKind
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizer import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizer
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizerResult import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizerResult
import org.jetbrains.kotlin.analysis.api.fir.getCandidateSymbols import org.jetbrains.kotlin.analysis.api.fir.getCandidateSymbols
import org.jetbrains.kotlin.analysis.api.fir.isImplicitDispatchReceiver
import org.jetbrains.kotlin.analysis.api.fir.utils.FirBodyReanalyzingVisitorVoid import org.jetbrains.kotlin.analysis.api.fir.utils.FirBodyReanalyzingVisitorVoid
import org.jetbrains.kotlin.analysis.api.fir.utils.computeImportableName import org.jetbrains.kotlin.analysis.api.fir.utils.computeImportableName
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
@@ -274,7 +274,7 @@ internal class KtFirImportOptimizer(
val dispatchReceiver = dispatchReceiver val dispatchReceiver = dispatchReceiver
if ( if (
dispatchReceiver !is FirResolvedQualifier || dispatchReceiver !is FirResolvedQualifier ||
dispatchReceiver.source?.kind != KtFakeSourceElementKind.ImplicitReceiver !dispatchReceiver.isImplicitDispatchReceiver
) { ) {
return null return null
} }
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.analysis.api.components.ShortenCommand
import org.jetbrains.kotlin.analysis.api.components.ShortenOption import org.jetbrains.kotlin.analysis.api.components.ShortenOption
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.components.ElementsToShortenCollector.PartialOrderOfScope.Companion.toPartialOrder import org.jetbrains.kotlin.analysis.api.fir.components.ElementsToShortenCollector.PartialOrderOfScope.Companion.toPartialOrder
import org.jetbrains.kotlin.analysis.api.fir.isImplicitDispatchReceiver
import org.jetbrains.kotlin.analysis.api.fir.utils.FirBodyReanalyzingVisitorVoid import org.jetbrains.kotlin.analysis.api.fir.utils.FirBodyReanalyzingVisitorVoid
import org.jetbrains.kotlin.analysis.api.fir.utils.computeImportableName import org.jetbrains.kotlin.analysis.api.fir.utils.computeImportableName
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
@@ -427,6 +428,8 @@ private class ElementsToShortenCollector(
} }
private fun processTypeQualifier(resolvedQualifier: FirResolvedQualifier) { private fun processTypeQualifier(resolvedQualifier: FirResolvedQualifier) {
if (resolvedQualifier.isImplicitDispatchReceiver) return
val wholeClassQualifier = resolvedQualifier.classId ?: return val wholeClassQualifier = resolvedQualifier.classId ?: return
val qualifierPsi = resolvedQualifier.psi ?: return val qualifierPsi = resolvedQualifier.psi ?: return
if (!qualifierPsi.textRange.intersects(selection)) return if (!qualifierPsi.textRange.intersects(selection)) return
@@ -232,6 +232,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("extensionFromObject.kt")
public void testExtensionFromObject() throws Exception {
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/extensionFromObject.kt");
}
@Test @Test
@TestMetadata("multipleImport.kt") @TestMetadata("multipleImport.kt")
public void testMultipleImport() throws Exception { public void testMultipleImport() throws Exception {
@@ -232,6 +232,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("extensionFromObject.kt")
public void testExtensionFromObject() throws Exception {
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/extensionFromObject.kt");
}
@Test @Test
@TestMetadata("multipleImport.kt") @TestMetadata("multipleImport.kt")
public void testMultipleImport() throws Exception { public void testMultipleImport() throws Exception {
@@ -0,0 +1,15 @@
// FILE: main.kt
package test
import dependency.Bar.extFun
fun usage(str: String) {
<expr>str.extFun()</expr>
}
// FILE: dependency.kt
package dependency
object Bar {
fun String.extFun() {}
}
@@ -0,0 +1,5 @@
Before shortening: str.extFun()
with DO_NOT_SHORTEN:
with SHORTEN_IF_ALREADY_IMPORTED:
with SHORTEN_AND_IMPORT:
with SHORTEN_AND_STAR_IMPORT: