diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 57bd4c424be..e2210d8d9c9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -50,9 +50,9 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFunction -import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.jvm.JvmClassName import java.util.* @@ -116,9 +116,9 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq if (lambdaOrFunIfInside != null) { return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!) } - val property = getParameterIfInConstructor(location, psiFile, lineNumber) - if (property != null) { - return SourcePosition.createFromElement(property) + val elementInDeclaration = getElementForDeclarationLine(location, psiFile, lineNumber) + if (elementInDeclaration != null) { + return SourcePosition.createFromElement(elementInDeclaration) } if (lineNumber > psiFile.getLineCount() && myDebugProcess.isDexDebug()) { @@ -134,18 +134,25 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq return SourcePosition.createFromLine(psiFile, lineNumber) } - private fun getParameterIfInConstructor(location: Location, file: KtFile, lineNumber: Int): KtParameter? { + // Returns a property or a constructor if debugger stops at class declaration + private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? { val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null val elementAt = file.findElementAt(lineStartOffset) val contextElement = KotlinCodeFragmentFactory.getContextElement(elementAt) + + if (contextElement !is KtClass) return null + val methodName = location.method().name() - if (contextElement is KtClass && JvmAbi.isGetterName(methodName)) { - val parameterForGetter = contextElement.getPrimaryConstructor()?.valueParameters?.firstOrNull() { - it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName - } ?: return null - return parameterForGetter + return when { + JvmAbi.isGetterName(methodName) -> { + val parameterForGetter = contextElement.getPrimaryConstructor()?.valueParameters?.firstOrNull() { + it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName + } ?: return null + parameterForGetter + } + methodName == "" -> contextElement.getPrimaryConstructor() + else -> null } - return null } private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? { 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 6ac3c13bf55..a3e620178d2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt @@ -16,31 +16,31 @@ package org.jetbrains.kotlin.idea.debugger.stepping -import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.NamedMethodFilter -import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.util.Range import com.sun.jdi.Location +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.JvmAbi -import org.jetbrains.kotlin.psi.KtAnonymousInitializer -import org.jetbrains.kotlin.psi.KtConstructor -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtPropertyAccessor +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 class KotlinBasicStepMethodFilter( - val resolvedElement: KtElement, + val descriptor: CallableDescriptor, val myCallingExpressionLines: Range ) : NamedMethodFilter { private val myTargetMethodName: String init { - myTargetMethodName = when (resolvedElement) { - is KtAnonymousInitializer -> "" - is KtConstructor<*> -> "" - is KtPropertyAccessor -> JvmAbi.getterName((resolvedElement.property).name!!) - else -> resolvedElement.name!! + myTargetMethodName = when (descriptor) { + is ClassDescriptor, is ConstructorDescriptor -> "" + is PropertyAccessorDescriptor -> JvmAbi.getterName(descriptor.correspondingProperty.name.asString()) + else -> descriptor.name.asString() } } @@ -52,13 +52,35 @@ class KotlinBasicStepMethodFilter( val method = location.method() if (myTargetMethodName != method.name()) return false - val sourcePosition = runReadAction { SourcePosition.createFromElement(resolvedElement) } ?: return false val positionManager = process.positionManager ?: return false - val classes = positionManager.getAllClasses(sourcePosition) + val descriptor = runReadAction { + val elementAt = positionManager.getSourcePosition(location)?.elementAt - return classes.any { - it == location.declaringType() || DebuggerUtilsEx.isAssignableFrom(it.name(), location.declaringType()) + val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) { + it !is KtProperty || !it.isLocal + } + + if (declaration is KtClass && method.name() == "") { + (declaration.resolveToDescriptor() as? ClassDescriptor)?.unsubstitutedPrimaryConstructor + } else { + declaration?.resolveToDescriptor() + } + } ?: return false + + fun compareDescriptors(d1: DeclarationDescriptor, d2: DeclarationDescriptor): Boolean { + return d1 == d2 || d1.original == d2.original } + + 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)} + } + + return false } + + } \ 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 bde298d9ef0..6c79d7e35f5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt @@ -3,23 +3,23 @@ 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.DeclarationDescriptor import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import javax.swing.Icon class KotlinMethodSmartStepTarget( - val resolvedElement: KtElement, + val descriptor: CallableDescriptor, label: String, highlightElement: PsiElement, lines: Range ): SmartStepTarget(label, highlightElement, false, lines) { override fun getIcon(): Icon? { return when { - resolvedElement is KtNamedFunction && resolvedElement.receiverTypeReference != null -> KotlinIcons.EXTENSION_FUNCTION + descriptor.isExtension -> KotlinIcons.EXTENSION_FUNCTION else -> KotlinIcons.FUNCTION } } @@ -43,8 +43,8 @@ class KotlinMethodSmartStepTarget( if (other == null || other !is KotlinMethodSmartStepTarget) return false - return resolvedElement == other.resolvedElement + return descriptor == other.descriptor } - override fun hashCode() = resolvedElement.hashCode() + override fun hashCode() = descriptor.hashCode() } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt index 7386dfb3777..819f285f0a8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt @@ -27,12 +27,14 @@ import com.intellij.util.Range import com.intellij.util.containers.OrderedSet import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.load.java.isFromJava import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall @@ -146,20 +148,15 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor] if (delegatedResolvedCall == null) { val getter = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor) - if (getter is KtPropertyAccessor && (getter.bodyExpression != null || getter.equalsToken != null)) { + if (getter is KtPropertyAccessor && getter.hasBody()) { val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor) - result.add(KotlinMethodSmartStepTarget(getter, label, expression, lines)) + result.add(KotlinMethodSmartStepTarget(getterDescriptor, label, expression, lines)) } } else { val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor - if (delegatedPropertyGetterDescriptor is CallableMemberDescriptor) { - val function = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, delegatedPropertyGetterDescriptor) - if (function is KtNamedFunction || function is KtSecondaryConstructor) { - val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor) - result.add(KotlinMethodSmartStepTarget(function as KtFunction, label, expression, lines)) - } - } + val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor) + result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, label, expression, lines)) } } } @@ -172,19 +169,22 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { val descriptor = resolvedCall.resultingDescriptor if (descriptor is FunctionDescriptor && !isIntrinsic(descriptor)) { - val resolvedElement = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) - if (resolvedElement is KtNamedFunction || resolvedElement is KtConstructor<*>) { + if (descriptor.isFromJava) { + (DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) as? PsiMethod)?.let { + result.add(MethodSmartStepTarget(it, null, expression, false, lines)) + } + } + else { + if (descriptor is ConstructorDescriptor && descriptor.isPrimary) { + val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) + if (psiElement is KtClass && psiElement.getAnonymousInitializers().isEmpty()) { + // There is no constructor or init block, so do not show it in smart step into + return + } + } + val label = KotlinMethodSmartStepTarget.calcLabel(descriptor) - result.add(KotlinMethodSmartStepTarget(resolvedElement as KtFunction, label, expression, lines)) - } - else if (resolvedElement is PsiMethod) { - result.add(MethodSmartStepTarget(resolvedElement, null, expression, false, lines)) - } - else if (resolvedElement is KtClass) { - resolvedElement.getAnonymousInitializers().firstOrNull()?.let { - val label = KotlinMethodSmartStepTarget.calcLabel(descriptor) - result.add(KotlinMethodSmartStepTarget(it, label, expression, lines)) - } + result.add(KotlinMethodSmartStepTarget(descriptor, label, expression, lines)) } } } @@ -195,7 +195,7 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? { return when (stepTarget) { - is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.resolvedElement, stepTarget.callingExpressionLines!!) + is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.callingExpressionLines!!) is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline) else -> super.createMethodFilter(stepTarget) } diff --git a/idea/testData/debugger/tinyApp/outs/smartStepIntoConstructor.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoConstructor.out index eed97d40ad5..53843329e55 100644 --- a/idea/testData/debugger/tinyApp/outs/smartStepIntoConstructor.out +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoConstructor.out @@ -9,30 +9,36 @@ LineBreakpoint created at smartStepIntoConstructor.kt:35 LineBreakpoint created at smartStepIntoConstructor.kt:39 LineBreakpoint created at smartStepIntoConstructor.kt:43 LineBreakpoint created at smartStepIntoConstructor.kt:47 +LineBreakpoint created at smartStepIntoConstructor.kt:51 +LineBreakpoint created at smartStepIntoConstructor.kt:55 !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! smartStepIntoConstructor.SmartStepIntoConstructorKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' smartStepIntoConstructor.kt:7 -smartStepIntoConstructor.kt:51 +smartStepIntoConstructor.kt:59 smartStepIntoConstructor.kt:11 -smartStepIntoConstructor.kt:52 +smartStepIntoConstructor.kt:60 smartStepIntoConstructor.kt:15 -smartStepIntoConstructor.kt:54 +smartStepIntoConstructor.kt:62 smartStepIntoConstructor.kt:19 -smartStepIntoConstructor.kt:57 +smartStepIntoConstructor.kt:65 smartStepIntoConstructor.kt:23 -smartStepIntoConstructor.kt:61 -smartStepIntoConstructor.kt:27 -smartStepIntoConstructor.kt:66 -smartStepIntoConstructor.kt:31 smartStepIntoConstructor.kt:69 -smartStepIntoConstructor.kt:35 +smartStepIntoConstructor.kt:27 smartStepIntoConstructor.kt:74 +smartStepIntoConstructor.kt:31 +smartStepIntoConstructor.kt:77 +smartStepIntoConstructor.kt:35 +smartStepIntoConstructor.kt:82 smartStepIntoConstructor.kt:39 -smartStepIntoConstructor.kt:81 -smartStepIntoConstructor.kt:43 smartStepIntoConstructor.kt:89 -smartStepIntoConstructor.kt:47 +smartStepIntoConstructor.kt:43 smartStepIntoConstructor.kt:97 +smartStepIntoConstructor.kt:47 +smartStepIntoConstructor.kt:105 +smartStepIntoConstructor.kt:51 +smartStepIntoConstructor.kt:112 +smartStepIntoConstructor.kt:55 +smartStepIntoConstructor.kt:113 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/smartStepIntoInterfaceFun.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoInterfaceFun.out new file mode 100644 index 00000000000..7961ee09529 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoInterfaceFun.out @@ -0,0 +1,13 @@ +LineBreakpoint created at smartStepIntoInterfaceFun.kt:23 +LineBreakpoint created at smartStepIntoInterfaceFun.kt:27 +!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! smartStepIntoInterfaceFun.SmartStepIntoInterfaceFunKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +smartStepIntoInterfaceFun.kt:23 +smartStepIntoInterfaceFun.kt:11 +smartStepIntoInterfaceFun.kt:27 +smartStepIntoInterfaceFun.kt:14 +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/smartStepIntoConstructor.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoConstructor.kt index 5ef4f816e33..a06fadad27e 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoConstructor.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoConstructor.kt @@ -45,6 +45,14 @@ fun main(args: Array) { // RESUME: 1 //Breakpoint! N(1) + // SMART_STEP_INTO_BY_INDEX: 1 + // RESUME: 1 + //Breakpoint! + O(1) + // SMART_STEP_INTO_BY_INDEX: 1 + // RESUME: 1 + //Breakpoint! + O(1, "1") } @@ -100,4 +108,8 @@ class N { constructor() { } +} +class O(i: T) { + constructor(i: Int, j: T): this(j) { + } } \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInterfaceFun.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInterfaceFun.kt new file mode 100644 index 00000000000..f87877dd620 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInterfaceFun.kt @@ -0,0 +1,28 @@ +// KT-13485 +package smartStepIntoInterfaceFun + +interface ObjectFace { + fun act() + fun act2(t: T) +} + +class ObjectClass : ObjectFace { + override fun act() { + println() + } + override fun act2(t: Int) { + println() + } +} + +fun main(args: Array) { + val simple: ObjectFace = ObjectClass() + // SMART_STEP_INTO_BY_INDEX: 1 + // RESUME: 1 + //Breakpoint! + simple.act() + // SMART_STEP_INTO_BY_INDEX: 1 + // RESUME: 1 + //Breakpoint! + simple.act2(1) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt index 8aede430a37..5400b14d747 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt @@ -55,6 +55,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes import java.io.File +import java.lang.AssertionError import javax.swing.SwingUtilities abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { @@ -213,7 +214,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { stepTarget -> when (stepTarget) { is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline) - is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.resolvedElement, stepTarget.getCallingExpressionLines()!!) + is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.getCallingExpressionLines()!!) is MethodSmartStepTarget -> BasicStepMethodFilter(stepTarget.method, stepTarget.getCallingExpressionLines()) else -> null } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index fbf4f2a2fde..408710fcd41 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -890,6 +890,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { doCustomTest(fileName); } + @TestMetadata("smartStepIntoInterfaceFun.kt") + public void testSmartStepIntoInterfaceFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInterfaceFun.kt"); + doCustomTest(fileName); + } + @TestMetadata("smartStepIntoInterfaceImpl.kt") public void testSmartStepIntoInterfaceImpl() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInterfaceImpl.kt");