diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt index 44bca5ea909..28cac09d9c1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt @@ -74,4 +74,13 @@ public fun ResolvedCall<*>.getExplicitReceiverValue(): ReceiverValue { ExplicitReceiverKind.EXTENSION_RECEIVER, ExplicitReceiverKind.BOTH_RECEIVERS -> getExtensionReceiver() else -> ReceiverValue.NO_RECEIVER } +} + +public fun ResolvedCall<*>.getImplicitReceiverValue(): ReceiverValue { + return when (getExplicitReceiverKind()) { + ExplicitReceiverKind.NO_EXPLICIT_RECEIVER -> if (extensionReceiver.exists()) extensionReceiver else dispatchReceiver + ExplicitReceiverKind.DISPATCH_RECEIVER -> extensionReceiver + ExplicitReceiverKind.EXTENSION_RECEIVER -> dispatchReceiver + else -> ReceiverValue.NO_RECEIVER + } } \ No newline at end of file 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 4dccb27435d..867f02f713b 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 @@ -38,13 +38,13 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver import org.jetbrains.kotlin.types.JetType -import java.util.ArrayList -import java.util.Collections -import java.util.HashMap +import java.util.* data class ExtractionOptions( val inferUnitTypeForUnusedValues: Boolean = true, @@ -69,7 +69,8 @@ data class ResolvedReferenceInfo( val refExpr: JetSimpleNameExpression, val offsetInBody: Int, val resolveResult: ResolveResult, - val smartCast: JetType? + val smartCast: JetType?, + val possibleTypes: Set ) data class ExtractionData( @@ -160,6 +161,19 @@ data class ExtractionData( else Collections.emptyMap() } + fun getPossibleTypes(expression: JetExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set { + val typeInfo = context[BindingContext.EXPRESSION_TYPE_INFO, expression] ?: return emptySet() + + (resolvedCall?.getImplicitReceiverValue() as? ThisReceiver)?.let { + return typeInfo.dataFlowInfo.getPossibleTypes(DataFlowValueFactory.createDataFlowValue(it)) + } + + val type = resolvedCall?.resultingDescriptor?.returnType ?: return emptySet() + val containingDescriptor = context[BindingContext.LEXICAL_SCOPE, expression]?.ownerDescriptor ?: return emptySet() + val dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, context, containingDescriptor) + return typeInfo.dataFlowInfo.getPossibleTypes(dataFlowValue) + } + fun getBrokenReferencesInfo(body: JetBlockExpression): List { val originalContext = bindingContext ?: return listOf() @@ -174,11 +188,14 @@ data class ExtractionData( val originalResolveResult = refOffsetToDeclaration[offset] ?: continue val smartCast: JetType? + val possibleTypes: Set // Qualified property reference: a.b val qualifiedExpression = ref.getQualifiedExpressionForSelector() if (qualifiedExpression != null) { - smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr.getParent() as JetExpression] + val smartCastTarget = originalResolveResult.originalRefExpr.getParent() as JetExpression + smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget] + possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext) val receiverDescriptor = (originalResolveResult.resolvedCall?.getDispatchReceiver() as? ThisReceiver)?.getDeclarationDescriptor() if (smartCast == null @@ -187,6 +204,7 @@ data class ExtractionData( } else { smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr] + possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext) } val parent = ref.getParent() @@ -207,11 +225,11 @@ data class ExtractionData( 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)) + referencesInfo.add(ResolvedReferenceInfo(ref, offset, variableResolveResult, smartCast, possibleTypes)) + referencesInfo.add(ResolvedReferenceInfo(ref, offset, functionResolveResult, smartCast, possibleTypes)) } else { - referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast)) + referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast, possibleTypes)) } } } 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 1113226c695..0af7fe15f79 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 @@ -59,8 +59,8 @@ import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.approximateWithResolvableType import org.jetbrains.kotlin.idea.util.isResolvableInScope -import org.jetbrains.kotlin.lexer.JetToken import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.lexer.JetToken import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.renderer.DescriptorRenderer @@ -68,6 +68,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns @@ -498,18 +499,23 @@ private class MutableParameter( override val argumentText: String, override val originalDescriptor: DeclarationDescriptor, override val receiverCandidate: Boolean, - private val targetScope: LexicalScope? + private val targetScope: LexicalScope?, + private val originalType: JetType, + private val possibleTypes: Set ): Parameter { // All modifications happen in the same thread private var writable: Boolean = true - private val defaultTypes = HashSet() + private val defaultTypes = LinkedHashSet() private val typePredicates = HashSet() var refCount: Int = 0 fun addDefaultType(jetType: JetType) { assert(writable) { "Can't add type to non-writable parameter $currentName" } - defaultTypes.add(jetType) + + if (jetType in possibleTypes) { + defaultTypes.add(jetType) + } } fun addTypePredicate(predicate: TypePredicate) { @@ -524,7 +530,10 @@ private class MutableParameter( private val defaultType: JetType by lazy { writable = false - TypeIntersector.intersectTypes(originalDescriptor.builtIns, JetTypeChecker.DEFAULT, defaultTypes)!! + if (defaultTypes.isNotEmpty()) { + TypeIntersector.intersectTypes(originalDescriptor.builtIns, JetTypeChecker.DEFAULT, defaultTypes)!! + } + else originalType } private val parameterTypeCandidates: List by lazy { @@ -595,6 +604,44 @@ private fun ExtractionData.inferParametersInfo( val extractedDescriptorToParameter = HashMap() + fun suggestParameterType( + extractFunctionRef: Boolean, + originalDescriptor: DeclarationDescriptor, + parameterExpression: JetExpression?, + receiverToExtract: ReceiverValue, + resolvedCall: ResolvedCall<*>?, + useSmartCastsIfPossible: Boolean + ): JetType { + return when { + extractFunctionRef -> { + originalDescriptor as FunctionDescriptor + KotlinBuiltIns.getInstance().getFunctionType(Annotations.EMPTY, + originalDescriptor.getExtensionReceiverParameter()?.getType(), + originalDescriptor.getValueParameters().map { it.getType() }, + originalDescriptor.getReturnType() ?: DEFAULT_RETURN_TYPE) + } + parameterExpression != null -> + (if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression] else null) + ?: 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() + val typeByDataFlowInfo = if (useSmartCastsIfPossible) { + bindingContext[BindingContext.EXPRESSION_TYPE_INFO, calleeExpression]?.dataFlowInfo?.let { dataFlowInfo -> + val possibleTypes = dataFlowInfo.getPossibleTypes(DataFlowValueFactory.createDataFlowValue(receiverToExtract)) + if (possibleTypes.isNotEmpty()) CommonSupertypes.commonSupertype(possibleTypes) else null + } + } else null + typeByDataFlowInfo ?: receiverToExtract.getType() + } + receiverToExtract.exists() -> receiverToExtract.getType() + else -> null + } ?: DEFAULT_PARAMETER_TYPE + } + for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) { val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult val ref = refInfo.refExpr @@ -671,31 +718,7 @@ private fun ExtractionData.inferParametersInfo( else -> (originalRef.getParent() as? JetThisExpression) ?: originalRef } - val parameterType = when { - extractFunctionRef -> { - originalDescriptor as FunctionDescriptor - KotlinBuiltIns.getInstance().getFunctionType(Annotations.EMPTY, - originalDescriptor.getExtensionReceiverParameter()?.getType(), - originalDescriptor.getValueParameters().map { it.getType() }, - originalDescriptor.getReturnType() ?: DEFAULT_RETURN_TYPE) - } - 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 -> - val possibleTypes = dataFlowInfo.getPossibleTypes(DataFlowValueFactory.createDataFlowValue(receiverToExtract)) - if (possibleTypes.isNotEmpty()) CommonSupertypes.commonSupertype(possibleTypes) else null - } ?: receiverToExtract.getType() - } - receiverToExtract.exists() -> receiverToExtract.getType() - else -> null - } ?: DEFAULT_PARAMETER_TYPE + val parameterType = suggestParameterType(extractFunctionRef, originalDescriptor, parameterExpression, receiverToExtract, resolvedCall, true) val parameter = extractedDescriptorToParameter.getOrPut(descriptorToExtract) { var argumentText = @@ -720,7 +743,9 @@ private fun ExtractionData.inferParametersInfo( argumentText = "$receiverTypeText::$argumentText" } - MutableParameter(argumentText, descriptorToExtract, extractThis, targetScope) + val originalType = suggestParameterType(extractFunctionRef, originalDescriptor, parameterExpression, receiverToExtract, resolvedCall, false) + + MutableParameter(argumentText, descriptorToExtract, extractThis, targetScope, originalType, refInfo.possibleTypes) } if (!extractThis) { diff --git a/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt new file mode 100644 index 00000000000..32f8f6bfec3 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt @@ -0,0 +1,24 @@ +// SUGGESTED_NAMES: i, getX +// PARAM_TYPES: kotlin.Any +// PARAM_DESCRIPTOR: value-parameter val o: kotlin.Any defined in foo + +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = when (o) { + is A -> { + if (o is T) o.a + o.t else o.a + } + else -> o.hashCode() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt.after b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt.after new file mode 100644 index 00000000000..346748b6642 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt.after @@ -0,0 +1,28 @@ +// SUGGESTED_NAMES: i, getX +// PARAM_TYPES: kotlin.Any +// PARAM_DESCRIPTOR: value-parameter val o: kotlin.Any defined in foo + +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = i(o) +} + +private fun i(o: Any): Int { + return when (o) { + is A -> { + if (o is T) o.a + o.t else o.a + } + else -> o.hashCode() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt new file mode 100644 index 00000000000..8ba0249ffed --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt @@ -0,0 +1,23 @@ +// PARAM_TYPES: A +// PARAM_DESCRIPTOR: value-parameter val o: kotlin.Any defined in foo + +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = when (o) { + is A -> { + if (o is T) o.a + o.t else o.a + } + else -> o.hashCode() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt.after b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt.after new file mode 100644 index 00000000000..38144bd9480 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt.after @@ -0,0 +1,25 @@ +// PARAM_TYPES: A +// PARAM_DESCRIPTOR: value-parameter val o: kotlin.Any defined in foo + +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = when (o) { + is A -> { + i(o) + } + else -> o.hashCode() + } +} + +private fun i(o: A) = if (o is T) o.a + o.t else o.a \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt new file mode 100644 index 00000000000..3c9308393e5 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt @@ -0,0 +1,20 @@ +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = when (o) { + is A -> { + if (o is T) o.a + o.t else o.a + } + else -> o.hashCode() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt.conflicts b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt.conflicts new file mode 100644 index 00000000000..51c3b4db6b4 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt.conflicts @@ -0,0 +1 @@ +Cannot extract method since following types are not denotable in the target scope: {A & T} \ 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 4ffc948e35e..a6ac3eca9ac 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -1784,6 +1784,24 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("multipleTypes2.kt") + public void testMultipleTypes2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("multipleTypes3.kt") + public void testMultipleTypes3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("multipleTypes4.kt") + public void testMultipleTypes4() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("nonNullableTypes.kt") public void testNonNullableTypes() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt");