Support smart step into for inline function arguments

This commit is contained in:
Natalia Ukhorskaya
2015-10-23 11:59:33 +03:00
parent c98ebffbcf
commit dd30462fd1
13 changed files with 213 additions and 59 deletions
@@ -19,55 +19,58 @@ 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.idea.core.refactoring.isMultiLine
import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.util.OperatorNameConventions
public class KotlinLambdaMethodFilter(
lambda: KtFunctionLiteralExpression,
private val myCallingExpressionLines: Range<Int>
private val lambda: KtFunction,
private val myCallingExpressionLines: Range<Int>,
private val isInline: Boolean
): 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())
if (lambda.isMultiLine()) {
var firstStatementPosition: SourcePosition? = null
var lastStatementPosition: SourcePosition? = null
val body = lambda.bodyExpression as KtBlockExpression
val statements = body.statements
if (statements.isNotEmpty()) {
firstStatementPosition = SourcePosition.createFromElement(statements.first())
if (firstStatementPosition != null) {
val lastStatement = statements.last()
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.file, lastStatement.textRange.endOffset)
}
}
myFirstStatementPosition = firstStatementPosition
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1
}
else {
myFirstStatementPosition = SourcePosition.createFromElement(lambda)
myLastStatementLine = myFirstStatementPosition!!.line
}
myFirstStatementPosition = firstStatementPosition
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.getLine() else -1
}
override fun getBreakpointPosition(): SourcePosition? {
return myFirstStatementPosition
}
override fun getLastStatementLine(): Int {
return myLastStatementLine
}
override fun getBreakpointPosition() = myFirstStatementPosition
override fun getLastStatementLine() = myLastStatementLine
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
val method = location.method()
if (isInline) {
return isInsideInlineArgument(lambda, location, process)
}
return isLambdaName(method.name())
}
override fun getCallingExpressionLines(): Range<Int>? {
return myCallingExpressionLines
}
override fun getCallingExpressionLines() = if (isInline) Range(0, 999) else myCallingExpressionLines
companion object {
public fun isLambdaName(name: String?): Boolean {
@@ -16,27 +16,23 @@
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.KotlinIcons
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.util.OperatorNameConventions
import javax.swing.Icon
public class KotlinLambdaSmartStepTarget(
label: String,
highlightElement: KtFunctionLiteralExpression,
lines: Range<Int>
highlightElement: KtFunction,
lines: Range<Int>,
val isInline: Boolean
): SmartStepTarget(label, highlightElement, true, lines) {
override fun getIcon() = KotlinIcons.LAMBDA
fun getLambda() = getHighlightElement() as KtFunctionLiteralExpression
fun getLambda() = getHighlightElement() as KtFunction
companion object {
fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String {
@@ -66,20 +66,33 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
element.accept(object: KtTreeVisitorVoid() {
override fun visitFunctionLiteralExpression(expression: KtFunctionLiteralExpression) {
val context = expression.analyze()
val resolvedCall = expression.getParentCall(context).getResolvedCall(context)
if (resolvedCall != null && !InlineUtil.isInline(resolvedCall.getResultingDescriptor())) {
val arguments = resolvedCall.getValueArguments()
recordFunctionLiteral(expression.functionLiteral)
}
override fun visitNamedFunction(function: KtNamedFunction) {
if (!recordFunctionLiteral(function)) {
super.visitNamedFunction(function)
}
}
private fun recordFunctionLiteral(function: KtFunction): Boolean {
val context = function.analyze()
val resolvedCall = function.getParentCall(context).getResolvedCall(context)
if (resolvedCall != null) {
val arguments = resolvedCall.valueArguments
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
if (argument.arguments.any { getArgumentExpression(it) == function }) {
val label = KotlinLambdaSmartStepTarget.calcLabel(resolvedCall.resultingDescriptor, param.name)
result.add(KotlinLambdaSmartStepTarget(label, function, lines, InlineUtil.isInline(resolvedCall.resultingDescriptor)))
return true
}
}
}
return false
}
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtFunctionLiteralExpression)?.functionLiteral ?: it.getArgumentExpression()
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
// skip calls in object declarations
}
@@ -181,7 +194,7 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
return when (stepTarget) {
is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.resolvedElement, stepTarget.getCallingExpressionLines()!!)
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!! )
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline)
else -> super.createMethodFilter(stepTarget)
}
}
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil
@@ -68,7 +69,7 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
val file = sourcePosition.file as? KtFile ?: return null
if (sourcePosition.line < 0) return null
val containingFunction = sourcePosition.elementAt.getParentOfType<KtNamedFunction>(false) ?: return null
val containingFunction = sourcePosition.elementAt.parents.firstOrNull { it is KtNamedFunction && !it.isLocal } ?: return null
val startLineNumber = containingFunction.getLineNumber(true)
val endLineNumber = containingFunction.getLineNumber(false)
@@ -221,12 +222,13 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
return inlineFunctionCalls.flatMap {
it.valueArguments
.map { it.getArgumentExpression() }
.filterIsInstance<KtFunctionLiteralExpression>()
.map { it.functionLiteral }
.map { getArgumentExpression(it) }
.filterIsInstance<KtFunction>()
}
}
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtFunctionLiteralExpression)?.functionLiteral ?: it.getArgumentExpression()
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
val file = sourcePosition.file as? KtFile ?: return emptyList()
val lineNumber = sourcePosition.line
+1 -1
View File
@@ -4,4 +4,4 @@ fun foo() {
inline fun f1(f: () -> Unit) {}
// EXISTS: f1(() -> Unit)
// EXISTS: f1(() -> Unit), f1: f.invoke()
@@ -0,0 +1,7 @@
fun foo() {
<caret>f1(fun () { })
}
inline fun f1(f: () -> Unit) {}
// EXISTS: f1(() -> Unit), f1: f.invoke()
@@ -0,0 +1,13 @@
LineBreakpoint created at smartStepIntoInlinedFunLiteral.kt:6
!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! smartStepIntoInlinedFunLiteral.SmartStepIntoInlinedFunLiteralKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
smartStepIntoInlinedFunLiteral.kt:6
smartStepIntoInlinedFunLiteral.kt:9
smartStepIntoInlinedFunLiteral.kt:10
smartStepIntoInlinedFunLiteral.kt:13
smartStepIntoInlinedFunLiteral.kt:14
smartStepIntoInlinedFunLiteral.kt:18
smartStepIntoInlinedFunLiteral.kt:20
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 smartStepIntoInlinedFunctionalExpression.kt:6
!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! smartStepIntoInlinedFunctionalExpression.SmartStepIntoInlinedFunctionalExpressionKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
smartStepIntoInlinedFunctionalExpression.kt:6
smartStepIntoInlinedFunctionalExpression.kt:9
smartStepIntoInlinedFunctionalExpression.kt:10
smartStepIntoInlinedFunctionalExpression.kt:13
smartStepIntoInlinedFunctionalExpression.kt:14
smartStepIntoInlinedFunctionalExpression.kt:18
smartStepIntoInlinedFunctionalExpression.kt:20
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,45 @@
package smartStepIntoInlinedFunLiteral
fun main(args: Array<String>) {
val array = arrayOf(1, 2)
//Breakpoint!
val myClass = MyClass()
// smart step into f2.invoke(), one-line lambda
myClass.f1 { test() }
.f2 { test() }
// smart step into map.invoke(), multiline lambda
array.map {
it *2
}
// smart step into filter.invoke()
array.map { it * 2 }
.filter {
it > 2
}
}
class MyClass {
inline fun f1(f1Param: () -> Unit): MyClass {
test()
f1Param()
return this
}
inline fun f2(f1Param: () -> Unit): MyClass {
test()
f1Param()
return this
}
}
fun test() {}
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
@@ -0,0 +1,45 @@
package smartStepIntoInlinedFunctionalExpression
fun main(args: Array<String>) {
val array = arrayOf(1, 2)
//Breakpoint!
val myClass = MyClass()
// smart step into f2.invoke(), one-line lambda
myClass.f1(fun () { test() })
.f2(fun () { test() })
// smart step into map.invoke(), multiline lambda
array.map(fun (it): Int {
return it * 2
})
// smart step into filter.invoke()
array.map(fun (it): Int { return it * 2 })
.filter(fun (it): Boolean {
return it > 2
})
}
class MyClass {
inline fun f1(f1Param: () -> Unit): MyClass {
test()
f1Param()
return this
}
inline fun f2(f1Param: () -> Unit): MyClass {
test()
f1Param()
return this
}
}
fun test() {}
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
@@ -27,7 +27,6 @@ import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.impl.PositionUtil
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.debugger.ui.breakpoints.BreakpointManager
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.roots.JdkOrderEntry
@@ -43,6 +42,8 @@ import com.intellij.xdebugger.breakpoints.XBreakpoint
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
import com.intellij.xdebugger.breakpoints.XBreakpointType
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
import com.sun.jdi.request.StepRequest
import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType
import org.jetbrains.kotlin.idea.debugger.stepping.*
@@ -162,6 +163,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
}
when {
!line.startsWith("//") -> return
line.startsWith("// STEP_INTO: ") -> repeat("// STEP_INTO: ") { stepInto(this) }
line.startsWith("// STEP_OUT: ") -> repeat("// STEP_OUT: ") { doStepOut() }
line.startsWith("// STEP_OVER: ") -> repeat("// STEP_OVER: ") { doStepOver() }
@@ -188,14 +190,11 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
}
private fun createSmartStepIntoFilters(): List<MethodFilter> {
val breakpointManager = DebuggerManagerEx.getInstanceEx(getProject())?.getBreakpointManager()
val breakpoint = breakpointManager?.getBreakpoints()?.first { it is LineBreakpoint }
val line = (breakpoint as LineBreakpoint).getLineIndex()
val contextElement = ContextUtil.getContextElement(evaluationContext)!!
val line = runReadAction { contextElement.getLineNumber() }
return runReadAction {
val containingFile = breakpoint.getPsiFile()
if (containingFile == null) throw AssertionError("Couldn't find file for breakpoint at the line $line")
val containingFile = contextElement.containingFile
val position = MockSourcePosition(_file = containingFile, _line = line)
@@ -204,7 +203,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
stepTargets.filterIsInstance<SmartStepTarget>().map {
stepTarget ->
when (stepTarget) {
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!)
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline)
is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.resolvedElement, stepTarget.getCallingExpressionLines()!!)
is MethodSmartStepTarget -> BasicStepMethodFilter(stepTarget.getMethod(), stepTarget.getCallingExpressionLines())
else -> null
@@ -523,6 +523,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doCustomTest(fileName);
}
@TestMetadata("smartStepIntoInlinedFunLiteral.kt")
public void testSmartStepIntoInlinedFunLiteral() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunLiteral.kt");
doCustomTest(fileName);
}
@TestMetadata("smartStepIntoInlinedFunctionalExpression.kt")
public void testSmartStepIntoInlinedFunctionalExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunctionalExpression.kt");
doCustomTest(fileName);
}
@TestMetadata("stepIntoStdlibInlineFun2step.kt")
public void testStepIntoStdlibInlineFun2step() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");
@@ -125,6 +125,12 @@ public class SmartStepIntoTestGenerated extends AbstractSmartStepIntoTest {
doTest(fileName);
}
@TestMetadata("inlinedFunctionalExpression.kt")
public void testInlinedFunctionalExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/inlinedFunctionalExpression.kt");
doTest(fileName);
}
@TestMetadata("invoke.kt")
public void testInvoke() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt");