Debugger: refactor smart step into to use descriptors instead of psi elements (KT-13485)

#KT-13485 Fixed
This commit is contained in:
Natalia Ukhorskaya
2016-10-27 17:13:24 +03:00
committed by Nikolay Krasko
parent f7203da2d6
commit 0fb5a18a26
10 changed files with 163 additions and 68 deletions
@@ -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 == "<init>" -> contextElement.getPrimaryConstructor()
else -> null
}
return null
}
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
@@ -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<Int>
) : NamedMethodFilter {
private val myTargetMethodName: String
init {
myTargetMethodName = when (resolvedElement) {
is KtAnonymousInitializer -> "<init>"
is KtConstructor<*> -> "<init>"
is KtPropertyAccessor -> JvmAbi.getterName((resolvedElement.property).name!!)
else -> resolvedElement.name!!
myTargetMethodName = when (descriptor) {
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
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() == "<init>") {
(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
}
}
@@ -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<Int>
): 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()
}
@@ -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)
}
@@ -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
@@ -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
@@ -45,6 +45,14 @@ fun main(args: Array<String>) {
// 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<T>(i: T) {
constructor(i: Int, j: T): this(j) {
}
}
@@ -0,0 +1,28 @@
// KT-13485
package smartStepIntoInterfaceFun
interface ObjectFace<T> {
fun act()
fun act2(t: T)
}
class ObjectClass : ObjectFace<Int> {
override fun act() {
println()
}
override fun act2(t: Int) {
println()
}
}
fun main(args: Array<String>) {
val simple: ObjectFace<Int> = ObjectClass()
// SMART_STEP_INTO_BY_INDEX: 1
// RESUME: 1
//Breakpoint!
simple.act()
// SMART_STEP_INTO_BY_INDEX: 1
// RESUME: 1
//Breakpoint!
simple.act2(1)
}
@@ -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
}
@@ -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");