From 102edd5a0e0b486d8eed20fba77d27ab5f48c5ba Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Tue, 29 Mar 2016 16:57:03 +0300 Subject: [PATCH] Debugger: do not insert array in imports --- ChangeLog.md | 1 + .../evaluate/KotlinCodeFragmentFactory.kt | 42 +++++++++- .../outs/createExpressionCastToBuiltIn.out | 31 +++++++ .../tinyApp/outs/createExpressionSimple.out | 2 +- .../outs/createExpressionWithArray.out | 38 +++++++++ .../createExpressionCastToBuiltIn.kt | 12 +++ .../frame/createExpressionSimple.kt | 2 +- .../frame/createExpressionWithArray.kt | 13 +++ .../tinyApp/src/forTests/MyJavaClass.java | 15 ++++ .../AbstractKotlinEvaluateExpressionTest.kt | 83 +++++++++++++------ ...KotlinEvaluateExpressionTestGenerated.java | 6 ++ 11 files changed, 213 insertions(+), 32 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/createExpressionCastToBuiltIn.out create mode 100644 idea/testData/debugger/tinyApp/outs/createExpressionWithArray.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/createExpression/createExpressionCastToBuiltIn.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionWithArray.kt diff --git a/ChangeLog.md b/ChangeLog.md index 60553741849..6fac1f7770a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,7 @@ - Configure Kotlin: show only changed files in the notification "Kotlin not configured" - Configure Kotlin: restore all changed files in undo action - Convert java expression to kotlin when open Evaluate Expression dialog from Variables View +- Fix Evaluate Expression for expression created for array element ### Java to Kotlin converter diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index acd33a48ef6..d728b35a98e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -21,6 +21,7 @@ import com.intellij.debugger.engine.evaluation.CodeFragmentFactory import com.intellij.debugger.engine.evaluation.CodeFragmentKind import com.intellij.debugger.engine.evaluation.TextWithImports import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.intellij.ide.highlighter.JavaFileType import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project @@ -28,6 +29,7 @@ import com.intellij.openapi.util.Key import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.PsiTypesUtil import com.intellij.util.IncorrectOperationException import com.intellij.util.concurrency.Semaphore import com.intellij.xdebugger.XDebuggerManager @@ -70,7 +72,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { project, "fragment.kt", item.text, - item.imports, + initImports(item.imports), contextElement ) } @@ -79,7 +81,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { project, "fragment.kt", item.text, - item.imports, + initImports(item.imports), contextElement ) } @@ -117,6 +119,28 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { return codeFragment } + private fun initImports(imports: String?): String? { + if (imports != null && !imports.isEmpty()) { + return imports.split(KtCodeFragment.IMPORT_SEPARATOR) + .mapNotNull { fixImportIfNeeded(it) } + .joinToString(KtCodeFragment.IMPORT_SEPARATOR) + } + return null + } + + private fun fixImportIfNeeded(import: String): String? { + // skip arrays + if (import.endsWith("[]")) { + return fixImportIfNeeded(import.removeSuffix("[]").trim()) + } + + // skip primitive types + if (PsiTypesUtil.boxIfPossible(import) != import) { + return null + } + return import + } + private fun getWrappedContextElement(project: Project, context: PsiElement?) = wrapContextIfNeeded(project, getContextElement(context)) @@ -130,16 +154,28 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { null } + val importList = try { + kotlinCodeFragment.importsAsImportList()?.let { + (PsiFileFactory.getInstance(project).createFileFromText( + "dummy.java", JavaFileType.INSTANCE, it.text + ) as? PsiJavaFile)?.importList + } + } + catch(e: IncorrectOperationException) { + null + } + if (javaExpression != null) { var convertedFragment: KtExpressionCodeFragment? = null project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") { val newText = javaExpression.j2kText() + val newImports = importList?.j2kText() if (newText != null) { convertedFragment = KtExpressionCodeFragment( project, kotlinCodeFragment.name, newText, - kotlinCodeFragment.importsToString(), + newImports, kotlinCodeFragment.context ) diff --git a/idea/testData/debugger/tinyApp/outs/createExpressionCastToBuiltIn.out b/idea/testData/debugger/tinyApp/outs/createExpressionCastToBuiltIn.out new file mode 100644 index 00000000000..0a5e8134105 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/createExpressionCastToBuiltIn.out @@ -0,0 +1,31 @@ +LineBreakpoint created at createExpressionCastToBuiltIn.kt:7 +!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! createExpressionCastToBuiltIn.CreateExpressionCastToBuiltInKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +createExpressionCastToBuiltIn.kt:7 +package createExpressionCastToBuiltIn + +fun main(args: Array) { + val primitiveArray = intArrayOf(1) + val stringArray = arrayOf("a") + //Breakpoint! + val a = 1 +} + +// PRINT_FRAME +// SKIP: value, hash, hash32 +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT + +Compile bytecode for args +Compile bytecode for primitiveArray +Compile bytecode for primitiveArray[0] +Compile bytecode for stringArray +Compile bytecode for stringArray[0] + frame = main + local = args (expression = args) + local = primitiveArray (expression = primitiveArray) + element = 0 (expression = primitiveArray[0]) + local = stringArray (expression = stringArray) + element = 0 (expression = stringArray[0]) +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/outs/createExpressionSimple.out b/idea/testData/debugger/tinyApp/outs/createExpressionSimple.out index 636ca7b3c0e..a203bb1eea6 100644 --- a/idea/testData/debugger/tinyApp/outs/createExpressionSimple.out +++ b/idea/testData/debugger/tinyApp/outs/createExpressionSimple.out @@ -20,7 +20,7 @@ fun main(args: Array) { } // PRINT_FRAME -// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION frame = main local = args (expression = args) diff --git a/idea/testData/debugger/tinyApp/outs/createExpressionWithArray.out b/idea/testData/debugger/tinyApp/outs/createExpressionWithArray.out new file mode 100644 index 00000000000..127abe89d99 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/createExpressionWithArray.out @@ -0,0 +1,38 @@ +LineBreakpoint created at createExpressionWithArray.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! createExpressionWithArray.CreateExpressionWithArrayKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +createExpressionWithArray.kt:9 +package createExpressionWithArray + +import forTests.MyJavaClass + +fun main(args: Array) { + val baseArray = arrayOf(MyJavaClass().getBaseClassValue()) + val innerArray = arrayOf(MyJavaClass().getInnerClassValue()) + //Breakpoint! + val a = 1 +} + +// PRINT_FRAME +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT + +Compile bytecode for args +Compile bytecode for baseArray +Compile bytecode for baseArray[0] +Compile bytecode for baseArray[0].i2 +Compile bytecode for innerArray +Compile bytecode for innerArray[0] +Compile bytecode for (innerArray[0] as InnerClass).i +Compile bytecode for (innerArray[0] as InnerClass).i2 + frame = main + local = args (expression = args) + local = baseArray (expression = baseArray) + element = 0 (expression = baseArray[0]) + field = i2 (expression = baseArray[0].i2) + local = innerArray (expression = innerArray) + element = 0 (expression = innerArray[0]) + field = i (expression = (innerArray[0] as InnerClass).i) + field = i2 (expression = (innerArray[0] as InnerClass).i2) +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/createExpression/createExpressionCastToBuiltIn.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/createExpression/createExpressionCastToBuiltIn.kt new file mode 100644 index 00000000000..483b8eacc7a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/createExpression/createExpressionCastToBuiltIn.kt @@ -0,0 +1,12 @@ +package createExpressionCastToBuiltIn + +fun main(args: Array) { + val primitiveArray = intArrayOf(1) + val stringArray = arrayOf("a") + //Breakpoint! + val a = 1 +} + +// PRINT_FRAME +// SKIP: value, hash, hash32 +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionSimple.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionSimple.kt index 577ee8b7e7a..908420e949e 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionSimple.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionSimple.kt @@ -16,4 +16,4 @@ fun main(args: Array) { } // PRINT_FRAME -// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionWithArray.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionWithArray.kt new file mode 100644 index 00000000000..07384a5513b --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionWithArray.kt @@ -0,0 +1,13 @@ +package createExpressionWithArray + +import forTests.MyJavaClass + +fun main(args: Array) { + val baseArray = arrayOf(MyJavaClass().getBaseClassValue()) + val innerArray = arrayOf(MyJavaClass().getInnerClassValue()) + //Breakpoint! + val a = 1 +} + +// PRINT_FRAME +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT diff --git a/idea/testData/debugger/tinyApp/src/forTests/MyJavaClass.java b/idea/testData/debugger/tinyApp/src/forTests/MyJavaClass.java index 4381a648d61..35d9f14f56c 100644 --- a/idea/testData/debugger/tinyApp/src/forTests/MyJavaClass.java +++ b/idea/testData/debugger/tinyApp/src/forTests/MyJavaClass.java @@ -23,4 +23,19 @@ public class MyJavaClass { private static class PrivateJavaClass { public final int prop = 1; } + + public static class BaseClass { + public final int i2 = 1; + } + + public BaseClass getBaseClassValue() { + return new BaseClass(); + } + public BaseClass getInnerClassValue() { + return new InnerClass(); + } + + public static class InnerClass extends BaseClass { + public final int i = 1; + } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt index 6c387a6e9f2..753442eea23 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt @@ -24,12 +24,10 @@ import com.intellij.debugger.engine.evaluation.TextWithImports import com.intellij.debugger.engine.evaluation.TextWithImportsImpl import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl import com.intellij.debugger.engine.events.DebuggerCommandImpl +import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.settings.NodeRendererSettings import com.intellij.debugger.ui.impl.watch.* -import com.intellij.debugger.ui.tree.FieldDescriptor -import com.intellij.debugger.ui.tree.LocalVariableDescriptor -import com.intellij.debugger.ui.tree.StackFrameDescriptor -import com.intellij.debugger.ui.tree.StaticDescriptor +import com.intellij.debugger.ui.tree.* import com.intellij.execution.process.ProcessOutputTypes import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.io.FileUtil @@ -112,7 +110,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { createAdditionalBreakpoints(fileText) val shouldPrintFrame = isDirectiveDefined(fileText, "// PRINT_FRAME") - val skipInPrintFrame = if (shouldPrintFrame) findLinesWithPrefixesRemoved(fileText, "// SKIP: ") else emptyList() + val skipInPrintFrame = if (shouldPrintFrame) findListWithPrefixes(fileText, "// SKIP: ") else emptyList() val descriptorViewOptions = DescriptorViewOptions.valueOf(findStringWithPrefixes(fileText, "// DESCRIPTOR_VIEW_OPTIONS: ") ?: "FULL") val expressions = loadTestDirectivesPairs(fileText, "// EXPRESSION: ", "// RESULT: ") @@ -232,23 +230,24 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { internal class PrinterConfig( val variablesToSkipInPrintFrame: List = emptyList(), - val descriptorOptionsOptions: DescriptorViewOptions = DescriptorViewOptions.FULL + val viewOptions: DescriptorViewOptions = DescriptorViewOptions.FULL ) { enum class DescriptorViewOptions { FULL, - NAME_AND_EXPRESSION + NAME_EXPRESSION, + NAME_EXPRESSION_RESULT } fun shouldRenderSourcesPosition(): Boolean { - return when(descriptorOptionsOptions) { + return when(viewOptions) { DescriptorViewOptions.FULL -> true else -> false } } fun shouldRenderExpression(): Boolean { - return when(descriptorOptionsOptions) { - DescriptorViewOptions.NAME_AND_EXPRESSION -> true + return when { + viewOptions.toString().contains("EXPRESSION") -> true else -> false } } @@ -256,10 +255,14 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { fun renderLabel(descriptor: NodeDescriptorImpl): String { return when { descriptor is WatchItemDescriptor -> descriptor.calcValueName() - descriptorOptionsOptions == DescriptorViewOptions.NAME_AND_EXPRESSION -> descriptor.name ?: descriptor.label + viewOptions.toString().contains("NAME") -> descriptor.name ?: descriptor.label else -> descriptor.label } } + + fun shouldComputeResultOfCreateExpression(): Boolean { + return viewOptions == DescriptorViewOptions.NAME_EXPRESSION_RESULT + } } private inner class Printer(private val config: PrinterConfig) { @@ -309,19 +312,29 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { } if (config.shouldRenderExpression() && descriptor is ValueDescriptorImpl) { - var expression: PsiExpression? = null - debuggerContext.debugProcess!!.managerThread.invokeAndWait(object : DebuggerCommandImpl() { - override fun action() { - expression = runReadAction { - descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, debuggerContext) as? PsiExpression + val expression = invokeInManagerThread { + descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, it) as? PsiExpression + } + + if (expression != null) { + val text = TextWithImportsImpl(expression) + val imports = expression.getUserData(DebuggerTreeNodeExpression.ADDITIONAL_IMPORTS_KEY)?.joinToString { it } ?: "" + + val codeFragment = KotlinCodeFragmentFactory().createPresentationCodeFragment( + TextWithImportsImpl(text.kind, text.text, text.imports + imports, text.fileType), + debuggerContext.sourcePosition.elementAt, project + ) + val codeFragmentText = codeFragment.text + + if (config.shouldComputeResultOfCreateExpression()) { + invokeInManagerThread { + it.suspendContext?.evaluate( + TextWithImportsImpl(text.kind, codeFragmentText, codeFragment.importsToString(), text.fileType), + null) } } - }) - if (expression != null) { - val text = KotlinCodeFragmentFactory().createPresentationCodeFragment( - TextWithImportsImpl(expression!!), debuggerContext.sourcePosition.elementAt, project - ).text - append(" (expression = $text)") + + append(" (expression = $codeFragmentText)") } } append("\n") @@ -332,6 +345,16 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { return false } + private fun invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? { + var result: T? = null + debuggerContext.debugProcess!!.managerThread.invokeAndWait(object : DebuggerCommandImpl() { + override fun action() { + result = runReadAction { f(debuggerContext) } + } + }) + return result + } + private fun getPrefix(descriptor: NodeDescriptorImpl): String { val prefix = when (descriptor) { is StackFrameDescriptor -> "frame" @@ -340,6 +363,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { is StaticDescriptor -> "static" is ThisDescriptorImpl -> "this" is FieldDescriptor -> "field" + is ArrayElementDescriptor -> "element" is MessageDescriptor -> "" else -> "unknown" } @@ -422,7 +446,11 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { return KotlinCodeFragmentFactory().createWrappingContext(text, labels, KotlinCodeFragmentFactory.getContextElement(contextElement), project)!! } - private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String) { + private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String?) { + return evaluate(TextWithImportsImpl(codeFragmentKind, text, "", KotlinFileType.INSTANCE), expectedResult) + } + + private fun SuspendContextImpl.evaluate(item: TextWithImportsImpl, expectedResult: String?) { runReadAction { val sourcePosition = ContextUtil.getSourcePosition(this) val contextElement = createContextElement(this) @@ -432,7 +460,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { try { val evaluator = - EvaluatorBuilderImpl.build(TextWithImportsImpl(codeFragmentKind, text, "", KotlinFileType.INSTANCE), + EvaluatorBuilderImpl.build(item, contextElement, sourcePosition) @@ -441,11 +469,12 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext) val actualResult = value.asValue().asString() - - Assert.assertTrue("Evaluate expression returns wrong result for $text:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult) + if (expectedResult != null) { + Assert.assertTrue("Evaluate expression returns wrong result for ${item.text}:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult) + } } catch (e: EvaluateException) { - Assert.assertTrue("Evaluate expression throws wrong exception for $text:\nexpected = $expectedResult\nactual = ${e.message}\n", expectedResult == e.message?.replaceFirst(ID_PART_REGEX, "id=ID")) + Assert.assertTrue("Evaluate expression throws wrong exception for ${item.text}:\nexpected = $expectedResult\nactual = ${e.message}\n", expectedResult == e.message?.replaceFirst(ID_PART_REGEX, "id=ID")) } } } 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 b366910024a..5d893738731 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -501,6 +501,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doSingleBreakpointTest(fileName); } + @TestMetadata("createExpressionWithArray.kt") + public void testCreateExpressionWithArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionWithArray.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("delegatedPropertyInClass.kt") public void testDelegatedPropertyInClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");