Extract Function: Fix extraction of explicit/implicit invoke()

#KT-8116 Fixed
This commit is contained in:
Alexey Sedunov
2015-06-23 16:19:51 +03:00
parent f6965b5004
commit c6f6637b25
12 changed files with 142 additions and 7 deletions
@@ -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))
}
}
}
@@ -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 ->
@@ -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)
@@ -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>
}
@@ -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()
}
@@ -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>
}
@@ -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()
}
@@ -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>
}
@@ -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()
}
@@ -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>
}
@@ -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()
}
@@ -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")