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.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
@@ -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("/", ".")
}
@@ -9,4 +9,4 @@ fun main() {
}
// EXPRESSION: i
// RESULT: 1: I
// RESULT: "InlineMe(x=1)": Ljava/lang/String;
@@ -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
@@ -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)
}
}
}