From de7b6cb3a1919a88803ac61a916e553a900ebc87 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Wed, 6 Aug 2014 17:08:01 +0400 Subject: [PATCH] Evaluate expression: support EE on method with expression body, on properties #KT-5485 Fixed --- .../extractFunctionForDebuggerUtil.kt | 68 ++++++++++++++++--- .../intentions/InsertExplicitTypeArguments.kt | 36 ++++++---- .../debugger/tinyApp/outs/whenEntry.out | 14 ++++ .../tinyApp/outs/withoutBodyFunctions.out | 21 ++++++ .../tinyApp/outs/withoutBodyProperties.out | 21 ++++++ .../outs/withoutBodyTypeParameters.out | 8 +++ .../evaluate/multipleBreakpoints/whenEntry.kt | 31 +++++++++ .../withoutBodyFunctions.kt | 46 +++++++++++++ .../withoutBodyProperties.kt | 45 ++++++++++++ .../withoutBodyTypeParameters.kt | 14 ++++ ...KotlinEvaluateExpressionTestGenerated.java | 20 ++++++ 11 files changed, 300 insertions(+), 24 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/whenEntry.out create mode 100644 idea/testData/debugger/tinyApp/outs/withoutBodyFunctions.out create mode 100644 idea/testData/debugger/tinyApp/outs/withoutBodyProperties.out create mode 100644 idea/testData/debugger/tinyApp/outs/withoutBodyTypeParameters.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt index 90c2b9f9abf..d10f7a99703 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -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() +} diff --git a/idea/src/org/jetbrains/jet/plugin/intentions/InsertExplicitTypeArguments.kt b/idea/src/org/jetbrains/jet/plugin/intentions/InsertExplicitTypeArguments.kt index 29507fff243..b6eb09fd6f5 100644 --- a/idea/src/org/jetbrains/jet/plugin/intentions/InsertExplicitTypeArguments.kt +++ b/idea/src/org/jetbrains/jet/plugin/intentions/InsertExplicitTypeArguments.kt @@ -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( "insert.explicit.type.arguments", javaClass()) { @@ -48,23 +49,32 @@ public class InsertExplicitTypeArguments : JetSelfTargetingIntention") + 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) + } + } } \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/outs/whenEntry.out b/idea/testData/debugger/tinyApp/outs/whenEntry.out new file mode 100644 index 00000000000..ce58aa31996 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/whenEntry.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/withoutBodyFunctions.out b/idea/testData/debugger/tinyApp/outs/withoutBodyFunctions.out new file mode 100644 index 00000000000..e441b225400 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/withoutBodyFunctions.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/withoutBodyProperties.out b/idea/testData/debugger/tinyApp/outs/withoutBodyProperties.out new file mode 100644 index 00000000000..ed220adff2a --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/withoutBodyProperties.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/withoutBodyTypeParameters.out b/idea/testData/debugger/tinyApp/outs/withoutBodyTypeParameters.out new file mode 100644 index 00000000000..9d9d882b13c --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/withoutBodyTypeParameters.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt new file mode 100644 index 00000000000..a63a4e5390a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt @@ -0,0 +1,31 @@ +package whenEntry + +fun main(args: Array) { + 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 + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt new file mode 100644 index 00000000000..dd7ea62ce7f --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt @@ -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) { + aGet + aGet2 + + fooWithBody(2) + foo(2) + fooOneLine(2) + fooEmpty(2) +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties.kt new file mode 100644 index 00000000000..09ef542cb32 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties.kt @@ -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) { + a + aDelegate = 1 + aLambda + + A().prop +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt new file mode 100644 index 00000000000..b68a4cf777a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt @@ -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(i: T) = i + +fun run(i: () -> Int) = 1 + +fun main(args: Array) { + foo(2) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index a95fca18317..de4da9671c2 100644 --- a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -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")