diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt index 92020ccfbda..1c153312992 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinBasicStepMethodFilter.kt @@ -18,17 +18,14 @@ package org.jetbrains.kotlin.idea.debugger.stepping import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.NamedMethodFilter +import com.intellij.psi.SmartPsiElementPointer import com.intellij.util.Range -import com.intellij.util.SofterReference import com.sun.jdi.Location -import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor -import org.jetbrains.kotlin.codegen.SamCodegenUtil -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.* +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DECLARATION import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny -import org.jetbrains.kotlin.idea.core.getDirectlyOverriddenDeclarations +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde 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 @@ -36,32 +33,26 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate import org.jetbrains.kotlin.resolve.DescriptorUtils class KotlinBasicStepMethodFilter( - targetDescriptor: CallableMemberDescriptor, + private val declarationPtr: SmartPsiElementPointer?, + private val isInvoke: Boolean, + private val targetMethodName: String, private val myCallingExpressionLines: Range ) : NamedMethodFilter { - private val myTargetMethodName: String = when (targetDescriptor) { - is ClassDescriptor, is ConstructorDescriptor -> "" - is PropertyAccessorDescriptor -> JvmAbi.getterName(targetDescriptor.correspondingProperty.name.asString()) - else -> targetDescriptor.name.asString() + init { + assert(declarationPtr != null || isInvoke) } - private val _targetDescriptor = SofterReference( - (targetDescriptor as? FunctionDescriptor)?.let { SamCodegenUtil.getOriginalIfSamAdapter(it) } ?: targetDescriptor - ) - override fun getCallingExpressionLines() = myCallingExpressionLines - override fun getMethodName() = myTargetMethodName + override fun getMethodName() = targetMethodName override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean { - val targetDescriptor = _targetDescriptor.get() ?: return true - val method = location.method() - if (myTargetMethodName != method.name()) return false + if (targetMethodName != method.name()) return false val positionManager = process.positionManager - val currentDescriptor = runReadAction { + val (currentDescriptor, currentDeclaration) = runReadAction { val elementAt = positionManager.getSourcePosition(location)?.elementAt val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) { @@ -69,44 +60,36 @@ class KotlinBasicStepMethodFilter( } if (declaration is KtClass && method.name() == "") { - declaration.resolveToDescriptorIfAny()?.unsubstitutedPrimaryConstructor + declaration.resolveToDescriptorIfAny()?.unsubstitutedPrimaryConstructor to declaration } else { - declaration?.resolveToDescriptorIfAny() + declaration?.resolveToDescriptorIfAny() to declaration } - } ?: return false // TODO: Check that we can always find a descriptor (libraries with sources, libraries without sources) + } + + if (currentDescriptor == null || currentDeclaration == null) { + return false + } @Suppress("FoldInitializerAndIfToElvis") if (currentDescriptor !is CallableMemberDescriptor) return false if (currentDescriptor.kind != DECLARATION) return false - if (targetDescriptor is FunctionInvokeDescriptor) { + if (isInvoke) { // There can be only one 'invoke' target at the moment so consider position as expected. // Descriptors can be not-equal, say when parameter has type `(T) -> T` and lambda is `Int.() -> Int`. return true } - if (compareDescriptors(currentDescriptor, targetDescriptor)) return true + val declaration = declarationPtr?.element + ?: return true // Element is lost. But we know that name is matches, so stop. - // 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 (baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfTarget, currentDescriptor) }) { + if (currentDeclaration.isEquivalentTo(declaration)) { return true } return DescriptorUtils.getAllOverriddenDescriptors(currentDescriptor).any { baseOfCurrent -> - baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfCurrent, baseOfTarget) } + val currentBaseDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(currentDeclaration.project, baseOfCurrent) + declaration.isEquivalentTo(currentBaseDeclaration) } } -} - -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/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt index 29f4660a8b3..9f8619414fb 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinMethodSmartStepIntoTarget.kt @@ -4,23 +4,39 @@ import com.intellij.debugger.actions.SmartStepTarget import com.intellij.psi.PsiElement import com.intellij.util.Range import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import javax.swing.Icon class KotlinMethodSmartStepTarget( - val descriptor: CallableMemberDescriptor, + descriptor: CallableMemberDescriptor, + val declaration: KtDeclaration?, label: String, highlightElement: PsiElement, lines: Range ) : SmartStepTarget(label, highlightElement, false, lines) { + val isInvoke = descriptor is FunctionInvokeDescriptor + + init { + assert(declaration != null || isInvoke) + } + + private val isExtension = descriptor.isExtension + + val targetMethodName: String = when (descriptor) { + is ClassDescriptor, is ConstructorDescriptor -> "" + is PropertyAccessorDescriptor -> JvmAbi.getterName(descriptor.correspondingProperty.name.asString()) + else -> descriptor.name.asString() + } + override fun getIcon(): Icon? { return when { - descriptor.isExtension -> KotlinIcons.EXTENSION_FUNCTION + isExtension -> KotlinIcons.EXTENSION_FUNCTION else -> KotlinIcons.FUNCTION } } @@ -44,19 +60,19 @@ class KotlinMethodSmartStepTarget( if (other == null || other !is KotlinMethodSmartStepTarget) return false - if (descriptor is FunctionInvokeDescriptor && other.descriptor is FunctionInvokeDescriptor) { + if (isInvoke && other.isInvoke) { // Don't allow to choose several invoke targets in smart step into as we can't distinguish them reliably during debug return true } - return descriptor == other.descriptor + return declaration === other.declaration } override fun hashCode(): Int { - if (descriptor is FunctionInvokeDescriptor) { + if (isInvoke) { // Predefined value to make all FunctionInvokeDescriptor targets equal return 42 } - return descriptor.hashCode() + return declaration!!.hashCode() } } \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt index 414e88e0157..d5e24fbabfa 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt @@ -26,13 +26,11 @@ import com.intellij.psi.PsiMethod import com.intellij.util.Range import com.intellij.util.containers.OrderedSet import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor +import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType import org.jetbrains.kotlin.builtins.isSuspendFunctionType import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods import org.jetbrains.kotlin.config.JvmTarget -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.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils @@ -40,6 +38,7 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.isFromJava import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -150,62 +149,69 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { } override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { - val resolvedCall = expression.getResolvedCall(bindingContext) - if (resolvedCall != null) { - val propertyDescriptor = resolvedCall.resultingDescriptor - if (propertyDescriptor is PropertyDescriptor) { - val getterDescriptor = propertyDescriptor.getter - if (getterDescriptor != null && !getterDescriptor.isDefault) { - val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor] - if (delegatedResolvedCall == null) { - val getter = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor) - if (getter is KtPropertyAccessor && getter.hasBody()) { - val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor) - result.add(KotlinMethodSmartStepTarget(getterDescriptor, label, expression, lines)) - } - } else { - val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor - val label = - "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor) - result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, label, expression, lines)) - } - } + recordGetter(expression) + super.visitSimpleNameExpression(expression) + } + + private fun recordGetter(expression: KtSimpleNameExpression) { + val resolvedCall = expression.getResolvedCall(bindingContext) ?: return + val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return + + val getterDescriptor = propertyDescriptor.getter + if (getterDescriptor == null || getterDescriptor.isDefault) return + + val ktDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor) as? KtDeclaration ?: return + + val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor] + if (delegatedResolvedCall != null) { + val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor + val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor) + result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, ktDeclaration, label, expression, lines)) + } else { + if (ktDeclaration is KtPropertyAccessor && ktDeclaration.hasBody()) { + val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor) + result.add(KotlinMethodSmartStepTarget(getterDescriptor, ktDeclaration, label, expression, lines)) } } - super.visitSimpleNameExpression(expression) } private fun recordFunction(expression: KtExpression) { val resolvedCall = expression.getResolvedCall(bindingContext) ?: return val descriptor = resolvedCall.resultingDescriptor - if (descriptor is FunctionDescriptor && !isIntrinsic(descriptor)) { - 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 - } - } + if (descriptor !is FunctionDescriptor || isIntrinsic(descriptor)) return - val callLabel = KotlinMethodSmartStepTarget.calcLabel(descriptor) - val label = when (descriptor) { - is FunctionInvokeDescriptor -> { - when (expression) { - is KtSimpleNameExpression -> "${runReadAction { expression.text }}.$callLabel" - else -> callLabel - } - } - else -> callLabel - } - - result.add(KotlinMethodSmartStepTarget(descriptor, label, expression, lines)) + val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) + if (descriptor.isFromJava) { + (declaration as? PsiMethod)?.let { + result.add(MethodSmartStepTarget(it, null, declaration, false, lines)) } + } else { + if (declaration == null && !isInvokeInBuiltinFunction(descriptor)) { + return + } + + if (declaration !is KtDeclaration?) return + + if (descriptor is ConstructorDescriptor && descriptor.isPrimary) { + if (declaration is KtClass && declaration.getAnonymousInitializers().isEmpty()) { + // There is no constructor or init block, so do not show it in smart step into + return + } + } + + val callLabel = KotlinMethodSmartStepTarget.calcLabel(descriptor) + val label = when (descriptor) { + is FunctionInvokeDescriptor -> { + when (expression) { + is KtSimpleNameExpression -> "${runReadAction { expression.text }}.$callLabel" + else -> callLabel + } + } + else -> callLabel + } + + result.add(KotlinMethodSmartStepTarget(descriptor, declaration, label, expression, lines)) } } }, null) @@ -216,7 +222,12 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? { return when (stepTarget) { is KotlinMethodSmartStepTarget -> - KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.callingExpressionLines!!) + KotlinBasicStepMethodFilter( + stepTarget.declaration?.createSmartPointer(), + stepTarget.isInvoke, + stepTarget.targetMethodName, + stepTarget.callingExpressionLines!! + ) is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter( stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend @@ -231,4 +242,10 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { private fun isIntrinsic(descriptor: CallableMemberDescriptor): Boolean { return methods.getIntrinsic(descriptor) != null } + + private fun isInvokeInBuiltinFunction(descriptor: DeclarationDescriptor): Boolean { + if (descriptor !is FunctionInvokeDescriptor) return false + val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false + return classDescriptor.defaultType.isBuiltinFunctionalType + } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt index aee9152bf8e..e8fd93911a5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt @@ -59,6 +59,7 @@ import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType import org.jetbrains.kotlin.idea.debugger.stepping.* import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes @@ -246,7 +247,11 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { KotlinLambdaMethodFilter( stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline, stepTarget.isSuspend) is KotlinMethodSmartStepTarget -> - KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.getCallingExpressionLines()!!) + KotlinBasicStepMethodFilter( + stepTarget.declaration?.createSmartPointer(), + stepTarget.isInvoke, + stepTarget.targetMethodName, + stepTarget.getCallingExpressionLines()!!) is MethodSmartStepTarget -> BasicStepMethodFilter(stepTarget.method, stepTarget.getCallingExpressionLines()) else -> null }