172: Revert "RecursiveMethodCallMarkerInfo and SuspendCallMarkerInfo forced to target Leaf-elements"
This reverts commit c78ec71e17c09ddb1094e44c84d9a1b92a3b509f.
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.inspections.RecursivePropertyAccessorInspection
|
||||
import org.jetbrains.kotlin.idea.util.getReceiverTargetDescriptor
|
||||
import org.jetbrains.kotlin.lexer.KtToken
|
||||
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.Receiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import java.util.*
|
||||
|
||||
class KotlinRecursiveCallLineMarkerProvider : LineMarkerProvider {
|
||||
override fun getLineMarkerInfo(element: PsiElement) = null
|
||||
|
||||
override fun collectSlowLineMarkers(elements: MutableList<PsiElement>, result: MutableCollection<LineMarkerInfo<*>>) {
|
||||
val markedLineNumbers = HashSet<Int>()
|
||||
|
||||
for (element in elements) {
|
||||
ProgressManager.checkCanceled()
|
||||
if (element is KtElement) {
|
||||
val lineNumber = element.getLineNumber()
|
||||
if (lineNumber !in markedLineNumbers && isRecursiveCall(element)) {
|
||||
markedLineNumbers.add(lineNumber)
|
||||
result.add(RecursiveMethodCallMarkerInfo(element))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEnclosingFunction(element: KtElement, stopOnNonInlinedLambdas: Boolean): KtNamedFunction? {
|
||||
for (parent in element.parents) {
|
||||
when (parent) {
|
||||
is KtFunctionLiteral -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return null
|
||||
is KtNamedFunction -> {
|
||||
when (parent.parent) {
|
||||
is KtBlockExpression, is KtClassBody, is KtFile, is KtScript -> return parent
|
||||
else -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return null
|
||||
}
|
||||
}
|
||||
is KtClassOrObject -> return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isRecursiveCall(element: KtElement): Boolean {
|
||||
if (RecursivePropertyAccessorInspection.isRecursivePropertyAccess(element)) return true
|
||||
if (RecursivePropertyAccessorInspection.isRecursiveSyntheticPropertyAccess(element)) return true
|
||||
// Fast check for names without resolve
|
||||
val resolveName = getCallNameFromPsi(element) ?: return false
|
||||
val enclosingFunction = getEnclosingFunction(element, false) ?: return false
|
||||
|
||||
val enclosingFunctionName = enclosingFunction.name
|
||||
if (enclosingFunctionName != OperatorNameConventions.INVOKE.asString()
|
||||
&& enclosingFunctionName != 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
|
||||
|
||||
val call = bindingContext[BindingContext.CALL, element] ?: return false
|
||||
val resolvedCall = bindingContext[BindingContext.RESOLVED_CALL, call] ?: return false
|
||||
|
||||
if (resolvedCall.candidateDescriptor.original != enclosingFunctionDescriptor) return false
|
||||
|
||||
fun isDifferentReceiver(receiver: Receiver?): Boolean {
|
||||
if (receiver !is ReceiverValue) return false
|
||||
|
||||
val receiverOwner = receiver.getReceiverTargetDescriptor(bindingContext) ?: return true
|
||||
|
||||
return when (receiverOwner) {
|
||||
is SimpleFunctionDescriptor -> receiverOwner != enclosingFunctionDescriptor
|
||||
is ClassDescriptor -> receiverOwner != enclosingFunctionDescriptor.containingDeclaration
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
|
||||
if (isDifferentReceiver(resolvedCall.dispatchReceiver)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
private class RecursiveMethodCallMarkerInfo(callElement: KtElement)
|
||||
: LineMarkerInfo<KtElement>(
|
||||
callElement,
|
||||
callElement.textRange,
|
||||
AllIcons.Gutter.RecursiveMethod,
|
||||
Pass.LINE_MARKERS,
|
||||
{ "Recursive call" },
|
||||
null,
|
||||
GutterIconRenderer.Alignment.RIGHT
|
||||
) {
|
||||
|
||||
override fun createGutterRenderer(): GutterIconRenderer? {
|
||||
return object : LineMarkerInfo.LineMarkerGutterIconRenderer<KtElement>(this) {
|
||||
override fun getClickAction() = null // to place breakpoint on mouse click
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun PsiElement.getLineNumber(): Int {
|
||||
return PsiDocumentManager.getInstance(project).getDocument(containingFile)!!.getLineNumber(textOffset)
|
||||
}
|
||||
|
||||
private fun getCallNameFromPsi(element: KtElement): Name? {
|
||||
when (element) {
|
||||
is KtSimpleNameExpression -> {
|
||||
val elementParent = element.getParent()
|
||||
when (elementParent) {
|
||||
is KtCallExpression -> return Name.identifier(element.getText())
|
||||
is KtOperationExpression -> {
|
||||
val operationReference = elementParent.operationReference
|
||||
if (element == operationReference) {
|
||||
val node = operationReference.getReferencedNameElementType()
|
||||
return if (node is KtToken) {
|
||||
val conventionName = if (elementParent is KtPrefixExpression)
|
||||
OperatorConventions.getNameForOperationSymbol(node, true, false)
|
||||
else
|
||||
OperatorConventions.getNameForOperationSymbol(node)
|
||||
|
||||
conventionName ?: Name.identifier(element.getText())
|
||||
}
|
||||
else {
|
||||
Name.identifier(element.getText())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is KtArrayAccessExpression ->
|
||||
return OperatorNameConventions.GET
|
||||
is KtThisExpression ->
|
||||
if (element.getParent() is KtCallExpression) {
|
||||
return OperatorNameConventions.INVOKE
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
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.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
|
||||
import org.jetbrains.kotlin.descriptors.accessors
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingContext.*
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.isBuiltInCoroutineContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class KotlinSuspendCallLineMarkerProvider : LineMarkerProvider {
|
||||
private class SuspendCallMarkerInfo(callElement: KtElement, message: String) : LineMarkerInfo<KtElement>(
|
||||
callElement,
|
||||
callElement.textRange,
|
||||
KotlinIcons.SUSPEND_CALL,
|
||||
Pass.LINE_MARKERS,
|
||||
{ message },
|
||||
null,
|
||||
GutterIconRenderer.Alignment.RIGHT
|
||||
) {
|
||||
override fun createGutterRenderer(): GutterIconRenderer? {
|
||||
return object : LineMarkerInfo.LineMarkerGutterIconRenderer<KtElement>(this) {
|
||||
override fun getClickAction(): AnAction? = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? = null
|
||||
|
||||
override fun collectSlowLineMarkers(
|
||||
elements: MutableList<PsiElement>,
|
||||
result: MutableCollection<LineMarkerInfo<*>>
|
||||
) {
|
||||
val markedLineNumbers = HashSet<Int>()
|
||||
|
||||
for (element in elements) {
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
if (element !is KtExpression) continue
|
||||
val lineNumber = element.getLineNumber()
|
||||
if (lineNumber in markedLineNumbers) continue
|
||||
if (!element.hasSuspendCalls()) continue
|
||||
|
||||
markedLineNumbers += lineNumber
|
||||
result += if (element is KtForExpression) {
|
||||
SuspendCallMarkerInfo(element.loopRange!!, "Suspending iteration")
|
||||
} else {
|
||||
SuspendCallMarkerInfo(element, "Suspend function call")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtExpression.isValidCandidateExpression(): Boolean {
|
||||
if (this is KtOperationReferenceExpression || this is KtForExpression || this is KtProperty || this is KtNameReferenceExpression) return true
|
||||
val parent = parent
|
||||
if (parent is KtCallExpression && parent.calleeExpression == this) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun KtExpression.hasSuspendCalls(bindingContext: BindingContext = analyze(BodyResolveMode.PARTIAL)): Boolean {
|
||||
if (!isValidCandidateExpression()) return false
|
||||
|
||||
return when (this) {
|
||||
is KtForExpression -> {
|
||||
val iteratorResolvedCall = bindingContext[LOOP_RANGE_ITERATOR_RESOLVED_CALL, loopRange]
|
||||
val loopRangeHasNextResolvedCall = bindingContext[LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, loopRange]
|
||||
val loopRangeNextResolvedCall = bindingContext[LOOP_RANGE_NEXT_RESOLVED_CALL, loopRange]
|
||||
listOf(iteratorResolvedCall, loopRangeHasNextResolvedCall, loopRangeNextResolvedCall).any {
|
||||
it?.resultingDescriptor?.isSuspend == true
|
||||
}
|
||||
}
|
||||
is KtProperty -> {
|
||||
if (hasDelegateExpression()) {
|
||||
val variableDescriptor = bindingContext[DECLARATION_TO_DESCRIPTOR, this] as? VariableDescriptorWithAccessors
|
||||
val accessors = variableDescriptor?.accessors ?: emptyList()
|
||||
accessors.any { accessor ->
|
||||
val delegatedFunctionDescriptor = bindingContext[DELEGATED_PROPERTY_RESOLVED_CALL, accessor]?.resultingDescriptor
|
||||
delegatedFunctionDescriptor?.isSuspend == true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val resolvedCall = getResolvedCall(bindingContext)
|
||||
if ((resolvedCall?.resultingDescriptor as? FunctionDescriptor)?.isSuspend == true) true
|
||||
else {
|
||||
val propertyDescriptor = resolvedCall?.resultingDescriptor as? PropertyDescriptor
|
||||
val s = propertyDescriptor?.fqNameSafe?.asString()
|
||||
s?.startsWith("kotlin.coroutines.") == true && s.endsWith(".coroutineContext")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
operator fun Any.get(a: Int) {
|
||||
if (a > 0) {
|
||||
<lineMarker descr="Recursive call">this[a - 1]</lineMarker>
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
override fun <lineMarker descr="Overrides function in 'Any'">equals</lineMarker>(other: Any?): Boolean {
|
||||
this <lineMarker descr="Recursive call">==</lineMarker> other
|
||||
return true
|
||||
}
|
||||
|
||||
operator fun inc(): A {
|
||||
this<lineMarker descr="Recursive call">++</lineMarker>
|
||||
<lineMarker descr="Recursive call">++</lineMarker>this
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun component1(): Int {
|
||||
// TODO: should be recursion marker too
|
||||
val (a) = this
|
||||
return 1
|
||||
}
|
||||
|
||||
operator fun unaryPlus() {
|
||||
<lineMarker descr="Recursive call">+</lineMarker>this
|
||||
}
|
||||
|
||||
operator fun unaryMinus() {
|
||||
<lineMarker descr="Recursive call">-</lineMarker>this
|
||||
}
|
||||
|
||||
operator fun plus(a: Int) {
|
||||
this <lineMarker descr="Recursive call">+</lineMarker> 1
|
||||
this += 1
|
||||
}
|
||||
|
||||
operator fun invoke() {
|
||||
val a = A()
|
||||
a()
|
||||
a.invoke()
|
||||
|
||||
this.<lineMarker descr="Recursive call">invoke</lineMarker>()
|
||||
<lineMarker descr="Recursive call">this</lineMarker>()
|
||||
}
|
||||
}
|
||||
|
||||
class B
|
||||
operator fun B.invoke() {
|
||||
<lineMarker descr="Recursive call">this</lineMarker>()
|
||||
<lineMarker descr="Recursive call">invoke</lineMarker>()
|
||||
}
|
||||
Reference in New Issue
Block a user