From be194c3460e273c62bccddb527ead648ac96a56a Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Tue, 29 Oct 2019 20:07:02 +0900 Subject: [PATCH] ReplaceWith: suggest for "invoke" extension #KT-8597 Fixed --- .../CallableUsageReplacementStrategy.kt | 10 +++++++++- .../kotlin/idea/codeInliner/CodeInliner.kt | 20 ++++++++++++++++++- .../jetbrains/kotlin/idea/intentions/Utils.kt | 4 ++++ .../DeprecatedSymbolUsageFixBase.kt | 8 ++++++-- .../operatorCalls/invoke.kt | 14 +++++++++++++ .../operatorCalls/invoke.kt.after | 14 +++++++++++++ .../operatorCalls/invoke2.kt | 18 +++++++++++++++++ .../operatorCalls/invoke2.kt.after | 18 +++++++++++++++++ .../operatorCalls/invoke3.kt | 14 +++++++++++++ .../operatorCalls/invoke3.kt.after | 14 +++++++++++++ .../operatorCalls/invoke4.kt | 17 ++++++++++++++++ .../operatorCalls/invoke4.kt.after | 17 ++++++++++++++++ .../idea/quickfix/QuickFixTestGenerated.java | 20 +++++++++++++++++++ 13 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt.after create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt.after create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt.after create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt create mode 100644 idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInliner/CallableUsageReplacementStrategy.kt b/idea/src/org/jetbrains/kotlin/idea/codeInliner/CallableUsageReplacementStrategy.kt index 97cd3e2e2d0..721d77cd38a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInliner/CallableUsageReplacementStrategy.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInliner/CallableUsageReplacementStrategy.kt @@ -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 } diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInliner/CodeInliner.kt b/idea/src/org/jetbrains/kotlin/idea/codeInliner/CodeInliner.kt index 7e48db187b3..36505ff5598 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInliner/CodeInliner.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInliner/CodeInliner.kt @@ -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( .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 -> { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt index 1d2cbb8af6b..3b27a34683b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt index a9d6f0f42d4..eb5fa8ea585 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt @@ -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 diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt new file mode 100644 index 00000000000..a5ee2d1a1de --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt @@ -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 { + // do something + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt.after new file mode 100644 index 00000000000..ae44f31f98a --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt.after @@ -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 + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt new file mode 100644 index 00000000000..2fe9ec0054b --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt @@ -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) { + executor { + // do something + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt.after new file mode 100644 index 00000000000..3f11fbfcec6 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt.after @@ -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 + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt new file mode 100644 index 00000000000..b38ae6f000f --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt @@ -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) { + invoke { + // do something + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt.after new file mode 100644 index 00000000000..c11583b68ee --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt.after @@ -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 + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt new file mode 100644 index 00000000000..053c046481d --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt @@ -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 { + // do something + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt.after new file mode 100644 index 00000000000..d5fc9ac93ec --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt.after @@ -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 + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index b9fa6d427b2..622ef461ac0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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");