Debugger: initial support for member extension functions in evaluate expression

This commit is contained in:
Natalia Ukhorskaya
2015-11-10 15:04:55 +03:00
parent ba9e06d25b
commit ea50d8af02
7 changed files with 196 additions and 8 deletions
@@ -389,9 +389,6 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val valueParameters = extractedFunction.valueParameters val valueParameters = extractedFunction.valueParameters
var paramIndex = 0 var paramIndex = 0
for (param in parameters) { for (param in parameters) {
if (param.callText.contains(THIS_NAME)) continue
val valueParameter = valueParameters[paramIndex++] val valueParameter = valueParameters[paramIndex++]
val paramRef = valueParameter.typeReference val paramRef = valueParameter.typeReference
@@ -38,15 +38,14 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector import org.jetbrains.kotlin.psi.psiUtil.*
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.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.util.* import java.util.*
interface Parameter { interface Parameter {
@@ -95,6 +94,17 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
} }
} }
class WrapInWithReplacement(override val parameter: Parameter): ParameterReplacement {
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.text)
val replace = call.replace(replacingExpression)
return (replace as KtCallExpression).functionLiteralArguments.first().getFunctionLiteral().bodyExpression!!.statements.first()
}
override fun copy(parameter: Parameter) = WrapInWithReplacement(parameter)
}
class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement { class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement {
override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter) override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter)
@@ -695,7 +695,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val adjustedParameters = paramsInfo.parameters.filterTo(HashSet<Parameter>()) { it.refCount > 0 } val adjustedParameters = paramsInfo.parameters.filterTo(HashSet<Parameter>()) { it.refCount > 0 }
val receiverCandidates = adjustedParameters.filterTo(HashSet<Parameter>()) { it.receiverCandidate } val receiverCandidates = adjustedParameters.filterTo(HashSet<Parameter>()) { it.receiverCandidate }
val receiverParameter = if (receiverCandidates.size == 1) receiverCandidates.first() else null val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null
receiverParameter?.let { adjustedParameters.remove(it) } receiverParameter?.let { adjustedParameters.remove(it) }
return AnalysisResult( return AnalysisResult(
@@ -84,6 +84,11 @@ internal fun ExtractionData.inferParametersInfo(
extractReceiver(receiverToExtract, ref, project, originalElements, extractReceiver(receiverToExtract, ref, project, originalElements,
originalDescriptor, info, options, targetScope, refInfo, originalDescriptor, info, options, targetScope, refInfo,
originalDeclaration, originalRef, extractedDescriptorToParameter, resolvedCall, pseudocode, codeFragmentText, bindingContext, false) originalDeclaration, originalRef, extractedDescriptorToParameter, resolvedCall, pseudocode, codeFragmentText, bindingContext, false)
if (options.canWrapInWith && resolvedCall != null && isMemberExtensionFunction(resolvedCall, ref)) {
extractReceiver(resolvedCall.dispatchReceiver, ref, project, originalElements,
originalDescriptor, info, options, targetScope, refInfo,
originalDeclaration, originalRef, extractedDescriptorToParameter, resolvedCall, pseudocode, codeFragmentText, bindingContext, true)
}
} }
val varNameValidator = NewDeclarationNameValidator( val varNameValidator = NewDeclarationNameValidator(
@@ -159,7 +164,7 @@ private fun extractReceiver(
} as? ClassifierDescriptor } as? ClassifierDescriptor
} }
if (referencedClassifierDescriptor != null) { if (referencedClassifierDescriptor != null && !(isMemberExtensionFunction && options.canWrapInWith)) {
if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable( if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable(
info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor
)) return )) return
@@ -253,6 +258,7 @@ private fun extractReceiver(
info.replacementMap.putValue(refInfo.offsetInBody, info.replacementMap.putValue(refInfo.offsetInBody,
when { when {
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter) hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
else -> RenameReplacement(parameter) else -> RenameReplacement(parameter)
}) })
@@ -297,4 +303,12 @@ private fun suggestParameterType(
receiverToExtract.exists() -> receiverToExtract.type receiverToExtract.exists() -> receiverToExtract.type
else -> null else -> null
} ?: builtIns.defaultParameterType } ?: 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 || ref.parent.parent !is KtQualifiedExpression) return false
val resultingDescriptor = resolvedCall.resultingDescriptor
return resultingDescriptor is FunctionDescriptor && resolvedCall.extensionReceiver != ReceiverValue.NO_RECEIVER && resolvedCall.dispatchReceiver != ReceiverValue.NO_RECEIVER
} }
@@ -0,0 +1,41 @@
LineBreakpoint created at extensionMemberFunction.kt:13
LineBreakpoint created at extensionMemberFunction.kt:19
LineBreakpoint created at extensionMemberFunction.kt:25
LineBreakpoint created at extensionMemberFunction.kt:31
LineBreakpoint created at extensionMemberFunction.kt:41
LineBreakpoint created at extensionMemberFunction.kt:46
LineBreakpoint created at extensionMemberFunction.kt:51
LineBreakpoint created at extensionMemberFunction.kt:56
LineBreakpoint created at extensionMemberFunction.kt:70
LineBreakpoint created at extensionMemberFunction.kt:76
LineBreakpoint created at extensionMemberFunction.kt:82
LineBreakpoint created at extensionMemberFunction.kt:88
LineBreakpoint created at extensionMemberFunction.kt:98
LineBreakpoint created at extensionMemberFunction.kt:103
LineBreakpoint created at extensionMemberFunction.kt:108
LineBreakpoint created at extensionMemberFunction.kt:113
!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! extensionMemberFunction.ExtensionMemberFunctionKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
extensionMemberFunction.kt:13
Compile bytecode for extClass.testPublic()
extensionMemberFunction.kt:19
extensionMemberFunction.kt:25
Compile bytecode for extClass.testPrivate()
extensionMemberFunction.kt:31
extensionMemberFunction.kt:41
extensionMemberFunction.kt:46
extensionMemberFunction.kt:51
extensionMemberFunction.kt:56
extensionMemberFunction.kt:70
Compile bytecode for extClass.testCompPublic()
extensionMemberFunction.kt:76
extensionMemberFunction.kt:82
Compile bytecode for extClass.testCompPrivate()
extensionMemberFunction.kt:88
extensionMemberFunction.kt:98
extensionMemberFunction.kt:103
extensionMemberFunction.kt:108
extensionMemberFunction.kt:113
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,120 @@
package extensionMemberFunction
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: Following declarations are unavailable in debug scope: testPublic
//Breakpoint!
testPublic()
}
// EXPRESSION: extClass.testPrivate()
// RESULT: 1: I
//Breakpoint!
extClass.testPrivate()
with(extClass) {
// EXPRESSION: testPrivate()
// RESULT: Following declarations are unavailable in debug scope: testPrivate
//Breakpoint!
testPrivate()
}
extClass.testExtMember()
}
fun ExtClass.testExtMember() {
// EXPRESSION: testPublic()
// RESULT: Following declarations are unavailable in debug scope: testPublic
//Breakpoint!
testPublic()
// EXPRESSION: this.testPublic()
// RESULT: A receiver of type extensionMemberFunction.ExtClass is required
//Breakpoint!
this.testPublic()
// EXPRESSION: testPrivate()
// RESULT: Following declarations are unavailable in debug scope: testPrivate
//Breakpoint!
testPrivate()
// EXPRESSION: this.testPrivate()
// RESULT: A receiver of type extensionMemberFunction.ExtClass is required
//Breakpoint!
this.testPrivate()
}
public fun ExtClass.testPublic() = a
private fun ExtClass.testPrivate() = a
companion object {
public fun ExtClass.testCompPublic() = a
private fun ExtClass.testCompPrivate() = a
fun testCompanion(extClass: ExtClass) {
// EXPRESSION: extClass.testCompPublic()
// RESULT: 1: I
//Breakpoint!
extClass.testCompPublic()
with(extClass) {
// EXPRESSION: testCompPublic()
// RESULT: Following declarations are unavailable in debug scope: testCompPublic
//Breakpoint!
testCompPublic()
}
// EXPRESSION: extClass.testCompPrivate()
// RESULT: 1: I
//Breakpoint!
extClass.testCompPrivate()
with(extClass) {
// EXPRESSION: testCompPrivate()
// RESULT: Following declarations are unavailable in debug scope: testCompPrivate
//Breakpoint!
testCompPrivate()
}
extClass.testExtCompanion()
}
fun ExtClass.testExtCompanion() {
// EXPRESSION: testCompPublic()
// RESULT: Following declarations are unavailable in debug scope: testCompPublic
//Breakpoint!
testCompPublic()
// EXPRESSION: this.testCompPublic()
// RESULT: A receiver of type extensionMemberFunction.ExtClass is required
//Breakpoint!
this.testCompPublic()
// EXPRESSION: testCompPrivate()
// RESULT: Following declarations are unavailable in debug scope: testCompPrivate
//Breakpoint!
testCompPrivate()
// EXPRESSION: this.testCompPrivate()
// RESULT: A receiver of type extensionMemberFunction.ExtClass is required
//Breakpoint!
this.testCompPrivate()
}
}
}
class ExtClass {
val a = 1
}
@@ -676,6 +676,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doMultipleBreakpointsTest(fileName); doMultipleBreakpointsTest(fileName);
} }
@TestMetadata("extensionMemberFunction.kt")
public void testExtensionMemberFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.kt");
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("funFromOuterClassInLamdba.kt") @TestMetadata("funFromOuterClassInLamdba.kt")
public void testFunFromOuterClassInLamdba() throws Exception { public void testFunFromOuterClassInLamdba() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt");