From 5e97387e8d4ccc8bce54fa923b5929cc56b70dd2 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 17 Aug 2015 22:01:15 +0300 Subject: [PATCH] Public utility to detect read/write access for an expression Implemented ReadWriteAccessDetector which gives read/write access in Highlight Usages and Find Usages + More correct UsageType detection --- .../kotlin/psi/psiUtil/jetPsiUtil.kt | 24 +++++++ .../kotlin/idea/findUsages/UsageTypeUtils.kt | 17 ++--- .../references/JetReferenceContributor.kt | 35 ++-------- .../KotlinReadWriteAccessDetector.kt | 67 +++++++++++++++++++ .../idea/search/usagesSearch/searchHelpers.kt | 23 +++++-- .../kotlin/idea/search/usagesSearch/utils.kt | 24 ------- idea/src/META-INF/plugin.xml | 2 + .../SyntheticProperties.results.txt | 2 +- .../kotlin/variable/readAccess.0.kt | 13 ++++ .../kotlin/variable/readAccess.results.txt | 6 ++ .../kotlin/variable/writeAccess.0.kt | 13 ++++ .../kotlin/variable/writeAccess.results.txt | 5 ++ .../JetFindUsagesTestGenerated.java | 21 ++++++ 13 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReadWriteAccessDetector.kt create mode 100644 idea/testData/findUsages/kotlin/variable/readAccess.0.kt create mode 100644 idea/testData/findUsages/kotlin/variable/readAccess.results.txt create mode 100644 idea/testData/findUsages/kotlin/variable/writeAccess.0.kt create mode 100644 idea/testData/findUsages/kotlin/variable/writeAccess.results.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt index df387d2ca5a..a10ff664fe5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.types.expressions.OperatorConventions +import org.jetbrains.kotlin.utils.addToStdlib.constant import java.util.ArrayList import java.util.Collections import kotlin.test.assertTrue @@ -307,6 +308,29 @@ public inline fun flatMapDescendantsOfTypeVisitor(ac return forEachDescendantOfTypeVisitor { accumulator.addAll(map(it)) } } +// ----------- Read/write access ----------------------------------------------------------------------------------------------------------------------- + +public enum class ReferenceAccess { + READ, WRITE, READ_WRITE +} + +public fun JetExpression.readWriteAccess(): ReferenceAccess { + var expression = getQualifiedExpressionForSelectorOrThis() + while (expression.parent is JetParenthesizedExpression) { + expression = expression.parent as JetParenthesizedExpression + } + + val assignment = expression.getAssignmentByLHS() + if (assignment != null) { + return if (assignment.operationToken == JetTokens.EQ) ReferenceAccess.WRITE else ReferenceAccess.READ_WRITE + } + + return if ((expression.parent as? JetUnaryExpression)?.operationToken in constant { setOf(JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) }) + ReferenceAccess.READ_WRITE + else + ReferenceAccess.READ +} + // ----------- Other ----------------------------------------------------------------------------------------------------------------------- public fun JetClassOrObject.effectiveDeclarations(): List { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/findUsages/UsageTypeUtils.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/findUsages/UsageTypeUtils.kt index 124068962d1..58c1d494a12 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/findUsages/UsageTypeUtils.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/findUsages/UsageTypeUtils.kt @@ -17,10 +17,7 @@ package org.jetbrains.kotlin.idea.findUsages import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.lexer.JetTokens -import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.descriptors.ConstructorDescriptor @@ -33,12 +30,12 @@ import com.intellij.psi.PsiReferenceExpression import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.findUsages.UsageTypeEnum.* import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch import org.jetbrains.kotlin.idea.references.JetArrayAccessReference import org.jetbrains.kotlin.idea.references.JetInvokeFunctionReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.unwrappedTargets import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.DescriptorUtils public object UsageTypeUtils { @@ -149,15 +146,9 @@ public object UsageTypeUtils { } } - return when { - (refExpr.getParentOfTypesAndPredicate(false, javaClass()) { JetPsiUtil.isAssignment(it) }) - ?.getLeft().isAncestor(refExpr) -> - WRITE - - refExpr.getNonStrictParentOfType() != null -> - READ - - else -> null + return when (refExpr.readWriteAccess()) { + ReferenceAccess.READ -> READ + ReferenceAccess.WRITE, ReferenceAccess.READ_WRITE -> WRITE } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt index 7dfa4ec0d87..54a9b1228d9 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt @@ -23,9 +23,8 @@ import org.jetbrains.kotlin.idea.kdoc.KDocReference import org.jetbrains.kotlin.kdoc.psi.impl.KDocName import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS -import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis -import org.jetbrains.kotlin.utils.addToStdlib.constant +import org.jetbrains.kotlin.psi.psiUtil.ReferenceAccess +import org.jetbrains.kotlin.psi.psiUtil.readWriteAccess public class JetReferenceContributor() : PsiReferenceContributor() { public override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { @@ -37,10 +36,10 @@ public class JetReferenceContributor() : PsiReferenceContributor() { registerMultiProvider(javaClass()) { if (it.getReferencedNameElementType() != JetTokens.IDENTIFIER) return@registerMultiProvider emptyArray() - when (it.access()) { - Access.READ -> arrayOf(SyntheticPropertyAccessorReference.Getter(it)) - Access.WRITE -> arrayOf(SyntheticPropertyAccessorReference.Setter(it)) - Access.READ_WRITE -> arrayOf(SyntheticPropertyAccessorReference.Getter(it), SyntheticPropertyAccessorReference.Setter(it)) + when (it.readWriteAccess()) { + ReferenceAccess.READ -> arrayOf(SyntheticPropertyAccessorReference.Getter(it)) + ReferenceAccess.WRITE -> arrayOf(SyntheticPropertyAccessorReference.Setter(it)) + ReferenceAccess.READ_WRITE -> arrayOf(SyntheticPropertyAccessorReference.Getter(it), SyntheticPropertyAccessorReference.Setter(it)) } } @@ -74,28 +73,6 @@ public class JetReferenceContributor() : PsiReferenceContributor() { } } - //TODO: there should be some common util for that - private enum class Access { - READ, WRITE, READ_WRITE - } - - private fun JetSimpleNameExpression.access(): Access { - var expression = getQualifiedExpressionForSelectorOrThis() - while (expression.getParent() is JetParenthesizedExpression) { - expression = expression.getParent() as JetParenthesizedExpression - } - - val assignment = expression.getAssignmentByLHS() - if (assignment != null) { - return if (assignment.getOperationToken() == JetTokens.EQ) Access.WRITE else Access.READ_WRITE - } - - return if ((expression.getParent() as? JetUnaryExpression)?.getOperationToken() in constant { setOf(JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) }) - Access.READ_WRITE - else - Access.READ - } - private fun PsiReferenceRegistrar.registerProvider(elementClass: Class, factory: (E) -> JetReference) { registerMultiProvider(elementClass, { arrayOf(factory(it)) }) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReadWriteAccessDetector.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReadWriteAccessDetector.kt new file mode 100644 index 00000000000..e07fc7f4600 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinReadWriteAccessDetector.kt @@ -0,0 +1,67 @@ +/* + * 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.search.ideaExtensions + +import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import org.jetbrains.kotlin.asJava.KotlinLightMethod +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.ReferenceAccess +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.readWriteAccess + +public class KotlinReadWriteAccessDetector : ReadWriteAccessDetector() { + override fun isReadWriteAccessible(element: PsiElement) = element is JetVariableDeclaration || element is JetParameter + + override fun isDeclarationWriteAccess(element: PsiElement) = isReadWriteAccessible(element) + + override fun getReferenceAccess(referencedElement: PsiElement, reference: PsiReference): ReadWriteAccessDetector.Access { + if (!isReadWriteAccessible(referencedElement)) { + return ReadWriteAccessDetector.Access.Read + } + + val refTarget = reference.resolve() + if (refTarget is KotlinLightMethod) { + val origin = refTarget.getOrigin() + val declaration: JetNamedDeclaration = when (origin) { + is JetPropertyAccessor -> origin.getNonStrictParentOfType() + is JetProperty, is JetParameter -> origin as JetNamedDeclaration + else -> null + } ?: return ReadWriteAccessDetector.Access.ReadWrite + + return when (refTarget.name) { + JvmAbi.getterName(declaration.name!!) -> return ReadWriteAccessDetector.Access.Read + JvmAbi.setterName(declaration.name!!) -> return ReadWriteAccessDetector.Access.Write + else -> ReadWriteAccessDetector.Access.ReadWrite + } + } + + return getExpressionAccess(reference.element) + } + + override fun getExpressionAccess(expression: PsiElement): ReadWriteAccessDetector.Access { + if (expression !is JetExpression) return ReadWriteAccessDetector.Access.Read + + return when (expression.readWriteAccess()) { + ReferenceAccess.READ -> ReadWriteAccessDetector.Access.Read + ReferenceAccess.WRITE -> ReadWriteAccessDetector.Access.Write + ReferenceAccess.READ_WRITE -> ReadWriteAccessDetector.Access.ReadWrite + } + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt index f73a2293971..a6f4c135267 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/searchHelpers.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.search.usagesSearch +import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiReference import com.intellij.psi.search.GlobalSearchScope @@ -30,6 +31,7 @@ import org.jetbrains.kotlin.idea.references.JetSimpleNameReference import org.jetbrains.kotlin.idea.references.matchesTarget import org.jetbrains.kotlin.idea.search.KOTLIN_NAMED_ARGUMENT_SEARCH_CONTEXT import org.jetbrains.kotlin.idea.search.and +import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.False import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchFilter.True import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope @@ -237,8 +239,6 @@ class FunctionUsagesSearchHelper( } } -val isPropertyReadOnlyUsage = (PsiReference::isPropertyReadOnlyUsage).searchFilter - // Used for JetProperty and JetParameter class PropertyUsagesSearchHelper( public val readUsages: Boolean = true, @@ -294,12 +294,21 @@ class PropertyUsagesSearchHelper( } override fun makeFilter(target: UsagesSearchTarget): UsagesSearchFilter { - val readWriteFilter = when { - readUsages && writeUsages -> True - readUsages -> isPropertyReadOnlyUsage - writeUsages -> !isPropertyReadOnlyUsage - else -> False + val readWriteFilter = object: UsagesSearchFilter { + val detector = KotlinReadWriteAccessDetector() + + override fun accepts(ref: PsiReference, item: UsagesSearchRequestItem): Boolean { + if (readUsages == writeUsages) return readUsages + + val access = detector.getReferenceAccess(target.element, ref) + return when (access) { + ReadWriteAccessDetector.Access.Read -> readUsages + ReadWriteAccessDetector.Access.Write -> writeUsages + ReadWriteAccessDetector.Access.ReadWrite -> true + } + } } + var result = isTargetOrOverrideUsage and readWriteFilter and isFilteredImport if (!namedArgumentUsages) { result = result and !(PsiReference::isNamedArgumentUsage).searchFilter diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt index 6641dca8722..6544c80caf8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/utils.kt @@ -22,18 +22,14 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.PsiReference import com.intellij.psi.search.SearchScope import org.jetbrains.kotlin.asJava.KotlinLightElement -import org.jetbrains.kotlin.asJava.KotlinLightMethod import org.jetbrains.kotlin.asJava.KotlinNoOriginLightMethod import org.jetbrains.kotlin.asJava.unwrapped import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor -import org.jetbrains.kotlin.idea.findUsages.UsageTypeEnum -import org.jetbrains.kotlin.idea.findUsages.UsageTypeUtils import org.jetbrains.kotlin.idea.references.unwrappedTargets import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors -import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.resolve.BindingContext @@ -226,23 +222,3 @@ fun PsiReference.isCallableOverrideUsage(declaration: JetNamedDeclaration): Bool && OverrideResolver.overrides(usageDescriptor, targetDescriptor) } } - - -// Check if reference resolves to property getter -// Works for JetProperty and JetParameter -fun PsiReference.isPropertyReadOnlyUsage(): Boolean { - if (UsageTypeUtils.getUsageType(getElement()) == UsageTypeEnum.READ) return true - - val refTarget = resolve() - if (refTarget is KotlinLightMethod) { - val origin = refTarget.getOrigin() - val declaration: JetNamedDeclaration? = when (origin) { - is JetPropertyAccessor -> origin.getNonStrictParentOfType() - is JetProperty, is JetParameter -> origin as JetNamedDeclaration - else -> null - } - return declaration != null && refTarget.getName() == JvmAbi.getterName(declaration.getName()!!) - } - - return false -} diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 7df0566acaf..6f9292eafde 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -516,6 +516,8 @@ + + v = 1 + v = 2 + print(v) + ++v + v-- + print(-v) + v += 1 + v -= 1 +} diff --git a/idea/testData/findUsages/kotlin/variable/readAccess.results.txt b/idea/testData/findUsages/kotlin/variable/readAccess.results.txt new file mode 100644 index 00000000000..03bb4ee12a0 --- /dev/null +++ b/idea/testData/findUsages/kotlin/variable/readAccess.results.txt @@ -0,0 +1,6 @@ +Value read (10: 12) print(-v) +Value read (7: 11) print(v) +Value write (11: 5) v += 1 +Value write (12: 5) v -= 1 +Value write (8: 7) ++v +Value write (9: 5) v-- \ No newline at end of file diff --git a/idea/testData/findUsages/kotlin/variable/writeAccess.0.kt b/idea/testData/findUsages/kotlin/variable/writeAccess.0.kt new file mode 100644 index 00000000000..a4652acb3ff --- /dev/null +++ b/idea/testData/findUsages/kotlin/variable/writeAccess.0.kt @@ -0,0 +1,13 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.JetProperty +// OPTIONS: usages +// OPTIONS: skipRead +fun foo() { + var v = 1 + v = 2 + print(v) + ++v + v-- + print(-v) + v += 1 + v -= 1 +} diff --git a/idea/testData/findUsages/kotlin/variable/writeAccess.results.txt b/idea/testData/findUsages/kotlin/variable/writeAccess.results.txt new file mode 100644 index 00000000000..4c1c35b7799 --- /dev/null +++ b/idea/testData/findUsages/kotlin/variable/writeAccess.results.txt @@ -0,0 +1,5 @@ +Value write (11: 5) v += 1 +Value write (12: 5) v -= 1 +Value write (6: 5) v = 2 +Value write (8: 7) ++v +Value write (9: 5) v-- \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/JetFindUsagesTestGenerated.java b/idea/tests/org/jetbrains/kotlin/findUsages/JetFindUsagesTestGenerated.java index 81593666055..270ae56540c 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/JetFindUsagesTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/findUsages/JetFindUsagesTestGenerated.java @@ -1026,6 +1026,27 @@ public class JetFindUsagesTestGenerated extends AbstractJetFindUsagesTest { doTest(fileName); } } + + @TestMetadata("idea/testData/findUsages/kotlin/variable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Variable extends AbstractJetFindUsagesTest { + public void testAllFilesPresentInVariable() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/findUsages/kotlin/variable"), Pattern.compile("^(.+)\\.0\\.kt$"), true); + } + + @TestMetadata("readAccess.0.kt") + public void testReadAccess() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/variable/readAccess.0.kt"); + doTest(fileName); + } + + @TestMetadata("writeAccess.0.kt") + public void testWriteAccess() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/findUsages/kotlin/variable/writeAccess.0.kt"); + doTest(fileName); + } + } } @TestMetadata("idea/testData/findUsages/java")