From ef08110354b4bed2d51336a07881438263cedc9c Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 14 Jun 2018 23:44:46 +0300 Subject: [PATCH] Evaluator: Render inline class values using its toString() implementation (KT-27414) --- .../evaluate/KotlinCodeFragmentFactory.kt | 7 +-- .../evaluate/KotlinEvaluationBuilder.kt | 33 +++++++++++- .../inlineMethodsInSignature.kt | 2 +- .../inlineMethodsInSignature.out | 2 +- .../AbstractKotlinEvaluateExpressionTest.kt | 50 ++++++++++--------- 5 files changed, 64 insertions(+), 30 deletions(-) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index b2699723072..94cb4bba8ac 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -51,6 +51,7 @@ import org.jetbrains.kotlin.idea.refactoring.j2kText import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass import org.jetbrains.kotlin.j2k.AfterConversionPass import org.jetbrains.kotlin.psi.* @@ -269,9 +270,9 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return kotlinCodeFragment } - override fun isContextAccepted(contextElement: PsiElement?): Boolean { - return when { - // PsiCodeBlock -> DummyHolder -> originalElement + override fun isContextAccepted(contextElement: PsiElement?): Boolean = runReadAction { + when { + // PsiCodeBlock -> DummyHolder -> originalElement contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context) contextElement == null -> false contextElement.language == KotlinFileType.INSTANCE.language -> true diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index b9e38818229..7f506fd1015 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -36,6 +36,7 @@ import com.intellij.psi.PsiFileFactory import com.intellij.psi.impl.PsiFileFactoryImpl import com.intellij.psi.search.GlobalSearchScope import com.intellij.testFramework.LightVirtualFile +import com.intellij.testFramework.runInEdtAndWait import com.intellij.util.ExceptionUtil import com.sun.jdi.* import com.sun.jdi.request.EventRequest @@ -70,6 +71,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasse import org.jetbrains.kotlin.idea.debugger.getBackingFieldName import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments @@ -82,6 +84,7 @@ import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.isInlineClassType import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.Opcodes.ASM5 @@ -220,7 +223,12 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour companion object { private fun extractAndCompile(codeFragment: KtCodeFragment, sourcePosition: SourcePosition, context: EvaluationContextImpl): CompiledDataDescriptor { - codeFragment.checkForErrors() + val (bindingContext) = codeFragment.checkForErrors() + + if (codeFragment.wrapToStringIfNeeded(bindingContext)) { + // Repeat analysis with toString() added + codeFragment.checkForErrors() + } val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line) ?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}") @@ -255,6 +263,29 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour parametersDescriptor) } + private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean { + if (this !is KtExpressionCodeFragment) { + return false + } + + val contentElement = runReadAction { getContentElement() } + val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type + if (contentElement != null && expressionType?.isInlineClassType() == true) { + val newExpression = runReadAction { + val expressionText = contentElement.text + KtPsiFactory(project).createExpression("($expressionText).toString()") + } + runInEdtAndWait { + project.executeWriteCommand("Wrap with 'toString()'") { + contentElement.replace(newExpression) + } + } + return true + } + + return false + } + private fun getClassName(fileName: String): String { return fileName.substringBeforeLast(".class").replace("/", ".") } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.kt index 02c7d2f9d4b..cbdb9facd7f 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.kt @@ -9,4 +9,4 @@ fun main() { } // EXPRESSION: i -// RESULT: 1: I \ No newline at end of file +// RESULT: "InlineMe(x=1)": Ljava/lang/String; \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.out index c80547703de..ddece1ccef3 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineMethodsInSignature.out @@ -2,7 +2,7 @@ LineBreakpoint created at inlineMethodsInSignature.kt:8 Run Java Connected to the target VM inlineMethodsInSignature.kt:8 -Compile bytecode for i +Compile bytecode for (i).toString() Disconnected from the target VM Process finished with exit code 0 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 f8f66520d97..8aa6c8d9d8e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt @@ -447,39 +447,41 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { } private fun SuspendContextImpl.evaluate(item: TextWithImportsImpl, expectedResult: String?) { - runReadAction { - val sourcePosition = ContextUtil.getSourcePosition(this) + val sourcePosition = ContextUtil.getSourcePosition(this) + val contextElement = ContextUtil.getContextElement(debuggerContext)!! - val contextElement = ContextUtil.getContextElement(debuggerContext)!! - Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement.text}", - KotlinCodeFragmentFactory().isContextAccepted(contextElement)) + assert(KotlinCodeFragmentFactory().isContextAccepted(contextElement)) { + val text = runReadAction { contextElement.text } + "KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. " + + "ContextElement = $text" + } - contextElement.putCopyableUserData(KotlinCodeFragmentFactory.DEBUG_CONTEXT_FOR_TESTS, this@AbstractKotlinEvaluateExpressionTest.debuggerContext) + contextElement.putCopyableUserData( + KotlinCodeFragmentFactory.DEBUG_CONTEXT_FOR_TESTS, + this@AbstractKotlinEvaluateExpressionTest.debuggerContext + ) + runActionInSuspendCommand { + try { + val evaluator = runReadAction { EvaluatorBuilderImpl.build(item, contextElement, sourcePosition, project) } + ?: throw AssertionError("Cannot create an Evaluator for Evaluate Expression") - runActionInSuspendCommand { - try { - val evaluator = EvaluatorBuilderImpl.build(item, contextElement, sourcePosition, project) - ?: throw AssertionError("Cannot create an Evaluator for Evaluate Expression") - - val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext) - val actualResult = value.asValue().asString() - if (expectedResult != null) { - Assert.assertEquals( - "Evaluate expression returns wrong result for ${item.text}:\n" + + val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext) + val actualResult = value.asValue().asString() + if (expectedResult != null) { + Assert.assertEquals( + "Evaluate expression returns wrong result for ${item.text}:\n" + "expected = $expectedResult\n" + "actual = $actualResult\n", - expectedResult, actualResult) - } + expectedResult, actualResult) } - catch (e: EvaluateException) { - val expectedMessage = e.message?.replaceFirst(ID_PART_REGEX, "id=ID") - Assert.assertEquals( - "Evaluate expression throws wrong exception for ${item.text}:\n" + + } catch (e: EvaluateException) { + val expectedMessage = e.message?.replaceFirst(ID_PART_REGEX, "id=ID") + Assert.assertEquals( + "Evaluate expression throws wrong exception for ${item.text}:\n" + "expected = $expectedResult\n" + "actual = $expectedMessage\n", - expectedResult, expectedMessage) - } + expectedResult, expectedMessage) } } }