[kotlin] k2: change signature: support imports required for default values

GitOrigin-RevId: d852c4a0c9f9aa807f69efa97f65fab5525032b7
This commit is contained in:
Anna Kozlova
2023-11-01 12:52:21 +00:00
committed by intellij-monorepo-bot
parent 8d820dab14
commit ba6fb24127
22 changed files with 199 additions and 34 deletions
@@ -0,0 +1,136 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.codeinsight.utils
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.descendantsOfType
import com.intellij.psi.util.elementType
import com.intellij.refactoring.suggested.createSmartPointer
import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.base.util.quoteIfNeeded
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtInstanceExpressionWithLabel
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
object AddQualifiersUtil {
fun addQualifiersRecursively(root: KtElement): KtElement {
if (root is KtNameReferenceExpression) return applyIfApplicable(root) ?: root
root.descendantsOfType<KtNameReferenceExpression>()
.map { it.createSmartPointer() }
.toList()
.asReversed()
.forEach {
it.element?.let(::applyIfApplicable)
}
return root
}
context(KtAnalysisSession)
fun isApplicableTo(referenceExpression: KtNameReferenceExpression, contextSymbol: KtNamedSymbol): Boolean {
if (referenceExpression.parent is KtInstanceExpressionWithLabel) return false
val prevElement = PsiTreeUtil.skipWhitespacesAndCommentsBackward(referenceExpression)
if (prevElement.elementType == KtTokens.DOT) return false
val fqName = getFqName(contextSymbol)
if (contextSymbol is KtCallableSymbol && contextSymbol.isExtension || fqName?.parent()?.isRoot == true) return false
if (prevElement.elementType == KtTokens.COLONCOLON) {
if (contextSymbol is KtCallableSymbol && contextSymbol.getContainingSymbol() == null) return false
val prevSibling = prevElement?.getPrevSiblingIgnoringWhitespaceAndComments()
if (prevSibling is KtNameReferenceExpression || prevSibling is KtDotQualifiedExpression) return false
}
val file = referenceExpression.containingKtFile
val identifier = referenceExpression.getIdentifier()?.text
return !file.hasImportAlias() || file.importDirectives.none { it.aliasName == identifier && it.importedFqName == fqName }
}
fun applyTo(referenceExpression: KtNameReferenceExpression, fqName: FqName): KtElement {
val qualifier = fqName.parent().quoteIfNeeded().asString()
val project = referenceExpression.project
val psiFactory = KtPsiFactory(project)
return project.executeWriteCommand(KotlinBundle.message("add.full.qualifier"), groupId = null) {
when (val parent = referenceExpression.parent) {
is KtCallableReferenceExpression -> addOrReplaceQualifier(psiFactory, parent, qualifier)
is KtCallExpression -> replaceExpressionWithDotQualifier(psiFactory, parent, qualifier)
is KtUserType -> addQualifierToType(psiFactory, parent, qualifier)
else -> replaceExpressionWithQualifier(psiFactory, referenceExpression, fqName)
}
}
}
private fun getFqName(symbol: KtNamedSymbol): FqName? {
return when (symbol) {
is KtClassLikeSymbol -> symbol.classIdIfNonLocal?.asSingleFqName()
is KtCallableSymbol -> symbol.callableIdIfNonLocal?.asSingleFqName()
else -> null
}
}
@OptIn(KtAllowAnalysisFromWriteAction::class, KtAllowAnalysisOnEdt::class)
private fun applyIfApplicable(referenceExpression: KtNameReferenceExpression): KtElement? {
val fqName = allowAnalysisFromWriteAction {
allowAnalysisOnEdt {
analyze(referenceExpression) {
val symbol = referenceExpression.mainReference.resolveToSymbols().singleOrNull() as? KtNamedSymbol ?: return null
val fqName = getFqName(symbol) ?: return null
if (!isApplicableTo(referenceExpression, symbol)) return null
fqName
}
}
}
return applyTo(referenceExpression, fqName)
}
private fun addOrReplaceQualifier(factory: KtPsiFactory, expression: KtCallableReferenceExpression, qualifier: String): KtElement {
val receiver = expression.receiverExpression
return if (receiver != null) {
replaceExpressionWithDotQualifier(factory, receiver, qualifier)
} else {
val qualifierExpression = factory.createExpression(qualifier)
expression.addBefore(qualifierExpression, expression.firstChild) as KtElement
}
}
private fun replaceExpressionWithDotQualifier(psiFactory: KtPsiFactory, expression: KtExpression, qualifier: String): KtElement {
val expressionWithQualifier = psiFactory.createExpressionByPattern("$0.$1", qualifier, expression)
return expression.replace(expressionWithQualifier) as KtElement
}
private fun addQualifierToType(psiFactory: KtPsiFactory, userType: KtUserType, qualifier: String): KtElement {
val type = userType.parent as? KtNullableType ?: userType
val typeWithQualifier = psiFactory.createType("$qualifier.${type.text}")
return type.parent.replace(typeWithQualifier) as KtElement
}
private fun replaceExpressionWithQualifier(
psiFactory: KtPsiFactory,
referenceExpression: KtNameReferenceExpression,
fqName: FqName
): KtElement {
val expressionWithQualifier = psiFactory.createExpression(fqName.asString())
return referenceExpression.replace(expressionWithQualifier) as KtElement
}
}
@@ -0,0 +1,5 @@
package usages
fun foo(i: Int) {}
fun callFoo() = foo(a.b.c.Dep.Companion.MY_CONSTANT_FROM_DEP)
@@ -5,5 +5,3 @@ import a.b.c.Dep
fun foo<caret>(i: Int) {}
fun callFoo() = foo(Dep.MY_CONSTANT_FROM_DEP)
// IGNORE_K2
@@ -3,5 +3,3 @@ package usages
fun foo<caret>() {}
fun callFoo() = foo()
// IGNORE_K2
@@ -0,0 +1,5 @@
package usages
fun foo(i: Int) {}
fun callFoo() = foo(a.b.c.Dep.Companion.MY_CONSTANT_FROM_DEP)
@@ -5,5 +5,3 @@ import a.b.c.Dep
fun foo<caret>(i: Int) {}
fun callFoo() = foo(Dep.MY_CONSTANT_FROM_DEP)
// IGNORE_K2
@@ -3,5 +3,3 @@ package usages
fun foo<caret>() {}
fun callFoo() = foo()
// IGNORE_K2
@@ -0,0 +1,5 @@
package usages
fun Int.foo() {}
fun callFoo() = a.b.c.Dep.Companion.MY_CONSTANT_FROM_DEP.foo()
@@ -5,5 +5,3 @@ import a.b.c.Dep
fun Int.foo<caret>() {}
fun callFoo() = Dep.MY_CONSTANT_FROM_DEP.foo()
// IGNORE_K2
@@ -3,5 +3,3 @@ package usages
fun foo<caret>() {}
fun callFoo() = foo()
// IGNORE_K2
@@ -0,0 +1,11 @@
package usages
import a.b.Dep2.Companion.NUMBER
import a.b.c.Dep.Companion.MY_CONSTANT_FROM_DEP
val Int.foo: String get() = "hello"
fun callFoo() = Dep2().eval(MY_CONSTANT_FROM_DEP + NUMBER).foo
fun t() {
val s = Dep2().eval(MY_CONSTANT_FROM_DEP + NUMBER).foo + " world"
}
@@ -9,5 +9,3 @@ fun callFoo() = Dep2().eval(Dep.MY_CONSTANT_FROM_DEP + Dep2.NUMBER).foo
fun t() {
val s = Dep2().eval(Dep.MY_CONSTANT_FROM_DEP + Dep2.NUMBER).foo + " world"
}
// IGNORE_K2
@@ -5,6 +5,4 @@ val <caret>foo: String get() = "hello"
fun callFoo() = foo
fun t() {
val s = foo + " world"
}
// IGNORE_K2
}
@@ -0,0 +1,10 @@
package usages
import a.b.c.Dep.Companion.MY_CONSTANT_FROM_DEP
val Int.foo: String get() = "hello"
fun callFoo() = MY_CONSTANT_FROM_DEP.foo
fun t() {
val s = MY_CONSTANT_FROM_DEP.foo + " world"
}
@@ -8,5 +8,3 @@ fun callFoo() = Dep.MY_CONSTANT_FROM_DEP.foo
fun t() {
val s = Dep.MY_CONSTANT_FROM_DEP.foo + " world"
}
// IGNORE_K2
@@ -5,6 +5,4 @@ val <caret>foo: String get() = "hello"
fun callFoo() = foo
fun t() {
val s = foo + " world"
}
// IGNORE_K2
}
@@ -0,0 +1,10 @@
package usages
import a.b.c.Dep.Companion.MY_CONSTANT_FROM_DEP
val Int.foo: String get() = "hello"
fun callFoo() = MY_CONSTANT_FROM_DEP.foo
fun t() {
val s = MY_CONSTANT_FROM_DEP.foo + " world"
}
@@ -8,5 +8,3 @@ fun callFoo() = Dep.MY_CONSTANT_FROM_DEP.foo
fun t() {
val s = Dep.MY_CONSTANT_FROM_DEP.foo + " world"
}
// IGNORE_K2
@@ -5,6 +5,4 @@ val <caret>foo: String get() = "hello"
fun callFoo() = foo
fun t() {
val s = foo + " world"
}
// IGNORE_K2
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.types.KtErrorType
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.idea.codeinsight.utils.AddQualifiersUtil
import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.*
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinModifiableMethodDescriptor.Kind
import org.jetbrains.kotlin.idea.refactoring.changeSignature.ui.*
@@ -115,11 +116,11 @@ internal class KotlinChangeSignatureDialog(
override fun createRefactoringProcessor(): BaseRefactoringProcessor {
val changeInfo = evaluateChangeSignatureInfo()
val changeInfo = evaluateChangeSignatureInfo(false)
return KotlinChangeSignatureProcessor(project, changeInfo)
}
private fun evaluateChangeSignatureInfo(): KotlinChangeInfo {
private fun evaluateChangeSignatureInfo(forPreview: Boolean): KotlinChangeInfo {
val callable = myMethod.method
var receiverInfo: KotlinParameterInfo? = null
val parameters = parametersTableModel.items.map { parameter ->
@@ -133,11 +134,15 @@ internal class KotlinChangeSignatureDialog(
val codeFragment = parameter.defaultValueCodeFragment as KtExpressionCodeFragment
//todo if (!forPreview) AddFullQualifierIntention.Holder.addQualifiersRecursively(codeFragment)
if (!forPreview) AddQualifiersUtil.addQualifiersRecursively(codeFragment)
val oldDefaultValue = parameterInfo.defaultValueForCall
if (codeFragment.text != (if (oldDefaultValue != null) oldDefaultValue.text else "") && parameter.parameter.defaultValueAsDefaultParameter) {
parameterInfo.defaultValue = codeFragment.getContentElement()
if (codeFragment.text != (if (oldDefaultValue != null) oldDefaultValue.text else "")) {
if (parameter.parameter.defaultValueAsDefaultParameter) {
parameterInfo.defaultValue = codeFragment.getContentElement()
} else {
parameterInfo.defaultValueForCall = codeFragment.getContentElement()
}
}
parameterInfo
@@ -188,7 +193,7 @@ internal class KotlinChangeSignatureDialog(
}
override fun calculateSignature(): String {
val changeSignatureInfo = evaluateChangeSignatureInfo()
val changeSignatureInfo = evaluateChangeSignatureInfo(true)
return changeSignatureInfo.getNewSignature()
}
@@ -21,5 +21,6 @@
<orderEntry type="module" module-name="kotlin.refactorings.common.tests" scope="TEST" />
<orderEntry type="module" module-name="kotlin.refactorings.k2" scope="TEST" />
<orderEntry type="module" module-name="kotlin.fir.frontend-independent.tests" scope="TEST" />
<orderEntry type="module" module-name="kotlin.code-insight.utils" scope="TEST" />
</component>
</module>
@@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.k2.refactoring.changeSignature
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.idea.codeinsight.utils.AddQualifiersUtil
import org.jetbrains.kotlin.idea.refactoring.changeSignature.BaseKotlinChangeSignatureTest
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
@@ -29,7 +30,7 @@ class KotlinFirChangeSignatureTest :
}
override fun addFullQualifier(fragment: KtExpressionCodeFragment) {
TODO("Not yet implemented")
AddQualifiersUtil.addQualifiersRecursively(fragment)
}
override fun KotlinChangeInfo.createKotlinParameter(