diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt index 000c7ad1cbc..3b180f7ae1b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt @@ -228,7 +228,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult } public fun classNameForPosition(sourcePosition: SourcePosition): String? { - val psiElement = sourcePosition.getElementAt() + val psiElement = runReadAction { sourcePosition.getElementAt() } if (psiElement == null) { return null } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt new file mode 100644 index 00000000000..5cccd256c47 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.debugger.stepping + +import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.BreakpointStepMethodFilter +import com.intellij.debugger.engine.DebugProcessImpl +import com.intellij.debugger.engine.LambdaMethodFilter +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiElementFactory +import com.intellij.util.Range +import com.sun.jdi.Location +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.psi.JetBlockExpression +import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression +import org.jetbrains.kotlin.types.expressions.OperatorConventions + +public class KotlinLambdaMethodFilter( + lambda: JetFunctionLiteralExpression, + private val myCallingExpressionLines: Range +): BreakpointStepMethodFilter { + private val myFirstStatementPosition: SourcePosition? + private val myLastStatementLine: Int + + init { + var firstStatementPosition: SourcePosition? = null + var lastStatementPosition: SourcePosition? = null + val body = lambda.getBodyExpression()!! + val statements = body.getStatements() + if (statements.isNotEmpty()) { + firstStatementPosition = SourcePosition.createFromElement(statements.first()) + if (firstStatementPosition != null) { + val lastStatement = statements.last() + lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.getFile(), lastStatement.getTextRange().getEndOffset()) + } + } + myFirstStatementPosition = firstStatementPosition + myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.getLine() else -1 + } + + override fun getBreakpointPosition(): SourcePosition? { + return myFirstStatementPosition + } + + override fun getLastStatementLine(): Int { + return myLastStatementLine + } + + override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean { + val method = location.method() + return isLambdaName(method.name()) + } + + override fun getCallingExpressionLines(): Range? { + return myCallingExpressionLines + } + + companion object { + public fun isLambdaName(name: String?): Boolean { + return name == OperatorConventions.INVOKE.asString() + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaSmartStepIntoTarget.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaSmartStepIntoTarget.kt new file mode 100644 index 00000000000..87cfc560890 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaSmartStepIntoTarget.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.debugger.stepping + +import com.intellij.debugger.actions.MethodSmartStepTarget +import com.intellij.debugger.actions.SmartStepTarget +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.util.Range +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.JetIcons +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.JetElement +import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import javax.swing.Icon + +public class KotlinLambdaSmartStepTarget( + label: String, + highlightElement: JetFunctionLiteralExpression, + lines: Range +): SmartStepTarget(label, highlightElement, true, lines) { + override fun getIcon() = JetIcons.LAMBDA + + fun getLambda() = getHighlightElement() as JetFunctionLiteralExpression + + companion object { + fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String { + return "${descriptor.getName().asString()}: ${paramName.asString()}.${OperatorConventions.INVOKE.asString()}()" + } + } +} \ 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 b4ecdd03891..9c0ee2cacba 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSmartStepIntoHandler.kt @@ -20,25 +20,23 @@ import com.intellij.debugger.SourcePosition import com.intellij.debugger.actions.JvmSmartStepIntoHandler import com.intellij.debugger.actions.MethodSmartStepTarget import com.intellij.debugger.actions.SmartStepTarget -import com.intellij.debugger.engine.BasicStepMethodFilter -import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.MethodFilter import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiMethod import com.intellij.util.Range import com.intellij.util.containers.OrderedSet -import com.sun.jdi.Location import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde -import org.jetbrains.kotlin.idea.debugger.MockSourcePosition -import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.inline.InlineUtil public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { @@ -68,7 +66,18 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { element.accept(object: JetTreeVisitorVoid() { override fun visitFunctionLiteralExpression(expression: JetFunctionLiteralExpression) { - // skip calls in function literals + val context = expression.analyzeAndGetResult().bindingContext + val resolvedCall = expression.getParentCall(context).getResolvedCall(context) + if (resolvedCall != null && !InlineUtil.isInline(resolvedCall.getResultingDescriptor())) { + val arguments = resolvedCall.getValueArguments() + for ((param, argument) in arguments) { + if (argument.getArguments().any { it.getArgumentExpression() == expression}) { + val label = KotlinLambdaSmartStepTarget.calcLabel(resolvedCall.getResultingDescriptor(), param.getName()) + result.add(KotlinLambdaSmartStepTarget(label, expression, lines)) + break + } + } + } } override fun visitObjectLiteralExpression(expression: JetObjectLiteralExpression) { @@ -177,9 +186,10 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { } override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? { - if (stepTarget is KotlinMethodSmartStepTarget) { - return KotlinBasicStepMethodFilter(stepTarget) + return when (stepTarget) { + is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget) + is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!! ) + else -> super.createMethodFilter(stepTarget) } - return super.createMethodFilter(stepTarget) } } diff --git a/idea/testData/debugger/smartStepInto/funLiteral.kt b/idea/testData/debugger/smartStepInto/funLiteral.kt index f17391eda06..595f2ce508f 100644 --- a/idea/testData/debugger/smartStepInto/funLiteral.kt +++ b/idea/testData/debugger/smartStepInto/funLiteral.kt @@ -7,4 +7,4 @@ fun foo() { fun f1(f: () -> Unit) {} fun f2() {} -// EXISTS: f1(Function0) \ No newline at end of file +// EXISTS: f1(Function0), f1: f.invoke() \ No newline at end of file diff --git a/idea/testData/debugger/smartStepInto/inlinedFunLiteral.kt b/idea/testData/debugger/smartStepInto/inlinedFunLiteral.kt new file mode 100644 index 00000000000..c5be7a28d75 --- /dev/null +++ b/idea/testData/debugger/smartStepInto/inlinedFunLiteral.kt @@ -0,0 +1,7 @@ +fun foo() { + f1() { } +} + +inline fun f1(f: () -> Unit) {} + +// EXISTS: f1(Function0) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractSmartStepIntoTest.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractSmartStepIntoTest.kt index 826ec7120d3..e1d7336dae6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractSmartStepIntoTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractSmartStepIntoTest.kt @@ -23,6 +23,7 @@ import com.intellij.psi.util.PsiFormatUtil import com.intellij.psi.util.PsiFormatUtilBase import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.debugger.stepping.KotlinLambdaSmartStepTarget import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase @@ -40,7 +41,7 @@ public abstract class AbstractSmartStepIntoTest : JetLightCodeInsightFixtureTest val position = MockSourcePosition(_file = fixture.getFile(), _line = line, _offset = offset, _editor = fixture.getEditor()) - val actual = KotlinSmartStepIntoHandler().findSmartStepTargets(position).map { renderTarget(it) } + val actual = KotlinSmartStepIntoHandler().findSmartStepTargets(position).map { it.getPresentation() } val expected = InTextDirectivesUtils.findListWithPrefixes(fixture.getFile()?.getText()!!.replace("\\,", "+++"), "// EXISTS: ").map { it.replace("+++", ",") } @@ -73,30 +74,6 @@ public abstract class AbstractSmartStepIntoTest : JetLightCodeInsightFixtureTest return sb.toString() } - private fun renderTarget(target: SmartStepTarget): String { - val sb = StringBuilder() - - val label = target.getLabel() - if (label != null) { - sb.append(label) - } - when (target) { - is MethodSmartStepTarget -> { - sb.append(PsiFormatUtil.formatMethod( - target.getMethod(), - PsiSubstitutor.EMPTY, - PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS, - PsiFormatUtilBase.SHOW_TYPE, - 999 - )) - } - else -> { - sb.append("Renderer for ${target.javaClass} should be implemented") - } - } - return sb.toString() - } - override fun getTestDataPath(): String? { return PluginTestCaseBase.getTestDataPathBase() + "/debugger/smartStepInto" } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/SmartStepIntoTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/SmartStepIntoTestGenerated.java index f591c4dedb1..5de5b55a4e2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/SmartStepIntoTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/SmartStepIntoTestGenerated.java @@ -113,6 +113,12 @@ public class SmartStepIntoTestGenerated extends AbstractSmartStepIntoTest { doTest(fileName); } + @TestMetadata("inlinedFunLiteral.kt") + public void testInlinedFunLiteral() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/inlinedFunLiteral.kt"); + doTest(fileName); + } + @TestMetadata("invoke.kt") public void testInvoke() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt");