Debugger: Do not use expression code fragments for evaluator (KT-32736, KT-32741)

It seems like it doesn't make much sense to create expression code fragments.
People type statements or several expressions separated with a semicolon to a single-line expression line.
That is exactly what block code fragment was designed for.
This commit is contained in:
Yan Zhulanow
2019-10-10 21:26:26 +09:00
parent 39662c09e9
commit 716c9f79f7
7 changed files with 66 additions and 19 deletions
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.DebuggerManagerEx import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImports import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.impl.DebuggerContextImpl
@@ -56,13 +55,7 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val contextElement = getContextElement(context) val contextElement = getContextElement(context)
val constructor = when (item.kind) { val codeFragment = KtBlockCodeFragment(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
null -> error("Code fragment kind should be set")
CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
}
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
supplyDebugInformation(item, codeFragment, context) supplyDebugInformation(item, codeFragment, context)
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR) { expression: KtExpression -> codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR) { expression: KtExpression ->
@@ -228,7 +221,7 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val kotlinCodeFragment = createCodeFragment(item, context, project) val kotlinCodeFragment = createCodeFragment(item, context, project)
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) { if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtCodeFragment) {
val javaExpression = try { val javaExpression = try {
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context) PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
} catch (e: IncorrectOperationException) { } catch (e: IncorrectOperationException) {
@@ -238,20 +238,30 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePositi
} }
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean { private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
if (this !is KtExpressionCodeFragment) { val expression = runReadAction {
return false when (this) {
} is KtExpressionCodeFragment -> getContentElement()
is KtBlockCodeFragment -> getContentElement().statements.lastOrNull()
else -> {
LOG.error("Invalid code fragment type: ${this.javaClass}")
null
}
}
} ?: return false
val contentElement = runReadAction { getContentElement() } return wrapToStringIfNeeded(expression, bindingContext)
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type }
if (contentElement != null && expressionType?.isInlineClassType() == true) {
private fun wrapToStringIfNeeded(expression: KtExpression, bindingContext: BindingContext): Boolean {
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type ?: return false
if (expressionType.isInlineClassType()) {
val newExpression = runReadAction { val newExpression = runReadAction {
val expressionText = contentElement.text val expressionText = expression.text
KtPsiFactory(project).createExpression("($expressionText).toString()") KtPsiFactory(expression.project).createExpression("($expressionText).toString()")
} }
runInEdtAndWait { runInEdtAndWait {
project.executeWriteCommand("Wrap with 'toString()'") { expression.project.executeWriteCommand("Wrap with 'toString()'") {
contentElement.replace(newExpression) expression.replace(newExpression)
} }
} }
return true return true
@@ -185,6 +185,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/funFromSuperClass.kt"); runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/funFromSuperClass.kt");
} }
@TestMetadata("functionDeclaration.kt")
public void testFunctionDeclaration() throws Exception {
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/functionDeclaration.kt");
}
@TestMetadata("genericCrossinlineArgument.kt") @TestMetadata("genericCrossinlineArgument.kt")
public void testGenericCrossinlineArgument() throws Exception { public void testGenericCrossinlineArgument() throws Exception {
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/genericCrossinlineArgument.kt"); runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/genericCrossinlineArgument.kt");
@@ -400,6 +405,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/simple.kt"); runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/simple.kt");
} }
@TestMetadata("statements.kt")
public void testStatements() throws Exception {
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/statements.kt");
}
@TestMetadata("staticField.kt") @TestMetadata("staticField.kt")
public void testStaticField() throws Exception { public void testStaticField() throws Exception {
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/staticField.kt"); runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/staticField.kt");
@@ -0,0 +1,9 @@
package functionDeclaration
fun main() {
//Breakpoint!
val a = 5
}
// EXPRESSION: fun foo() {}
// RESULT: VOID_VALUE
@@ -0,0 +1,8 @@
LineBreakpoint created at functionDeclaration.kt:5
Run Java
Connected to the target VM
functionDeclaration.kt:5
Compile bytecode for fun foo() {}
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,9 @@
package statements
fun main() {
//Breakpoint!
val a = 5
}
// EXPRESSION: val b = 1
// RESULT: VOID_VALUE
@@ -0,0 +1,8 @@
LineBreakpoint created at statements.kt:5
Run Java
Connected to the target VM
statements.kt:5
Compile bytecode for val b = 1
Disconnected from the target VM
Process finished with exit code 0