diff --git a/Changelog.md b/Changelog.md index 94ae2e24862..2abda5a6f02 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,4 +12,7 @@ - Support conversion for annotation constructor calls - Place comments from the middle of the call to the end - Drop line breaks between operator arguments (except '+', "-", "&&" and "||") -- Add non-null assertions on call site for non-null parameters \ No newline at end of file +- Add non-null assertions on call site for non-null parameters + +### IDE +- Debugger can distinguish nested inline arguments \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index f1298eca23c..3c5d57c691b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -42,6 +42,8 @@ import org.jetbrains.kotlin.load.java.JvmAbi; import org.jetbrains.kotlin.load.java.SpecialBuiltinMembers; import org.jetbrains.kotlin.load.kotlin.nativeDeclarations.NativeKt; import org.jetbrains.kotlin.name.FqName; +import org.jetbrains.kotlin.psi.KtElement; +import org.jetbrains.kotlin.psi.KtFunction; import org.jetbrains.kotlin.psi.KtNamedFunction; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; @@ -51,6 +53,7 @@ import org.jetbrains.kotlin.resolve.calls.callResolverUtil.CallResolverUtilKt; import org.jetbrains.kotlin.resolve.constants.ArrayValue; import org.jetbrains.kotlin.resolve.constants.ConstantValue; import org.jetbrains.kotlin.resolve.constants.KClassValue; +import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; @@ -394,8 +397,18 @@ public class FunctionCodegen { int indexOfLambdaOrdinal = name.lastIndexOf("$"); if (indexOfLambdaOrdinal > 0) { int lambdaOrdinal = Integer.parseInt(name.substring(indexOfLambdaOrdinal + 1)); + + KtElement functionArgument = parentCodegen.element; + String functionName = "unknown"; + if (functionArgument instanceof KtFunction) { + ValueParameterDescriptor inlineArgumentDescriptor = + InlineUtil.getInlineArgumentDescriptor((KtFunction) functionArgument, parentCodegen.bindingContext); + if (inlineArgumentDescriptor != null) { + functionName = inlineArgumentDescriptor.getContainingDeclaration().getName().asString(); + } + } mv.visitLocalVariable( - JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT + lambdaOrdinal, + JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT + lambdaOrdinal + "$" + functionName, Type.INT_TYPE.getDescriptor(), null, methodBegin, methodEnd, lambdaFakeIndex); diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index ff0248cede6..a2ef0a9eb3e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -24,6 +24,8 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.inline.InlineUtil import java.util.* fun isInsideInlineFunctionBody(visibleVariables: List): Boolean { @@ -36,32 +38,49 @@ fun numberOfInlinedFunctions(visibleVariables: List): Int { fun isInsideInlineArgument(inlineArgument: KtFunction, location: Location, debugProcess: DebugProcessImpl): Boolean { val visibleVariables = location.visibleVariables(debugProcess) - val lambdaOrdinalIndex = runReadAction { lambdaOrdinalIndex(inlineArgument) } + + val context = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext + + val lambdaOrdinal = runReadAction { lambdaOrdinalByArgument(inlineArgument, context) } val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) } - return markerLocalVariables.firstOrNull { lambdaOrdinal(it.name()) == lambdaOrdinalIndex } != null + + val functionName = runReadAction { functionNameByArgument(inlineArgument, context) } + + return markerLocalVariables.firstOrNull { + lambdaOrdinalByLocalVariable(it.name()) == lambdaOrdinal && functionNameByLocalVariable(it.name()) == functionName + } != null } -private fun lambdaOrdinalIndex(elementAt: KtFunction): Int { - val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(elementAt) - - val type = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, elementAt) +private fun lambdaOrdinalByArgument(elementAt: KtFunction, context: BindingContext): Int { + val type = CodegenBinding.asmTypeForAnonymousClass(context, elementAt) return type.className.substringAfterLast("$").toInt() } +private fun functionNameByArgument(elementAt: KtFunction, context: BindingContext): String { + val inlineArgumentDescriptor = InlineUtil.getInlineArgumentDescriptor(elementAt, context) + return inlineArgumentDescriptor?.containingDeclaration?.name?.asString() ?: "unknown" +} + private fun Location.visibleVariables(debugProcess: DebugProcessImpl): List { val stackFrame = MockStackFrame(this, debugProcess.virtualMachineProxy.virtualMachine) return stackFrame.visibleVariables() } -private fun lambdaOrdinal(name: String): Int { - return try { - return name.substringAfter(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT).toInt() +private fun lambdaOrdinalByLocalVariable(name: String): Int { + try { + val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) + return Integer.parseInt(nameWithoutPrefix.substringBefore("$", nameWithoutPrefix)) } catch(e: NumberFormatException) { - 0 + return 0 } } +private fun functionNameByLocalVariable(name: String): String { + val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) + return nameWithoutPrefix.substringAfterLast("$", "unknown") +} + private class MockStackFrame(private val location: Location, private val vm: VirtualMachine) : StackFrame { private var visibleVariables: Map? = null diff --git a/idea/testData/debugger/tinyApp/outs/nestedInlineArguments.out b/idea/testData/debugger/tinyApp/outs/nestedInlineArguments.out new file mode 100644 index 00000000000..812912376fb --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/nestedInlineArguments.out @@ -0,0 +1,8 @@ +LineBreakpoint created at nestedInlineArguments.kt:13 lambdaOrdinal = 0 +!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! nestedInlineArguments.NestedInlineArgumentsKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +nestedInlineArguments.kt:13 +Compile bytecode for it.i +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/nestedInlineArguments.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/nestedInlineArguments.kt new file mode 100644 index 00000000000..a3254d04c4f --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/nestedInlineArguments.kt @@ -0,0 +1,19 @@ +package nestedInlineArguments + +fun main(args: Array) { + val list1 = listOf("a") + val list2 = listOf(Bar()) + list1.map { list2.foo(1) } +} + +inline fun List.foo(key: Int): T? { + // EXPRESSION: it.i + // RESULT: 1: I + //Breakpoint! (lambdaOrdinal = 0) + return this.firstOrNull { it.i == key } +} + +open class Foo +open class Bar { + val i = 1 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index 4592bf1841b..c1d6a95786a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -229,6 +229,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doSingleBreakpointTest(fileName); } + @TestMetadata("nestedInlineArguments.kt") + public void testNestedInlineArguments() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/nestedInlineArguments.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("onClassHeader.kt") public void testOnClassHeader() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onClassHeader.kt");