Debugger: initial support for member extension functions in evaluate expression
This commit is contained in:
@@ -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
|
||||||
|
|||||||
+13
-3
@@ -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)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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(
|
||||||
|
|||||||
+15
-1
@@ -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
|
||||||
+120
@@ -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
|
||||||
|
}
|
||||||
+6
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user