Separated references to get/set methods on synthetic extension usage into separate PsiReference's to avoid putting usages like "x++" into "Dynamic usages" group

This commit is contained in:
Valentin Kipyatkov
2015-07-10 16:50:03 +03:00
parent f53a0b0536
commit d8d00a83bb
4 changed files with 137 additions and 98 deletions
@@ -114,6 +114,8 @@ public abstract class AbstractJetReference<T : JetElement>(element: T)
}
return context[BindingContext.AMBIGUOUS_LABEL_TARGET, reference]
}
override fun toString() = javaClass.getSimpleName() + ": " + expression.getText()
}
public abstract class JetSimpleReference<T : JetReferenceExpression>(expression: T) : AbstractJetReference<T>(expression) {
@@ -16,12 +16,16 @@
package org.jetbrains.kotlin.idea.references
import com.intellij.psi.*
import org.jetbrains.kotlin.psi.*
import com.intellij.util.ProcessingContext
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.*
import com.intellij.util.ProcessingContext
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
public class JetReferenceContributor() : PsiReferenceContributor() {
public override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
@@ -29,38 +33,78 @@ public class JetReferenceContributor() : PsiReferenceContributor() {
registerProvider(javaClass<JetSimpleNameExpression>()) {
JetSimpleNameReference(it)
}
registerMultiProvider(javaClass<JetNameReferenceExpression>()) {
if (it.getReferencedNameElementType() != JetTokens.IDENTIFIER) return@registerMultiProvider emptyArray()
when (it.access()) {
Access.READ -> arrayOf(SyntheticPropertyAccessorReference(it, true))
Access.WRITE -> arrayOf(SyntheticPropertyAccessorReference(it, false))
Access.READ_WRITE -> arrayOf(SyntheticPropertyAccessorReference(it, true), SyntheticPropertyAccessorReference(it, false))
}
}
registerProvider(javaClass<JetConstructorDelegationReferenceExpression>()) {
JetConstructorDelegationReference(it)
}
registerProvider(javaClass<JetCallExpression>()) {
JetInvokeFunctionReference(it)
}
registerProvider(javaClass<JetArrayAccessExpression>()) {
JetArrayAccessReference(it)
}
registerProvider(javaClass<JetForExpression>()) {
JetForLoopInReference(it)
}
registerProvider(javaClass<JetPropertyDelegate>()) {
JetPropertyDelegationMethodsReference(it)
}
registerProvider(javaClass<JetMultiDeclaration>()) {
JetMultiDeclarationReference(it)
}
registerProvider(javaClass<KDocName>()) {
KDocReference(it)
}
}
}
private fun <E : JetElement, R : AbstractJetReference<E>> PsiReferenceRegistrar.registerProvider(
elementClass: Class<E>,
factory: (E) -> R
) {
//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 <E : JetElement> PsiReferenceRegistrar.registerProvider(elementClass: Class<E>, factory: (E) -> JetReference) {
registerMultiProvider(elementClass, { arrayOf(factory(it)) })
}
private fun <E : JetElement> PsiReferenceRegistrar.registerMultiProvider(elementClass: Class<E>, factory: (E) -> Array<PsiReference>) {
registerReferenceProvider(PlatformPatterns.psiElement(elementClass), object: PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
@suppress("UNCHECKED_CAST")
return arrayOf(factory(element as E))
return factory(element as E)
}
})
}
@@ -21,8 +21,6 @@ import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.IncorrectOperationException
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
@@ -35,69 +33,21 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.plugin.references.SimpleNameReferenceExtension
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.synthetic.SyntheticExtensionPropertyDescriptor
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.constant
public class JetSimpleNameReference(
jetSimpleNameExpression: JetSimpleNameExpression
) : JetSimpleReference<JetSimpleNameExpression>(jetSimpleNameExpression) {
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
val targets = super.getTargetDescriptors(context)
if (targets.none { it is SyntheticExtensionPropertyDescriptor }) return targets
val newTargets = SmartList<DeclarationDescriptor>()
for (target in targets) {
if (!(target !is SyntheticExtensionPropertyDescriptor)) {
val access = access()
if (access == Access.READ || access == Access.READ_WRITE) {
newTargets.add(target.getMethod)
}
if (access == Access.WRITE || access == Access.READ_WRITE) {
newTargets.addIfNotNull(target.setMethod)
}
}
else {
newTargets.add(target)
}
}
return newTargets
}
//TODO: there should be some common util for that
private enum class Access {
READ, WRITE, READ_WRITE
}
private fun access(): Access {
var expression = myElement.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
}
class JetSimpleNameReference(expression: JetSimpleNameExpression) : JetSimpleReference<JetSimpleNameExpression>(expression) {
override fun isReferenceTo(element: PsiElement?): Boolean {
if (element != null) {
if (!canBeReferenceTo(element)) return false
val extensions = Extensions.getArea(element.getProject()).getExtensionPoint(
SimpleNameReferenceExtension.EP_NAME).getExtensions()
val extensions = Extensions.getArea(element.getProject()).getExtensionPoint(SimpleNameReferenceExtension.EP_NAME).getExtensions()
for (extension in extensions) {
val value = extension.isReferenceTo(this, element)
if (value != null) {
@@ -124,7 +74,7 @@ public class JetSimpleNameReference(
return true
}
public override fun handleElementRename(newElementName: String?): PsiElement? {
override fun handleElementRename(newElementName: String?): PsiElement {
if (!canRename()) throw IncorrectOperationException()
if (newElementName == null) return expression;
@@ -136,38 +86,15 @@ public class JetSimpleNameReference(
}
}
@suppress("NAME_SHADOWING")
var newElementName = newElementName!!
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
if (bindingContext[BindingContext.REFERENCE_TARGET, expression] is SyntheticExtensionPropertyDescriptor) {
if (Name.isValidIdentifier(newElementName)) {
val newNameAsName = Name.identifier(newElementName)
val newName = when (access()) {
Access.READ -> SyntheticExtensionPropertyDescriptor.propertyNameByGetMethodName(newNameAsName)
Access.WRITE -> SyntheticExtensionPropertyDescriptor.propertyNameBySetMethodName(newNameAsName)
Access.READ_WRITE -> SyntheticExtensionPropertyDescriptor.propertyNameByGetMethodName(newNameAsName)
?: SyntheticExtensionPropertyDescriptor.propertyNameBySetMethodName(newNameAsName)
} ?: return expression //TODO: handle the case when get/set becomes ordinary method
newElementName = newName.getIdentifier()
}
}
val psiFactory = JetPsiFactory(expression)
val element = when (expression.getReferencedNameElementType()) {
JetTokens.FIELD_IDENTIFIER -> psiFactory.createFieldIdentifier(newElementName)
else -> {
val extensions = Extensions.getArea(expression.getProject()).getExtensionPoint(
SimpleNameReferenceExtension.EP_NAME).getExtensions()
var handled: PsiElement? = null
for (extension in extensions) {
handled = extension.handleElementRename(this, psiFactory, newElementName)
if (handled != null) {
break
}
}
handled ?: psiFactory.createNameIdentifier(newElementName)
else -> {
Extensions.getArea(expression.getProject()).getExtensionPoint(SimpleNameReferenceExtension.EP_NAME).getExtensions()
.asSequence()
.map { it.handleElementRename(this, psiFactory, newElementName) }
.firstOrNull { it != null } ?: psiFactory.createNameIdentifier(newElementName)
}
}
@@ -176,6 +103,7 @@ public class JetSimpleNameReference(
val elementType = nameElement.getNode()?.getElementType()
val opExpression = PsiTreeUtil.getParentOfType<JetExpression>(expression, javaClass<JetUnaryExpression>(), javaClass<JetBinaryExpression>())
if (elementType is JetToken && OperatorConventions.getNameForOperationSymbol(elementType) != null && opExpression != null) {
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
val oldDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, expression]
val newExpression = OperatorToFunctionIntention.convert(opExpression)
newExpression.accept(object : JetTreeVisitorVoid() {
@@ -209,7 +137,7 @@ public class JetSimpleNameReference(
fun bindToElement(element: PsiElement, shorteningMode: ShorteningMode): PsiElement =
element.getKotlinFqName()?.let { fqName -> bindToFqName(fqName, shorteningMode) } ?: expression
public fun bindToFqName(fqName: FqName, shorteningMode: ShorteningMode = ShorteningMode.DELAYED_SHORTENING): PsiElement {
fun bindToFqName(fqName: FqName, shorteningMode: ShorteningMode = ShorteningMode.DELAYED_SHORTENING): PsiElement {
if (fqName.isRoot()) return expression
val newExpression = expression.changeQualifiedName(fqName).getQualifiedElementSelector() as JetSimpleNameExpression
@@ -231,9 +159,5 @@ public class JetSimpleNameReference(
return newExpression
}
override fun toString(): String {
return javaClass<JetSimpleNameReference>().getSimpleName() + ": " + expression.getText()
}
override fun getCanonicalText(): String = expression.getText()
}
@@ -0,0 +1,69 @@
/*
* 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.references
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.JetNameReferenceExpression
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.synthetic.SyntheticExtensionPropertyDescriptor
import org.jetbrains.kotlin.utils.addIfNotNull
class SyntheticPropertyAccessorReference(expression: JetNameReferenceExpression, val getter: Boolean) : JetSimpleReference<JetNameReferenceExpression>(expression) {
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
val descriptors = super.getTargetDescriptors(context)
if (descriptors.none { it is SyntheticExtensionPropertyDescriptor }) return emptyList()
val result = SmartList<FunctionDescriptor>()
for (descriptor in descriptors) {
if (descriptor is SyntheticExtensionPropertyDescriptor) {
if (getter) {
result.add(descriptor.getMethod)
}
else {
result.addIfNotNull(descriptor.setMethod)
}
}
}
return result
}
override fun getRangeInElement() = TextRange(0, expression.getTextLength())
override fun canRename() = true
override fun handleElementRename(newElementName: String?): PsiElement? {
if (!Name.isValidIdentifier(newElementName!!)) return expression
val newNameAsName = Name.identifier(newElementName)
val newName = if (getter)
SyntheticExtensionPropertyDescriptor.propertyNameByGetMethodName(newNameAsName)
else
SyntheticExtensionPropertyDescriptor.propertyNameBySetMethodName(newNameAsName)
if (newName == null) return expression //TODO: handle the case when get/set becomes ordinary method
val nameIdentifier = JetPsiFactory(expression).createNameIdentifier(newName.getIdentifier())
expression.getReferencedNameElement().replace(nameIdentifier)
return expression
}
}