Extract Function: Fix extraction of explicit/implicit invoke()
#KT-8116 Fixed
This commit is contained in:
+21
-2
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -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 ->
|
||||
|
||||
+4
-1
@@ -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)
|
||||
|
||||
+8
@@ -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) {
|
||||
<selection>a.invoke()</selection>
|
||||
}
|
||||
Vendored
+12
@@ -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()
|
||||
}
|
||||
Vendored
+10
@@ -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) {
|
||||
<selection>a()</selection>
|
||||
}
|
||||
Vendored
+14
@@ -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()
|
||||
}
|
||||
+8
@@ -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) {
|
||||
<selection>a.invoke()</selection>
|
||||
}
|
||||
Vendored
+12
@@ -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()
|
||||
}
|
||||
+8
@@ -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) {
|
||||
<selection>a()</selection>
|
||||
}
|
||||
Vendored
+12
@@ -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()
|
||||
}
|
||||
+24
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user