From 6041d566d96443e861e03d8e391cc2438bcd2831 Mon Sep 17 00:00:00 2001 From: Evgeny Gerashchenko Date: Thu, 2 Apr 2015 01:46:45 +0300 Subject: [PATCH] Line markers for recursive calls. --- idea/src/META-INF/plugin.xml | 1 + .../KotlinRecursiveCallLineMarkerProvider.kt | 118 ++++++++++++++++++ .../recursiveCall/conventionCall.kt | 5 + .../lineMarker/recursiveCall/extension.kt | 5 + .../lineMarker/recursiveCall/generic.kt | 3 + .../recursiveCall/inInlinedLambda.kt | 5 + .../lineMarker/recursiveCall/inLambda.kt | 5 + .../lineMarker/recursiveCall/localClass.kt | 7 ++ .../recursiveCall/methodReference.kt | 3 + .../lineMarker/recursiveCall/nested.kt | 7 ++ .../recursiveCall/otherQualifier.kt | 7 ++ .../recursiveCall/severalCallsInOneLine.kt | 7 ++ .../lineMarker/recursiveCall/simple.kt | 5 + .../recursiveCall/superQualifier.kt | 12 ++ .../lineMarker/recursiveCall/thisQualifier.kt | 7 ++ .../codeInsight/LineMarkersTestGenerated.java | 88 +++++++++++++ 16 files changed, 285 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinRecursiveCallLineMarkerProvider.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/extension.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/generic.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/inInlinedLambda.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/inLambda.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/localClass.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/methodReference.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/nested.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/otherQualifier.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/severalCallsInOneLine.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/simple.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/superQualifier.kt create mode 100644 idea/testData/codeInsight/lineMarker/recursiveCall/thisQualifier.kt 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); + } + } }