Evaluator: Render inline class values using its toString() implementation (KT-27414)
This commit is contained in:
+4
-3
@@ -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
|
||||
|
||||
+32
-1
@@ -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("/", ".")
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@ fun main() {
|
||||
}
|
||||
|
||||
// EXPRESSION: i
|
||||
// RESULT: 1: I
|
||||
// RESULT: "InlineMe(x=1)": Ljava/lang/String;
|
||||
+1
-1
@@ -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
|
||||
|
||||
+26
-24
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user