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.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
|
||||||
|
|||||||
+32
-1
@@ -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("/", ".")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -9,4 +9,4 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXPRESSION: i
|
// 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
|
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
|
||||||
|
|||||||
+26
-24
@@ -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)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user