Handle cases when operator keyword is implicitly inherited

Relates to #KTIJ-18868

GitOrigin-RevId: 815c75723c63daa8646eabcda4d31608336894cb
This commit is contained in:
Vladimir Dolzhenko
2021-06-24 15:03:17 +00:00
committed by intellij-monorepo-bot
parent b6c313031d
commit 3a3f672cd5
7 changed files with 71 additions and 12 deletions
@@ -16,14 +16,13 @@ import com.intellij.util.Processor
import com.intellij.util.indexing.FileBasedIndex
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.search.KotlinSearchUsagesSupport.Companion.scriptDefinitionExists
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName as getKotlinFqNameOriginal
infix fun SearchScope.and(otherScope: SearchScope): SearchScope = intersectWith(otherScope)
@@ -144,4 +143,14 @@ fun PsiReference.isImportUsage(): Boolean =
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("getKotlinFqName()", "org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName")
)
fun PsiElement.getKotlinFqName(): FqName? = getKotlinFqNameOriginal()
fun PsiElement.getKotlinFqName(): FqName? = getKotlinFqNameOriginal()
fun KtElement.isPotentiallyOperator(): Boolean {
val namedFunction = safeAs<KtNamedFunction>() ?: return false
if (namedFunction.hasModifier(KtTokens.OPERATOR_KEYWORD)) return true
// operator modifier could be omitted for overriding function
if (!namedFunction.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return false
// TODO: it's fast PSI-based check, a proper check requires call to resolveDeclarationWithParents() that is not frontend-independent
return true
}
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.idea.search.KotlinSearchUsagesSupport.Companion.forc
import org.jetbrains.kotlin.idea.search.KotlinSearchUsagesSupport.Companion.getReceiverTypeSearcherInfo
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor
import org.jetbrains.kotlin.idea.search.isPotentiallyOperator
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor
import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor.Companion.logPresentation
@@ -119,8 +120,7 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
if (!options.searchForOperatorConventions) return null
// Java has no operator modifier
val operator = declaration !is KtElement ||
declaration.safeAs<KtModifierListOwner>()?.hasModifier(KtTokens.OPERATOR_KEYWORD) == true
val operator = declaration !is KtElement || declaration.isPotentiallyOperator()
val binaryOp = OperatorConventions.BINARY_OPERATION_NAMES.inverse()[name]
val assignmentOp = OperatorConventions.ASSIGNMENT_OPERATIONS.inverse()[name]
@@ -148,7 +148,7 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
operator && name == OperatorNameConventions.SET ->
IndexingOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options, isSet = true)
name == OperatorNameConventions.CONTAINS ->
operator && name == OperatorNameConventions.CONTAINS ->
ContainsOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.EQUALS ->
@@ -161,7 +161,7 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
options
)
name == OperatorNameConventions.COMPARE_TO ->
operator && name == OperatorNameConventions.COMPARE_TO ->
BinaryOperatorReferenceSearcher(
declaration,
listOf(KtTokens.LT, KtTokens.GT, KtTokens.LTEQ, KtTokens.GTEQ),
@@ -171,10 +171,10 @@ abstract class OperatorReferenceSearcher<TReferenceElement : KtElement>(
options
)
name == OperatorNameConventions.ITERATOR ->
operator && name == OperatorNameConventions.ITERATOR ->
IteratorOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
name == OperatorNameConventions.GET_VALUE || name == OperatorNameConventions.SET_VALUE || name == OperatorNameConventions.PROVIDE_DELEGATE ->
operator && (name == OperatorNameConventions.GET_VALUE || name == OperatorNameConventions.SET_VALUE || name == OperatorNameConventions.PROVIDE_DELEGATE) ->
PropertyDelegationOperatorReferenceSearcher(declaration, searchScope, consumer, optimizer, options)
else -> null
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.highlighter.markers.resolveDeclarationWithParents
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.refactoring.explicateAsText
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
@@ -371,7 +372,7 @@ internal fun checkNewNameUsagesRetargeting(
return
}
val operator = declaration.safeAs<KtModifierListOwner>()?.hasModifier(KtTokens.OPERATOR_KEYWORD) == true
val operator = declaration.isOperator()
for (candidateDescriptor in declaration.getResolutionScope().getRelevantDescriptors(declaration, newName)) {
val candidate =
@@ -388,3 +389,16 @@ internal fun checkNewNameUsagesRetargeting(
usages.filterIsInstanceTo<KtResolvableCollisionUsageInfo, MutableList<UsageInfo>>(newUsages)
}
}
internal fun KtElement.isOperator(): Boolean {
val namedFunction = safeAs<KtNamedFunction>() ?: return false
if (namedFunction.hasModifier(KtTokens.OPERATOR_KEYWORD)) return true
// operator modifier could be omitted for overriding function
if (!namedFunction.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return false
val resolveWithParents = resolveDeclarationWithParents(this as KtNamedFunction)
return resolveWithParents.overriddenDescriptors.any {
val psi = it.source.getPsi() ?: return@any false
psi !is KtElement || psi.safeAs<KtNamedFunction>()?.hasModifier(KtTokens.OPERATOR_KEYWORD) == true
}
}
@@ -116,6 +116,11 @@ public abstract class FindUsagesTestGenerated extends AbstractFindUsagesTest {
runTest("testData/findUsages/kotlin/conventions/invokeInObject.0.kt");
}
@TestMetadata("invokeInheritance.0.kt")
public void testInvokeInheritance() throws Exception {
runTest("testData/findUsages/kotlin/conventions/invokeInheritance.0.kt");
}
@TestMetadata("invokeWithVarArg.0.kt")
public void testInvokeWithVarArg() throws Exception {
runTest("testData/findUsages/kotlin/conventions/invokeWithVarArg.0.kt");
@@ -0,0 +1,17 @@
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction
// OPTIONS: usages
package pack
open class Base {
open operator fun invoke(){}
}
class Child: Base() {
override fun <caret>invoke(){}
}
fun test() {
val c = Child()
c()
c.invoke()
}
@@ -0,0 +1,11 @@
Checked type of c
Resolved Child()
Resolved c()
Searched references to c in non-Java files
Searched references to pack.Base
Searched references to pack.Child
Used plain search of pack.Base.invoke() in LocalSearchScope:
CLASS:Base
CLASS:Child
Used plain search of pack.Child.invoke() in LocalSearchScope:
CLASS:Child
@@ -0,0 +1,3 @@
Function call 16 c.invoke()
Function call 16 c.invoke()
Implicit 'invoke' 15 c()