Fix "smart step into" for classes with complex hierarchy (KT-16667)
#KT-16667 Fixed
This commit is contained in:
+31
-16
@@ -21,23 +21,25 @@ import com.intellij.debugger.engine.NamedMethodFilter
|
|||||||
import com.intellij.util.Range
|
import com.intellij.util.Range
|
||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.*
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.core.getDirectlyOverriddenDeclarations
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
|
||||||
import org.jetbrains.kotlin.resolve.findTopMostOverriddenDescriptors
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
|
||||||
class KotlinBasicStepMethodFilter(
|
class KotlinBasicStepMethodFilter(
|
||||||
val descriptor: CallableDescriptor,
|
val targetDescriptor: CallableMemberDescriptor,
|
||||||
val myCallingExpressionLines: Range<Int>
|
val myCallingExpressionLines: Range<Int>
|
||||||
) : NamedMethodFilter {
|
) : NamedMethodFilter {
|
||||||
private val myTargetMethodName = when (descriptor) {
|
private val myTargetMethodName = when (targetDescriptor) {
|
||||||
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
|
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
|
||||||
is PropertyAccessorDescriptor -> JvmAbi.getterName(descriptor.correspondingProperty.name.asString())
|
is PropertyAccessorDescriptor -> JvmAbi.getterName(targetDescriptor.correspondingProperty.name.asString())
|
||||||
else -> descriptor.name.asString()
|
else -> targetDescriptor.name.asString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCallingExpressionLines() = myCallingExpressionLines
|
override fun getCallingExpressionLines() = myCallingExpressionLines
|
||||||
@@ -50,7 +52,7 @@ class KotlinBasicStepMethodFilter(
|
|||||||
|
|
||||||
val positionManager = process.positionManager ?: return false
|
val positionManager = process.positionManager ?: return false
|
||||||
|
|
||||||
val descriptor = runReadAction {
|
val currentDescriptor = runReadAction {
|
||||||
val elementAt = positionManager.getSourcePosition(location)?.elementAt
|
val elementAt = positionManager.getSourcePosition(location)?.elementAt
|
||||||
|
|
||||||
val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) {
|
val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) {
|
||||||
@@ -62,21 +64,34 @@ class KotlinBasicStepMethodFilter(
|
|||||||
} else {
|
} else {
|
||||||
declaration?.resolveToDescriptor()
|
declaration?.resolveToDescriptor()
|
||||||
}
|
}
|
||||||
} ?: return true
|
} ?: return false // TODO: Check that we can always find a descriptor (libraries with sources, libraries without sources)
|
||||||
|
|
||||||
fun compareDescriptors(d1: DeclarationDescriptor, d2: DeclarationDescriptor): Boolean {
|
@Suppress("FoldInitializerAndIfToElvis")
|
||||||
return d1 == d2 || d1.original == d2.original
|
if (currentDescriptor !is CallableMemberDescriptor) return false
|
||||||
|
if (currentDescriptor.kind != DECLARATION) return false
|
||||||
|
|
||||||
|
if (compareDescriptors(currentDescriptor, targetDescriptor)) return true
|
||||||
|
|
||||||
|
// We should stop if current descriptor overrides the target one or some base descriptor of target
|
||||||
|
// (if target descriptor is delegation or fake override)
|
||||||
|
|
||||||
|
val baseDescriptors = when (targetDescriptor.kind) {
|
||||||
|
DELEGATION, FAKE_OVERRIDE ->
|
||||||
|
targetDescriptor.getDirectlyOverriddenDeclarations()
|
||||||
|
DECLARATION, SYNTHESIZED ->
|
||||||
|
listOf(targetDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compareDescriptors(descriptor, this.descriptor)) return true
|
if (baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfTarget, currentDescriptor) }) {
|
||||||
|
return true
|
||||||
if (descriptor is CallableDescriptor) {
|
|
||||||
return descriptor.findTopMostOverriddenDescriptors().any { compareDescriptors(it, this.descriptor) } ||
|
|
||||||
this.descriptor.findTopMostOverriddenDescriptors().any { compareDescriptors(it, descriptor)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return DescriptorUtils.getAllOverriddenDescriptors(currentDescriptor).any { baseOfCurrent ->
|
||||||
|
baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfCurrent, baseOfTarget) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compareDescriptors(d1: DeclarationDescriptor, d2: DeclarationDescriptor): Boolean {
|
||||||
|
return d1 == d2 || d1.original == d2.original
|
||||||
}
|
}
|
||||||
+2
-2
@@ -3,7 +3,7 @@ package org.jetbrains.kotlin.idea.debugger.stepping
|
|||||||
import com.intellij.debugger.actions.SmartStepTarget
|
import com.intellij.debugger.actions.SmartStepTarget
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.util.Range
|
import com.intellij.util.Range
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
|||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
|
|
||||||
class KotlinMethodSmartStepTarget(
|
class KotlinMethodSmartStepTarget(
|
||||||
val descriptor: CallableDescriptor,
|
val descriptor: CallableMemberDescriptor,
|
||||||
label: String,
|
label: String,
|
||||||
highlightElement: PsiElement,
|
highlightElement: PsiElement,
|
||||||
lines: Range<Int>
|
lines: Range<Int>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at smartStepIntoComponentFunction.kt:10
|
||||||
|
!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! smartStepIntoComponentFunction.SmartStepIntoComponentFunctionKt
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
smartStepIntoComponentFunction.kt:10
|
||||||
|
smartStepIntoComponentFunction.kt:11
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
LineBreakpoint created at smartStepIntoWithDelegates.kt:24
|
||||||
|
LineBreakpoint created at smartStepIntoWithDelegates.kt:32
|
||||||
|
LineBreakpoint created at smartStepIntoWithDelegates.kt:40
|
||||||
|
LineBreakpoint created at smartStepIntoWithDelegates.kt:49
|
||||||
|
LineBreakpoint created at smartStepIntoWithDelegates.kt:58
|
||||||
|
!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! smartStepIntoWithDelegates.SmartStepIntoWithDelegatesKt
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
smartStepIntoWithDelegates.kt:24
|
||||||
|
smartStepIntoWithDelegates.kt:12
|
||||||
|
smartStepIntoWithDelegates.kt:32
|
||||||
|
smartStepIntoWithDelegates.kt:12
|
||||||
|
smartStepIntoWithDelegates.kt:40
|
||||||
|
smartStepIntoWithDelegates.kt:8
|
||||||
|
smartStepIntoWithDelegates.kt:49
|
||||||
|
smartStepIntoWithDelegates.kt:8
|
||||||
|
smartStepIntoWithDelegates.kt:58
|
||||||
|
smartStepIntoWithDelegates.kt:12
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
LineBreakpoint created at smartStepIntoWithOverrides.kt:22
|
||||||
|
LineBreakpoint created at smartStepIntoWithOverrides.kt:30
|
||||||
|
LineBreakpoint created at smartStepIntoWithOverrides.kt:38
|
||||||
|
LineBreakpoint created at smartStepIntoWithOverrides.kt:47
|
||||||
|
LineBreakpoint created at smartStepIntoWithOverrides.kt:56
|
||||||
|
!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! smartStepIntoWithOverrides.SmartStepIntoWithOverridesKt
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
smartStepIntoWithOverrides.kt:22
|
||||||
|
smartStepIntoWithOverrides.kt:12
|
||||||
|
smartStepIntoWithOverrides.kt:30
|
||||||
|
smartStepIntoWithOverrides.kt:8
|
||||||
|
smartStepIntoWithOverrides.kt:38
|
||||||
|
smartStepIntoWithOverrides.kt:12
|
||||||
|
smartStepIntoWithOverrides.kt:47
|
||||||
|
smartStepIntoWithOverrides.kt:8
|
||||||
|
smartStepIntoWithOverrides.kt:56
|
||||||
|
smartStepIntoWithOverrides.kt:12
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package smartStepIntoComponentFunction
|
||||||
|
|
||||||
|
data class Test(val a: Int, val b: Int)
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val t = Test(12, 2)
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
t.component2()
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package smartStepIntoWithDelegates
|
||||||
|
|
||||||
|
interface A {
|
||||||
|
fun foo(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class AA : A {
|
||||||
|
override fun foo(): String = "AA"
|
||||||
|
}
|
||||||
|
|
||||||
|
class B(a: A) : A by a {
|
||||||
|
override fun foo(): String = "B"
|
||||||
|
}
|
||||||
|
|
||||||
|
class C(b: B) : A by b
|
||||||
|
|
||||||
|
class D(a: A) : A by a
|
||||||
|
|
||||||
|
fun test1() {
|
||||||
|
val c: C = C(B(AA()))
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
c.foo() // 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test2() {
|
||||||
|
val a: A = C(B(AA()))
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
a.foo() // 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test3() {
|
||||||
|
val d: D = D(AA())
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
d.foo() // 8
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test4() {
|
||||||
|
val aa: A = AA()
|
||||||
|
val c: B = B(AA())
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
aa.foo() + c.foo() // 8
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test5() {
|
||||||
|
val aa: A = AA()
|
||||||
|
val c: B = B(AA())
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 2
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
aa.foo() + c.foo() // 12 (Shouldn't stop at B.foo() even it's evaluated before C.foo())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
test1()
|
||||||
|
test2()
|
||||||
|
test3()
|
||||||
|
test4()
|
||||||
|
test5()
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
package smartStepIntoWithOverrides
|
||||||
|
|
||||||
|
abstract class A {
|
||||||
|
abstract fun foo(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
open class B : A() {
|
||||||
|
override fun foo(): String = "B"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class C : B() {
|
||||||
|
override fun foo(): String = "C"
|
||||||
|
}
|
||||||
|
|
||||||
|
class D : C()
|
||||||
|
|
||||||
|
fun test1() {
|
||||||
|
val d: D = D()
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
d.foo() // 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test2() {
|
||||||
|
val a: A = B()
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
a.foo() // 8
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test3() {
|
||||||
|
val a: A = C()
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
a.foo() // 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test4() {
|
||||||
|
val a: A = B()
|
||||||
|
val d: D = D()
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 1
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
a.foo() + d.foo() // 8
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test5() {
|
||||||
|
val a: A = B()
|
||||||
|
val d: D = D()
|
||||||
|
// SMART_STEP_INTO_BY_INDEX: 2
|
||||||
|
// RESUME: 1
|
||||||
|
//Breakpoint!
|
||||||
|
a.foo() + d.foo() // 12 (Shouldn't stop at B.foo() even it's evaluated before C.foo())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
test1()
|
||||||
|
test2()
|
||||||
|
test3()
|
||||||
|
test4()
|
||||||
|
test5()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1088,6 +1088,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
doCustomTest(fileName);
|
doCustomTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("smartStepIntoComponentFunction.kt")
|
||||||
|
public void testSmartStepIntoComponentFunction() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoComponentFunction.kt");
|
||||||
|
doCustomTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("smartStepIntoConstructor.kt")
|
@TestMetadata("smartStepIntoConstructor.kt")
|
||||||
public void testSmartStepIntoConstructor() throws Exception {
|
public void testSmartStepIntoConstructor() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoConstructor.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoConstructor.kt");
|
||||||
@@ -1142,6 +1148,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
doCustomTest(fileName);
|
doCustomTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("smartStepIntoWithDelegates.kt")
|
||||||
|
public void testSmartStepIntoWithDelegates() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithDelegates.kt");
|
||||||
|
doCustomTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("smartStepIntoWithOverrides.kt")
|
||||||
|
public void testSmartStepIntoWithOverrides() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithOverrides.kt");
|
||||||
|
doCustomTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("stepIntoStdlibInlineFun2step.kt")
|
@TestMetadata("stepIntoStdlibInlineFun2step.kt")
|
||||||
public void testStepIntoStdlibInlineFun2step() throws Exception {
|
public void testStepIntoStdlibInlineFun2step() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user