Support inlined functional expressions in debugger

This commit is contained in:
Michael Bogdanov
2015-04-28 14:23:34 +03:00
parent e0aa64b8d2
commit fe23f23664
6 changed files with 78 additions and 42 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.idea.debugger
import com.google.common.collect.ObjectArrays
import com.intellij.debugger.MultiRequestPositionManager
import com.intellij.debugger.NoDataException
import com.intellij.debugger.SourcePosition
@@ -57,8 +58,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineAnalyzerExtension
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.util.ArrayList
import java.util.WeakHashMap
import java.util.*
class PositionedElement(val className: String?, val element: PsiElement?)
@@ -83,9 +83,9 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
if (lineNumber >= 0) {
val lambdaIfInside = getLambdaIfInside(location, psiFile as JetFile, lineNumber)
val lambdaIfInside = getLambdaOrFunIfInside(location, psiFile as JetFile, lineNumber)
if (lambdaIfInside != null) {
return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression().getStatements().get(0))
return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression())
}
return SourcePosition.createFromLine(psiFile, lineNumber)
}
@@ -93,7 +93,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
throw NoDataException.INSTANCE
}
private fun getLambdaIfInside(location: Location, file: JetFile, lineNumber: Int): JetFunctionLiteral? {
private fun getLambdaOrFunIfInside(location: Location, file: JetFile, lineNumber: Int): JetFunction? {
val currentLocationFqName = location.declaringType().name()
if (currentLocationFqName == null) return null
@@ -101,8 +101,15 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
if (start == null || end == null) return null
val literals = CodeInsightUtils.findElementsOfClassInRange(file, start, end, javaClass<JetFunctionLiteral>())
if (literals == null || literals.size() == 0) return null
val functionLiterals: Array<out PsiElement>? = CodeInsightUtils.findElementsOfClassInRange(file, start, end, javaClass<JetFunctionLiteral>())
val functionalExpression: Array<out PsiElement>? = CodeInsightUtils.findElementsOfClassInRange(file, start, end, javaClass<JetNamedFunction>())
val literals =
if (functionLiterals == null) functionalExpression
else if (functionalExpression == null) functionLiterals
else functionLiterals.plus(functionalExpression).toTypedArray()
if (literals == null || literals.size() == 0) return null;
val isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null
val typeMapper = if (!isInLibrary)
@@ -112,7 +119,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).getInternalName()
for (literal in literals) {
val functionLiteral = literal as JetFunctionLiteral
val functionLiteral = literal as JetFunction
if (isInlinedLambda(functionLiteral, typeMapper.getBindingContext())) {
continue
}
@@ -321,6 +328,10 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
return PositionedElement(getJvmInternalNameForPropertyOwner(typeMapper, descriptor), element)
}
element is JetNamedFunction -> {
if (isInlinedLambda(element, typeMapper.getBindingContext())) {
return getInternalClassNameForElement(element.getParent(), typeMapper, file, isInLibrary)
}
val parent = getElementToCalculateClassName(element)
if (parent is JetClassOrObject) {
return PositionedElement(getJvmInternalNameForImpl(typeMapper, parent), element)
@@ -388,40 +399,8 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
return state.getTypeMapper()
}
public fun isInlinedLambda(functionLiteral: JetFunctionLiteral, context: BindingContext): Boolean {
val functionLiteralExpression = functionLiteral.getParent()
if (functionLiteralExpression == null) return false
var parent = functionLiteralExpression.getParent()
var valueArgument: PsiElement = functionLiteralExpression
while (parent is JetParenthesizedExpression || parent is JetBinaryExpressionWithTypeRHS || parent is JetLabeledExpression) {
valueArgument = parent
parent = parent.getParent()
}
while (parent is ValueArgument || parent is JetValueArgumentList) {
parent = parent.getParent()
}
if (parent !is JetElement) return false
val call = (parent as JetElement).getResolvedCall(context)
if (call == null) return false
if (!InlineUtil.isInline(call.getResultingDescriptor())) return false
for ((valueParameterDescriptor, resolvedValueArgument) in call.getValueArguments()) {
for (next in resolvedValueArgument.getArguments()) {
val expression = next.getArgumentExpression()
if (valueArgument == expression) {
return InlineAnalyzerExtension.checkInlinableParameter(
valueParameterDescriptor, expression, call.getResultingDescriptor(), null
)
}
}
}
return false
public fun isInlinedLambda(functionLiteral: JetFunction, context: BindingContext): Boolean {
return InlineUtil.isInlineLambda(functionLiteral, context, false)
}
private fun createKeyForTypeMapper(file: JetFile) = PackagePartClassUtils.getPackagePartInternalName(file)
@@ -0,0 +1,8 @@
LineBreakpoint created at inlineFunctionalExpression.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! inlineFunctionalExpression.InlineFunctionalExpressionPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
inlineFunctionalExpression.kt:9
inlineFunctionalExpression.kt:9
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 oneLineFunctionalExpression.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! oneLineFunctionalExpression.OneLineFunctionalExpressionPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
oneLineFunctionalExpression.kt:9
oneLineFunctionalExpression.kt:14
oneLineFunctionalExpression.kt:9
Compile bytecode for it
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,11 @@
package inlineFunctionalExpression
fun main(args: Array<String>) {
val a = array(1)
// EXPRESSION: it
// RESULT: Unresolved reference: it
// STEP_INTO: 1
//Breakpoint!
a.map(fun (it): Int { return it * 1 })
}
@@ -0,0 +1,16 @@
package oneLineFunctionalExpression
fun main(args: Array<String>) {
val a = A()
// EXPRESSION: it
// RESULT: 1: I
// STEP_INTO: 2
//Breakpoint!
a.foo (fun b(it): A { return a })
}
class A {
fun foo(f: (Int) -> A): A {
return f(1)
}
}
@@ -522,6 +522,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("inlineFunctionalExpression.kt")
public void testInlineFunctionalExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineFunctionalExpression.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("inlineLambda.kt")
public void testInlineLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt");
@@ -534,6 +540,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doSingleBreakpointTest(fileName);
}
@TestMetadata("oneLineFunctionalExpression.kt")
public void testOneLineFunctionalExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineFunctionalExpression.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("oneLineLambda.kt")
public void testOneLineLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt");