Evaluator: Render inline class values using its toString() implementation (KT-27414)

This commit is contained in:
Yan Zhulanow
2018-06-14 23:44:46 +03:00
parent 425467ca99
commit ef08110354
5 changed files with 64 additions and 30 deletions
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.idea.refactoring.j2kText
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable 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.idea.versions.getKotlinJvmRuntimeMarkerClass
import org.jetbrains.kotlin.j2k.AfterConversionPass import org.jetbrains.kotlin.j2k.AfterConversionPass
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -269,9 +270,9 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
return kotlinCodeFragment return kotlinCodeFragment
} }
override fun isContextAccepted(contextElement: PsiElement?): Boolean { override fun isContextAccepted(contextElement: PsiElement?): Boolean = runReadAction {
return when { when {
// PsiCodeBlock -> DummyHolder -> originalElement // PsiCodeBlock -> DummyHolder -> originalElement
contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context) contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
contextElement == null -> false contextElement == null -> false
contextElement.language == KotlinFileType.INSTANCE.language -> true contextElement.language == KotlinFileType.INSTANCE.language -> true
@@ -36,6 +36,7 @@ import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightVirtualFile import com.intellij.testFramework.LightVirtualFile
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.ExceptionUtil import com.intellij.util.ExceptionUtil
import com.sun.jdi.* import com.sun.jdi.*
import com.sun.jdi.request.EventRequest 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.debugger.getBackingFieldName
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE 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.application.runReadAction
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments 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.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5 import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
@@ -220,7 +223,12 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
companion object { companion object {
private fun extractAndCompile(codeFragment: KtCodeFragment, sourcePosition: SourcePosition, context: EvaluationContextImpl): CompiledDataDescriptor { 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) val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line)
?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}") ?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}")
@@ -255,6 +263,29 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
parametersDescriptor) 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 { private fun getClassName(fileName: String): String {
return fileName.substringBeforeLast(".class").replace("/", ".") return fileName.substringBeforeLast(".class").replace("/", ".")
} }
@@ -9,4 +9,4 @@ fun main() {
} }
// EXPRESSION: i // EXPRESSION: i
// RESULT: 1: I // RESULT: "InlineMe(x=1)": Ljava/lang/String;
@@ -2,7 +2,7 @@ LineBreakpoint created at inlineMethodsInSignature.kt:8
Run Java Run Java
Connected to the target VM Connected to the target VM
inlineMethodsInSignature.kt:8 inlineMethodsInSignature.kt:8
Compile bytecode for i Compile bytecode for (i).toString()
Disconnected from the target VM Disconnected from the target VM
Process finished with exit code 0 Process finished with exit code 0
@@ -447,39 +447,41 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
} }
private fun SuspendContextImpl.evaluate(item: TextWithImportsImpl, expectedResult: String?) { 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(KotlinCodeFragmentFactory().isContextAccepted(contextElement)) {
Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement.text}", val text = runReadAction { contextElement.text }
KotlinCodeFragmentFactory().isContextAccepted(contextElement)) "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 { val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext)
try { val actualResult = value.asValue().asString()
val evaluator = EvaluatorBuilderImpl.build(item, contextElement, sourcePosition, project) if (expectedResult != null) {
?: throw AssertionError("Cannot create an Evaluator for Evaluate Expression") 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" + "expected = $expectedResult\n" +
"actual = $actualResult\n", "actual = $actualResult\n",
expectedResult, actualResult) expectedResult, actualResult)
}
} }
catch (e: EvaluateException) { } catch (e: EvaluateException) {
val expectedMessage = e.message?.replaceFirst(ID_PART_REGEX, "id=ID") val expectedMessage = e.message?.replaceFirst(ID_PART_REGEX, "id=ID")
Assert.assertEquals( Assert.assertEquals(
"Evaluate expression throws wrong exception for ${item.text}:\n" + "Evaluate expression throws wrong exception for ${item.text}:\n" +
"expected = $expectedResult\n" + "expected = $expectedResult\n" +
"actual = $expectedMessage\n", "actual = $expectedMessage\n",
expectedResult, expectedMessage) expectedResult, expectedMessage)
}
} }
} }
} }