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
var paramIndex = 0
for (param in parameters) {
if (param.callText.contains(THIS_NAME)) continue
val valueParameter = valueParameters[paramIndex++]
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.name.FqName
import org.jetbrains.kotlin.psi.*
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.psi.psiUtil.*
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,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 {
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 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) }
return AnalysisResult(
@@ -84,6 +84,11 @@ internal fun ExtractionData.inferParametersInfo(
extractReceiver(receiverToExtract, ref, project, originalElements,
originalDescriptor, info, options, targetScope, refInfo,
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(
@@ -159,7 +164,7 @@ private fun extractReceiver(
} as? ClassifierDescriptor
}
if (referencedClassifierDescriptor != null) {
if (referencedClassifierDescriptor != null && !(isMemberExtensionFunction && options.canWrapInWith)) {
if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable(
info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor
)) return
@@ -253,6 +258,7 @@ private fun extractReceiver(
info.replacementMap.putValue(refInfo.offsetInBody,
when {
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
else -> RenameReplacement(parameter)
})
@@ -297,4 +303,12 @@ private fun suggestParameterType(
receiverToExtract.exists() -> receiverToExtract.type
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 || 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);
}
@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")
public void testFunFromOuterClassInLamdba() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt");