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()
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.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<JetType>
)
data class ExtractionData(
@@ -160,6 +161,19 @@ data class ExtractionData(
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> {
val originalContext = bindingContext ?: return listOf()
@@ -174,11 +188,14 @@ data class ExtractionData(
val originalResolveResult = refOffsetToDeclaration[offset] ?: continue
val smartCast: JetType?
val possibleTypes: Set<JetType>
// 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))
}
}
}
@@ -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<JetType>
): Parameter {
// All modifications happen in the same thread
private var writable: Boolean = true
private val defaultTypes = HashSet<JetType>()
private val defaultTypes = LinkedHashSet<JetType>()
private val typePredicates = HashSet<TypePredicate>()
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<JetType> by lazy {
@@ -595,6 +604,44 @@ private fun ExtractionData.inferParametersInfo(
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())) {
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) {
@@ -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);
}
@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");