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? {
val psiElement = sourcePosition.getElementAt()
val psiElement = runReadAction { sourcePosition.getElementAt() }
if (psiElement == 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.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)
}
}
+1 -1
View File
@@ -7,4 +7,4 @@ fun foo() {
fun f1(f: () -> Unit) {}
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.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"
}
@@ -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");