Evaluate expression: support EE on method with expression body, on properties

#KT-5485 Fixed
This commit is contained in:
Natalia Ukhorskaya
2014-08-06 17:08:01 +04:00
parent 3a7f66b7eb
commit de7b6cb3a1
11 changed files with 300 additions and 24 deletions
@@ -16,9 +16,7 @@
package org.jetbrains.jet.plugin.debugger.evaluate
import org.jetbrains.jet.lang.psi.JetCodeFragment
import com.intellij.psi.PsiFile
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.ErrorMessage
import org.jetbrains.jet.lang.psi.JetFile
@@ -33,11 +31,13 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Statu
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import org.jetbrains.jet.plugin.refactoring.extractFunction.validate
import org.jetbrains.jet.plugin.refactoring.extractFunction.generateFunction
import org.jetbrains.jet.lang.psi.JetImportList
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionOptions
import org.jetbrains.jet.plugin.refactoring.runReadAction
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiModificationTrackerImpl
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.plugin.intentions.InsertExplicitTypeArguments
import com.intellij.psi.util.PsiTreeUtil
fun getFunctionForExtractedFragment(
codeFragment: JetCodeFragment,
@@ -122,19 +122,65 @@ private fun addImportsToFile(newImportList: JetImportList?, tmpFile: JetFile) {
}
private fun addDebugExpressionBeforeContextElement(codeFragment: JetCodeFragment, contextElement: PsiElement): JetExpression? {
val parent = contextElement.getParent()
if (parent == null) return null
val psiFactory = JetPsiFactory(codeFragment)
parent.addBefore(psiFactory.createNewLine(), contextElement)
val elementBefore = when {
contextElement is JetProperty && !contextElement.isLocal() -> {
wrapInRunFun(contextElement.getDelegateExpressionOrInitializer()!!)
}
contextElement is JetDeclarationWithBody && !contextElement.hasBlockBody()-> {
wrapInRunFun(contextElement.getBodyExpression()!!)
}
contextElement is JetDeclarationWithBody && contextElement.hasBlockBody()-> {
val block = contextElement.getBodyExpression() as JetBlockExpression
block.getStatements().first ?: block.getLastChild()
}
contextElement is JetWhenEntry -> {
val entryExpression = contextElement.getExpression()
if (entryExpression is JetBlockExpression) {
entryExpression.getStatements().first ?: entryExpression.getLastChild()
}
else {
wrapInRunFun(entryExpression!!)
}
}
else -> {
contextElement
}
}
val parent = elementBefore?.getParent()
if (parent == null || elementBefore == null) return null
parent.addBefore(psiFactory.createNewLine(), elementBefore)
val debugExpression = codeFragment.getContentElement()
if (debugExpression == null) return null
val newDebugExpression = parent.addBefore(debugExpression, contextElement)
val newDebugExpression = parent.addBefore(debugExpression, elementBefore)
if (newDebugExpression == null) return null
parent.addBefore(psiFactory.createNewLine(), contextElement)
parent.addBefore(psiFactory.createNewLine(), elementBefore)
return newDebugExpression as JetExpression
}
private fun createRunFunction(body: JetExpression): JetCallExpression {
val callExpression = JetPsiFactory(body).createExpression("run { \n${body.getText()} \n}") as JetCallExpression
val typeArguments = InsertExplicitTypeArguments.createTypeArguments(callExpression)
if (typeArguments?.getArguments()?.isNotEmpty() ?: false) {
val calleeExpression = callExpression.getCalleeExpression()
callExpression.addAfter(typeArguments!!, calleeExpression)
}
return callExpression
}
private fun wrapInRunFun(expression: JetExpression): PsiElement? {
val newBody = createRunFunction(expression)
val replacedBody = (expression.replace(newBody) as JetCallExpression)
// Increment modification tracker to clear ResolveCache after changes in function body
(PsiManager.getInstance(expression.getProject()).getModificationTracker() as PsiModificationTrackerImpl).incCounter()
return replacedBody.getFunctionLiteralArguments().first().getFunctionLiteral().getBodyExpression()?.getFirstChild()
}
@@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.types.ErrorUtils
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import org.jetbrains.jet.lang.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.jet.lang.psi.JetTypeArgumentList
public class InsertExplicitTypeArguments : JetSelfTargetingIntention<JetCallExpression>(
"insert.explicit.type.arguments", javaClass()) {
@@ -48,23 +49,32 @@ public class InsertExplicitTypeArguments : JetSelfTargetingIntention<JetCallExpr
}
override fun applyTo(element: JetCallExpression, editor: Editor) {
val context = AnalyzerFacadeWithCache.getContextForElement(element)
val resolvedCall = element.getResolvedCall(context)
if (resolvedCall == null) return
val args = resolvedCall.getTypeArguments()
val types = resolvedCall.getCandidateDescriptor().getTypeParameters()
val psiFactory = JetPsiFactory(element)
val typeArgs = types.map {
assert(args[it] != null, "there is a null in the type arguments to transform")
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(args[it]!!)
}.joinToString(", ", "<", ">")
val argumentList = createTypeArguments(element)
if (argumentList == null) return
val callee = element.getCalleeExpression()
if (callee == null) return
element.addAfter(psiFactory.createTypeArguments(typeArgs), callee)
element.addAfter(argumentList, callee)
ShortenReferences.process(element.getTypeArgumentList()!!)
}
class object {
fun createTypeArguments(element: JetCallExpression): JetTypeArgumentList? {
val context = AnalyzerFacadeWithCache.getContextForElement(element)
val resolvedCall = element.getResolvedCall(context)
if (resolvedCall == null) return null
val args = resolvedCall.getTypeArguments()
val types = resolvedCall.getCandidateDescriptor().getTypeParameters()
val psiFactory = JetPsiFactory(element)
val typeArgs = types.map {
assert(args[it] != null, "there is a null in the type arguments to transform")
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(args[it]!!)
}.joinToString(", ", "<", ">")
return psiFactory.createTypeArguments(typeArgs)
}
}
}
@@ -0,0 +1,14 @@
LineBreakpoint created at whenEntry.kt:10
LineBreakpoint created at whenEntry.kt:18
LineBreakpoint created at whenEntry.kt:26
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!RT_JAR! whenEntry.WhenEntryPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
whenEntry.kt:9
Compile bytecode for a
whenEntry.kt:17
Compile bytecode for a
whenEntry.kt:25
Compile bytecode for a
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,21 @@
LineBreakpoint created at withoutBodyFunctions.kt:9
LineBreakpoint created at withoutBodyFunctions.kt:15
LineBreakpoint created at withoutBodyFunctions.kt:21
LineBreakpoint created at withoutBodyFunctions.kt:27
LineBreakpoint created at withoutBodyFunctions.kt:32
LineBreakpoint created at withoutBodyFunctions.kt:36
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!RT_JAR! withoutBodyFunctions.WithoutBodyFunctionsPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
withoutBodyFunctions.kt:8
Compile bytecode for 1 + 1
withoutBodyFunctions.kt:14
Compile bytecode for 1 + 2
withoutBodyFunctions.kt:20
Compile bytecode for i
withoutBodyFunctions.kt:26
Compile bytecode for i
withoutBodyFunctions.kt:31
Compile bytecode for i
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,21 @@
LineBreakpoint created at withoutBodyProperties.kt:8
LineBreakpoint created at withoutBodyProperties.kt:13
LineBreakpoint created at withoutBodyProperties.kt:18
LineBreakpoint created at withoutBodyProperties.kt:29
LineBreakpoint created at withoutBodyProperties.kt:36
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!RT_JAR! withoutBodyProperties.WithoutBodyPropertiesPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
withoutBodyProperties.kt:7
Compile bytecode for 1 + 1
withoutBodyProperties.kt:12
Compile bytecode for 1 + 2
withoutBodyProperties.kt:17
Compile bytecode for 1 + 3
withoutBodyProperties.kt:28
Compile bytecode for i
withoutBodyProperties.kt:28
withoutBodyProperties.kt:35
Compile bytecode for 1 + 4
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at withoutBodyTypeParameters.kt:8
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!RT_JAR! withoutBodyTypeParameters.WithoutBodyTypeParametersPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
withoutBodyTypeParameters.kt:7
Compile bytecode for i
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,31 @@
package whenEntry
fun main(args: Array<String>) {
val a = 1
// EXPRESSION: a
// RESULT: 1: I
val b = when {
//Breakpoint!
a == 1 -> 1 + 1
else -> 1 + 2
}
// EXPRESSION: a
// RESULT: 1: I
val c = when {
//Breakpoint!
a == 1 -> { 1 + 1 }
else -> 1 + 2
}
// EXPRESSION: a
// RESULT: 1: I
val d = when {
//Breakpoint!
a == 1 -> {
1 + 1
}
else -> 1 + 2
}
}
@@ -0,0 +1,46 @@
package withoutBodyFunctions
import kotlin.properties.Delegates
// EXPRESSION: 1 + 1
// RESULT: 2: I
val aGet: Int
//Breakpoint!
get() = 1
// EXPRESSION: 1 + 2
// RESULT: 3: I
val aGet2: Int
//Breakpoint!
get() { return 1 }
fun fooWithBody(i: Int): Int {
// EXPRESSION: i
// RESULT: 2: I
//Breakpoint!
return i
}
// EXPRESSION: i
// RESULT: 2: I
//Breakpoint!
fun foo(i: Int) = i
// EXPRESSION: i
// RESULT: 2: I
//Breakpoint!
fun fooOneLine(i: Int): Int { return 1 }
// Cannot stop at this breakpoint - empty body
//Breakpoint!
fun fooEmpty(i: Int) {}
fun main(args: Array<String>) {
aGet
aGet2
fooWithBody(2)
foo(2)
fooOneLine(2)
fooEmpty(2)
}
@@ -0,0 +1,45 @@
package withoutBodyProperties
import kotlin.properties.Delegates
// EXPRESSION: 1 + 1
// RESULT: 2: I
//Breakpoint!
val a = 1
// EXPRESSION: 1 + 2
// RESULT: 3: I
//Breakpoint!
var aDelegate: Int by Delegates.notNull()
// EXPRESSION: 1 + 3
// RESULT: 4: I
//Breakpoint!
val aLambda = { 1 + 1 }
class A {
{
// EXPRESSION: i
// RESULT: 1: I
// EXPRESSION: i
// RESULT: 2: I
for (i in 1..2) {
//Breakpoint!
val a = 1
}
}
// EXPRESSION: 1 + 4
// RESULT: 5: I
//Breakpoint!
val prop = 1
}
fun main(args: Array<String>) {
a
aDelegate = 1
aLambda
A().prop
}
@@ -0,0 +1,14 @@
package withoutBodyTypeParameters
import kotlin.properties.Delegates
// EXPRESSION: i
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
//Breakpoint!
fun foo<T>(i: T) = i
fun run(i: () -> Int) = 1
fun main(args: Array<String>) {
foo(2)
}
@@ -161,6 +161,26 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doMultipleBreakpointsTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/exceptions.kt");
}
@TestMetadata("whenEntry.kt")
public void testWhenEntry() throws Exception {
doMultipleBreakpointsTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt");
}
@TestMetadata("withoutBodyFunctions.kt")
public void testWithoutBodyFunctions() throws Exception {
doMultipleBreakpointsTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt");
}
@TestMetadata("withoutBodyProperties.kt")
public void testWithoutBodyProperties() throws Exception {
doMultipleBreakpointsTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties.kt");
}
@TestMetadata("withoutBodyTypeParameters.kt")
public void testWithoutBodyTypeParameters() throws Exception {
doMultipleBreakpointsTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt");
}
}
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/frame")