Rename: Implement Rename conflict analysis for classes. Qualify class references to resove rename conflicts when possible

#KT-8611 Fixed
 #KT-8562 Fixed
(cherry picked from commit 8989ccc)
This commit is contained in:
Alexey Sedunov
2016-06-14 15:16:59 +03:00
parent 77b0bb9849
commit c9f659e89b
32 changed files with 301 additions and 7 deletions
@@ -100,7 +100,7 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(KtCla
val classFromText = KtPsiFactory(project).createClass(builder.asString())
val body = sealedClass.getOrCreateBody()
val klass = body.addBefore(classFromText, body.rBrace) as KtClass
PsiElementRenameHandler.rename(klass, project, sealedClass, editor)
runInteractiveRename(klass, project, sealedClass, editor)
chooseAndImplementMethods(project, klass, editor)
}
@@ -142,11 +142,16 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(KtCla
baseClass, name, visibility)
val classFromText = factory.createClass(builder.asString())
val klass = container.parent.addAfter(classFromText, container) as KtClass
PsiElementRenameHandler.rename(klass, project, container, editor)
runInteractiveRename(klass, project, container, editor)
chooseAndImplementMethods(project, klass, editor)
}
}
private fun runInteractiveRename(klass: KtClass, project: Project, container: KtClassOrObject, editor: Editor) {
if (ApplicationManager.getApplication().isUnitTestMode) return
PsiElementRenameHandler.rename(klass, project, container, editor)
}
private fun chooseSubclassToCreate(baseClass: KtClass, baseName: String): CreateClassDialog? {
val sourceDir = baseClass.containingFile.containingDirectory
@@ -19,16 +19,23 @@ package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.asJava.KtLightClass
import org.jetbrains.kotlin.asJava.KtLightClassForExplicitDeclaration
import org.jetbrains.kotlin.asJava.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.SmartList
class RenameKotlinClassProcessor : RenameKotlinPsiProcessor() {
override fun canProcessElement(element: PsiElement): Boolean {
@@ -68,6 +75,23 @@ class RenameKotlinClassProcessor : RenameKotlinPsiProcessor() {
return bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element] != null
}
override fun findCollisions(
element: PsiElement,
newName: String?,
allRenames: MutableMap<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
if (newName == null) return
val declaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return
val descriptor = declaration.resolveToDescriptor() as ClassDescriptor
val collisions = SmartList<UsageInfo>()
checkRedeclarations(descriptor, newName, collisions)
checkOriginalUsagesRetargeting(declaration, newName, result, collisions)
checkNewNameUsagesRetargeting(declaration, newName, collisions)
result += collisions
}
private fun getClassOrObject(element: PsiElement?): PsiElement? = when (element) {
is KtLightClass ->
when (element) {
@@ -82,4 +106,10 @@ class RenameKotlinClassProcessor : RenameKotlinPsiProcessor() {
else ->
element as? KtClassOrObject
}
override fun renameElement(element: PsiElement, newName: String?, usages: Array<out UsageInfo>, listener: RefactoringElementListener?) {
super.renameElement(element, newName, usages, listener)
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
}
}
@@ -311,7 +311,7 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
refKindUsages[UsageKind.SIMPLE_PROPERTY_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(),
null)
usages.forEach { (it as? UsageInfoWithReplacement)?.apply() }
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
dropOverrideKeywordIfNecessary(element)
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getAllAccessibleVariables
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
@@ -47,13 +48,17 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
internal fun ResolvedCall<*>.noReceivers() = dispatchReceiver == null && extensionReceiver == null
@@ -81,7 +86,12 @@ internal fun checkRedeclarations(
is PackageFragmentDescriptor -> containingDescriptor.getMemberScope()
else -> return
}
containingScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES) { it.asString() == newName }.firstOrNull()?.let { candidateDescriptor ->
val descriptorKindFilter = when (descriptor) {
is ClassDescriptor -> DescriptorKindFilter.CLASSIFIERS
is PropertyDescriptor -> DescriptorKindFilter.VARIABLES
else -> return
}
containingScope.getDescriptorsFiltered(descriptorKindFilter) { it.asString() == newName }.firstOrNull()?.let { candidateDescriptor ->
val candidate = (candidateDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtNamedDeclaration ?: return
val what = candidate.renderDescription().capitalize()
val where = candidate.representativeContainer()?.renderDescription() ?: return
@@ -97,6 +107,7 @@ private fun LexicalScope.getRelevantDescriptors(
val nameAsName = Name.identifier(name)
return when (declaration) {
is KtProperty, is KtParameter, is PsiField -> getAllAccessibleVariables(nameAsName)
is KtClassOrObject, is PsiClass -> findClassifier(nameAsName, NoLookupLocation.FROM_IDE).singletonOrEmptyList()
else -> emptyList()
}
}
@@ -141,7 +152,28 @@ private fun checkUsagesRetargeting(
val psiFactory = KtPsiFactory(declaration)
val resolvedCall = refElement.getResolvedCall(context) ?: continue
val resolvedCall = refElement.getResolvedCall(context)
if (resolvedCall == null) {
val typeReference = refElement.getStrictParentOfType<KtTypeReference>() ?: continue
val referencedClass = context[BindingContext.TYPE, typeReference]?.constructor?.declarationDescriptor ?: continue
val referencedClassFqName = FqName(IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(referencedClass))
val newFqName = if (isNewName) referencedClassFqName.parent().child(Name.identifier(name)) else referencedClassFqName
val fakeVar = psiFactory.createDeclaration<KtProperty>("val __foo__: ${newFqName.asString()}")
val newContext = fakeVar.analyzeInContext(scope, refElement)
val referencedClassInNewContext = newContext[BindingContext.TYPE, fakeVar.typeReference!!]?.constructor?.declarationDescriptor
val candidateText = referencedClassInNewContext?.canonicalRender()
if (referencedClassInNewContext == null
|| ErrorUtils.isError(referencedClassInNewContext)
|| referencedClass.canonicalRender() == candidateText
|| accessibleDescriptors.any { it.canonicalRender() == candidateText }) {
usageIterator.set(UsageInfoWithFqNameReplacement(refElement, declaration, newFqName))
}
else {
reportShadowing(declaration, elementToBindUsageInfosTo, referencedClassInNewContext, refElement, newUsages)
}
continue
}
val callExpression = resolvedCall.call.callElement as? KtExpression ?: continue
val fullCallExpression = callExpression.getQualifiedExpressionForSelectorOrThis()
@@ -171,7 +203,8 @@ private fun checkUsagesRetargeting(
it.value.type.constructor.declarationDescriptor?.getThisLabelName() == expectedLabelName
}
val canQualifyThis = receiversWithExpectedName.size <= 1
val canQualifyThis = receiversWithExpectedName.isEmpty()
|| receiversWithExpectedName.size == 1 && (declaration !is KtClassOrObject || expectedLabelName != name)
if (canQualifyThis) {
psiFactory.createExpressionByPattern("${implicitReceiver.explicateAsText()}.$0", callExpression)
}
@@ -198,7 +231,7 @@ private fun checkUsagesRetargeting(
val newContext = qualifiedExpression.analyzeInContext(scope, refElement)
val newResolvedCall = newCallee.getResolvedCall(newContext)
val candidateText = newResolvedCall?.candidateDescriptor?.canonicalRender()
val candidateText = newResolvedCall?.candidateDescriptor?.getImportableDescriptor()?.canonicalRender()
if (newResolvedCall != null
&& !accessibleDescriptors.any { it.canonicalRender() == candidateText }