From 54e5e4d2ea7eda0bfcacdfe6cd6b0f4aab1487a1 Mon Sep 17 00:00:00 2001 From: "Konstantin.Nisht" Date: Mon, 20 Apr 2020 16:58:05 +0300 Subject: [PATCH] IDEA-228815: Do not use references with recursive calls GitOrigin-RevId: e4f13ec8bc709f2b543db8122455b3abf9ecb977 --- .../style/inference/search/searchUtils.kt | 63 +++++++++++++++---- ...rMethodParametersTypesIntentionTest.groovy | 4 ++ .../RecursiveCalls.groovy | 7 +++ .../RecursiveCalls_after.groovy | 7 +++ 4 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls.groovy create mode 100644 plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls_after.groovy diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/style/inference/search/searchUtils.kt b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/style/inference/search/searchUtils.kt index fb083944aa58..aae34aced5b0 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/style/inference/search/searchUtils.kt +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/style/inference/search/searchUtils.kt @@ -1,4 +1,6 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:Suppress("RemoveExplicitTypeArguments") + package org.jetbrains.plugins.groovy.intentions.style.inference.search import com.intellij.psi.PsiElement @@ -7,11 +9,16 @@ import com.intellij.psi.search.* import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.util.parentOfType import com.intellij.util.Processor +import com.intellij.util.containers.map2Array +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement +import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor import org.jetbrains.plugins.groovy.lang.psi.api.GrFunctionalExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod -@Suppress("RemoveExplicitTypeArguments") + internal fun searchWithClosureAvoidance(element: GrMethod, scope: SearchScope): List { val requestsCollector = SearchRequestCollector(SearchSession()) val collector: MutableList = mutableListOf() @@ -21,28 +28,60 @@ internal fun searchWithClosureAvoidance(element: GrMethod, scope: SearchScope): val query: QuerySearchRequest = queries.removeAt(queries.lastIndex) if (!query.runQuery()) break query.collector.takeQueryRequests().forEach { queries.add(it) } - query.collector.takeSearchRequests().forEach { dumpSearchRequest(requestsCollector, it, element) } + query.collector.takeSearchRequests().forEach { requestsCollector.adoptSearchRequest(it, element) } } PsiSearchHelper.getInstance(element.project).processRequests(requestsCollector) { ref -> collector.add(ref); true } return collector } -private fun dumpSearchRequest(requestsCollector: SearchRequestCollector, request: PsiSearchRequest, target: GrMethod) = with(request) { - requestsCollector.searchWord(word, searchScope, searchContext, caseSensitive, target, ScopeFilteringRequestProcessor(target, processor)) +private fun SearchRequestCollector.adoptSearchRequest(request: PsiSearchRequest, target: GrMethod) = with(request) { + searchWord(word, searchScope, searchContext, caseSensitive, target, ScopeFilteringRequestProcessor(target, processor)) } private class ScopeFilteringRequestProcessor(private val anchorElement: GrMethod, private val delegateProcessor: RequestResultProcessor) : RequestResultProcessor() { - override fun processTextOccurrence(element: PsiElement, offsetInElement: Int, consumer: Processor): Boolean { - val enclosingClosure = element.parentOfType() - val call = enclosingClosure?.parentOfType() - val arguments = call?.closureArguments?.asList()?.plus(call.expressionArguments.asList()) - if (arguments?.contains(enclosingClosure) == true && call.callReference?.methodName == anchorElement.name) { - return true + + private class CollisionFinder(vararg val bannedIdentifiers: String) : GroovyRecursiveElementVisitor() { + var foundCollision = false + + override fun visitElement(element: GroovyPsiElement) { + if (!foundCollision) super.visitElement(element) } - else { - return delegateProcessor.processTextOccurrence(element, offsetInElement, consumer) + + override fun visitReferenceExpression(referenceExpression: GrReferenceExpression) { + if (referenceExpression.referenceName in bannedIdentifiers) { + foundCollision = true + } + else { + super.visitReferenceExpression(referenceExpression) + } + } + + override fun visitFunctionalExpression(expression: GrFunctionalExpression) { + // it is sufficient for references searcher to know that argument has closure type, no need to go deeper } } + + override fun processTextOccurrence(element: PsiElement, offsetInElement: Int, consumer: Processor): Boolean { + val enclosingClosure: GrFunctionalExpression? = element.parentOfType() + val call: GrMethodCall? = enclosingClosure?.parentOfType() + val arguments: List? = call?.closureArguments?.asList()?.plus(call.expressionArguments.asList()) + if (arguments?.contains(enclosingClosure) == true && call.callReference?.methodName == anchorElement.name) { + return true + } + val enclosingCall: GrMethodCall? = element.parentOfType()?.takeIf { it.invokedExpression === element } + if (enclosingCall is GrMethodCall) { + val enclosingMethod: GrMethod? = enclosingCall.parentOfType()?.takeIf { it === anchorElement } + val bannedIdentifiers: Array = enclosingMethod?.parameters?.map2Array { it.name } ?: emptyArray() + val collisionFinder = CollisionFinder(anchorElement.name, *bannedIdentifiers) + if (enclosingCall.expressionArguments.any { argument -> collisionFinder.apply { argument.accept(this) }.foundCollision }) { + // there should be provided some connection between method return type and its argument, + // but currently this option is absent + return true + } + } + return delegateProcessor.processTextOccurrence(element, offsetInElement, consumer) + } + } \ No newline at end of file diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/InferMethodParametersTypesIntentionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/InferMethodParametersTypesIntentionTest.groovy index 7297c78892c4..db4f9ecaba6c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/InferMethodParametersTypesIntentionTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/InferMethodParametersTypesIntentionTest.groovy @@ -560,4 +560,8 @@ class AA { static void foo(Integer a) {} } void testForInLoopWithMap() { doTest(true) } + + void testRecursiveCalls() { + doTest(true) + } } diff --git a/plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls.groovy b/plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls.groovy new file mode 100644 index 000000000000..f6e6389d34eb --- /dev/null +++ b/plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls.groovy @@ -0,0 +1,7 @@ +def foo(x) { + x +} + +def bar() { + foo(foo(1)) +} \ No newline at end of file diff --git a/plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls_after.groovy b/plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls_after.groovy new file mode 100644 index 000000000000..056abbcf989a --- /dev/null +++ b/plugins/groovy/testdata/refactoring/inferMethodParametersTypes/RecursiveCalls_after.groovy @@ -0,0 +1,7 @@ +Integer foo(Integer x) { + x +} + +def bar() { + foo(foo(1)) +} \ No newline at end of file