From 60fbcb7e3870195750290eb1e3653df5dd065fc1 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Mon, 26 Sep 2016 15:11:48 +0300 Subject: [PATCH] Refactoring: extract top-level functions from KotlinSteppingCommandProvider (cherry picked from commit 2daac45) --- .../stepping/KotlinSteppingCommandProvider.kt | 363 +++++++++--------- 1 file changed, 184 insertions(+), 179 deletions(-) diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 0710f7d64b1..99e34394717 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -72,13 +72,15 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { val file = sourcePosition.file as? KtFile ?: return null if (sourcePosition.line < 0) return null - val containingFunction = sourcePosition.elementAt.parents.firstOrNull { it is KtNamedFunction && !it.isLocal } ?: return null + val containingFunction = sourcePosition.elementAt.parents + .filterIsInstance() + .firstOrNull { !it.isLocal } ?: return null - val startLineNumber = containingFunction.getLineNumber(true) - val endLineNumber = containingFunction.getLineNumber(false) + val startLineNumber = containingFunction.getLineNumber(true) + 1 + val endLineNumber = containingFunction.getLineNumber(false) + 1 if (startLineNumber > endLineNumber) return null - val linesRange = startLineNumber + 1..endLineNumber + 1 + val linesRange = startLineNumber..endLineNumber val inlineArgumentsToSkip = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null @@ -87,122 +89,6 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArgumentsToSkip, additionalElementsToSkip) } - private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List? { - val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition) - if (!inlineFunctionCalls.isEmpty()) { - val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls) - if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) { - return inlineArguments - } - - if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) { - return emptyList() - } - } - - if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) { - return emptyList() - } - - return null - } - - private fun PsiElement.getAdditionalElementsToSkip(): List { - val result = arrayListOf() - val ifParent = getParentOfType(false) - if (ifParent != null) { - if (ifParent.then.contains(this)) { - ifParent.elseKeyword?.let { result.add(it) } - ifParent.`else`?.let { result.add(it) } - } - } - val tryParent = getParentOfType(false) - if (tryParent != null) { - val catchClause = getParentOfType(false) - if (catchClause != null) { - result.addAll(tryParent.catchClauses.filter { it != catchClause }) - } - } - - val whenEntry = getParentOfType(false) - if (whenEntry != null) { - if (whenEntry.expression.contains(this)) { - val whenParent = whenEntry.getParentOfType(false) - if (whenParent != null) { - result.addAll(whenParent.entries.filter { it != whenEntry }) - } - } - } - - return result - } - - private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean { - val ifParent = getParentOfType(false) - if (ifParent != null) { - // if (inlineFunCall()) {...} - if (ifParent.condition.contains(this)) { - return true - } - - /* - if (...) inlineFunCall() - else inlineFunCall() - */ - val ifParentElementAt = elementAt.getParentOfType(false) - if (ifParentElementAt == null) { - if (ifParent.then.contains(this)) { - return true - } - if (ifParent.`else`.contains(this)) { - return true - } - } - } - - val tryParent = getParentOfType(false) - if (tryParent != null) { - /* try { inlineFunCall() } - catch()... - */ - if (tryParent.tryBlock.contains(this)) { - return true - } - } - - val whenEntry = getParentOfType(false) - if (whenEntry != null) { - // inlineFunCall -> ... - if (whenEntry.conditions.any { it.contains(this) } ) { - return true - } - - // 1 == 2 -> inlineFunCall() - if (whenEntry.expression.contains(this)) { - val parentEntryElementAt = elementAt.getParentOfType(false) ?: return true - return parentEntryElementAt == whenEntry && - whenEntry.conditions.any { it.contains(elementAt) } - } - } - - val whileParent = getParentOfType(false) - if (whileParent != null) { - // while (inlineFunCall()) {...} - if (whileParent.condition.contains(this)) { - return true - } - - // last statement in while - return (whileParent.body as? KtBlockExpression)?.statements?.lastOrNull()?.getLineNumber() == elementAt.getLineNumber() - } - - return false - } - - private fun PsiElement?.contains(element: PsiElement): Boolean { - return this?.textRange?.contains(element.textRange) ?: false - } - @TestOnly fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? { return getStepOutCommand(suspendContext, debugContext.sourcePosition) @@ -228,79 +114,196 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument) } +} - private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List { - val elementAt = file.findElementAt(offset) ?: return emptyList() - val containingFunction = elementAt.getParentOfType(false) ?: return emptyList() +private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List? { + val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition) + if (!inlineFunctionCalls.isEmpty()) { + val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls) + if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) { + return inlineArguments + } - val descriptor = containingFunction.resolveToDescriptor() - if (!InlineUtil.isInline(descriptor)) return emptyList() - - val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline( - containingFunction.getResolutionFacade(), - containingFunction.analyzeFully(), - containingFunction, - false - ).filterIsInstance() - - return inlineFunctionsCalls - } - - private fun getInlineArgumentsIfAny(inlineFunctionCalls: List): List { - return inlineFunctionCalls.flatMap { - it.valueArguments - .map { getArgumentExpression(it) } - .filterIsInstance() + if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) { + return emptyList() } } - private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() + // Step over calls to lambda arguments in inline function while execution is already in that function + if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) { + return emptyList() + } - private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List { - val file = sourcePosition.file as? KtFile ?: return emptyList() - val lineNumber = sourcePosition.line - var elementAt = sourcePosition.elementAt + return null +} - var startOffset = file.getLineStartOffset(lineNumber) ?: elementAt.startOffset - val endOffset = file.getLineEndOffset(lineNumber) ?: elementAt.endOffset +private fun PsiElement.getAdditionalElementsToSkip(): List { + val result = arrayListOf() + val ifParent = getParentOfType(false) + if (ifParent != null) { + if (ifParent.then.contains(this)) { + ifParent.elseKeyword?.let { result.add(it) } + ifParent.`else`?.let { result.add(it) } + } + } + val tryParent = getParentOfType(false) + if (tryParent != null) { + val catchClause = getParentOfType(false) + if (catchClause != null) { + result.addAll(tryParent.catchClauses.filter { it != catchClause }) + } + } - var topMostElement: PsiElement? = null - while (topMostElement !is KtElement && startOffset < endOffset) { - elementAt = file.findElementAt(startOffset) - if (elementAt != null) { - topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, startOffset) + val whenEntry = getParentOfType(false) + if (whenEntry != null) { + if (whenEntry.expression.contains(this)) { + val whenParent = whenEntry.getParentOfType(false) + if (whenParent != null) { + result.addAll(whenParent.entries.filter { it != whenEntry }) } - startOffset++ + } + } + + return result +} + +private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean { + val ifParent = getParentOfType(false) + if (ifParent != null) { + // if (inlineFunCall()) {...} + if (ifParent.condition.contains(this)) { + return true } - if (topMostElement !is KtElement) return emptyList() - - val start = topMostElement.startOffset - val end = topMostElement.endOffset - - fun isInlineCall(expr: KtExpression): Boolean { - val context = expr.analyze(BodyResolveMode.PARTIAL) - val resolvedCall = expr.getResolvedCall(context) ?: return false - return InlineUtil.isInline(resolvedCall.resultingDescriptor) - } - - val allInlineFunctionCalls = CodeInsightUtils. - findElementsOfClassInRange(file, start, end, KtExpression::class.java) - .map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) } - .filterIsInstance() - .filter { isInlineCall(it) } - .toSet() - - // It is necessary to check range because of multiline assign - var linesRange = lineNumber..lineNumber - return allInlineFunctionCalls.filter { - val shouldInclude = it.getLineNumber() in linesRange - if (shouldInclude) { - linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false)) + /* + if (...) inlineFunCall() + else inlineFunCall() + */ + val ifParentElementAt = elementAt.getParentOfType(false) + if (ifParentElementAt == null) { + if (ifParent.then.contains(this)) { + return true + } + if (ifParent.`else`.contains(this)) { + return true } - shouldInclude } } + + val tryParent = getParentOfType(false) + if (tryParent != null) { + /* try { inlineFunCall() } + catch()... + */ + if (tryParent.tryBlock.contains(this)) { + return true + } + } + + val whenEntry = getParentOfType(false) + if (whenEntry != null) { + // inlineFunCall -> ... + if (whenEntry.conditions.any { it.contains(this) } ) { + return true + } + + // 1 == 2 -> inlineFunCall() + if (whenEntry.expression.contains(this)) { + val parentEntryElementAt = elementAt.getParentOfType(false) ?: return true + return parentEntryElementAt == whenEntry && + whenEntry.conditions.any { it.contains(elementAt) } + } + } + + val whileParent = getParentOfType(false) + if (whileParent != null) { + // while (inlineFunCall()) {...} + if (whileParent.condition.contains(this)) { + return true + } + + // last statement in while + return (whileParent.body as? KtBlockExpression)?.statements?.lastOrNull()?.getLineNumber() == elementAt.getLineNumber() + } + + return false +} + +private fun PsiElement?.contains(element: PsiElement): Boolean { + return this?.textRange?.contains(element.textRange) ?: false +} + +private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List { + val elementAt = file.findElementAt(offset) ?: return emptyList() + val containingFunction = elementAt.getParentOfType(false) ?: return emptyList() + + val descriptor = containingFunction.resolveToDescriptor() + if (!InlineUtil.isInline(descriptor)) return emptyList() + + val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline( + containingFunction.getResolutionFacade(), + containingFunction.analyzeFully(), + containingFunction, + false + ).filterIsInstance() + + return inlineFunctionsCalls +} + +private fun getInlineArgumentsIfAny(inlineFunctionCalls: List): List { + return inlineFunctionCalls.flatMap { + it.valueArguments + .map { getArgumentExpression(it) } + .filterIsInstance() + } +} + +private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() + +private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List { + val file = sourcePosition.file as? KtFile ?: return emptyList() + val lineNumber = sourcePosition.line + var elementAt = sourcePosition.elementAt + + var startOffset = file.getLineStartOffset(lineNumber) ?: elementAt.startOffset + val endOffset = file.getLineEndOffset(lineNumber) ?: elementAt.endOffset + + var topMostElement: PsiElement? = null + while (topMostElement !is KtElement && startOffset < endOffset) { + elementAt = file.findElementAt(startOffset) + if (elementAt != null) { + topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, startOffset) + } + startOffset++ + } + + if (topMostElement !is KtElement) return emptyList() + + val start = topMostElement.startOffset + val end = topMostElement.endOffset + + fun isInlineCall(expr: KtExpression): Boolean { + val context = expr.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = expr.getResolvedCall(context) ?: return false + return InlineUtil.isInline(resolvedCall.resultingDescriptor) + } + + val allInlineFunctionCalls = CodeInsightUtils. + findElementsOfClassInRange(file, start, end, KtExpression::class.java) + .map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) } + .filterIsInstance() + .filter { isInlineCall(it) } + .toSet() + + // It is necessary to check range because of multiline assign + var linesRange = lineNumber..lineNumber + return allInlineFunctionCalls.filter { + val shouldInclude = it.getLineNumber() in linesRange + if (shouldInclude) { + linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false)) + } + shouldInclude + } } sealed class Action(val position: XSourcePositionImpl?) { @@ -362,6 +365,8 @@ fun getStepOverPosition( return Action.STEP_OVER() } + // Predict step over location by assuming that it will be next valid location in the range of current function. + // This prediction doesn't work in branching position when there're several valid location after current position. val locations = computedReferenceType.allLineLocations() .dropWhile { it != location } .drop(1)