Extract Function: Ignore internal smartcasts

#KT-8458 Fixed
This commit is contained in:
Alexey Sedunov
2015-09-03 17:08:37 +03:00
parent ba5d5cc87b
commit e03067846f
10 changed files with 230 additions and 39 deletions
@@ -74,4 +74,13 @@ public fun ResolvedCall<*>.getExplicitReceiverValue(): ReceiverValue {
ExplicitReceiverKind.EXTENSION_RECEIVER, ExplicitReceiverKind.BOTH_RECEIVERS -> getExtensionReceiver() ExplicitReceiverKind.EXTENSION_RECEIVER, ExplicitReceiverKind.BOTH_RECEIVERS -> getExtensionReceiver()
else -> ReceiverValue.NO_RECEIVER 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
}
} }
@@ -38,13 +38,13 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall 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.calls.tasks.isSynthesizedInvoke
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.JetType
import java.util.ArrayList import java.util.*
import java.util.Collections
import java.util.HashMap
data class ExtractionOptions( data class ExtractionOptions(
val inferUnitTypeForUnusedValues: Boolean = true, val inferUnitTypeForUnusedValues: Boolean = true,
@@ -69,7 +69,8 @@ data class ResolvedReferenceInfo(
val refExpr: JetSimpleNameExpression, val refExpr: JetSimpleNameExpression,
val offsetInBody: Int, val offsetInBody: Int,
val resolveResult: ResolveResult, val resolveResult: ResolveResult,
val smartCast: JetType? val smartCast: JetType?,
val possibleTypes: Set<JetType>
) )
data class ExtractionData( data class ExtractionData(
@@ -160,6 +161,19 @@ data class ExtractionData(
else Collections.emptyMap<Int, ResolveResult>() else Collections.emptyMap<Int, ResolveResult>()
} }
fun getPossibleTypes(expression: JetExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set<JetType> {
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<ResolvedReferenceInfo> { fun getBrokenReferencesInfo(body: JetBlockExpression): List<ResolvedReferenceInfo> {
val originalContext = bindingContext ?: return listOf() val originalContext = bindingContext ?: return listOf()
@@ -174,11 +188,14 @@ data class ExtractionData(
val originalResolveResult = refOffsetToDeclaration[offset] ?: continue val originalResolveResult = refOffsetToDeclaration[offset] ?: continue
val smartCast: JetType? val smartCast: JetType?
val possibleTypes: Set<JetType>
// Qualified property reference: a.b // Qualified property reference: a.b
val qualifiedExpression = ref.getQualifiedExpressionForSelector() val qualifiedExpression = ref.getQualifiedExpressionForSelector()
if (qualifiedExpression != null) { 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 = val receiverDescriptor =
(originalResolveResult.resolvedCall?.getDispatchReceiver() as? ThisReceiver)?.getDeclarationDescriptor() (originalResolveResult.resolvedCall?.getDispatchReceiver() as? ThisReceiver)?.getDeclarationDescriptor()
if (smartCast == null if (smartCast == null
@@ -187,6 +204,7 @@ data class ExtractionData(
} }
else { else {
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr] smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
} }
val parent = ref.getParent() val parent = ref.getParent()
@@ -207,11 +225,11 @@ data class ExtractionData(
val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!, val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!,
descriptor = originalFunctionCall.getResultingDescriptor(), descriptor = originalFunctionCall.getResultingDescriptor(),
declaration = synthesizedInvokeDeclaration) declaration = synthesizedInvokeDeclaration)
referencesInfo.add(ResolvedReferenceInfo(ref, offset, variableResolveResult, smartCast)) referencesInfo.add(ResolvedReferenceInfo(ref, offset, variableResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(ref, offset, functionResolveResult, smartCast)) referencesInfo.add(ResolvedReferenceInfo(ref, offset, functionResolveResult, smartCast, possibleTypes))
} }
else { else {
referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast)) referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast, possibleTypes))
} }
} }
} }
@@ -59,8 +59,8 @@ import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
import org.jetbrains.kotlin.idea.util.isResolvableInScope import org.jetbrains.kotlin.idea.util.isResolvableInScope
import org.jetbrains.kotlin.lexer.JetToken
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.JetToken
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer 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.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny 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.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
@@ -498,18 +499,23 @@ private class MutableParameter(
override val argumentText: String, override val argumentText: String,
override val originalDescriptor: DeclarationDescriptor, override val originalDescriptor: DeclarationDescriptor,
override val receiverCandidate: Boolean, override val receiverCandidate: Boolean,
private val targetScope: LexicalScope? private val targetScope: LexicalScope?,
private val originalType: JetType,
private val possibleTypes: Set<JetType>
): Parameter { ): Parameter {
// All modifications happen in the same thread // All modifications happen in the same thread
private var writable: Boolean = true private var writable: Boolean = true
private val defaultTypes = HashSet<JetType>() private val defaultTypes = LinkedHashSet<JetType>()
private val typePredicates = HashSet<TypePredicate>() private val typePredicates = HashSet<TypePredicate>()
var refCount: Int = 0 var refCount: Int = 0
fun addDefaultType(jetType: JetType) { fun addDefaultType(jetType: JetType) {
assert(writable) { "Can't add type to non-writable parameter $currentName" } 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) { fun addTypePredicate(predicate: TypePredicate) {
@@ -524,7 +530,10 @@ private class MutableParameter(
private val defaultType: JetType by lazy { private val defaultType: JetType by lazy {
writable = false 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<JetType> by lazy { private val parameterTypeCandidates: List<JetType> by lazy {
@@ -595,6 +604,44 @@ private fun ExtractionData.inferParametersInfo(
val extractedDescriptorToParameter = HashMap<DeclarationDescriptor, MutableParameter>() val extractedDescriptorToParameter = HashMap<DeclarationDescriptor, MutableParameter>()
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())) { for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) {
val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult
val ref = refInfo.refExpr val ref = refInfo.refExpr
@@ -671,31 +718,7 @@ private fun ExtractionData.inferParametersInfo(
else -> (originalRef.getParent() as? JetThisExpression) ?: originalRef else -> (originalRef.getParent() as? JetThisExpression) ?: originalRef
} }
val parameterType = when { val parameterType = suggestParameterType(extractFunctionRef, originalDescriptor, parameterExpression, receiverToExtract, resolvedCall, true)
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 parameter = extractedDescriptorToParameter.getOrPut(descriptorToExtract) { val parameter = extractedDescriptorToParameter.getOrPut(descriptorToExtract) {
var argumentText = var argumentText =
@@ -720,7 +743,9 @@ private fun ExtractionData.inferParametersInfo(
argumentText = "$receiverTypeText::$argumentText" 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) { if (!extractThis) {
@@ -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 = <selection>when (o) {
is A -> {
if (o is T) o.a + o.t else o.a
}
else -> o.hashCode()
}</selection>
}
@@ -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()
}
}
@@ -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 -> {
<selection>if (o is T) o.a + o.t else o.a</selection>
}
else -> o.hashCode()
}
}
@@ -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
@@ -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) <selection>o.a + o.t</selection> else o.a
}
else -> o.hashCode()
}
}
@@ -0,0 +1 @@
Cannot extract method since following types are not denotable in the target scope: {A &amp; T}
@@ -1784,6 +1784,24 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doExtractFunctionTest(fileName); 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") @TestMetadata("nonNullableTypes.kt")
public void testNonNullableTypes() throws Exception { public void testNonNullableTypes() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt");