Smart step into function literal

This commit is contained in:
Natalia Ukhorskaya
2015-07-15 14:44:15 +03:00
parent f7e35fab42
commit d75c21a5a7
8 changed files with 159 additions and 36 deletions
@@ -228,7 +228,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
} }
public fun classNameForPosition(sourcePosition: SourcePosition): String? { public fun classNameForPosition(sourcePosition: SourcePosition): String? {
val psiElement = sourcePosition.getElementAt() val psiElement = runReadAction { sourcePosition.getElementAt() }
if (psiElement == null) { if (psiElement == null) {
return null return null
} }
@@ -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<Int>
): 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<Int>? {
return myCallingExpressionLines
}
companion object {
public fun isLambdaName(name: String?): Boolean {
return name == OperatorConventions.INVOKE.asString()
}
}
}
@@ -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<Int>
): 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()}()"
}
}
}
@@ -20,25 +20,23 @@ import com.intellij.debugger.SourcePosition
import com.intellij.debugger.actions.JvmSmartStepIntoHandler import com.intellij.debugger.actions.JvmSmartStepIntoHandler
import com.intellij.debugger.actions.MethodSmartStepTarget import com.intellij.debugger.actions.MethodSmartStepTarget
import com.intellij.debugger.actions.SmartStepTarget 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.debugger.engine.MethodFilter
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethod
import com.intellij.util.Range import com.intellij.util.Range
import com.intellij.util.containers.OrderedSet import com.intellij.util.containers.OrderedSet
import com.sun.jdi.Location
import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze 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.CodeInsightUtils
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde 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.psi.*
import org.jetbrains.kotlin.resolve.BindingContext 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.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil
public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() { public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
@@ -68,7 +66,18 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
element.accept(object: JetTreeVisitorVoid() { element.accept(object: JetTreeVisitorVoid() {
override fun visitFunctionLiteralExpression(expression: JetFunctionLiteralExpression) { 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) { override fun visitObjectLiteralExpression(expression: JetObjectLiteralExpression) {
@@ -177,9 +186,10 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
} }
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? { override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
if (stepTarget is KotlinMethodSmartStepTarget) { return when (stepTarget) {
return KotlinBasicStepMethodFilter(stepTarget) is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget)
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!! )
else -> super.createMethodFilter(stepTarget)
} }
return super.createMethodFilter(stepTarget)
} }
} }
+1 -1
View File
@@ -7,4 +7,4 @@ fun foo() {
fun f1(f: () -> Unit) {} fun f1(f: () -> Unit) {}
fun f2() {} fun f2() {}
// EXISTS: f1(Function0<? extends Unit>) // EXISTS: f1(Function0<? extends Unit>), f1: f.invoke()
@@ -0,0 +1,7 @@
fun foo() {
<caret>f1() { }
}
inline fun f1(f: () -> Unit) {}
// EXISTS: f1(Function0<? extends Unit>)
@@ -23,6 +23,7 @@ import com.intellij.psi.util.PsiFormatUtil
import com.intellij.psi.util.PsiFormatUtilBase import com.intellij.psi.util.PsiFormatUtilBase
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase 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.debugger.stepping.KotlinSmartStepIntoHandler
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase 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 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("+++", ",") } val expected = InTextDirectivesUtils.findListWithPrefixes(fixture.getFile()?.getText()!!.replace("\\,", "+++"), "// EXISTS: ").map { it.replace("+++", ",") }
@@ -73,30 +74,6 @@ public abstract class AbstractSmartStepIntoTest : JetLightCodeInsightFixtureTest
return sb.toString() 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? { override fun getTestDataPath(): String? {
return PluginTestCaseBase.getTestDataPathBase() + "/debugger/smartStepInto" return PluginTestCaseBase.getTestDataPathBase() + "/debugger/smartStepInto"
} }
@@ -113,6 +113,12 @@ public class SmartStepIntoTestGenerated extends AbstractSmartStepIntoTest {
doTest(fileName); 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") @TestMetadata("invoke.kt")
public void testInvoke() throws Exception { public void testInvoke() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt");