Support inlined functional expressions in debugger
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
|
import com.google.common.collect.ObjectArrays
|
||||||
import com.intellij.debugger.MultiRequestPositionManager
|
import com.intellij.debugger.MultiRequestPositionManager
|
||||||
import com.intellij.debugger.NoDataException
|
import com.intellij.debugger.NoDataException
|
||||||
import com.intellij.debugger.SourcePosition
|
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.InlineAnalyzerExtension
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import java.util.ArrayList
|
import java.util.*
|
||||||
import java.util.WeakHashMap
|
|
||||||
|
|
||||||
class PositionedElement(val className: String?, val element: PsiElement?)
|
class PositionedElement(val className: String?, val element: PsiElement?)
|
||||||
|
|
||||||
@@ -83,9 +83,9 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
|
|
||||||
|
|
||||||
if (lineNumber >= 0) {
|
if (lineNumber >= 0) {
|
||||||
val lambdaIfInside = getLambdaIfInside(location, psiFile as JetFile, lineNumber)
|
val lambdaIfInside = getLambdaOrFunIfInside(location, psiFile as JetFile, lineNumber)
|
||||||
if (lambdaIfInside != null) {
|
if (lambdaIfInside != null) {
|
||||||
return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression().getStatements().get(0))
|
return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression())
|
||||||
}
|
}
|
||||||
return SourcePosition.createFromLine(psiFile, lineNumber)
|
return SourcePosition.createFromLine(psiFile, lineNumber)
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
throw NoDataException.INSTANCE
|
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()
|
val currentLocationFqName = location.declaringType().name()
|
||||||
if (currentLocationFqName == null) return null
|
if (currentLocationFqName == null) return null
|
||||||
|
|
||||||
@@ -101,8 +101,15 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
|
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
|
||||||
if (start == null || end == null) return null
|
if (start == null || end == null) return null
|
||||||
|
|
||||||
val literals = CodeInsightUtils.findElementsOfClassInRange(file, start, end, javaClass<JetFunctionLiteral>())
|
val functionLiterals: Array<out PsiElement>? = CodeInsightUtils.findElementsOfClassInRange(file, start, end, javaClass<JetFunctionLiteral>())
|
||||||
if (literals == null || literals.size() == 0) return null
|
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 isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null
|
||||||
val typeMapper = if (!isInLibrary)
|
val typeMapper = if (!isInLibrary)
|
||||||
@@ -112,7 +119,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
|
|
||||||
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).getInternalName()
|
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).getInternalName()
|
||||||
for (literal in literals) {
|
for (literal in literals) {
|
||||||
val functionLiteral = literal as JetFunctionLiteral
|
val functionLiteral = literal as JetFunction
|
||||||
if (isInlinedLambda(functionLiteral, typeMapper.getBindingContext())) {
|
if (isInlinedLambda(functionLiteral, typeMapper.getBindingContext())) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -321,6 +328,10 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
return PositionedElement(getJvmInternalNameForPropertyOwner(typeMapper, descriptor), element)
|
return PositionedElement(getJvmInternalNameForPropertyOwner(typeMapper, descriptor), element)
|
||||||
}
|
}
|
||||||
element is JetNamedFunction -> {
|
element is JetNamedFunction -> {
|
||||||
|
if (isInlinedLambda(element, typeMapper.getBindingContext())) {
|
||||||
|
return getInternalClassNameForElement(element.getParent(), typeMapper, file, isInLibrary)
|
||||||
|
}
|
||||||
|
|
||||||
val parent = getElementToCalculateClassName(element)
|
val parent = getElementToCalculateClassName(element)
|
||||||
if (parent is JetClassOrObject) {
|
if (parent is JetClassOrObject) {
|
||||||
return PositionedElement(getJvmInternalNameForImpl(typeMapper, parent), element)
|
return PositionedElement(getJvmInternalNameForImpl(typeMapper, parent), element)
|
||||||
@@ -388,40 +399,8 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
return state.getTypeMapper()
|
return state.getTypeMapper()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun isInlinedLambda(functionLiteral: JetFunctionLiteral, context: BindingContext): Boolean {
|
public fun isInlinedLambda(functionLiteral: JetFunction, context: BindingContext): Boolean {
|
||||||
val functionLiteralExpression = functionLiteral.getParent()
|
return InlineUtil.isInlineLambda(functionLiteral, context, false)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createKeyForTypeMapper(file: JetFile) = PackagePartClassUtils.getPackagePartInternalName(file)
|
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
|
||||||
+11
@@ -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 })
|
||||||
|
}
|
||||||
|
|
||||||
+16
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -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);
|
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")
|
@TestMetadata("inlineLambda.kt")
|
||||||
public void testInlineLambda() throws Exception {
|
public void testInlineLambda() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt");
|
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);
|
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")
|
@TestMetadata("oneLineLambda.kt")
|
||||||
public void testOneLineLambda() throws Exception {
|
public void testOneLineLambda() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user