diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt index 687b3bd9d40..dcd4ddb58e0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt @@ -21,23 +21,25 @@ import com.intellij.debugger.engine.NamedMethodFilter import com.intellij.util.Range import com.sun.jdi.Location 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.core.getDirectlyOverriddenDeclarations import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate -import org.jetbrains.kotlin.resolve.findTopMostOverriddenDescriptors +import org.jetbrains.kotlin.resolve.DescriptorUtils class KotlinBasicStepMethodFilter( - val descriptor: CallableDescriptor, + val targetDescriptor: CallableMemberDescriptor, val myCallingExpressionLines: Range ) : NamedMethodFilter { - private val myTargetMethodName = when (descriptor) { + private val myTargetMethodName = when (targetDescriptor) { is ClassDescriptor, is ConstructorDescriptor -> "" - is PropertyAccessorDescriptor -> JvmAbi.getterName(descriptor.correspondingProperty.name.asString()) - else -> descriptor.name.asString() + is PropertyAccessorDescriptor -> JvmAbi.getterName(targetDescriptor.correspondingProperty.name.asString()) + else -> targetDescriptor.name.asString() } override fun getCallingExpressionLines() = myCallingExpressionLines @@ -50,7 +52,7 @@ class KotlinBasicStepMethodFilter( val positionManager = process.positionManager ?: return false - val descriptor = runReadAction { + val currentDescriptor = runReadAction { val elementAt = positionManager.getSourcePosition(location)?.elementAt val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) { @@ -62,21 +64,34 @@ class KotlinBasicStepMethodFilter( } else { 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 { - return d1 == d2 || d1.original == d2.original + @Suppress("FoldInitializerAndIfToElvis") + 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 (descriptor is CallableDescriptor) { - return descriptor.findTopMostOverriddenDescriptors().any { compareDescriptors(it, this.descriptor) } || - this.descriptor.findTopMostOverriddenDescriptors().any { compareDescriptors(it, descriptor)} + if (baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfTarget, currentDescriptor) }) { + return true } - 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 } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt index 6c79d7e35f5..d47398bc72a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt @@ -3,7 +3,7 @@ package org.jetbrains.kotlin.idea.debugger.stepping import com.intellij.debugger.actions.SmartStepTarget import com.intellij.psi.PsiElement 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.idea.KotlinIcons import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers @@ -12,7 +12,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import javax.swing.Icon class KotlinMethodSmartStepTarget( - val descriptor: CallableDescriptor, + val descriptor: CallableMemberDescriptor, label: String, highlightElement: PsiElement, lines: Range diff --git a/idea/testData/debugger/tinyApp/outs/smartStepIntoComponentFunction.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoComponentFunction.out new file mode 100644 index 00000000000..d0d129b02d8 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoComponentFunction.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/smartStepIntoWithDelegates.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoWithDelegates.out new file mode 100644 index 00000000000..51cf00b3c8e --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoWithDelegates.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/smartStepIntoWithOverrides.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoWithOverrides.out new file mode 100644 index 00000000000..23acc546932 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoWithOverrides.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoComponentFunction.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoComponentFunction.kt new file mode 100644 index 00000000000..26658bf5609 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoComponentFunction.kt @@ -0,0 +1,11 @@ +package smartStepIntoComponentFunction + +data class Test(val a: Int, val b: Int) + +fun main(args: Array) { + val t = Test(12, 2) + // SMART_STEP_INTO_BY_INDEX: 1 + // RESUME: 1 + //Breakpoint! + t.component2() +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithDelegates.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithDelegates.kt new file mode 100644 index 00000000000..0e4de000387 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithDelegates.kt @@ -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) { + test1() + test2() + test3() + test4() + test5() +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithOverrides.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithOverrides.kt new file mode 100644 index 00000000000..0fae3c5980f --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoWithOverrides.kt @@ -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) { + test1() + test2() + test3() + test4() + test5() +} + diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index da906f10472..845fc04f8a7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -1088,6 +1088,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { 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") public void testSmartStepIntoConstructor() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoConstructor.kt"); @@ -1142,6 +1148,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { 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") public void testStepIntoStdlibInlineFun2step() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");