mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger] breakpoint on return inside one line if-return statement in Kotlin
^IDEA-315974 GitOrigin-RevId: 03c949e90b39f5cf9fd1c9b149b20ef7c4f56873
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b02075f3b7
commit
c9c9d58c9f
+4
-5
@@ -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 {
|
||||
|
||||
+61
-40
@@ -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));
|
||||
}
|
||||
|
||||
+13
-6
@@ -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)
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+7
-1
@@ -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? {
|
||||
|
||||
+14
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+28
-8
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
+11
-5
@@ -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
|
||||
Reference in New Issue
Block a user