Fix stepping for inline functions inside when, if, try

This commit is contained in:
Natalia Ukhorskaya
2015-09-16 15:38:36 +03:00
parent e92f14e49c
commit 33b5e6b255
15 changed files with 512 additions and 17 deletions
@@ -30,8 +30,8 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.refactoring.getLineCount
import org.jetbrains.kotlin.idea.core.refactoring.getLineEndOffset
import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber
import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.psi.*
@@ -61,25 +61,116 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
val inlinedArguments = getInlinedArgumentsIfAny(sourcePosition)
if (inlinedArguments.isEmpty()) return null
if (inlinedArguments.any { it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
return null
}
val additionalElementsToSkip = sourcePosition.elementAt.getAdditionalElementsToSkip()
val containingFunction = sourcePosition.elementAt.getParentOfType<JetNamedFunction>(false)
val startLineNumber = containingFunction?.getLineNumber(true) ?: return null
val endLineNumber = containingFunction?.getLineNumber(false) ?: return null
val linesRange = startLineNumber + 1..endLineNumber + 1
val locations = computedReferenceType.allLineLocations()
val countOfLinesInFile = file.getLineCount()
.dropWhile { it != location }
.drop(1)
.filter { it.method() == location.method() && it.lineNumber() in linesRange }
.dropWhile { it.lineNumber() == location.lineNumber() }
val nextLine = sourcePosition.line + 2 /* +1 - because of locations are counted from 1 and +1 - because we want next line */
for (locationAtLine in locations) {
val lineNumber = locationAtLine.lineNumber()
val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue
if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue
if (additionalElementsToSkip.any { it.textRange.contains(lineStartOffset) }) continue
for (lineNumber in nextLine..countOfLinesInFile) {
val locationAtLine = locations.firstOrNull { it.lineNumber() == lineNumber }
if (locationAtLine != null) {
val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue
if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue
val elementAt = file.findElementAt(lineStartOffset) ?: continue
val elementAt = file.findElementAt(lineStartOffset)
val xPosition = XSourcePositionImpl.createByElement(elementAt) ?: return null
val xPosition = XSourcePositionImpl.createByElement(elementAt) ?: return null
return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, ignoreBreakpoints)
}
return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, ignoreBreakpoints)
return suspendContext.debugProcess.createStepOutCommand(suspendContext)!!
}
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
val result = arrayListOf<PsiElement>()
val ifParent = getParentOfType<JetIfExpression>(false)
if (ifParent != null) {
if (ifParent.then?.textRange?.contains(this.textRange) ?: false) {
ifParent.elseKeyword?.let { result.add(it) }
ifParent.`else`?.let { result.add(it) }
}
}
val tryParent = getParentOfType<JetTryExpression>(false)
if (tryParent != null) {
val catchClause = getParentOfType<JetCatchClause>(false)
if (catchClause != null) {
result.addAll(tryParent.catchClauses.filter { it != catchClause })
}
}
return null
val whenEntry = getParentOfType<JetWhenEntry>(false)
if (whenEntry != null) {
if (whenEntry.expression?.textRange?.contains(this.textRange) ?: false) {
val whenParent = whenEntry.getParentOfType<JetWhenExpression>(false)
if (whenParent != null) {
result.addAll(whenParent.entries.filter { it != whenEntry })
}
}
}
return result
}
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
val ifParent = getParentOfType<JetIfExpression>(false)
if (ifParent != null) {
// if (inlineFunCall()) {...}
if (ifParent.condition?.textRange?.contains(this.textRange) ?: false) {
return true
}
/*
<caret>if (...) inlineFunCall()
else inlineFunCall()
*/
val ifParentElementAt = elementAt.getParentOfType<JetIfExpression>(false)
if (ifParentElementAt == null) {
if (ifParent.then?.textRange?.contains(this.textRange) ?: false) {
return true
}
if (ifParent.`else`?.textRange?.contains(this.textRange) ?: false) {
return true
}
}
}
val tryParent = getParentOfType<JetTryExpression>(false)
if (tryParent != null) {
/* try { inlineFunCall() }
catch()...
*/
if (tryParent.tryBlock.textRange?.contains(this.textRange) ?: false) {
return true
}
}
val whenEntry = getParentOfType<JetWhenEntry>(false)
if (whenEntry != null) {
// <caret>inlineFunCall -> ...
if (whenEntry.conditions.any { it.textRange.contains(this.textRange) } ) {
return true
}
// <caret>1 == 2 -> inlineFunCall()
if (whenEntry.expression?.textRange?.contains(this.textRange) ?: false) {
val parentEntryElementAt = elementAt.getParentOfType<JetWhenEntry>(false) ?: return true
return parentEntryElementAt == whenEntry &&
whenEntry.conditions.any { it.textRange.contains(elementAt.textRange) }
}
}
return false
}
override fun getStepOutCommand(suspendContext: SuspendContextImpl?, stepSize: Int): DebugProcessImpl.ResumeCommand? {
@@ -231,12 +322,12 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
startOffset++
}
if (topMostElement == null) return emptyList()
if (topMostElement !is JetElement) return emptyList()
val start = topMostElement.startOffset
val end = topMostElement.endOffset
return CodeInsightUtils.
val allInlinedArguments = CodeInsightUtils.
findElementsOfClassInRange(file, start, end, JetFunctionLiteral::class.java, JetNamedFunction::class.java)
.filter { JetPsiUtil.getParentCallIfPresent(it as JetExpression) != null }
.filterIsInstance<JetFunction>()
@@ -244,5 +335,16 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
val context = it.analyze(BodyResolveMode.PARTIAL)
InlineUtil.isInlinedArgument(it, context, false)
}
// It is necessary to check range because of multiline assign
var linesRange = lineNumber..lineNumber
return allInlinedArguments.filter {
val parentCall = JetPsiUtil.getParentCallIfPresent(it)!!
val shouldInclude = parentCall.getLineNumber() in linesRange
if (shouldInclude) {
linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.end, it.getLineNumber(false))
}
shouldInclude
}
}
}
}
@@ -302,6 +302,10 @@ fun PsiFile.getLineEndOffset(line: Int): Int? {
return PsiDocumentManager.getInstance(project).getDocument(this)?.getLineEndOffset(line)
}
fun PsiElement.getLineNumber(start: Boolean = true): Int {
return PsiDocumentManager.getInstance(project).getDocument(this.containingFile)?.getLineNumber(if (start) this.startOffset else this.endOffset) ?: 0
}
fun PsiElement.getLineCount(): Int {
val doc = getContainingFile()?.let { file -> PsiDocumentManager.getInstance(getProject()).getDocument(file) }
if (doc != null) {
@@ -0,0 +1,28 @@
LineBreakpoint created at stepOverIfWithInline.kt:5
!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! stepOverIfWithInline.StepOverIfWithInlinePackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOverIfWithInline.kt:5
stepOverIfWithInline.kt:7
stepOverIfWithInline.kt:11
stepOverIfWithInline.kt:7
stepOverIfWithInline.kt:15
stepOverIfWithInline.kt:18
stepOverIfWithInline.kt:15
stepOverIfWithInline.kt:21
stepOverIfWithInline.kt:41
stepOverIfWithInline.kt:21
stepOverIfWithInline.kt:24
stepOverIfWithInline.kt:25
stepOverIfWithInline.kt:24
stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:41
stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:32
stepOverIfWithInline.kt:41
stepOverIfWithInline.kt:32
stepOverIfWithInline.kt:36
stepOverIfWithInline.kt:32
stepOverIfWithInline.kt:38
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,10 @@
LineBreakpoint created at stepOverInlineFunctionInReturn.kt:9
!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! stepOverInlineFunctionInReturn.StepOverInlineFunctionInReturnPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOverInlineFunctionInReturn.kt:9
stepOverInlineFunctionInReturn.kt:10
stepOverInlineFunctionInReturn.kt:4
stepOverInlineFunctionInReturn.kt:5
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -11,7 +11,8 @@ stepOverInlinedLambda.kt:19
stepOverInlinedLambda.kt:20
stepOverInlinedLambda.kt:23
stepOverInlinedLambda.kt:29
stepOverInlinedLambda.kt:30
stepOverInlinedLambda.kt:31
stepOverInlinedLambda.kt:32
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,22 @@
LineBreakpoint created at stepOverTryCatchWithInline.kt:14
!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! stepOverTryCatchWithInline.StepOverTryCatchWithInlinePackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOverTryCatchWithInline.kt:14
stepOverTryCatchWithInline.kt:16
stepOverTryCatchWithInline.kt:17
stepOverTryCatchWithInline.kt:48
stepOverTryCatchWithInline.kt:17
stepOverTryCatchWithInline.kt:24
stepOverTryCatchWithInline.kt:25
stepOverTryCatchWithInline.kt:28
stepOverTryCatchWithInline.kt:35
stepOverTryCatchWithInline.kt:36
stepOverTryCatchWithInline.kt:48
stepOverTryCatchWithInline.kt:36
stepOverTryCatchWithInline.kt:39
stepOverTryCatchWithInline.kt:43
stepOverTryCatchWithInline.kt:8
stepOverTryCatchWithInline.kt:10
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 stepOverWhenInReturn.kt:10
!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! stepOverWhenInReturn.StepOverWhenInReturnPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOverWhenInReturn.kt:10
stepOverWhenInReturn.kt:11
stepOverWhenInReturn.kt:17
stepOverWhenInReturn.kt:11
stepOverWhenInReturn.kt:10
stepOverWhenInReturn.kt:4
stepOverWhenInReturn.kt:5
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,48 @@
LineBreakpoint created at stepOverWhenWithInline.kt:5
!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! stepOverWhenWithInline.StepOverWhenWithInlinePackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOverWhenWithInline.kt:5
stepOverWhenWithInline.kt:8
stepOverWhenWithInline.kt:9
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:9
stepOverWhenWithInline.kt:7
stepOverWhenWithInline.kt:14
stepOverWhenWithInline.kt:17
stepOverWhenWithInline.kt:18
stepOverWhenWithInline.kt:13
stepOverWhenWithInline.kt:26
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:26
stepOverWhenWithInline.kt:27
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:27
stepOverWhenWithInline.kt:25
stepOverWhenWithInline.kt:32
stepOverWhenWithInline.kt:34
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:34
stepOverWhenWithInline.kt:32
stepOverWhenWithInline.kt:38
stepOverWhenWithInline.kt:43
stepOverWhenWithInline.kt:38
stepOverWhenWithInline.kt:51
stepOverWhenWithInline.kt:52
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:52
stepOverWhenWithInline.kt:51
stepOverWhenWithInline.kt:58
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:58
stepOverWhenWithInline.kt:57
stepOverWhenWithInline.kt:64
stepOverWhenWithInline.kt:65
stepOverWhenWithInline.kt:63
stepOverWhenWithInline.kt:76
stepOverWhenWithInline.kt:83
stepOverWhenWithInline.kt:76
stepOverWhenWithInline.kt:75
stepOverWhenWithInline.kt:80
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,47 @@
package stepOverIfWithInline
fun main(args: Array<String>) {
//Breakpoint!
val prop = 1
// False with braces
val a = if (1 > 2) {
foo { test(1) }
}
else {
foo { test(1) }
}
// False without braces
val b = if (1 > 2)
foo { test(1) }
else
foo { test(1) }
// One line
val c = if (1 > 2) foo { test(1) } else foo { test(1) }
// Else on next line, false
val d = if (1 > 2) foo { test(1) }
else foo { test(1) }
// Else on next line, true
val e = if (1 < 2) foo { test(1) }
else foo { test(1) }
// Inline function call in condition
val f = if (foo { test(1) } > 2) {
foo { test(1) }
}
else {
foo { test(1) }
}
}
inline fun foo(f: () -> Int): Int {
val a = 1
return f()
}
fun test(i: Int) = 1
// STEP_OVER: 30
@@ -0,0 +1,22 @@
package stepOverInlineFunctionInReturn
fun main(args: Array<String>) {
f()
}
fun f(): Int {
//Breakpoint!
val a = 1
return foo {
test(2)
}
}
inline fun foo(f: () -> Int): Int {
val a = 1
return f()
}
fun test(i: Int) = 1
// STEP_OVER: 4
@@ -27,6 +27,8 @@ fun main(args: Array<String>) {
})
val b = foo { test(1) }
foo(fun (){ test(1) })
}
inline fun foo(f: () -> Unit) {
@@ -47,4 +49,4 @@ class A {
fun test(i: Int) = 1
// STEP_OVER: 11
// STEP_OVER: 13
@@ -0,0 +1,54 @@
package stepOverTryCatchWithInline
fun main(args: Array<String>) {
try {
bar()
}
catch(e: Exception) {
val a = 1
}
}
fun bar() {
//Breakpoint!
val prop = 1
// Try
try {
foo { test(1) }
}
catch(e: Exception) {
foo { test(1) }
}
// Many catch clauses
try {
throw IllegalStateException()
}
catch(e: IllegalStateException) {
foo { test(1) }
}
catch(e: Exception) {
foo { test(1) }
}
// exception in lambda
try {
foo { throw IllegalStateException() }
}
catch(e: Exception) {
foo { test(1) }
}
// Exception without catch
foo { throw IllegalStateException() }
val prop2 = 1
}
inline fun foo(f: () -> Int): Int {
val a = 1
return f()
}
fun test(i: Int) = 1
// STEP_OVER: 30
@@ -0,0 +1,23 @@
package stepOverWhenInReturn
fun main(args: Array<String>) {
whenInReturn()
}
fun whenInReturn(): Int {
val a = 1
//Breakpoint!
return when(a) {
1 -> foo { 1 }
else -> 3
}
}
inline fun foo(f: () -> Int): Int {
val a = 1
return f()
}
fun test(i: Int) = 1
// STEP_OVER: 6
@@ -0,0 +1,89 @@
package stepOverWhenWithInline
fun main(args: Array<String>) {
//Breakpoint!
val prop = 1
// Break after second
val a = when {
1 > 2 -> foo { test(1) }
2 > 1 -> foo { test(1) }
else -> foo { test(1) }
}
val b = when {
1 > 2 -> {
foo { test(1) }
}
2 > 1 -> {
foo { test(1) }
}
else -> {
foo { test(1) }
}
}
val c = when {
foo { test(1) } > 2 -> 1
2 > foo { test(1) } -> 2
else -> foo { test(1) }
}
// When with expression
val a1 = when(prop) {
2 -> foo { test(1) }
1 -> foo { test(1) }
else -> foo { test(1) }
}
val b1 = when(prop) {
2 -> {
foo { test(1) }
}
1 -> {
foo { test(1) }
}
else -> {
foo { test(1) }
}
}
// Break after first
val c1 = when(prop) {
foo { test(1) } -> 1
foo { test(2) } -> 2
else -> foo { test(1) }
}
val a2 = when {
2 > 1 -> foo { test(1) }
1 > 2 -> foo { test(1) }
else -> foo { test(1) }
}
val b2 = when {
2 > 1 -> {
foo { test(1) }
}
1 > 2 -> {
foo { test(1) }
}
else -> {
foo { test(1) }
}
}
val c2 = when {
2 > foo { test(1) } -> 2
foo { test(1) } > 2 -> 1
else -> foo { test(1) }
}
}
inline fun foo(f: () -> Int): Int {
val a = 1
return f()
}
fun test(i: Int) = i
// STEP_OVER: 50
@@ -343,6 +343,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepOverTest(fileName);
}
@TestMetadata("stepOverIfWithInline.kt")
public void testStepOverIfWithInline() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverIfWithInline.kt");
doStepOverTest(fileName);
}
@TestMetadata("stepOverInlineFunctionInReturn.kt")
public void testStepOverInlineFunctionInReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlineFunctionInReturn.kt");
doStepOverTest(fileName);
}
@TestMetadata("stepOverInlinedLambda.kt")
public void testStepOverInlinedLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambda.kt");
@@ -360,6 +372,24 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverSimpleFun.kt");
doStepOverTest(fileName);
}
@TestMetadata("stepOverTryCatchWithInline.kt")
public void testStepOverTryCatchWithInline() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverTryCatchWithInline.kt");
doStepOverTest(fileName);
}
@TestMetadata("stepOverWhenInReturn.kt")
public void testStepOverWhenInReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverWhenInReturn.kt");
doStepOverTest(fileName);
}
@TestMetadata("stepOverWhenWithInline.kt")
public void testStepOverWhenWithInline() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverWhenWithInline.kt");
doStepOverTest(fileName);
}
}
@TestMetadata("idea/testData/debugger/tinyApp/src/stepping/filters")