Extract Function: Ignore internal smartcasts
#KT-8458 Fixed
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
+26
-8
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
-31
@@ -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) {
|
||||
|
||||
+24
@@ -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>
|
||||
}
|
||||
Vendored
+28
@@ -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()
|
||||
}
|
||||
}
|
||||
+23
@@ -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()
|
||||
}
|
||||
}
|
||||
Vendored
+25
@@ -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
|
||||
+20
@@ -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()
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Cannot extract method since following types are not denotable in the target scope: {A & T}
|
||||
+18
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user