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")