172: Revert "RecursiveMethodCallMarkerInfo and SuspendCallMarkerInfo forced to target Leaf-elements"

This reverts commit c78ec71e17c09ddb1094e44c84d9a1b92a3b509f.
This commit is contained in:
Nikolay Krasko
2018-01-11 20:12:23 +03:00
parent 984d9feeae
commit 1010c1285a
3 changed files with 340 additions and 0 deletions
@@ -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
}
@@ -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>()
}