Extract Function: Add support for member extension properties

This commit is contained in:
Alexey Sedunov
2015-11-30 16:19:40 +03:00
parent 39b46bb3ef
commit ffd8863875
7 changed files with 248 additions and 30 deletions
@@ -83,4 +83,6 @@ public fun ResolvedCall<*>.getImplicitReceiverValue(): ReceiverValue {
ExplicitReceiverKind.EXTENSION_RECEIVER -> dispatchReceiver
else -> ReceiverValue.NO_RECEIVER
}
}
}
public fun ResolvedCall<*>.hasBothReceivers() = dispatchReceiver.exists() && extensionReceiver.exists()
@@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
@@ -32,6 +33,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.Analysis
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.*
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
import org.jetbrains.kotlin.idea.util.isAnnotatedNotNull
import org.jetbrains.kotlin.idea.util.isAnnotatedNullable
@@ -39,14 +41,16 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.util.*
interface Parameter {
@@ -95,15 +99,27 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
}
}
class WrapInWithReplacement(override val parameter: Parameter): ParameterReplacement {
abstract class WrapInWithReplacement : Replacement {
abstract val argumentText: String
override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
val call = e.parents.firstIsInstance<KtCallExpression>().getQualifiedExpressionForSelectorOrThis()
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", parameter.name, call)
val call = (e as? KtSimpleNameExpression)?.getQualifiedElement() ?: return e
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", argumentText, call)
val replace = call.replace(replacingExpression)
return (replace as KtCallExpression).functionLiteralArguments.first().getFunctionLiteral().bodyExpression!!.statements.first()
}
}
override fun copy(parameter: Parameter) = WrapInWithReplacement(parameter)
class WrapParameterInWithReplacement(override val parameter: Parameter): WrapInWithReplacement(), ParameterReplacement {
override val argumentText: String
get() = parameter.name
override fun copy(parameter: Parameter) = WrapParameterInWithReplacement(parameter)
}
class WrapCompanionInWithReplacement(val descriptor: ClassDescriptor): WrapInWithReplacement() {
override val argumentText: String
get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor)
}
class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement {
@@ -44,6 +44,7 @@ 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.resolvedCallUtil.hasBothReceivers
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
@@ -75,7 +76,8 @@ data class ResolvedReferenceInfo(
val refExpr: KtSimpleNameExpression,
val resolveResult: ResolveResult,
val smartCast: KotlinType?,
val possibleTypes: Set<KotlinType>
val possibleTypes: Set<KotlinType>,
val shouldSkipPrimaryReceiver: Boolean
)
internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyableUserDataProperty(Key.create("RESOLVE_RESULT"))
@@ -202,6 +204,7 @@ data class ExtractionData(
val smartCast: KotlinType?
val possibleTypes: Set<KotlinType>
val shouldSkipPrimaryReceiver: Boolean
// Qualified property reference: a.b
val qualifiedExpression = newRef.getQualifiedExpressionForSelector()
@@ -211,13 +214,15 @@ data class ExtractionData(
possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext)
val receiverDescriptor =
(originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor
if (smartCast == null
&& !DescriptorUtils.isCompanionObject(receiverDescriptor)
&& qualifiedExpression.receiverExpression !is KtSuperExpression) continue
shouldSkipPrimaryReceiver = smartCast == null
&& !DescriptorUtils.isCompanionObject(receiverDescriptor)
&& qualifiedExpression.receiverExpression !is KtSuperExpression
if (shouldSkipPrimaryReceiver && !(originalResolveResult.resolvedCall?.hasBothReceivers() ?: false)) continue
}
else {
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
shouldSkipPrimaryReceiver = false
}
val parent = newRef.parent
@@ -241,11 +246,11 @@ data class ExtractionData(
val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!,
descriptor = originalFunctionCall.resultingDescriptor,
declaration = invokeDeclaration)
referencesInfo.add(ResolvedReferenceInfo(newRef, variableResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(newRef, functionResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(newRef, variableResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver))
referencesInfo.add(ResolvedReferenceInfo(newRef, functionResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver))
}
else {
referencesInfo.add(ResolvedReferenceInfo(newRef, originalResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(newRef, originalResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver))
}
}
}
@@ -35,8 +35,10 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.hasBothReceivers
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
@@ -44,8 +46,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
import org.jetbrains.kotlin.types.CommonSupertypes
import org.jetbrains.kotlin.types.KotlinType
@@ -90,9 +92,22 @@ internal fun ExtractionData.inferParametersInfo(
else -> extensionReceiver
} as? ReceiverValue) ?: ReceiverValue.NO_RECEIVER
extractReceiver(receiverToExtract, info, targetScope, refInfo, extractedDescriptorToParameter, pseudocode, bindingContext, false)
if (options.canWrapInWith && resolvedCall != null && isMemberExtensionFunction(resolvedCall, ref)) {
extractReceiver(resolvedCall.dispatchReceiver, info, targetScope, refInfo, extractedDescriptorToParameter, pseudocode, bindingContext, true)
val twoReceivers = resolvedCall != null && resolvedCall.hasBothReceivers()
val dispatchReceiverDescriptor = (resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor
if (twoReceivers
&& resolvedCall!!.extensionReceiver is ExpressionReceiver
&& DescriptorUtils.isCompanionObject(dispatchReceiverDescriptor)) {
info.replacementMap.putValue(refInfo.resolveResult.originalRefExpr,
WrapCompanionInWithReplacement(dispatchReceiverDescriptor as ClassDescriptor))
continue
}
if (!refInfo.shouldSkipPrimaryReceiver) {
extractReceiver(receiverToExtract, info, targetScope, refInfo, extractedDescriptorToParameter, pseudocode, bindingContext, false)
}
if (options.canWrapInWith && twoReceivers) {
extractReceiver(resolvedCall!!.dispatchReceiver, info, targetScope, refInfo, extractedDescriptorToParameter, pseudocode, bindingContext, true)
}
}
@@ -132,7 +147,7 @@ private fun ExtractionData.extractReceiver(
extractedDescriptorToParameter: HashMap<DeclarationDescriptor, MutableParameter>,
pseudocode: Pseudocode,
bindingContext: BindingContext,
isMemberExtensionFunction: Boolean
isMemberExtension: Boolean
) {
val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult
@@ -162,12 +177,17 @@ private fun ExtractionData.extractReceiver(
} as? ClassifierDescriptor
}
if (referencedClassifierDescriptor != null && !(isMemberExtensionFunction && options.canWrapInWith)) {
if (referencedClassifierDescriptor != null) {
if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable(
info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor
)) return
if (referencedClassifierDescriptor is ClassDescriptor) {
if (options.canWrapInWith
&& resolvedCall != null
&& resolvedCall.hasBothReceivers()
&& DescriptorUtils.isCompanionObject(referencedClassifierDescriptor)) {
info.replacementMap.putValue(originalRef, WrapCompanionInWithReplacement(referencedClassifierDescriptor as ClassDescriptor))
} else if (referencedClassifierDescriptor is ClassDescriptor) {
info.replacementMap.putValue(originalRef, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe))
}
}
@@ -255,7 +275,7 @@ private fun ExtractionData.extractReceiver(
}
val replacement = when {
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
isMemberExtension -> WrapParameterInWithReplacement(parameter)
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
else -> RenameReplacement(parameter)
}
@@ -303,12 +323,4 @@ private fun suggestParameterType(
else -> null
} ?: builtIns.defaultParameterType
}
private fun isMemberExtensionFunction(resolvedCall: ResolvedCall<*>, ref: KtSimpleNameExpression): Boolean {
// TODO temporary hack because we couldn't correctly extract member extension function with two explicit receivers
if (ref.parent !is KtCallExpression) return false
val resultingDescriptor = resolvedCall.resultingDescriptor
return resultingDescriptor is FunctionDescriptor && resolvedCall.extensionReceiver != ReceiverValue.NO_RECEIVER && resolvedCall.dispatchReceiver != ReceiverValue.NO_RECEIVER
}
@@ -0,0 +1,53 @@
LineBreakpoint created at extensionMemberProperty.kt:13
LineBreakpoint created at extensionMemberProperty.kt:19
LineBreakpoint created at extensionMemberProperty.kt:25
LineBreakpoint created at extensionMemberProperty.kt:31
LineBreakpoint created at extensionMemberProperty.kt:41
LineBreakpoint created at extensionMemberProperty.kt:46
LineBreakpoint created at extensionMemberProperty.kt:51
LineBreakpoint created at extensionMemberProperty.kt:56
LineBreakpoint created at extensionMemberProperty.kt:74
LineBreakpoint created at extensionMemberProperty.kt:80
LineBreakpoint created at extensionMemberProperty.kt:86
LineBreakpoint created at extensionMemberProperty.kt:92
LineBreakpoint created at extensionMemberProperty.kt:102
LineBreakpoint created at extensionMemberProperty.kt:107
LineBreakpoint created at extensionMemberProperty.kt:112
LineBreakpoint created at extensionMemberProperty.kt:117
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! extensionMemberProperty.ExtensionMemberPropertyKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
extensionMemberProperty.kt:13
Compile bytecode for extClass.testPublic
extensionMemberProperty.kt:19
Compile bytecode for testPublic
extensionMemberProperty.kt:25
Compile bytecode for extClass.testPrivate
extensionMemberProperty.kt:31
Compile bytecode for testPrivate
extensionMemberProperty.kt:41
Compile bytecode for testPublic
extensionMemberProperty.kt:46
Compile bytecode for this.testPublic
extensionMemberProperty.kt:51
Compile bytecode for testPrivate
extensionMemberProperty.kt:56
Compile bytecode for this.testPrivate
extensionMemberProperty.kt:74
Compile bytecode for extClass.testCompPublic
extensionMemberProperty.kt:80
Compile bytecode for testCompPublic
extensionMemberProperty.kt:86
Compile bytecode for extClass.testCompPrivate
extensionMemberProperty.kt:92
Compile bytecode for testCompPrivate
extensionMemberProperty.kt:102
Compile bytecode for testCompPublic
extensionMemberProperty.kt:107
Compile bytecode for this.testCompPublic
extensionMemberProperty.kt:112
Compile bytecode for testCompPrivate
extensionMemberProperty.kt:117
Compile bytecode for this.testCompPrivate
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,124 @@
package extensionMemberProperty
fun main(args: Array<String>) {
MemberClass().testMember(ExtClass())
MemberClass.testCompanion(ExtClass())
}
class MemberClass {
fun testMember(extClass: ExtClass) {
// EXPRESSION: extClass.testPublic
// RESULT: 1: I
//Breakpoint!
extClass.testPublic
with(extClass) {
// EXPRESSION: testPublic
// RESULT: 1: I
//Breakpoint!
testPublic
}
// EXPRESSION: extClass.testPrivate
// RESULT: 1: I
//Breakpoint!
extClass.testPrivate
with(extClass) {
// EXPRESSION: testPrivate
// RESULT: 1: I
//Breakpoint!
testPrivate
}
extClass.testExtMember()
}
fun ExtClass.testExtMember() {
// EXPRESSION: testPublic
// RESULT: 1: I
//Breakpoint!
testPublic
// EXPRESSION: this.testPublic
// RESULT: 1: I
//Breakpoint!
this.testPublic
// EXPRESSION: testPrivate
// RESULT: 1: I
//Breakpoint!
testPrivate
// EXPRESSION: this.testPrivate
// RESULT: 1: I
//Breakpoint!
this.testPrivate
}
public val ExtClass.testPublic: Int
get() = a
private val ExtClass.testPrivate: Int
get() = a
companion object {
public val ExtClass.testCompPublic: Int
get() = a
private val ExtClass.testCompPrivate: Int
get() = a
fun testCompanion(extClass: ExtClass) {
// EXPRESSION: extClass.testCompPublic
// RESULT: 1: I
//Breakpoint!
extClass.testCompPublic
with(extClass) {
// EXPRESSION: testCompPublic
// RESULT: 1: I
//Breakpoint!
testCompPublic
}
// EXPRESSION: extClass.testCompPrivate
// RESULT: 1: I
//Breakpoint!
extClass.testCompPrivate
with(extClass) {
// EXPRESSION: testCompPrivate
// RESULT: 1: I
//Breakpoint!
testCompPrivate
}
extClass.testExtCompanion()
}
fun ExtClass.testExtCompanion() {
// EXPRESSION: testCompPublic
// RESULT: 1: I
//Breakpoint!
testCompPublic
// EXPRESSION: this.testCompPublic
// RESULT: 1: I
//Breakpoint!
this.testCompPublic
// EXPRESSION: testCompPrivate
// RESULT: 1: I
//Breakpoint!
testCompPrivate
// EXPRESSION: this.testCompPrivate
// RESULT: 1: I
//Breakpoint!
this.testCompPrivate
}
}
}
class ExtClass {
val a = 1
}
@@ -712,6 +712,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("extensionMemberProperty.kt")
public void testExtensionMemberProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.kt");
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("funFromOuterClassInLamdba.kt")
public void testFunFromOuterClassInLamdba() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt");