From 3a3f672cd515f813c2e3e5b450acffeadd6980a1 Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Thu, 24 Jun 2021 15:23:47 +0200 Subject: [PATCH] Handle cases when operator keyword is implicitly inherited Relates to #KTIJ-18868 GitOrigin-RevId: 815c75723c63daa8646eabcda4d31608336894cb --- .../kotlin/idea/search/searchUtil.kt | 19 ++++++++++++++----- .../operators/OperatorReferenceSearcher.kt | 12 ++++++------ .../refactoring/rename/renameConflictUtils.kt | 16 +++++++++++++++- .../findUsages/FindUsagesTestGenerated.java | 5 +++++ .../kotlin/conventions/invokeInheritance.0.kt | 17 +++++++++++++++++ .../kotlin/conventions/invokeInheritance.log | 11 +++++++++++ .../conventions/invokeInheritance.results.txt | 3 +++ 7 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.0.kt create mode 100644 plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.log create mode 100644 plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.results.txt diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt index 4724b9e554fd..2faaf550f0d5 100644 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt @@ -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() \ No newline at end of file +fun PsiElement.getKotlinFqName(): FqName? = getKotlinFqNameOriginal() + +fun KtElement.isPotentiallyOperator(): Boolean { + val namedFunction = safeAs() ?: 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 +} \ No newline at end of file diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt index 385902250b08..6a473b9859c7 100644 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/usagesSearch/operators/OperatorReferenceSearcher.kt @@ -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( if (!options.searchForOperatorConventions) return null // Java has no operator modifier - val operator = declaration !is KtElement || - declaration.safeAs()?.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( 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( 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( 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 diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt index 8ac71a8fbd2d..6e207ebcf72c 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt @@ -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()?.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>(newUsages) } } + +internal fun KtElement.isOperator(): Boolean { + val namedFunction = safeAs() ?: 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()?.hasModifier(KtTokens.OPERATOR_KEYWORD) == true + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java index b71ecd58d975..930de12d762d 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/findUsages/FindUsagesTestGenerated.java @@ -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"); diff --git a/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.0.kt b/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.0.kt new file mode 100644 index 000000000000..08a7e7e23595 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.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 invoke(){} +} + +fun test() { + val c = Child() + c() + c.invoke() +} diff --git a/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.log b/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.log new file mode 100644 index 000000000000..0086f352d3b4 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.log @@ -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 diff --git a/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.results.txt b/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.results.txt new file mode 100644 index 000000000000..feacd1c3720e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/findUsages/kotlin/conventions/invokeInheritance.results.txt @@ -0,0 +1,3 @@ +Function call 16 c.invoke() +Function call 16 c.invoke() +Implicit 'invoke' 15 c() \ No newline at end of file