ReplaceWith: suggest for "invoke" extension

#KT-8597 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-10-29 20:07:02 +09:00
committed by Yan Zhulanow
parent bb7d4c224f
commit be194c3460
13 changed files with 184 additions and 4 deletions
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.codeInliner
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.idea.intentions.isInvokeOperator
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -34,7 +35,14 @@ class CallableUsageReplacementStrategy(
if (!resolvedCall.status.isSuccess) return null
val callElement = when (resolvedCall) {
is VariableAsFunctionResolvedCall -> resolvedCall.variableCall.call.callElement
is VariableAsFunctionResolvedCall -> {
val callElement = resolvedCall.variableCall.call.callElement
if (resolvedCall.resultingDescriptor.isInvokeOperator) {
callElement.parent as? KtCallExpression ?: callElement
} else {
callElement
}
}
else -> resolvedCall.call.callElement
}
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.inspections.RedundantUnitExpressionInspection
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.intentions.isInvokeOperator
import org.jetbrains.kotlin.idea.util.CommentSaver
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -135,7 +137,23 @@ class CodeInliner<TCallElement : KtElement>(
.forEach { ImportInsertHelper.getInstance(project).importDescriptor(file, it) }
val replacementPerformer = when (elementToBeReplaced) {
is KtExpression -> ExpressionReplacementPerformer(codeToInline, elementToBeReplaced)
is KtExpression -> {
if (descriptor.isInvokeOperator) {
val call = elementToBeReplaced as? KtCallExpression
?: (elementToBeReplaced as? KtDotQualifiedExpression)?.selectorExpression as? KtCallExpression
val callee = call?.calleeExpression
if (callee != null && callee.text != OperatorNameConventions.INVOKE.asString()) {
val receiverExpression = (codeToInline.mainExpression as? KtQualifiedExpression)?.receiverExpression
when {
elementToBeReplaced is KtCallExpression && receiverExpression is KtThisExpression ->
receiverExpression.replace(callee)
elementToBeReplaced is KtDotQualifiedExpression ->
receiverExpression?.replace(psiFactory.createExpressionByPattern("$0.$1", receiverExpression, callee))
}
}
}
ExpressionReplacementPerformer(codeToInline, elementToBeReplaced)
}
is KtAnnotationEntry -> AnnotationEntryReplacementPerformer(codeToInline, elementToBeReplaced)
is KtSuperTypeCallEntry -> SuperTypeCallEntryReplacementPerformer(codeToInline, elementToBeReplaced)
else -> {
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.types.isFlexible
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.util.OperatorChecks
import org.jetbrains.kotlin.util.OperatorNameConventions
fun KtContainerNode.description(): String? {
when (node.elementType) {
@@ -364,3 +365,6 @@ fun KotlinType.reflectToRegularFunctionType(): KotlinType {
if (isKSuspendFunctionType) builtIns.getSuspendFunction(parameterCount) else builtIns.getFunction(parameterCount)
return KotlinTypeFactory.simpleNotNullType(annotations, classDescriptor, arguments)
}
val CallableDescriptor.isInvokeOperator: Boolean
get() = this is FunctionDescriptor && isOperator && name == OperatorNameConventions.INVOKE
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
import org.jetbrains.kotlin.idea.codeInliner.ClassUsageReplacementStrategy
import org.jetbrains.kotlin.idea.codeInliner.UsageReplacementStrategy
import org.jetbrains.kotlin.idea.core.OptionalParametersHelper
import org.jetbrains.kotlin.idea.intentions.isInvokeOperator
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
@@ -193,7 +194,10 @@ abstract class DeprecatedSymbolUsageFixBase(
): UsageReplacementStrategy? {
val resolutionFacade = element.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
val resolvedCall = element.getResolvedCall(bindingContext)
var target = resolvedCall?.resultingDescriptor?.takeIf { it.isInvokeOperator }
?: element.mainReference.resolveToDescriptors(bindingContext).singleOrNull()
?: return null
var replacePatternFromSymbol =
fetchReplaceWithPattern(target, resolutionFacade.project, element, replaceWith.replaceInWholeProject)
@@ -208,7 +212,7 @@ abstract class DeprecatedSymbolUsageFixBase(
when (target) {
is CallableDescriptor -> {
val resolvedCall = element.getResolvedCall(bindingContext) ?: return null
if (resolvedCall == null) return null
if (!resolvedCall.isReallySuccess()) return null
val replacement = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(
replaceWith, target, resolutionFacade, reformat
@@ -0,0 +1,14 @@
// "Replace with 'execute(action)'" "true"
class Executor {
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
}
fun usage(executor: Executor) {
<caret>executor {
// do something
}
}
@@ -0,0 +1,14 @@
// "Replace with 'execute(action)'" "true"
class Executor {
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
}
fun usage(executor: Executor) {
executor.execute {
// do something
}
}
@@ -0,0 +1,18 @@
// "Replace with 'Foo.execute(action)'" "true"
class Executor {
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("Foo.execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
}
object Foo {
fun execute(action: () -> Unit) {}
}
fun usage(executor: Executor) {
<caret>executor {
// do something
}
}
@@ -0,0 +1,18 @@
// "Replace with 'Foo.execute(action)'" "true"
class Executor {
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("Foo.execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
}
object Foo {
fun execute(action: () -> Unit) {}
}
fun usage(executor: Executor) {
Foo.execute {
// do something
}
}
@@ -0,0 +1,14 @@
// "Replace with 'execute(action)'" "true"
class Executor {
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
fun usage(executor: Executor) {
<caret>invoke {
// do something
}
}
}
@@ -0,0 +1,14 @@
// "Replace with 'execute(action)'" "true"
class Executor {
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
fun usage(executor: Executor) {
execute {
// do something
}
}
}
@@ -0,0 +1,17 @@
// "Replace with 'execute(action)'" "true"
class Executor {
val self: Executor
get() = this
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
}
fun usage(executor: Executor) {
executor.<caret>self {
// do something
}
}
@@ -0,0 +1,17 @@
// "Replace with 'execute(action)'" "true"
class Executor {
val self: Executor
get() = this
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
operator fun invoke(action: () -> Unit) {}
fun execute(action: () -> Unit) {}
}
fun usage(executor: Executor) {
executor.self.execute {
// do something
}
}
@@ -7061,6 +7061,26 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/in.kt");
}
@TestMetadata("invoke.kt")
public void testInvoke() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt");
}
@TestMetadata("invoke2.kt")
public void testInvoke2() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt");
}
@TestMetadata("invoke3.kt")
public void testInvoke3() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt");
}
@TestMetadata("invoke4.kt")
public void testInvoke4() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt");
}
@TestMetadata("plusAssign.kt")
public void testPlusAssign() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/plusAssign.kt");