mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[kotlin] Support rebinding extension function call with implicit receiver
#KTIJ-28934 GitOrigin-RevId: 5a4eaed4162fe80486513eb826473882afa12c3a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f13b6bc163
commit
c5863f78fd
@@ -0,0 +1,8 @@
|
||||
// BIND_TO barFoo
|
||||
fun foo() {
|
||||
0 <caret>fooBar 1
|
||||
}
|
||||
|
||||
infix fun Int.fooBar(other: Int) { this + other }
|
||||
|
||||
infix fun Int.barFoo(other: Int) { this + other }
|
||||
@@ -0,0 +1,8 @@
|
||||
// BIND_TO barFoo
|
||||
fun foo() {
|
||||
0 <caret>barFoo 1
|
||||
}
|
||||
|
||||
infix fun Int.fooBar(other: Int) { this + other }
|
||||
|
||||
infix fun Int.barFoo(other: Int) { this + other }
|
||||
@@ -0,0 +1,23 @@
|
||||
// FILE: test/UnQualified.kt
|
||||
// BIND_TO test.bar.barFoo
|
||||
package test
|
||||
|
||||
import test.foo.fooBar
|
||||
|
||||
fun foo() {
|
||||
0 <caret>fooBar 1
|
||||
}
|
||||
|
||||
// FILE: test/foo/Test.kt
|
||||
package test.foo
|
||||
|
||||
infix fun Int.fooBar(other: Int) {
|
||||
this + other
|
||||
}
|
||||
|
||||
// FILE: test/bar/Test.kt
|
||||
package test.bar
|
||||
|
||||
infix fun Int.barFoo(other: Int) {
|
||||
this + other
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// FILE: test/UnQualified.kt
|
||||
// BIND_TO test.bar.barFoo
|
||||
package test
|
||||
|
||||
import test.bar.barFoo
|
||||
|
||||
fun foo() {
|
||||
0 <caret>barFoo 1
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.k2.refactoring
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.references.KDocReference
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleReference
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression
|
||||
@@ -69,41 +70,44 @@ internal class K2ReferenceMutateService : KtReferenceMutateServiceBase() {
|
||||
|
||||
private class ReplaceResult(val replacedElement: KtElement, val canBeShortened: Boolean = true)
|
||||
|
||||
@OptIn(KtAllowAnalysisOnEdt::class, KtAllowAnalysisFromWriteAction::class)
|
||||
@RequiresWriteLock
|
||||
override fun bindToFqName(
|
||||
simpleNameReference: KtSimpleNameReference,
|
||||
fqName: FqName,
|
||||
shorteningMode: KtSimpleNameReference.ShorteningMode, // delayed shortening is not supported
|
||||
targetElement: PsiElement?
|
||||
): PsiElement {
|
||||
val expression = simpleNameReference.expression
|
||||
if (fqName.isRoot) return expression
|
||||
val oldTarget = simpleNameReference.resolve()
|
||||
val isImportable = (oldTarget as? KtCallableDeclaration)?.receiverTypeReference == null || oldTarget is KtClassLikeDeclaration
|
||||
if (isImportable && oldTarget?.kotlinFqName == fqName) return expression
|
||||
val writableFqn = if (fqName.pathSegments().last().asString() == "Companion") {
|
||||
fqName.parent()
|
||||
} else {
|
||||
fqName
|
||||
): PsiElement = allowAnalysisOnEdt {
|
||||
return allowAnalysisFromWriteAction {
|
||||
val expression = simpleNameReference.expression
|
||||
if (fqName.isRoot) return expression
|
||||
val oldTarget = simpleNameReference.resolve()
|
||||
val isImportable = (oldTarget as? KtCallableDeclaration)?.receiverTypeReference == null || oldTarget is KtClassLikeDeclaration
|
||||
if (isImportable && oldTarget?.kotlinFqName == fqName) return expression
|
||||
val writableFqn = if (fqName.pathSegments().last().asString() == "Companion") {
|
||||
fqName.parent()
|
||||
} else {
|
||||
fqName
|
||||
}
|
||||
val elementToReplace = expression.getQualifiedElementOrCallableRef()
|
||||
val result = modifyPsiWithOptimizedImports(expression.containingKtFile) {
|
||||
when (elementToReplace) {
|
||||
is KtUserType -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtDotQualifiedExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtCallExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtCallableReferenceExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtSimpleNameExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
else -> null
|
||||
} ?: return@modifyPsiWithOptimizedImports null
|
||||
} ?: return expression
|
||||
val shouldShorten = shorteningMode != KtSimpleNameReference.ShorteningMode.NO_SHORTENING && result.canBeShortened
|
||||
if (shouldShorten) {
|
||||
shortenReferences(result.replacedElement) ?: expression
|
||||
} else result.replacedElement
|
||||
}
|
||||
val elementToReplace = expression.getQualifiedElementOrCallableRef()
|
||||
val result = modifyPsiWithOptimizedImports(expression.containingKtFile) {
|
||||
when (elementToReplace) {
|
||||
is KtUserType -> elementToReplace.replaceWith(writableFqn)
|
||||
is KtDotQualifiedExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtCallExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtCallableReferenceExpression -> elementToReplace.replaceWith(writableFqn, targetElement)
|
||||
is KtSimpleNameExpression -> elementToReplace.replaceWith(writableFqn)
|
||||
else -> null
|
||||
} ?: return@modifyPsiWithOptimizedImports null
|
||||
} ?: return expression
|
||||
val shouldShorten = shorteningMode != KtSimpleNameReference.ShorteningMode.NO_SHORTENING && result.canBeShortened
|
||||
return if (shouldShorten) {
|
||||
shortenReferences(result.replacedElement) ?: expression
|
||||
} else result.replacedElement
|
||||
}
|
||||
|
||||
private fun KtUserType.replaceWith(fqName: FqName): ReplaceResult {
|
||||
private fun KtUserType.replaceWith(fqName: FqName, targetElement: PsiElement?): ReplaceResult {
|
||||
val replacedElement = if (qualifier == null) {
|
||||
val typeArgText = typeArgumentList?.text ?: ""
|
||||
val newReference = KtPsiFactory(project).createType(fqName.asString() + typeArgText).typeElement as? KtUserType
|
||||
@@ -114,9 +118,9 @@ internal class K2ReferenceMutateService : KtReferenceMutateServiceBase() {
|
||||
if (parentFqn.isRoot) {
|
||||
deleteQualifier()
|
||||
} else {
|
||||
qualifier?.replaceWith(parentFqn) // do recursive short name replacement to preserve type arguments
|
||||
qualifier?.replaceWith(parentFqn, targetElement) // do recursive short name replacement to preserve type arguments
|
||||
}
|
||||
referenceExpression?.replaceShortName(fqName)?.parent as KtUserType
|
||||
referenceExpression?.replaceShortName(fqName, targetElement)?.parent as KtUserType
|
||||
}
|
||||
return ReplaceResult(replacedElement)
|
||||
}
|
||||
@@ -131,7 +135,7 @@ internal class K2ReferenceMutateService : KtReferenceMutateServiceBase() {
|
||||
val isPartOfImport = parentOfType<KtImportDirective>(withSelf = false) != null
|
||||
val selectorExpression = selectorExpression ?: return null
|
||||
val newSelectorExpression = when (selectorExpression) {
|
||||
is KtSimpleNameExpression -> selectorExpression.replaceShortName(fqName)
|
||||
is KtSimpleNameExpression -> selectorExpression.replaceShortName(fqName, targetElement)
|
||||
is KtCallExpression -> {
|
||||
val newCall = selectorExpression.replaceShortName(fqName)
|
||||
if (targetElement?.isCallableAsExtensionFunction() == true) {
|
||||
@@ -179,13 +183,21 @@ internal class K2ReferenceMutateService : KtReferenceMutateServiceBase() {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun KtSimpleNameExpression.replaceWith(fqName: FqName): ReplaceResult {
|
||||
val newNameExpr = replaceShortName(fqName)
|
||||
return ReplaceResult(newNameExpr.replaceWithQualified(fqName, newNameExpr))
|
||||
private fun KtSimpleNameExpression.replaceWith(fqName: FqName, targetElement: PsiElement?): ReplaceResult {
|
||||
val newNameExpr = replaceShortName(fqName, targetElement)
|
||||
return if (targetElement?.isCallableAsExtensionFunction() == true) { // operation references (infix function calls)
|
||||
newNameExpr.containingKtFile.addImport(fqName)
|
||||
ReplaceResult(newNameExpr, false)
|
||||
} else {
|
||||
ReplaceResult(newNameExpr.replaceWithQualified(fqName, newNameExpr))
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtSimpleNameExpression.replaceShortName(fqName: FqName): KtExpression {
|
||||
val newNameExpression = KtPsiFactory(project).createSimpleName(fqName.shortName().asString())
|
||||
private fun KtSimpleNameExpression.replaceShortName(fqName: FqName, targetElement: PsiElement?): KtExpression {
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
val shortName = fqName.shortName().asString()
|
||||
val isInfixFun = targetElement is KtNamedFunction && targetElement.modifierList?.hasModifier(KtTokens.INFIX_KEYWORD) == true
|
||||
val newNameExpression = if (isInfixFun) psiFactory.createOperationName(shortName) else psiFactory.createSimpleName(shortName)
|
||||
return replaced(newNameExpression)
|
||||
}
|
||||
|
||||
|
||||
@@ -368,6 +368,24 @@ public abstract class K2BindToElementTestGenerated extends AbstractK2BindToEleme
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@TestMetadata("../../idea/tests/testData/refactoring/bindToElement/operationReference")
|
||||
public static class OperationReference extends AbstractK2BindToElementTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
@TestMetadata("RootPkg.kt")
|
||||
public void testRootPkg() throws Exception {
|
||||
runTest("../../idea/tests/testData/refactoring/bindToElement/operationReference/RootPkg.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("UnQualified.kt")
|
||||
public void testUnQualified() throws Exception {
|
||||
runTest("../../idea/tests/testData/refactoring/bindToElement/operationReference/UnQualified.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@TestMetadata("../../idea/tests/testData/refactoring/bindToElement/propertyTypeReference")
|
||||
public static class PropertyTypeReference extends AbstractK2BindToElementTest {
|
||||
|
||||
Reference in New Issue
Block a user