Refactoring: extract top-level functions from KotlinSteppingCommandProvider

(cherry picked from commit 2daac45)
This commit is contained in:
Nikolay Krasko
2016-09-26 15:11:48 +03:00
committed by Nikolay Krasko
parent dd42420a1e
commit 60fbcb7e38
@@ -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<KtNamedFunction>()
.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<KtFunction>? {
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<PsiElement> {
val result = arrayListOf<PsiElement>()
val ifParent = getParentOfType<KtIfExpression>(false)
if (ifParent != null) {
if (ifParent.then.contains(this)) {
ifParent.elseKeyword?.let { result.add(it) }
ifParent.`else`?.let { result.add(it) }
}
}
val tryParent = getParentOfType<KtTryExpression>(false)
if (tryParent != null) {
val catchClause = getParentOfType<KtCatchClause>(false)
if (catchClause != null) {
result.addAll(tryParent.catchClauses.filter { it != catchClause })
}
}
val whenEntry = getParentOfType<KtWhenEntry>(false)
if (whenEntry != null) {
if (whenEntry.expression.contains(this)) {
val whenParent = whenEntry.getParentOfType<KtWhenExpression>(false)
if (whenParent != null) {
result.addAll(whenParent.entries.filter { it != whenEntry })
}
}
}
return result
}
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
val ifParent = getParentOfType<KtIfExpression>(false)
if (ifParent != null) {
// if (inlineFunCall()) {...}
if (ifParent.condition.contains(this)) {
return true
}
/*
<caret>if (...) inlineFunCall()
else inlineFunCall()
*/
val ifParentElementAt = elementAt.getParentOfType<KtIfExpression>(false)
if (ifParentElementAt == null) {
if (ifParent.then.contains(this)) {
return true
}
if (ifParent.`else`.contains(this)) {
return true
}
}
}
val tryParent = getParentOfType<KtTryExpression>(false)
if (tryParent != null) {
/* try { inlineFunCall() }
catch()...
*/
if (tryParent.tryBlock.contains(this)) {
return true
}
}
val whenEntry = getParentOfType<KtWhenEntry>(false)
if (whenEntry != null) {
// <caret>inlineFunCall -> ...
if (whenEntry.conditions.any { it.contains(this) } ) {
return true
}
// <caret>1 == 2 -> inlineFunCall()
if (whenEntry.expression.contains(this)) {
val parentEntryElementAt = elementAt.getParentOfType<KtWhenEntry>(false) ?: return true
return parentEntryElementAt == whenEntry &&
whenEntry.conditions.any { it.contains(elementAt) }
}
}
val whileParent = getParentOfType<KtWhileExpression>(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<KtNamedFunction> {
val elementAt = file.findElementAt(offset) ?: return emptyList()
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
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<KtNamedFunction>()
return inlineFunctionsCalls
}
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
return inlineFunctionCalls.flatMap {
it.valueArguments
.map { getArgumentExpression(it) }
.filterIsInstance<KtFunction>()
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<KtCallExpression> {
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<PsiElement> {
val result = arrayListOf<PsiElement>()
val ifParent = getParentOfType<KtIfExpression>(false)
if (ifParent != null) {
if (ifParent.then.contains(this)) {
ifParent.elseKeyword?.let { result.add(it) }
ifParent.`else`?.let { result.add(it) }
}
}
val tryParent = getParentOfType<KtTryExpression>(false)
if (tryParent != null) {
val catchClause = getParentOfType<KtCatchClause>(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<KtWhenEntry>(false)
if (whenEntry != null) {
if (whenEntry.expression.contains(this)) {
val whenParent = whenEntry.getParentOfType<KtWhenExpression>(false)
if (whenParent != null) {
result.addAll(whenParent.entries.filter { it != whenEntry })
}
startOffset++
}
}
return result
}
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
val ifParent = getParentOfType<KtIfExpression>(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<KtCallExpression>()
.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))
/*
<caret>if (...) inlineFunCall()
else inlineFunCall()
*/
val ifParentElementAt = elementAt.getParentOfType<KtIfExpression>(false)
if (ifParentElementAt == null) {
if (ifParent.then.contains(this)) {
return true
}
if (ifParent.`else`.contains(this)) {
return true
}
shouldInclude
}
}
val tryParent = getParentOfType<KtTryExpression>(false)
if (tryParent != null) {
/* try { inlineFunCall() }
catch()...
*/
if (tryParent.tryBlock.contains(this)) {
return true
}
}
val whenEntry = getParentOfType<KtWhenEntry>(false)
if (whenEntry != null) {
// <caret>inlineFunCall -> ...
if (whenEntry.conditions.any { it.contains(this) } ) {
return true
}
// <caret>1 == 2 -> inlineFunCall()
if (whenEntry.expression.contains(this)) {
val parentEntryElementAt = elementAt.getParentOfType<KtWhenEntry>(false) ?: return true
return parentEntryElementAt == whenEntry &&
whenEntry.conditions.any { it.contains(elementAt) }
}
}
val whileParent = getParentOfType<KtWhileExpression>(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<KtNamedFunction> {
val elementAt = file.findElementAt(offset) ?: return emptyList()
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
val descriptor = containingFunction.resolveToDescriptor()
if (!InlineUtil.isInline(descriptor)) return emptyList()
val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline(
containingFunction.getResolutionFacade(),
containingFunction.analyzeFully(),
containingFunction,
false
).filterIsInstance<KtNamedFunction>()
return inlineFunctionsCalls
}
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
return inlineFunctionCalls.flatMap {
it.valueArguments
.map { getArgumentExpression(it) }
.filterIsInstance<KtFunction>()
}
}
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
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<KtCallExpression>()
.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)