[debugger] breakpoint on return inside one line if-return statement in Kotlin

^IDEA-315974

GitOrigin-RevId: 03c949e90b39f5cf9fd1c9b149b20ef7c4f56873
This commit is contained in:
Vladimir Parfinenko
2023-06-16 09:43:44 +00:00
committed by intellij-monorepo-bot
parent b02075f3b7
commit c9c9d58c9f
18 changed files with 241 additions and 76 deletions
@@ -21,7 +21,6 @@ import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.PsiLambdaExpression;
/**
@@ -31,10 +30,10 @@ public class JavaSourcePositionHighlighter extends SourcePositionHighlighter imp
@Override
public TextRange getHighlightRange(SourcePosition sourcePosition) {
// Highlight only return keyword in case of conditional return breakpoint.
if (sourcePosition.getElementAt() instanceof PsiKeyword retKeyword &&
retKeyword.getText().equals(PsiKeyword.RETURN) &&
retKeyword == JavaLineBreakpointType.findSingleConditionalReturn(sourcePosition)) {
return retKeyword.getTextRange();
PsiElement element = sourcePosition.getElementAt();
if (JavaLineBreakpointType.isReturnKeyword(element) &&
element == JavaLineBreakpointType.findSingleConditionalReturn(sourcePosition)) {
return element.getTextRange();
}
// Highlight only lambda body in case of lambda breakpoint.
@@ -181,24 +181,32 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
}
}
// Adjust position if we've stopped on conditional return.
if (myDebugProcess.getVirtualMachineProxy().canGetBytecodes()) {
PsiElement ret = JavaLineBreakpointType.findSingleConditionalReturn(sourcePosition);
SourcePosition condRetPos = adjustPositionForConditionalReturn(myDebugProcess, location, psiFile, lineNumber);
if (condRetPos != null) {
sourcePosition = condRetPos;
}
return new JavaSourcePosition(sourcePosition, location.declaringType(), method, lambdaOrdinal);
}
@Nullable
public static SourcePosition adjustPositionForConditionalReturn(DebugProcess debugProcess, Location location, PsiFile file, int lineNumber) {
if (debugProcess.getVirtualMachineProxy().canGetBytecodes()) {
PsiElement ret = JavaLineBreakpointType.findSingleConditionalReturn(file, lineNumber);
if (ret != null) {
byte[] bytecodes = method.bytecodes();
byte[] bytecodes = DebuggerUtilsEx.getMethod(location).bytecodes();
int bytecodeOffs = Math.toIntExact(location.codeIndex());
// Implicit return instruction at the end of bytecode should not be treated as conditional return.
// (Note that we also relay on the fact that all return instructions have no operands.)
if (0 <= bytecodeOffs && bytecodeOffs < bytecodes.length - 1) {
int opcode = bytecodes[bytecodeOffs] & 0xFF;
if (Opcodes.IRETURN <= opcode && opcode <= Opcodes.RETURN) {
sourcePosition = SourcePosition.createFromOffset(sourcePosition.getFile(), ret.getTextOffset());
return SourcePosition.createFromOffset(file, ret.getTextOffset());
}
}
}
}
return new JavaSourcePosition(sourcePosition, location.declaringType(), method, lambdaOrdinal);
return null;
}
public static class JavaSourcePosition extends RemappedSourcePosition {
@@ -17,6 +17,7 @@ import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.LeafElement;
import com.intellij.util.DocumentUtil;
import com.intellij.util.Processor;
import com.intellij.util.SmartList;
@@ -46,8 +47,6 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import static org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties.COND_RET_CODE;
/**
* Base class for java line-connected breakpoints (line, method, field)
*
@@ -115,7 +114,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
PsiElement startMethod = DebuggerUtilsEx.getContainingMethod(pos);
List<PsiLambdaExpression> lambdas = DebuggerUtilsEx.collectLambdas(pos, true);
PsiKeyword condRet = findSingleConditionalReturn(project, document, position.getLine());
PsiElement condRet = findSingleConditionalReturn(project, document, position.getLine());
if ((lambdas.isEmpty() || (lambdas.contains(startMethod) && lambdas.size() == 1)) && condRet == null) {
return Collections.emptyList();
@@ -155,9 +154,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
if (condRet != null) {
PsiElement method = DebuggerUtilsEx.getContainingMethod(condRet);
int ordinal = method instanceof PsiLambdaExpression
? Math.toIntExact(lambdas.stream().takeWhile(l -> l != method).count())
: -1;
int ordinal = lambdas.indexOf(method);
res.add(new ConditionalReturnJavaBreakpointVariant(position, condRet, ordinal)); //conditional return
}
@@ -166,47 +163,65 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
return res;
}
public static @Nullable PsiKeyword findSingleConditionalReturn(@Nullable SourcePosition pos) {
/**
* @param pos specifies the line in the file where {@code return} is looked for
*/
public static @Nullable PsiElement findSingleConditionalReturn(@Nullable SourcePosition pos) {
if (pos == null) return null;
Project project = pos.getFile().getProject();
Document document = PsiDocumentManager.getInstance(project).getDocument(pos.getFile());
if (document == null) return null;
return findSingleConditionalReturn(project, document, pos.getLine());
return findSingleConditionalReturn(pos.getFile(), pos.getLine());
}
public static @Nullable PsiKeyword findSingleConditionalReturn(@NotNull Project project, @NotNull Document document, int line) {
public static @Nullable PsiElement findSingleConditionalReturn(@NotNull PsiFile file, int line) {
Project project = file.getProject();
Document document = PsiDocumentManager.getInstance(project).getDocument(file);
if (document == null) return null;
return findSingleConditionalReturn(project, document, line);
}
protected static @Nullable PsiElement findSingleConditionalReturn(@NotNull Project project, @NotNull Document document, int line) {
if (!DocumentUtil.isValidLine(line, document)) return null;
TextRange curLineRange = DocumentUtil.getLineTextRange(document, line);
class CondRetFinder implements Processor<PsiElement> {
@Nullable PsiKeyword conditionalReturn = null;
class RetFinder implements Processor<PsiElement> {
// Our ultimate goal is to find the single return statement which is executed conditionally to break on it.
// However, in most cases it's enough to just check that return is not the first on the line
// (code like `workHard(); return result();` is quite rare,
// also note that putting breakpoint on such return would not lead to catastrophic circumstances).
// We ignore multiple returns because it's not easy to map them in source code to multiple return instructions in bytecode.
boolean somethingBeforeReturn = false;
@Nullable PsiElement singleReturn = null;
@Override
public boolean process(PsiElement element) {
if (element instanceof PsiKeyword retKeyword && element.getText().equals(PsiKeyword.RETURN)) {
if (conditionalReturn != null) {
// it's not easy to map multiple returns in source code to multiple returns in bytecode
conditionalReturn = null;
if (isReturnKeyword(element)) {
if (singleReturn != null) {
singleReturn = null;
return false;
}
PsiElement cur = element;
while (cur != null && cur.getTextOffset() >= curLineRange.getStartOffset()) {
PsiElement parent = cur.getParent();
if (parent instanceof PsiIfStatement ifStmt &&
(cur == ifStmt.getThenBranch() || cur == ifStmt.getElseBranch())) {
conditionalReturn = retKeyword;
}
cur = parent;
if (!somethingBeforeReturn) {
assert singleReturn == null;
return false;
}
singleReturn = element;
}
if (!(element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
somethingBeforeReturn = true;
}
return true;
}
}
CondRetFinder finder = new CondRetFinder();
RetFinder finder = new RetFinder();
new XDebuggerUtilImpl().iterateLine(project, document, line, finder);
return finder.conditionalReturn;
return finder.singleReturn;
}
public static boolean isReturnKeyword(@NotNull PsiElement element) {
// Don't check for PsiKeyword to cover many languages at once.
return element instanceof LeafElement && element.getText().equals("return");
}
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
@@ -238,7 +253,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
return DebuggerUtilsEx.getContainingMethod(position);
}
public static Stream<Location> collectInlineConditionalReturnLocations(@NotNull Method method, int lineNumber) {
protected static Stream<Location> collectInlineConditionalReturnLocations(@NotNull Method method, int lineNumber) {
assert lineNumber > 0;
class Visitor extends MethodVisitor implements MethodBytecodeUtil.InstructionOffsetReader {
final SmartList<Integer> returnOffsets = new SmartList<>();
@@ -288,9 +303,15 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
this.lambdaCount = lambdaCount;
}
public JavaBreakpointVariant(@NotNull XSourcePosition position) {
this(position, -1);
}
@Override
public @NotNull String getText() {
return JavaDebuggerBundle.message("breakpoint.variant.text.line.and.lambda", lambdaCount);
return lambdaCount >= 0
? JavaDebuggerBundle.message("breakpoint.variant.text.line.and.lambda", lambdaCount)
: JavaDebuggerBundle.message("breakpoint.variant.text.line.and.lambda.uknown.count");
}
}
@@ -299,7 +320,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
private final Integer myEncodedInlinePosition;
public ExactJavaBreakpointVariant(@NotNull XSourcePosition position, @Nullable PsiElement element, Integer encodedInlinePosition) {
super(position, -1 /* unused */);
super(position);
myElement = element;
myEncodedInlinePosition = encodedInlinePosition;
}
@@ -337,7 +358,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
public class LineJavaBreakpointVariant extends ExactJavaBreakpointVariant {
public LineJavaBreakpointVariant(@NotNull XSourcePosition position, @Nullable PsiElement element, int lambdaOrdinal) {
super(position, element, lambdaOrdinal);
super(position, element, JavaLineBreakpointProperties.encodeInlinePosition(lambdaOrdinal, false));
}
@NotNull
@@ -354,7 +375,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
public class LambdaJavaBreakpointVariant extends ExactJavaBreakpointVariant {
public LambdaJavaBreakpointVariant(@NotNull XSourcePosition position, @NotNull PsiElement element, int lambdaOrdinal) {
super(position, element, lambdaOrdinal);
super(position, element, JavaLineBreakpointProperties.encodeInlinePosition(lambdaOrdinal, false));
}
@Override
@@ -364,8 +385,8 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
}
public class ConditionalReturnJavaBreakpointVariant extends ExactJavaBreakpointVariant {
public ConditionalReturnJavaBreakpointVariant(@NotNull XSourcePosition position, PsiKeyword element, int lambdaOrdinal) {
super(position, element, COND_RET_CODE - lambdaOrdinal - 1);
public ConditionalReturnJavaBreakpointVariant(@NotNull XSourcePosition position, PsiElement element, int lambdaOrdinal) {
super(position, element, JavaLineBreakpointProperties.encodeInlinePosition(lambdaOrdinal, true));
}
@Override
@@ -408,9 +429,9 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
return ReadAction.compute(() -> {
SourcePosition linePosition = createLineSourcePosition((XLineBreakpointImpl)breakpoint);
if (linePosition != null) {
if (condRet) {
// Ignore lambda ordinal, return element should be unique on the line.
return XSourcePositionImpl.createByElement(findSingleConditionalReturn(linePosition));
PsiElement theReturn = condRet ? findSingleConditionalReturn(linePosition) : null;
if (theReturn != null) {
return XSourcePositionImpl.createByElement(theReturn);
} else {
return DebuggerUtilsEx.toXSourcePosition(new PositionManagerImpl.JavaSourcePosition(linePosition, lambdaOrdinal));
}
@@ -4,20 +4,27 @@ package org.jetbrains.java.debugger.breakpoints.properties;
import com.intellij.util.xmlb.annotations.OptionTag;
import com.intellij.util.xmlb.annotations.Transient;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaLineBreakpointProperties extends JavaBreakpointProperties<JavaLineBreakpointProperties> {
// TODO: rework encoding of inline position, introduce enum/class-based external API
// null - stop at all positions on the line
// null - stop at line and all lambdas
// -1 - stop only at the base position (first on the line)
// 0 or more - index of the lambda on the line to stop at
// -10 or less - stop only at single conditional return statement (-10 at the base method, (-10-i) at the i-th lambda)
private Integer encodedInlinePosition = null;
private @Nullable Integer encodedInlinePosition = null;
public static final int COND_RET_CODE = -10;
private static final int COND_RET_CODE = -10;
public static int encodeInlinePosition(int lambdaOrdinal, boolean conditionalReturn) {
return !conditionalReturn
? lambdaOrdinal
: COND_RET_CODE - lambdaOrdinal - 1;
}
@Transient
public Integer getLambdaOrdinal() {
public @Nullable Integer getLambdaOrdinal() {
if (encodedInlinePosition == null) {
return null;
}
@@ -30,11 +37,11 @@ public class JavaLineBreakpointProperties extends JavaBreakpointProperties<JavaL
}
@OptionTag("lambda-ordinal") // naming is a historic accident
public Integer getEncodedInlinePosition() {
public @Nullable Integer getEncodedInlinePosition() {
return encodedInlinePosition;
}
public void setEncodedInlinePosition(Integer inlinePositionEncoded) {
public void setEncodedInlinePosition(@Nullable Integer inlinePositionEncoded) {
encodedInlinePosition = inlinePositionEncoded;
}
@@ -518,6 +518,7 @@ capture.node.settings.link=\ settings
array.filter.node.clear.link=\ clear
breakpoint.variant.text.line=Line
breakpoint.variant.text.line.and.lambda=Line and {0,choice,1#Lambda|2#Lambdas}
breakpoint.variant.text.line.and.lambda.uknown.count=Line and Lambda(s)
debugger.attach.group.name.java=Java
debugger.attach.group.name.java.read.only=Java Read Only
debugger.memory.agent.timeout.error=Calculation timeout (heap is too large and memory agent can't execute this operation in reasonable time, it's preferable to capture the heap dump)
@@ -415,8 +415,8 @@ public abstract class ExecutionWithDebuggerToolsTestCase extends ExecutionTestCa
}
case "ConditionalReturn" -> {
breakpoint = breakpointManager.addLineBreakpoint(document, commentLine + 1, p -> {
// Note that we don't support `return` inside of lambda in unit tests.
p.setEncodedInlinePosition(JavaLineBreakpointProperties.COND_RET_CODE);
int lambdaOrdinal = -1; // Note that we don't support `return` inside of lambda in unit tests.
p.setEncodedInlinePosition(JavaLineBreakpointProperties.encodeInlinePosition(lambdaOrdinal, true));
});
if (breakpoint != null) {
systemPrintln("ConditionalReturnBreakpoint created at " + breakpointLocation);
@@ -9,6 +9,7 @@ import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.DebuggerUtils.isSynthetic
import com.intellij.debugger.engine.PositionManagerImpl
import com.intellij.debugger.engine.PositionManagerWithMultipleStackFrames
import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.impl.DebuggerUtilsAsync
@@ -149,6 +150,10 @@ class KotlinPositionManager(private val debugProcess: DebugProcess) : MultiReque
throw NoDataException.INSTANCE
}
PositionManagerImpl.adjustPositionForConditionalReturn(debugProcess, location, psiFile, sourceLineNumber)?.let {
return it
}
val sourcePosition = createSourcePosition(location, psiFile, sourceLineNumber)
?: SourcePosition.createFromLine(psiFile, sourceLineNumber)
@@ -177,7 +182,8 @@ class KotlinPositionManager(private val debugProcess: DebugProcess) : MultiReque
if (!location.hasVisibleInlineLambdasOnLines(lines)) {
return KotlinSourcePositionWithEntireLineHighlighted(sourcePosition)
}
return sourcePosition
return sourcePosition
}
private fun createSourcePosition(location: Location, file: KtFile, sourceLineNumber: Int): SourcePosition? {
@@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.debugger.core
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.SourcePositionHighlighter
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
import com.intellij.openapi.util.TextRange
import com.intellij.psi.util.parentOfType
import org.jetbrains.kotlin.idea.base.psi.isOneLiner
@@ -13,15 +14,26 @@ import org.jetbrains.kotlin.psi.KtFunctionLiteral
class KotlinSourcePositionHighlighter : SourcePositionHighlighter() {
override fun getHighlightRange(sourcePosition: SourcePosition?): TextRange? {
if (sourcePosition is KotlinSourcePositionWithEntireLineHighlighted ||
if (sourcePosition == null ||
sourcePosition is KotlinSourcePositionWithEntireLineHighlighted ||
sourcePosition is KotlinReentrantSourcePosition) {
return null
}
val lambda = sourcePosition?.elementAt?.parentOfType<KtFunctionLiteral>()
val element = sourcePosition.elementAt ?: return null
// Highlight only return keyword in case of conditional return breakpoint.
if (JavaLineBreakpointType.isReturnKeyword(element) &&
element === JavaLineBreakpointType.findSingleConditionalReturn(sourcePosition)) {
return element.textRange
}
// Highlight only lambda body in case of lambda breakpoint.
val lambda = element.parentOfType<KtFunctionLiteral>()
if (lambda != null && lambda.isOneLiner()) {
return lambda.textRange
}
return null
}
}
@@ -107,7 +107,9 @@ class KotlinLineBreakpointType :
val pos = SourcePosition.createFromLine(file, position.line)
val lambdas = getLambdasAtLineIfAny(pos)
if (lambdas.isEmpty()) return emptyList()
val condRet = findSingleConditionalReturn(file, position.line)
if (lambdas.isEmpty() && condRet == null) return emptyList()
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
@@ -135,20 +137,29 @@ class KotlinLineBreakpointType :
result.add(KotlinBreakpointVariant(position, lambdas.size))
}
if (condRet != null) {
val method = condRet.getContainingMethod()
val ordinal = lambdas.indexOf(method)
result.add(ConditionalReturnJavaBreakpointVariant(position, condRet, ordinal))
}
return result
}
override fun getHighlightRange(breakpoint: XLineBreakpoint<JavaLineBreakpointProperties>): TextRange? {
val properties = breakpoint.properties ?: return null
val ordinal = properties.lambdaOrdinal ?: return null
val javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint) as? LineBreakpoint<*> ?: return null
val position = javaBreakpoint.sourcePosition ?: return null
val position = (BreakpointManager.getJavaBreakpoint(breakpoint) as? LineBreakpoint<*>)?.sourcePosition ?: return null
if (properties.isConditionalReturn) {
return findSingleConditionalReturn(position)?.textRange
}
val lambdaOrdinal = properties.lambdaOrdinal ?: return null
// Since lambda breakpoints are placed on the first lambda statement,
// we should find the function parent to highlight lambda breakpoints properly
val function = position.elementAt.parentOfType<KtFunction>() ?: return null
val updatedPosition = SourcePosition.createFromElement(function) ?: return null
return getLambdaByOrdinal(updatedPosition, ordinal)?.textRange
return getLambdaByOrdinal(updatedPosition, lambdaOrdinal)?.textRange
}
override fun getSourcePosition(breakpoint: XBreakpoint<JavaLineBreakpointProperties>): XSourcePosition? =
@@ -157,13 +168,22 @@ class KotlinLineBreakpointType :
private fun calculateSourcePosition(breakpoint: XBreakpoint<JavaLineBreakpointProperties>): XSourcePosition? {
val javaBreakpointProperties = breakpoint.properties ?: return null
val sourcePosition = createLineSourcePosition(breakpoint as XLineBreakpointImpl<*>) ?: return null
val ordinal = javaBreakpointProperties.lambdaOrdinal
val function = getLambdaByOrdinal(sourcePosition, ordinal) ?: return null
if (javaBreakpointProperties.isConditionalReturn) {
runReadAction {
findSingleConditionalReturn(sourcePosition)?.let {
XSourcePositionImpl.createByElement(it)
}
}?.let { return it }
}
val lambdaOrdinal = javaBreakpointProperties.lambdaOrdinal ?: return null
val function = getLambdaByOrdinal(sourcePosition, lambdaOrdinal) ?: return null
val firstStatement = function.bodyBlockExpression?.statements?.firstOrNull() ?: return null
return runReadAction {
val linePosition = SourcePosition.createFromElement(firstStatement) ?: return@runReadAction null
DebuggerUtilsEx.toXSourcePosition(
PositionManagerImpl.JavaSourcePosition(linePosition, ordinal)
PositionManagerImpl.JavaSourcePosition(linePosition, lambdaOrdinal)
)
}
}
@@ -23,6 +23,16 @@ public class BreakpointHighlightingTestGenerated extends AbstractBreakpointHighl
KotlinTestUtils.runTest(this::doCustomTest, this, testDataFilePath);
}
@TestMetadata("conditionalReturn.kt")
public void testConditionalReturn() throws Exception {
runTest("testData/highlighting/conditionalReturn.kt");
}
@TestMetadata("conditionalReturnInLambda.kt")
public void testConditionalReturnInLambda() throws Exception {
runTest("testData/highlighting/conditionalReturnInLambda.kt");
}
@TestMetadata("lambdasOnSameLine.kt")
public void testLambdasOnSameLine() throws Exception {
runTest("testData/highlighting/lambdasOnSameLine.kt");
@@ -23,6 +23,16 @@ public class IndyLambdaBreakpointHighlightingTestGenerated extends AbstractIndyL
KotlinTestUtils.runTest(this::doCustomTest, this, testDataFilePath);
}
@TestMetadata("conditionalReturn.kt")
public void testConditionalReturn() throws Exception {
runTest("testData/highlighting/conditionalReturn.kt");
}
@TestMetadata("conditionalReturnInLambda.kt")
public void testConditionalReturnInLambda() throws Exception {
runTest("testData/highlighting/conditionalReturnInLambda.kt");
}
@TestMetadata("lambdasOnSameLine.kt")
public void testLambdasOnSameLine() throws Exception {
runTest("testData/highlighting/lambdasOnSameLine.kt");
@@ -23,6 +23,16 @@ public class IrBreakpointHighlightingTestGenerated extends AbstractIrBreakpointH
KotlinTestUtils.runTest(this::doCustomTest, this, testDataFilePath);
}
@TestMetadata("conditionalReturn.kt")
public void testConditionalReturn() throws Exception {
runTest("testData/highlighting/conditionalReturn.kt");
}
@TestMetadata("conditionalReturnInLambda.kt")
public void testConditionalReturnInLambda() throws Exception {
runTest("testData/highlighting/conditionalReturnInLambda.kt");
}
@TestMetadata("lambdasOnSameLine.kt")
public void testLambdasOnSameLine() throws Exception {
runTest("testData/highlighting/lambdasOnSameLine.kt");
@@ -23,6 +23,16 @@ public class K1IdeK2CodeBreakpointHighlightingTestGenerated extends AbstractK1Id
KotlinTestUtils.runTest(this::doCustomTest, this, testDataFilePath);
}
@TestMetadata("conditionalReturn.kt")
public void testConditionalReturn() throws Exception {
runTest("testData/highlighting/conditionalReturn.kt");
}
@TestMetadata("conditionalReturnInLambda.kt")
public void testConditionalReturnInLambda() throws Exception {
runTest("testData/highlighting/conditionalReturnInLambda.kt");
}
@TestMetadata("lambdasOnSameLine.kt")
public void testLambdasOnSameLine() throws Exception {
runTest("testData/highlighting/lambdasOnSameLine.kt");
@@ -85,9 +85,10 @@ internal class BreakpointCreator(
}
}
comment.startsWith("//Breakpoint!") -> {
val ordinal = getPropertyFromComment(comment, "lambdaOrdinal")?.toInt()
val lambdaOrdinal = getPropertyFromComment(comment, "lambdaOrdinal")?.toInt()
val conditionalReturn = getPropertyFromComment(comment, "conditionalReturn").toBoolean()
val condition = getPropertyFromComment(comment, "condition")
createLineBreakpoint(breakpointManager, file, lineIndex, ordinal, condition)
createLineBreakpoint(breakpointManager, file, lineIndex, lambdaOrdinal, conditionalReturn, condition)
}
comment.startsWith("//FunctionBreakpoint!") -> {
createFunctionBreakpoint(breakpointManager, file, lineIndex, false)
@@ -117,7 +118,7 @@ internal class BreakpointCreator(
when (kind) {
"line" -> createBreakpoint(fileName, lineMarker) { psiFile, lineNumber ->
createLineBreakpoint(breakpointManager, psiFile, lineNumber + 1, ordinal, null)
createLineBreakpoint(breakpointManager, psiFile, lineNumber + 1, ordinal, false, null)
}
"fun" -> createBreakpoint(fileName, lineMarker) { psiFile, lineNumber ->
createFunctionBreakpoint(breakpointManager, psiFile, lineNumber, true)
@@ -182,6 +183,7 @@ internal class BreakpointCreator(
file: PsiFile,
lineIndex: Int,
lambdaOrdinal: Int?,
conditionalReturn: Boolean,
condition: String?
) {
val kotlinLineBreakpointType = findBreakpointType(KotlinLineBreakpointType::class.java)
@@ -192,7 +194,8 @@ internal class BreakpointCreator(
kotlinLineBreakpointType,
lineIndex,
file.virtualFile,
updatedLambdaOrdinal
updatedLambdaOrdinal,
conditionalReturn,
)
if (javaBreakpoint is LineBreakpoint<*>) {
@@ -217,12 +220,15 @@ internal class BreakpointCreator(
lineIndex: Int,
virtualFile: VirtualFile,
lambdaOrdinal: Int? = null,
conditionalReturn: Boolean = false,
): Breakpoint<out JavaBreakpointProperties<*>>? {
if (!breakpointType.canPutAt(virtualFile, lineIndex, project)) return null
val xBreakpoint = runWriteAction {
val properties = breakpointType.createBreakpointProperties(virtualFile, lineIndex)
if (properties is JavaLineBreakpointProperties) {
properties.encodedInlinePosition = lambdaOrdinal
properties.encodedInlinePosition =
if (lambdaOrdinal == null && !conditionalReturn) null
else JavaLineBreakpointProperties.encodeInlinePosition(lambdaOrdinal ?: -1, conditionalReturn)
}
breakpointManager.addLineBreakpoint(
@@ -0,0 +1,14 @@
package conditionalReturn
// RESUME: 20
fun main() {
headAndTail(setOf(1, 2))
headAndTail(setOf())
}
fun headAndTail(s: Set<Int>): Pair<Int, Int>? {
//Breakpoint!, conditionalReturn = true
val head = s.firstOrNull() ?: return null
val tail = s.last()
return Pair(head, tail)
}
@@ -0,0 +1,8 @@
LineBreakpoint created at conditionalReturn.kt:11
Run Java
Connected to the target VM
conditionalReturn.kt:11
Highlight lambda range: return
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,15 @@
package conditionalReturnInLambda
// RESUME: 20
fun main() {
for (x in listOf(37, 42)) {
//Breakpoint!, conditionalReturn = true, lambdaOrdinal = 2
oneTwoOne(x, { println("1") }, { if (it == 37) return@oneTwoOne; println("2") })
}
}
fun oneTwoOne(v: Int, one: (Int) -> Unit, two: (Int) -> Unit) {
one(v)
two(v)
one(v)
}
@@ -0,0 +1,8 @@
LineBreakpoint created at conditionalReturnInLambda.kt:7 lambdaOrdinal = 2
Run Java
Connected to the target VM
conditionalReturnInLambda.kt:7
Highlight lambda range: return
Disconnected from the target VM
Process finished with exit code 0