diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index c5f620090dc..c06c05dfde8 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -353,6 +353,7 @@
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt
new file mode 100644
index 00000000000..5e90c7ddc30
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.highlighter
+
+import com.intellij.codeHighlighting.Pass
+import com.intellij.codeInsight.daemon.LineMarkerInfo
+import com.intellij.codeInsight.daemon.LineMarkerProvider
+import com.intellij.icons.AllIcons
+import com.intellij.openapi.editor.markup.GutterIconRenderer
+import com.intellij.openapi.progress.ProgressManager
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
+import org.jetbrains.kotlin.psi.psiUtil.parents
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.isInlined
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
+import java.util.HashSet
+
+public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider {
+ override fun getLineMarkerInfo(element: PsiElement) = null
+
+ override fun collectSlowLineMarkers(elements: MutableList, result: MutableCollection>) {
+ val markedLineNumbers = HashSet()
+ for (element in elements) {
+ ProgressManager.checkCanceled()
+ if (element is JetElement) {
+ val lineNumber = element.getLineNumber()
+ if (lineNumber !in markedLineNumbers && isRecursiveCall(element)) {
+ markedLineNumbers.add(lineNumber)
+ result.add(RecursiveMethodCallMarkerInfo(element))
+ }
+ }
+ }
+ }
+
+ private fun getEnclosingFunction(element: JetElement): JetNamedFunction? {
+ for (parent in element.parents(false)) {
+ when (parent) {
+ is JetFunctionLiteral -> if (!parent.isInlined(parent.analyze())) return null
+ is JetNamedFunction -> return parent
+ is JetClassOrObject -> return null
+ }
+ }
+ return null
+ }
+
+ private fun isRecursiveCall(element: JetElement): Boolean {
+ if (!(element is JetSimpleNameExpression && element.getParent() is JetCallExpression || element is JetArrayAccessExpression))
+ return false
+ val enclosingFunction = getEnclosingFunction(element) ?: return false
+
+ val bindingContext = element.analyze()
+ val enclosingFunctionDescriptor = bindingContext[BindingContext.FUNCTION, enclosingFunction] ?: return false
+
+ val call = bindingContext[BindingContext.CALL, element] ?: return false
+ val resolvedCall = bindingContext[BindingContext.RESOLVED_CALL, call] ?: return false
+
+ if (resolvedCall.getCandidateDescriptor().getOriginal() != enclosingFunctionDescriptor) return false
+
+ val extensionReceiver = resolvedCall.getExtensionReceiver()
+ if (extensionReceiver is ExpressionReceiver) {
+ val thisTarget = (extensionReceiver.getExpression() as? JetThisExpression)?.getInstanceReference()?.getReference()?.resolve()
+ if (thisTarget != enclosingFunction) {
+ return false
+ }
+ }
+
+ val dispatchReceiver = resolvedCall.getDispatchReceiver()
+ if (dispatchReceiver is ExpressionReceiver) {
+ val thisTarget = (dispatchReceiver.getExpression() as? JetThisExpression)?.getInstanceReference()?.getReference()?.resolve()
+ if (thisTarget != enclosingFunction.getNonStrictParentOfType()) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ private class RecursiveMethodCallMarkerInfo(callElement: JetElement)
+ : LineMarkerInfo(
+ callElement,
+ callElement.getTextRange(),
+ AllIcons.Gutter.RecursiveMethod,
+ Pass.UPDATE_OVERRIDEN_MARKERS,
+ { "Recursive call" },
+ null,
+ GutterIconRenderer.Alignment.RIGHT
+ ) {
+
+ override fun createGutterRenderer(): GutterIconRenderer? {
+ return object : LineMarkerInfo.LineMarkerGutterIconRenderer(this) {
+ override fun getClickAction() = null // to place breakpoint on mouse click
+ }
+ }
+ }
+
+}
+
+private fun PsiElement.getLineNumber(): Int {
+ return PsiDocumentManager.getInstance(getProject()).getDocument(getContainingFile())!!.getLineNumber(getTextOffset())
+}
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt
new file mode 100644
index 00000000000..a06f20d858f
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt
@@ -0,0 +1,5 @@
+fun Any.get(a: Int) {
+ if (a > 0) {
+ this[a - 1]
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/extension.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/extension.kt
new file mode 100644
index 00000000000..0aecdd4abe4
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/extension.kt
@@ -0,0 +1,5 @@
+fun Any.f(a: Int) {
+ "".f(1)
+ this.f(2)
+ f(3)
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/generic.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/generic.kt
new file mode 100644
index 00000000000..9b63b46f971
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/generic.kt
@@ -0,0 +1,3 @@
+fun f(a: Int) {
+ f(55)
+}
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/inInlinedLambda.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/inInlinedLambda.kt
new file mode 100644
index 00000000000..58d28bac284
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/inInlinedLambda.kt
@@ -0,0 +1,5 @@
+fun f(a: Int) {
+ run {
+ f(a - 1)
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/inLambda.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/inLambda.kt
new file mode 100644
index 00000000000..c2998afcc76
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/inLambda.kt
@@ -0,0 +1,5 @@
+fun outer(a: Int) {
+ SwingUtilities.invokeLater {
+ outer(a - 1)
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/localClass.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/localClass.kt
new file mode 100644
index 00000000000..c26d1ef150c
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/localClass.kt
@@ -0,0 +1,7 @@
+fun f(a: Int) {
+ class Local {
+ fun member() {
+ f(11)
+ }
+ }
+}
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/methodReference.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/methodReference.kt
new file mode 100644
index 00000000000..6615b62ddd6
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/methodReference.kt
@@ -0,0 +1,3 @@
+fun f(a: Int) {
+ val x = ::f
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/nested.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/nested.kt
new file mode 100644
index 00000000000..ea675298f0d
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/nested.kt
@@ -0,0 +1,7 @@
+fun outer(a: Int) {
+ fun local(a: Int) {
+ if (a > 0) {
+ outer(a - 1)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/otherQualifier.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/otherQualifier.kt
new file mode 100644
index 00000000000..7c7a4be067d
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/otherQualifier.kt
@@ -0,0 +1,7 @@
+class F {
+ fun f(a: Int, other: F) {
+ if (a > 0) {
+ other.f(a - 1)
+ }
+ }
+}
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/severalCallsInOneLine.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/severalCallsInOneLine.kt
new file mode 100644
index 00000000000..6259419103a
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/severalCallsInOneLine.kt
@@ -0,0 +1,7 @@
+fun f(a: Int): Int {
+ if (a > 0) {
+ f(a - 1) + f(a + 1)
+ }
+
+ return f(a)
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/simple.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/simple.kt
new file mode 100644
index 00000000000..a186e8e76f8
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/simple.kt
@@ -0,0 +1,5 @@
+fun f(a: Int) {
+ if (a > 0) {
+ f(a - 1)
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/superQualifier.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/superQualifier.kt
new file mode 100644
index 00000000000..270aba1104f
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/superQualifier.kt
@@ -0,0 +1,12 @@
+open class Super {
+ open fun f(a: Int) {
+ }
+}
+
+class F: Super() {
+ override fun f(a: Int) {
+ if (a > 0) {
+ super.f(a - 1)
+ }
+ }
+}
diff --git a/idea/testData/codeInsight/lineMarker/recursiveCall/thisQualifier.kt b/idea/testData/codeInsight/lineMarker/recursiveCall/thisQualifier.kt
new file mode 100644
index 00000000000..59000bea619
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/recursiveCall/thisQualifier.kt
@@ -0,0 +1,7 @@
+class F {
+ fun f(a: Int) {
+ if (a > 0) {
+ this.f(a - 1)
+ }
+ }
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/LineMarkersTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/LineMarkersTestGenerated.java
index c0cd0a1b856..9024c6b015d 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/LineMarkersTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/LineMarkersTestGenerated.java
@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
LineMarkersTestGenerated.OverrideImplement.class,
+ LineMarkersTestGenerated.RecursiveCall.class,
})
@RunWith(JUnit3RunnerWithInners.class)
public class LineMarkersTestGenerated extends AbstractLineMarkersTest {
@@ -179,4 +180,91 @@ public class LineMarkersTestGenerated extends AbstractLineMarkersTest {
doTest(fileName);
}
}
+
+ @TestMetadata("idea/testData/codeInsight/lineMarker/recursiveCall")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class RecursiveCall extends AbstractLineMarkersTest {
+ public void testAllFilesPresentInRecursiveCall() throws Exception {
+ JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/lineMarker/recursiveCall"), Pattern.compile("^(.+)\\.kt$"), true);
+ }
+
+ @TestMetadata("conventionCall.kt")
+ public void testConventionCall() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("extension.kt")
+ public void testExtension() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/extension.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("generic.kt")
+ public void testGeneric() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/generic.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("inInlinedLambda.kt")
+ public void testInInlinedLambda() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/inInlinedLambda.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("inLambda.kt")
+ public void testInLambda() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/inLambda.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("localClass.kt")
+ public void testLocalClass() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/localClass.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("methodReference.kt")
+ public void testMethodReference() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/methodReference.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("nested.kt")
+ public void testNested() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/nested.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("otherQualifier.kt")
+ public void testOtherQualifier() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/otherQualifier.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("severalCallsInOneLine.kt")
+ public void testSeveralCallsInOneLine() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/severalCallsInOneLine.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/simple.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("superQualifier.kt")
+ public void testSuperQualifier() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/superQualifier.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("thisQualifier.kt")
+ public void testThisQualifier() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/thisQualifier.kt");
+ doTest(fileName);
+ }
+ }
}