diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt index 983ff0e76ef..044dc170613 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt @@ -28,13 +28,14 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.util.getThisReceiverOwner +import org.jetbrains.kotlin.lexer.JetToken +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.inline.InlineUtil -import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue -import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver +import org.jetbrains.kotlin.types.expressions.OperatorConventions import java.util.HashSet public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider { @@ -42,6 +43,7 @@ public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider { override fun collectSlowLineMarkers(elements: MutableList, result: MutableCollection>) { val markedLineNumbers = HashSet() + for (element in elements) { ProgressManager.checkCanceled() if (element is JetElement) { @@ -54,11 +56,16 @@ public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider { } } - private fun getEnclosingFunction(element: JetElement): JetNamedFunction? { + private fun getEnclosingFunction(element: JetElement, stopOnNonInlinedLambdas: Boolean): JetNamedFunction? { for (parent in element.parents) { when (parent) { - is JetFunctionLiteral -> if (!InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return null - is JetNamedFunction -> if (!InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return parent + is JetFunctionLiteral -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return null + is JetNamedFunction -> { + when (parent.getParent()) { + is JetBlockExpression, is JetClassBody, is JetFile, is JetScript -> return parent + else -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return null + } + } is JetClassOrObject -> return null } } @@ -66,8 +73,14 @@ public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider { } private fun isRecursiveCall(element: JetElement): Boolean { - if (element.getParent() is JetCallableReferenceExpression) return false - val enclosingFunction = getEnclosingFunction(element) ?: return false + // Fast check for names without resolve + val resolveName = getCallNameFromPsi(element) ?: return false + val enclosingFunction = getEnclosingFunction(element, false) ?: return false + + if (enclosingFunction.getName() != resolveName.asString()) return false + + // Check that there were no not-inlined lambdas on the way to enclosing function + if (enclosingFunction != getEnclosingFunction(element, true)) return false val bindingContext = element.analyze() val enclosingFunctionDescriptor = bindingContext[BindingContext.FUNCTION, enclosingFunction] ?: return false @@ -117,3 +130,34 @@ public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider { private fun PsiElement.getLineNumber(): Int { return PsiDocumentManager.getInstance(getProject()).getDocument(getContainingFile())!!.getLineNumber(getTextOffset()) } + +private fun getCallNameFromPsi(element: JetElement): Name? { + when (element) { + is JetSimpleNameExpression -> { + val elementParent = element.getParent() + when (elementParent) { + is JetCallExpression -> return Name.identifierNoValidate(element.getText()) + is JetOperationExpression -> { + val operationReference = elementParent.getOperationReference() + if (element == operationReference) { + val node = operationReference.getReferencedNameElementType() + return if (node is JetToken) { + OperatorConventions.getNameForOperationSymbol(node) ?: Name.identifierNoValidate(element.getText()) + } + else { + Name.identifierNoValidate(element.getText()) + } + } + } + } + } + is JetArrayAccessExpression -> + return Name.identifier("get") + is JetThisExpression -> + if (element.getParent() is JetCallExpression) { + return OperatorConventions.INVOKE + } + } + + return null +} \ No newline at end of file diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt index a06f20d858f..7725735db81 100644 --- a/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt +++ b/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt @@ -1,5 +1,52 @@ fun Any.get(a: Int) { if (a > 0) { - this[a - 1] + this[a - 1] } +} + +class A { + override fun equals(other: Any?): Boolean { + this == other + return true + } + + fun inc(): A { + this++ + ++this + return this + } + + fun component1(): Int { + // TODO: should be recursion marker too + val (a) = this + return 1 + } + + fun plus() { + +this + } + + fun minus() { + -this + } + + fun plus(a: Int) { + this + 1 + this += 1 + } + + fun invoke() { + val a = A() + a() + a.invoke() + + this.invoke() + this() + } +} + +class B +fun B.invoke() { + this() + invoke() } \ No newline at end of file