diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt index fab6f6e7ac3..4c2a105d12c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt @@ -45,6 +45,8 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.types.JetType data class ExtractionOptions( @@ -115,6 +117,7 @@ data class ExtractionData( val bindingContext: BindingContext? by Delegates.lazy { commonParent.getContextForContainingDeclarationBody() } private val itFakeDeclaration by Delegates.lazy { JetPsiFactory(originalFile).createParameter("it: Any?") } + private val synthesizedInvokeDeclaration by Delegates.lazy { JetPsiFactory(originalFile).createFunction("fun invoke() {}") } val refOffsetToDeclaration by Delegates.lazy { fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean { @@ -146,7 +149,8 @@ data class ExtractionData( val resolvedCall = ref.getResolvedCall(context) val descriptor = context[BindingContext.REFERENCE_TARGET, ref] ?: return val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? PsiNamedElement - ?: if (isExtractableIt(descriptor, context)) itFakeDeclaration else return + ?: if (isExtractableIt(descriptor, context)) itFakeDeclaration + else if (isSynthesizedInvoke(descriptor)) synthesizedInvokeDeclaration else return val offset = ref.getTextRange()!!.getStartOffset() - originalStartOffset resultMap[offset] = ResolveResult(ref, declaration, descriptor, resolvedCall) @@ -196,7 +200,22 @@ data class ExtractionData( val descriptor = context[BindingContext.REFERENCE_TARGET, ref] val isBadRef = !compareDescriptors(project, originalResolveResult.descriptor, descriptor) || smartCast != null if (isBadRef && !originalResolveResult.declaration.isInsideOf(originalElements)) { - referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast)) + val originalResolvedCall = originalResolveResult.resolvedCall as? VariableAsFunctionResolvedCall + val originalFunctionCall = originalResolvedCall?.functionCall + val originalVariableCall = originalResolvedCall?.variableCall + val invokeDescriptor = originalFunctionCall?.getResultingDescriptor() + if (invokeDescriptor != null && isSynthesizedInvoke(invokeDescriptor) && invokeDescriptor.isExtension) { + val variableResolveResult = originalResolveResult.copy(resolvedCall = originalVariableCall!!, + descriptor = originalVariableCall.getResultingDescriptor()) + val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!, + descriptor = originalFunctionCall.getResultingDescriptor(), + declaration = synthesizedInvokeDeclaration) + referencesInfo.add(ResolvedReferenceInfo(ref, offset, variableResolveResult, smartCast)) + referencesInfo.add(ResolvedReferenceInfo(ref, offset, functionResolveResult, smartCast)) + } + else { + referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast)) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 01b4951514d..2af0d1492e7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -72,6 +72,7 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement import org.jetbrains.kotlin.resolve.calls.CallTransformer import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory +import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke import org.jetbrains.kotlin.resolve.scopes.JetScope import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver @@ -601,7 +602,7 @@ private fun ExtractionData.inferParametersInfo( val extensionReceiver = resolvedCall?.getExtensionReceiver() val receiverToExtract = when { extensionReceiver == ReceiverValue.NO_RECEIVER, - resolvedCall?.getCall() is CallTransformer.CallForImplicitInvoke -> resolvedCall?.getDispatchReceiver() + isSynthesizedInvoke(originalDescriptor) -> resolvedCall?.getDispatchReceiver() else -> extensionReceiver } ?: ReceiverValue.NO_RECEIVER @@ -671,9 +672,13 @@ private fun ExtractionData.inferParametersInfo( originalDescriptor.getValueParameters().map { it.getType() }, originalDescriptor.getReturnType() ?: DEFAULT_RETURN_TYPE) } - parameterExpression != null -> bindingContext[BindingContext.SMARTCAST, parameterExpression] - ?: bindingContext.getType(parameterExpression) - ?: if (receiverToExtract.exists()) receiverToExtract.getType() else null + parameterExpression != null -> + bindingContext[BindingContext.SMARTCAST, parameterExpression] + ?: bindingContext.getType(parameterExpression) + ?: (parameterExpression as? JetReferenceExpression)?.let { + (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.getReturnType() + } + ?: if (receiverToExtract.exists()) receiverToExtract.getType() else null receiverToExtract is ThisReceiver -> { val calleeExpression = resolvedCall!!.getCall().getCalleeExpression() bindingContext[BindingContext.EXPRESSION_TYPE_INFO, calleeExpression]?.dataFlowInfo?.let { dataFlowInfo -> diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index 7981aaa9a35..69ed3fc5ed7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -79,7 +79,10 @@ fun ExtractionGeneratorConfiguration.getDeclarationText( } descriptor.receiverParameter?.let { - builder.receiver(it.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString()) + val receiverType = it.getParameterType(descriptor.extractionData.options.allowSpecialClassNames) + val receiverTypeAsString = receiverType.typeAsString() + val isFunctionType = KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(receiverType) + builder.receiver(if (isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString) } builder.name(if (descriptor.name == "" && generatorOptions.allowDummyName) "myFun" else descriptor.name) diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt new file mode 100644 index 00000000000..41d65b7a370 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt @@ -0,0 +1,8 @@ +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_DESCRIPTOR: value-parameter val invoke: kotlin.String.() -> kotlin.Unit defined in foo +// PARAM_TYPES: kotlin.String +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit + +fun foo(a: String, invoke: String.() -> Unit) { + a.invoke() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt.after b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt.after new file mode 100644 index 00000000000..11f4bc1f673 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt.after @@ -0,0 +1,12 @@ +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_DESCRIPTOR: value-parameter val invoke: kotlin.String.() -> kotlin.Unit defined in foo +// PARAM_TYPES: kotlin.String +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit + +fun foo(a: String, invoke: String.() -> Unit) { + __dummyTestFun__(a, invoke) +} + +private fun __dummyTestFun__(a: String, invoke: String.() -> Unit) { + a.invoke() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt new file mode 100644 index 00000000000..256258c0d9e --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt @@ -0,0 +1,10 @@ +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_TYPES: kotlin.String + +fun String.invoke() { + +} + +fun foo(a: String) { + a() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt.after b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt.after new file mode 100644 index 00000000000..731c0e05149 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt.after @@ -0,0 +1,14 @@ +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_TYPES: kotlin.String + +fun String.invoke() { + +} + +fun foo(a: String) { + __dummyTestFun__(a) +} + +private fun __dummyTestFun__(a: String) { + a() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt new file mode 100644 index 00000000000..b9f342f8854 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt @@ -0,0 +1,8 @@ +// PARAM_DESCRIPTOR: internal fun (kotlin.String.() -> kotlin.Unit).foo(a: kotlin.String): kotlin.Unit defined in root package +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit +// PARAM_TYPES: kotlin.String + +fun (String.() -> Unit).foo(a: String) { + a.invoke() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt.after b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt.after new file mode 100644 index 00000000000..b78cfea006e --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt.after @@ -0,0 +1,12 @@ +// PARAM_DESCRIPTOR: internal fun (kotlin.String.() -> kotlin.Unit).foo(a: kotlin.String): kotlin.Unit defined in root package +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit +// PARAM_TYPES: kotlin.String + +fun (String.() -> Unit).foo(a: String) { + __dummyTestFun__(a) +} + +private fun (String.() -> Unit).__dummyTestFun__(a: String) { + a.invoke() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt new file mode 100644 index 00000000000..357ee5da5fd --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt @@ -0,0 +1,8 @@ +// PARAM_DESCRIPTOR: internal fun (kotlin.String.() -> kotlin.Unit).foo(a: kotlin.String): kotlin.Unit defined in root package +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit +// PARAM_TYPES: kotlin.String + +fun (String.() -> Unit).foo(a: String) { + a() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt.after b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt.after new file mode 100644 index 00000000000..841f5e6fad1 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt.after @@ -0,0 +1,12 @@ +// PARAM_DESCRIPTOR: internal fun (kotlin.String.() -> kotlin.Unit).foo(a: kotlin.String): kotlin.Unit defined in root package +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.String defined in foo +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit +// PARAM_TYPES: kotlin.String + +fun (String.() -> Unit).foo(a: String) { + __dummyTestFun__(a) +} + +private fun (String.() -> Unit).__dummyTestFun__(a: String) { + a() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index c68f5ed50f6..e009781251e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -1805,11 +1805,35 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("paramAsExplicitInvoke.kt") + public void testParamAsExplicitInvoke() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("paramWithExtensionInvoke.kt") + public void testParamWithExtensionInvoke() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("qualifiedThisAsArgument.kt") public void testQualifiedThisAsArgument() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt"); doExtractFunctionTest(fileName); } + + @TestMetadata("thisAsExplicitInvoke.kt") + public void testThisAsExplicitInvoke() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsExplicitInvoke.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("thisAsImplicitInvoke.kt") + public void testThisAsImplicitInvoke() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/thisAsImplicitInvoke.kt"); + doExtractFunctionTest(fileName); + } } @TestMetadata("idea/testData/refactoring/extractFunction/parameters/it")