From 50dcef254dc7bfb10ef03263697b3f22ec0916c7 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Thu, 11 Sep 2014 14:15:27 +0400 Subject: [PATCH] Debugger: get correct context for breakpoints inside lambdas --- .../plugin/codeInsight/CodeInsightUtils.java | 62 ++++++++++++++++--- .../plugin/debugger/JetPositionManager.java | 44 +++++++++++-- .../extractFunctionForDebuggerUtil.kt | 59 +++++++++++++++--- ...lFilesPresentInFindElementAtBreakpoint.out | 0 .../tinyApp/outs/allFilesPresentInLambdas.out | 0 .../debugger/tinyApp/outs/callableBug.out | 9 +++ .../debugger/tinyApp/outs/inlineLambda.out | 8 +++ .../tinyApp/outs/lambdaOnSecondLine.out | 10 +++ .../debugger/tinyApp/outs/oneLineLambda.out | 10 +++ .../tinyApp/outs/twoLambdasOnOneLineFirst.out | 10 +++ .../outs/twoLambdasOnOneLineSecond.out | 12 ++++ .../evaluate/singleBreakpoint/callableBug.kt | 12 ++++ .../singleBreakpoint/lambdas/inlineLambda.kt | 11 ++++ .../lambdas/lambdaOnSecondLine.kt | 18 ++++++ .../singleBreakpoint/lambdas/oneLineLambda.kt | 16 +++++ .../lambdas/twoLambdasOnOneLineFirst.kt | 16 +++++ .../lambdas/twoLambdasOnOneLineSecond.kt | 18 ++++++ .../AbstractKotlinEvaluateExpressionTest.kt | 26 +++----- ...KotlinEvaluateExpressionTestGenerated.java | 48 +++++++++++++- 19 files changed, 351 insertions(+), 38 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/allFilesPresentInFindElementAtBreakpoint.out create mode 100644 idea/testData/debugger/tinyApp/outs/allFilesPresentInLambdas.out create mode 100644 idea/testData/debugger/tinyApp/outs/callableBug.out create mode 100644 idea/testData/debugger/tinyApp/outs/inlineLambda.out create mode 100644 idea/testData/debugger/tinyApp/outs/lambdaOnSecondLine.out create mode 100644 idea/testData/debugger/tinyApp/outs/oneLineLambda.out create mode 100644 idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineFirst.out create mode 100644 idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineSecond.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/callableBug.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/lambdaOnSecondLine.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineSecond.kt diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/CodeInsightUtils.java b/idea/src/org/jetbrains/jet/plugin/codeInsight/CodeInsightUtils.java index df5de6eddd7..97a52105db3 100644 --- a/idea/src/org/jetbrains/jet/plugin/codeInsight/CodeInsightUtils.java +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/CodeInsightUtils.java @@ -83,18 +83,10 @@ public class CodeInsightUtils { parent = parent.getParent(); } - if (!parent.equals(element1)) { - while (!parent.equals(element1.getParent())) { - element1 = element1.getParent(); - } - } + element1 = getTopmostParentInside(element1, parent); if (startOffset != element1.getTextRange().getStartOffset()) return PsiElement.EMPTY_ARRAY; - if (!parent.equals(element2)) { - while (!parent.equals(element2.getParent())) { - element2 = element2.getParent(); - } - } + element2 = getTopmostParentInside(element2, parent); if (endOffset != element2.getTextRange().getEndOffset()) return PsiElement.EMPTY_ARRAY; List array = new ArrayList(); @@ -133,6 +125,47 @@ public class CodeInsightUtils { return jetExpression; } + @Nullable + public static PsiElement[] findElementsOfClassInRange(@NotNull PsiFile file, int startOffset, int endOffset, Class aClass) { + PsiElement element1 = getElementAtOffsetIgnoreWhitespaceBefore(file, startOffset); + PsiElement element2 = getElementAtOffsetIgnoreWhitespaceAfter(file, endOffset); + + if (element1 == null || element2 == null) return PsiElement.EMPTY_ARRAY; + + startOffset = element1.getTextRange().getStartOffset(); + endOffset = element2.getTextRange().getEndOffset(); + + PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2); + if (parent == null) return PsiElement.EMPTY_ARRAY; + + element1 = getTopmostParentInside(element1, parent); + if (startOffset != element1.getTextRange().getStartOffset()) return PsiElement.EMPTY_ARRAY; + + element2 = getTopmostParentInside(element2, parent); + if (endOffset != element2.getTextRange().getEndOffset()) return PsiElement.EMPTY_ARRAY; + + PsiElement stopElement = element2.getNextSibling(); + List array = new ArrayList(); + for (PsiElement currentElement = element1; currentElement != stopElement; currentElement = currentElement.getNextSibling()) { + if (aClass.isInstance(currentElement)) { + array.add(currentElement); + } + array.addAll(PsiTreeUtil.findChildrenOfType(currentElement, aClass)); + } + + return PsiUtilCore.toPsiElementArray(array); + } + + @NotNull + private static PsiElement getTopmostParentInside(@NotNull PsiElement element, @NotNull PsiElement parent) { + if (!parent.equals(element)) { + while (!parent.equals(element.getParent())) { + element = element.getParent(); + } + } + return element; + } + @Nullable public static PsiElement getElementAtOffsetIgnoreWhitespaceBefore(@NotNull PsiFile file, int offset) { PsiElement element = file.findElementAt(offset); @@ -206,6 +239,15 @@ public class CodeInsightUtils { return CharArrayUtil.shiftForward(document.getCharsSequence(), lineStartOffset, " \t"); } + @Nullable + public static Integer getEndLineOffset(@NotNull PsiFile file, int line) { + Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + if (document == null) return null; + + int lineStartOffset = document.getLineEndOffset(line); + return CharArrayUtil.shiftBackward(document.getCharsSequence(), lineStartOffset, " \t"); + } + @Nullable public static PsiElement getTopmostElementAtOffset(@NotNull PsiElement element, int offset) { do { diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java b/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java index 1b084d6c64f..4ede2765c68 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java +++ b/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java @@ -56,6 +56,7 @@ import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.types.lang.InlineStrategy; import org.jetbrains.jet.lang.types.lang.InlineUtil; import org.jetbrains.jet.plugin.caches.resolve.ResolvePackage; +import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils; import org.jetbrains.jet.plugin.project.ResolveSessionForBodies; import org.jetbrains.jet.plugin.util.DebuggerUtils; import org.jetbrains.org.objectweb.asm.Type; @@ -93,12 +94,48 @@ public class JetPositionManager implements PositionManager { } if (lineNumber >= 0) { + JetFunctionLiteral lambdaIfInside = getLambdaIfInside(location, (JetFile) psiFile, lineNumber); + if (lambdaIfInside != null) { + return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression().getStatements().get(0)); + } return SourcePosition.createFromLine(psiFile, lineNumber); } throw new NoDataException(); } + private JetFunctionLiteral getLambdaIfInside(@NotNull Location location, @NotNull JetFile file, int lineNumber) { + String currentLocationFqName = location.declaringType().name(); + if (currentLocationFqName == null) return null; + + Integer start = CodeInsightUtils.getStartLineOffset(file, lineNumber); + Integer end = CodeInsightUtils.getEndLineOffset(file, lineNumber); + if (start == null || end == null) return null; + + PsiElement[] literals = CodeInsightUtils.findElementsOfClassInRange(file, start, end, JetFunctionLiteral.class); + if (literals == null || literals.length == 0) return null; + + boolean isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null; + JetTypeMapper typeMapper = !isInLibrary + ? prepareTypeMapper(file) + : createTypeMapperForLibraryFile(file.findElementAt(start), file); + + String currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(new FqName(currentLocationFqName)).getInternalName(); + for (PsiElement literal : literals) { + JetFunctionLiteral functionLiteral = (JetFunctionLiteral) literal; + if (isInlinedLambda(functionLiteral, typeMapper.getBindingContext())) { + continue; + } + + String internalClassName = getClassNameForElement(literal.getFirstChild(), typeMapper, file, isInLibrary); + if (internalClassName.equals(currentLocationClassName)) { + return functionLiteral; + } + } + + return null; + } + @Nullable private PsiFile getPsiFileByLocation(@NotNull Location location) { String sourceName; @@ -142,7 +179,7 @@ public class JetPositionManager implements PositionManager { public void run() { JetFile file = (JetFile) sourcePosition.getFile(); boolean isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null; - JetTypeMapper typeMapper = !isInLibrary ? prepareTypeMapper(file) : createTypeMapperForLibraryFile(sourcePosition); + JetTypeMapper typeMapper = !isInLibrary ? prepareTypeMapper(file) : createTypeMapperForLibraryFile(sourcePosition.getElementAt(), file); result.set(getClassNameForElement(sourcePosition.getElementAt(), typeMapper, file, isInLibrary)); } @@ -217,11 +254,10 @@ public class JetPositionManager implements PositionManager { return typeMapper.mapClass(classDescriptor).getInternalName(); } - private static JetTypeMapper createTypeMapperForLibraryFile(@NotNull SourcePosition position) { - JetElement element = getElementToCreateTypeMapperForLibraryFile(position.getElementAt()); + private static JetTypeMapper createTypeMapperForLibraryFile(@Nullable PsiElement notPositionedElement, @NotNull JetFile file) { + JetElement element = getElementToCreateTypeMapperForLibraryFile(notPositionedElement); ResolveSessionForBodies resolveSession = ResolvePackage.getLazyResolveSession(element); - JetFile file = (JetFile) position.getFile(); GenerationState state = new GenerationState(file.getProject(), ClassBuilderFactories.THROW_EXCEPTION, resolveSession.getModuleDescriptor(), resolveSession.resolveToElement(element), 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 1a13dabb3c9..a2dbff11f3e 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -77,16 +77,11 @@ fun getFunctionForExtractedFragment( val originalFile = breakpointFile as JetFile - val lineStart = CodeInsightUtils.getStartLineOffset(originalFile, breakpointLine) - if (lineStart == null) return null - val tmpFile = originalFile.createTempCopy { it } tmpFile.skipVisibilityCheck = true - val elementAtOffset = tmpFile.findElementAt(lineStart) - if (elementAtOffset == null) return null - - val contextElement: PsiElement = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, lineStart) ?: elementAtOffset + val contextElement = getExpressionToAddDebugExpressionBefore(tmpFile, codeFragment.getContext(), breakpointLine) + if (contextElement == null) return null // Don't evaluate smth when breakpoint is on package directive (ex. for package classes) if (contextElement is JetFile) { @@ -137,6 +132,52 @@ private fun addImportsToFile(newImportList: JetImportList?, tmpFile: JetFile) { } } +private fun JetFile.getElementInCopy(e: PsiElement): PsiElement? { + val offset = e.getTextRange()?.getStartOffset() + if (offset == null) { + return null + } + var elementAt = this.findElementAt(offset) + while (elementAt == null || elementAt!!.getTextRange()?.getEndOffset() != e.getTextRange()?.getEndOffset()) { + elementAt = elementAt?.getParent() + } + return elementAt +} + +private fun getExpressionToAddDebugExpressionBefore(tmpFile: JetFile, contextElement: PsiElement?, line: Int): PsiElement? { + if (contextElement == null) { + val lineStart = CodeInsightUtils.getStartLineOffset(tmpFile, line) + if (lineStart == null) return null + + val elementAtOffset = tmpFile.findElementAt(lineStart) + if (elementAtOffset == null) return null + + return CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, lineStart) ?: elementAtOffset + } + + fun shouldStop(el: PsiElement?, p: PsiElement?) = p is JetBlockExpression || el is JetDeclaration + + var elementAt = tmpFile.getElementInCopy(contextElement) + + var parent = elementAt?.getParent() + if (shouldStop(elementAt, parent)) { + return elementAt + } + + var parentOfParent = parent?.getParent() + + while (parent != null && parentOfParent != null) { + if (shouldStop(parent, parentOfParent)) { + break + } + + parent = parent?.getParent() + parentOfParent = parent?.getParent() + } + + return parent +} + private fun addDebugExpressionBeforeContextElement(codeFragment: JetCodeFragment, contextElement: PsiElement): JetExpression? { val psiFactory = JetPsiFactory(codeFragment) @@ -144,6 +185,10 @@ private fun addDebugExpressionBeforeContextElement(codeFragment: JetCodeFragment contextElement is JetProperty && !contextElement.isLocal() -> { wrapInRunFun(contextElement.getDelegateExpressionOrInitializer()!!) } + contextElement is JetFunctionLiteral -> { + val block = contextElement.getBodyExpression()!! + block.getStatements().first ?: block.getLastChild() + } contextElement is JetDeclarationWithBody && !contextElement.hasBlockBody()-> { wrapInRunFun(contextElement.getBodyExpression()!!) } diff --git a/idea/testData/debugger/tinyApp/outs/allFilesPresentInFindElementAtBreakpoint.out b/idea/testData/debugger/tinyApp/outs/allFilesPresentInFindElementAtBreakpoint.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/debugger/tinyApp/outs/allFilesPresentInLambdas.out b/idea/testData/debugger/tinyApp/outs/allFilesPresentInLambdas.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/debugger/tinyApp/outs/callableBug.out b/idea/testData/debugger/tinyApp/outs/callableBug.out new file mode 100644 index 00000000000..701e4af3d77 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/callableBug.out @@ -0,0 +1,9 @@ +LineBreakpoint created at callableBug.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!;!CUSTOM_LIBRARY!;!RT_JAR! callableBug.CallableBugPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +callableBug.kt:7 +Compile bytecode for callable +callableBug.kt:7 +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/inlineLambda.out b/idea/testData/debugger/tinyApp/outs/inlineLambda.out new file mode 100644 index 00000000000..a37bf635845 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/inlineLambda.out @@ -0,0 +1,8 @@ +LineBreakpoint created at inlineLambda.kt:9 +!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!;!CUSTOM_LIBRARY!;!RT_JAR! inlineLambda.InlineLambdaPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +inlineLambda.kt:8 +inlineLambda.kt:8 +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/lambdaOnSecondLine.out b/idea/testData/debugger/tinyApp/outs/lambdaOnSecondLine.out new file mode 100644 index 00000000000..87d4a4b2682 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/lambdaOnSecondLine.out @@ -0,0 +1,10 @@ +LineBreakpoint created at lambdaOnSecondLine.kt:10 +!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!;!CUSTOM_LIBRARY!;!RT_JAR! lambdaOnSecondLine.LambdaOnSecondLinePackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +lambdaOnSecondLine.kt:9 +lambdaOnSecondLine.kt:14 +lambdaOnSecondLine.kt:9 +Compile bytecode for it +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/oneLineLambda.out b/idea/testData/debugger/tinyApp/outs/oneLineLambda.out new file mode 100644 index 00000000000..77e27111907 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/oneLineLambda.out @@ -0,0 +1,10 @@ +LineBreakpoint created at oneLineLambda.kt:9 +!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!;!CUSTOM_LIBRARY!;!RT_JAR! oneLineLambda.OneLineLambdaPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +oneLineLambda.kt:8 +oneLineLambda.kt:13 +oneLineLambda.kt:8 +Compile bytecode for it +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/twoLambdasOnOneLineFirst.out b/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineFirst.out new file mode 100644 index 00000000000..67ab5bea45e --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineFirst.out @@ -0,0 +1,10 @@ +LineBreakpoint created at twoLambdasOnOneLineFirst.kt:9 +!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!;!CUSTOM_LIBRARY!;!RT_JAR! twoLambdasOnOneLineFirst.TwoLambdasOnOneLineFirstPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +twoLambdasOnOneLineFirst.kt:8 +twoLambdasOnOneLineFirst.kt:13 +twoLambdasOnOneLineFirst.kt:8 +Compile bytecode for it +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/twoLambdasOnOneLineSecond.out b/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineSecond.out new file mode 100644 index 00000000000..995732d847d --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineSecond.out @@ -0,0 +1,12 @@ +LineBreakpoint created at twoLambdasOnOneLineSecond.kt:9 +!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!;!CUSTOM_LIBRARY!;!RT_JAR! twoLambdasOnOneLineSecond.TwoLambdasOnOneLineSecondPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +twoLambdasOnOneLineSecond.kt:8 +twoLambdasOnOneLineSecond.kt:15 +twoLambdasOnOneLineSecond.kt:8 +twoLambdasOnOneLineSecond.kt:15 +twoLambdasOnOneLineSecond.kt:8 +Compile bytecode for it +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/singleBreakpoint/callableBug.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/callableBug.kt new file mode 100644 index 00000000000..2aa433f47d0 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/callableBug.kt @@ -0,0 +1,12 @@ +package callableBug + +fun main(args: Array) { + val callable = 1 + array(1, 2).map { + it + 1 + //Breakpoint! + }.forEach { it + 2 } +} + +// EXPRESSION: callable +// RESULT: 1: I \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt new file mode 100644 index 00000000000..b937ea8b53f --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt @@ -0,0 +1,11 @@ +package inlineLambda + +fun main(args: Array) { + val a = array(1) + // EXPRESSION: it + // RESULT: Unresolved reference: it + // STEP_INTO: 1 + //Breakpoint! + a.map { it * 1 } +} + diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/lambdaOnSecondLine.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/lambdaOnSecondLine.kt new file mode 100644 index 00000000000..244a98ee6aa --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/lambdaOnSecondLine.kt @@ -0,0 +1,18 @@ +package lambdaOnSecondLine + +fun main(args: Array) { + val a = A() + // EXPRESSION: it + // RESULT: 1: I + // STEP_INTO: 2 + a.foo { a } + //Breakpoint! + .foo { a } +} + +class A { + fun foo(f: (Int) -> A): A { + return f(1) + } +} + diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt new file mode 100644 index 00000000000..1f8eecd4952 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt @@ -0,0 +1,16 @@ +package oneLineLambda + +fun main(args: Array) { + val a = A() + // EXPRESSION: it + // RESULT: 1: I + // STEP_INTO: 2 + //Breakpoint! + a.foo { a } +} + +class A { + fun foo(f: (Int) -> A): A { + return f(1) + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt new file mode 100644 index 00000000000..1fa7e93a728 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt @@ -0,0 +1,16 @@ +package twoLambdasOnOneLineFirst + +fun main(args: Array) { + val a = A() + // EXPRESSION: it + // RESULT: 1: I + // STEP_INTO: 2 + //Breakpoint! + a.foo { a }.foo { a } +} + +class A { + fun foo(f: (Int) -> A): A { + return f(1) + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineSecond.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineSecond.kt new file mode 100644 index 00000000000..a33dfccf88c --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineSecond.kt @@ -0,0 +1,18 @@ +package twoLambdasOnOneLineSecond + +fun main(args: Array) { + val a = A() + // EXPRESSION: it + // RESULT: 2: I + // STEP_INTO: 4 + //Breakpoint! + a.foo { counter++; a }.foo { a } +} + +var counter = 1 + +class A { + fun foo(f: (Int) -> A): A { + return f(counter) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt index 6d8aaf6fba6..a2b3b696b67 100644 --- a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt +++ b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt @@ -59,7 +59,7 @@ import com.intellij.debugger.DebuggerManagerEx import com.intellij.psi.PsiDocumentManager import com.intellij.openapi.application.ModalityState -public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestCase() { +public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { private val logger = Logger.getLogger(javaClass())!! private val appender = object : AppenderSkeleton() { @@ -73,7 +73,7 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC private var oldLogLevel: Level? = null override fun setUp() { - super.setUp() + super.setUp() oldLogLevel = logger.getLevel() logger.setLevel(Level.DEBUG) @@ -84,7 +84,7 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC logger.setLevel(oldLogLevel) logger.removeAppender(appender) - super.tearDown() + super.tearDown() } fun doSingleBreakpointTest(path: String) { @@ -102,6 +102,13 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC createDebugProcess(path) + val count = InTextDirectivesUtils.getPrefixedInt(fileText, "// STEP_INTO: ") ?: 0 + if (count > 0) { + for (i in 1..count) { + onBreakpoint { stepInto() } + } + } + onBreakpoint { val exceptions = linkedMapOf() try { @@ -275,19 +282,6 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC return mainFile.getParentFile()?.listFiles()?.filter { it.name.startsWith(mainFileName) && it.name != mainFileName } ?: Collections.emptyList() } - private fun onBreakpoint(doOnBreakpoint: SuspendContextImpl.() -> Unit) { - super.onBreakpoint { - super.printContext(it) - it.doOnBreakpoint() - } - } - - private fun finish() { - onBreakpoint { - resume(this) - } - } - private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String) { ApplicationManager.getApplication()?.runReadAction { val sourcePosition = ContextUtil.getSourcePosition(this) 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 2abc9322733..274423a82aa 100644 --- a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -35,7 +35,7 @@ import java.util.regex.Pattern; public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluateExpressionTest { @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({SingleBreakpoint.Frame.class}) + @InnerTestClasses({SingleBreakpoint.Frame.class, SingleBreakpoint.Lambdas.class}) @RunWith(org.jetbrains.jet.JUnit3RunnerWithInners.class) public static class SingleBreakpoint extends AbstractKotlinEvaluateExpressionTest { @TestMetadata("abstractFunCall.kt") @@ -54,6 +54,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doSingleBreakpointTest(fileName); } + @TestMetadata("callableBug.kt") + public void testCallableBug() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/callableBug.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("classFromAnotherPackage.kt") public void testClassFromAnotherPackage() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/classFromAnotherPackage.kt"); @@ -262,6 +268,46 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat } + @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas") + @TestDataPath("$PROJECT_ROOT") + @RunWith(org.jetbrains.jet.JUnit3RunnerWithInners.class) + public static class Lambdas extends AbstractKotlinEvaluateExpressionTest { + public void testAllFilesPresentInLambdas() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("inlineLambda.kt") + public void testInlineLambda() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineLambda.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("lambdaOnSecondLine.kt") + public void testLambdaOnSecondLine() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/lambdaOnSecondLine.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("oneLineLambda.kt") + public void testOneLineLambda() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/oneLineLambda.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("twoLambdasOnOneLineFirst.kt") + public void testTwoLambdasOnOneLineFirst() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("twoLambdasOnOneLineSecond.kt") + public void testTwoLambdasOnOneLineSecond() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineSecond.kt"); + doSingleBreakpointTest(fileName); + } + + } + } @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints")