Implement rebinding of simple name references

This commit is contained in:
Alexey Sedunov
2014-02-19 19:56:25 +04:00
parent 86619f5f28
commit d439de4330
7 changed files with 247 additions and 3 deletions
@@ -3,4 +3,7 @@
name='com.intellij.refactoring.MultiFileTestCase.PerformAction void performAction(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile) 0'> name='com.intellij.refactoring.MultiFileTestCase.PerformAction void performAction(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item name='com.intellij.refactoring.RefactoringHelper void performOperation(com.intellij.openapi.project.Project, T) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root> </root>
@@ -19,6 +19,7 @@ package org.jetbrains.jet.lang.psi;
import com.intellij.lang.ASTNode; import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.util.SmartList; import com.intellij.util.SmartList;
import jet.runtime.typeinfo.KotlinSignature;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.JetNodeTypes; import org.jetbrains.jet.JetNodeTypes;
@@ -85,6 +86,7 @@ public class JetCallExpression extends JetReferenceExpression implements JetCall
return result; return result;
} }
@KotlinSignature("fun getValueArguments(): MutableList<out ValueArgument>")
@Override @Override
@NotNull @NotNull
public List<? extends ValueArgument> getValueArguments() { public List<? extends ValueArgument> getValueArguments() {
@@ -39,8 +39,6 @@ import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.PsiSearchScopeUtil import com.intellij.psi.search.PsiSearchScopeUtil
import org.jetbrains.jet.lang.psi.JetClassBody import org.jetbrains.jet.lang.psi.JetClassBody
import org.jetbrains.jet.lang.psi.JetParameterList import org.jetbrains.jet.lang.psi.JetParameterList
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.lang.psi.JetObjectDeclaration import org.jetbrains.jet.lang.psi.JetObjectDeclaration
import org.jetbrains.jet.lang.psi.JetNamedFunction import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.psi.JetProperty import org.jetbrains.jet.lang.psi.JetProperty
@@ -49,6 +47,14 @@ import org.jetbrains.jet.lang.psi.JetPropertyAccessor
import org.jetbrains.jet.lang.psi.JetParameter import org.jetbrains.jet.lang.psi.JetParameter
import com.intellij.psi.PsiParameterList import com.intellij.psi.PsiParameterList
import com.intellij.psi.PsiParameter import com.intellij.psi.PsiParameter
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.psi.JetUserType
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.psi.JetCallExpression
import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMember
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
fun PsiElement.getParentByTypesAndPredicate<T: PsiElement>( fun PsiElement.getParentByTypesAndPredicate<T: PsiElement>(
strict : Boolean = false, vararg parentClasses : Class<T>, predicate: (T) -> Boolean strict : Boolean = false, vararg parentClasses : Class<T>, predicate: (T) -> Boolean
@@ -210,3 +216,64 @@ fun PsiElement.parameterIndex(): Int {
else -> -1 else -> -1
} }
} }
/**
* Returns enclosing qualifying element for given [[JetSimpleNameExpression]]
* ([[JetQualifiedExpression]] or [[JetUserType]] or original expression)
*/
fun JetSimpleNameExpression.getQualifiedElement(): JetElement {
val baseExpression = (getParent() as? JetCallExpression) ?: this
val parent = baseExpression.getParent()
return when (parent) {
is JetQualifiedExpression -> if (parent.getSelectorExpression().isAncestor(baseExpression)) parent else baseExpression
is JetUserType -> if (parent.getReferenceExpression().isAncestor(baseExpression)) parent else baseExpression
else -> baseExpression
}
}
/**
* Returns rightmost selector of the qualified element (null if there is no such selector)
*/
fun JetElement.getQualifiedElementSelector(): JetElement? {
return when (this) {
is JetSimpleNameExpression -> this
is JetQualifiedExpression -> {
val selector = getSelectorExpression()
if (selector is JetCallExpression) selector.getCalleeExpression() else selector
}
is JetUserType -> getReferenceExpression()
else -> this
}
}
/**
* Returns outermost qualified element ([[JetQualifiedExpression]] or [[JetUserType]]) in the non-interleaving chain
* of qualified elements which enclose given expression
* If there is no such elements original expression is returned
*/
fun JetSimpleNameExpression.getOutermostNonInterleavingQualifiedElement(): JetElement {
var element = ((getParent() as? JetCallExpression) ?: this).getParent()
if (element !is JetQualifiedExpression && element !is JetUserType) return this
while (true) {
val parent = element!!.getParent()
if (parent !is JetQualifiedExpression && parent !is JetUserType) return element as JetElement
element = parent
}
}
/**
* Returns FqName for given declaration (either Java or Kotlin)
*/
fun PsiElement.getFqName(): FqName? {
return when (this) {
is PsiPackage -> FqName(getQualifiedName())
is PsiClass -> getQualifiedName()?.let { FqName(it) }
is PsiMember -> getName()?.let { name ->
val prefix = getContainingClass()?.getQualifiedName()
FqName(if (prefix != null) "$prefix.$name" else name)
}
is JetNamedDeclaration -> JetPsiUtil.getFQName(this)
else -> null
}
}
+1
View File
@@ -166,6 +166,7 @@
implementationClass="org.jetbrains.jet.plugin.codeInsight.surroundWith.statement.KotlinStatementSurroundDescriptor"/> implementationClass="org.jetbrains.jet.plugin.codeInsight.surroundWith.statement.KotlinStatementSurroundDescriptor"/>
<lang.unwrapDescriptor language="jet" implementationClass="org.jetbrains.jet.plugin.codeInsight.unwrap.KotlinUnwrapDescriptor"/> <lang.unwrapDescriptor language="jet" implementationClass="org.jetbrains.jet.plugin.codeInsight.unwrap.KotlinUnwrapDescriptor"/>
<quoteHandler fileType="Kotlin" className="org.jetbrains.jet.plugin.editor.KotlinQuoteHandler"/> <quoteHandler fileType="Kotlin" className="org.jetbrains.jet.plugin.editor.KotlinQuoteHandler"/>
<refactoring.helper implementation="org.jetbrains.jet.plugin.codeInsight.KotlinShortenReferencesRefactoringHelper"/>
<refactoring.moveHandler implementation="org.jetbrains.jet.plugin.refactoring.move.JetMoveFilesOrDirectoriesHandler"/> <refactoring.moveHandler implementation="org.jetbrains.jet.plugin.refactoring.move.JetMoveFilesOrDirectoriesHandler"/>
<refactoring.copyHandler implementation="org.jetbrains.jet.plugin.refactoring.copy.JetCopyClassHandler"/> <refactoring.copyHandler implementation="org.jetbrains.jet.plugin.refactoring.copy.JetCopyClassHandler"/>
<refactoring.changeSignatureUsageProcessor implementation="org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureUsageProcessor"/> <refactoring.changeSignatureUsageProcessor implementation="org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureUsageProcessor"/>
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2014 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.jet.plugin.codeInsight
import com.intellij.refactoring.RefactoringHelper
import com.intellij.usageView.UsageInfo
import com.intellij.openapi.project.Project
import java.util.HashSet
import com.intellij.openapi.util.Key
import com.intellij.openapi.application.ApplicationManager
import com.intellij.psi.SmartPsiElementPointer
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.plugin.refactoring.changeQualifiedName
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.jet.lang.psi.psiUtil.getOutermostNonInterleavingQualifiedElement
public class KotlinShortenReferencesRefactoringHelper: RefactoringHelper<Set<ReferenceBindRequest>> {
override fun prepareOperation(usages: Array<out UsageInfo>?): Set<ReferenceBindRequest>? {
if (usages != null && usages.isNotEmpty()) {
ApplicationManager.getApplication()!!.runWriteAction { usages[0].getProject().getElementsToShorten(true)!!.clear() }
}
return null
}
override fun performOperation(project: Project, operationData: Set<ReferenceBindRequest>?) {
ApplicationManager.getApplication()!!.runWriteAction {
project.getElementsToShorten(false)?.let { bindRequests ->
project.putUserData(ELEMENTS_TO_SHORTEN_KEY, null)
ShortenReferences.process(
bindRequests
.map() { req -> req.process()?.getOutermostNonInterleavingQualifiedElement() }
.filterNotNull()
)
}
}
}
}
private val ELEMENTS_TO_SHORTEN_KEY = Key.create<MutableSet<ReferenceBindRequest>>("ELEMENTS_TO_SHORTEN_KEY")
class ReferenceBindRequest(val refExpression: SmartPsiElementPointer<JetSimpleNameExpression>, val fqName: FqName) {
fun process(): JetSimpleNameExpression? {
fun bindToFqName(expression: JetSimpleNameExpression, fqName: FqName): JetSimpleNameExpression {
val qualifier = expression.changeQualifiedName(fqName)
val newExpression = qualifier.getQualifiedElementSelector() as JetSimpleNameExpression?
assert(newExpression != null) { "No selector in qualified element" }
return newExpression!!
}
val originalExpression = refExpression.getElement()
if (originalExpression == null) return null
return bindToFqName(originalExpression, fqName)
}
}
private fun Project.getElementsToShorten(createIfNeeded: Boolean): MutableSet<ReferenceBindRequest>? {
var elementsToShorten = getUserData(ELEMENTS_TO_SHORTEN_KEY)
if (createIfNeeded && elementsToShorten == null) {
elementsToShorten = HashSet()
putUserData(ELEMENTS_TO_SHORTEN_KEY, elementsToShorten)
}
return elementsToShorten
}
public fun Project.addReferenceBindRequest(request: ReferenceBindRequest) {
assert (ApplicationManager.getApplication()!!.isWriteAccessAllowed(), "Write access needed")
getElementsToShorten(true)!!.add(request)
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2014 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.jet.plugin.refactoring
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement
import org.jetbrains.jet.lang.psi.JetUserType
import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN
/**
* Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName
* Result is either the same as original element, or [[JetQualifiedExpression]], or [[JetUserType]]
* Note that FqName may not be empty
*/
fun JetSimpleNameExpression.changeQualifiedName(fqName: FqName): JetElement {
assert (!fqName.isRoot(), "Can't set empty FqName for element $this")
val project = getProject()
val shortName = fqName.shortName().asString()
val fqNameBase = (getParent() as? JetCallExpression)?.let { parent ->
val callCopy = parent.copy() as JetCallExpression
callCopy.getCalleeExpression()!!.replace(JetPsiFactory.createSimpleName(project, shortName)).getParent()!!.getText()
} ?: shortName
val text = if (!fqName.isOneSegmentFQN()) "${fqName.parent().asString()}.$fqNameBase" else fqNameBase
val elementToReplace = getQualifiedElement()
return when (elementToReplace) {
is JetUserType -> elementToReplace.replace(JetPsiFactory.createType(project, text).getTypeElement()!!)
else -> elementToReplace.replace(JetPsiFactory.createExpression(project, text))
} as JetElement
}
@@ -19,15 +19,19 @@ package org.jetbrains.jet.plugin.references;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IElementType;
import com.intellij.util.IncorrectOperationException; import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.psi.JetPsiFactory;
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; import org.jetbrains.jet.lang.psi.JetSimpleNameExpression;
import org.jetbrains.jet.lang.psi.psiUtil.PsiUtilPackage;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.codeInsight.CodeInsightPackage;
import org.jetbrains.jet.plugin.codeInsight.ReferenceBindRequest;
public class JetSimpleNameReference extends JetSimpleReference<JetSimpleNameExpression> { public class JetSimpleNameReference extends JetSimpleReference<JetSimpleNameExpression> {
public JetSimpleNameReference(@NotNull JetSimpleNameExpression jetSimpleNameExpression) { public JetSimpleNameReference(@NotNull JetSimpleNameExpression jetSimpleNameExpression) {
super(jetSimpleNameExpression); super(jetSimpleNameExpression);
} }
@@ -55,6 +59,35 @@ public class JetSimpleNameReference extends JetSimpleReference<JetSimpleNameExpr
return getExpression().getReferencedNameElement().replace(element); return getExpression().getReferencedNameElement().replace(element);
} }
// By default reference binding is delayed
@NotNull
@Override
public PsiElement bindToElement(@NotNull PsiElement element) {
return bindToElement(element, false);
}
@NotNull
public PsiElement bindToElement(@NotNull PsiElement element, boolean bindImmediately) {
Project project = element.getProject();
JetSimpleNameExpression currentExpression = getExpression();
FqName fqName = PsiUtilPackage.getFqName(element);
if (fqName == null) return currentExpression;
ReferenceBindRequest bindRequest = new ReferenceBindRequest(
SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(getExpression()),
fqName
);
if (bindImmediately) {
JetSimpleNameExpression newExpression = bindRequest.process();
return (newExpression != null) ? newExpression : currentExpression;
}
CodeInsightPackage.addReferenceBindRequest(project, bindRequest);
return currentExpression;
}
@Override @Override
public String toString() { public String toString() {
return JetSimpleNameReference.class.getSimpleName() + ": " + getExpression().getText(); return JetSimpleNameReference.class.getSimpleName() + ": " + getExpression().getText();