Introduce Type Alias: Replace type usages in constructor calls. Do not replace usages of existing type aliases

#KT-14685 Fixed
This commit is contained in:
Alexey Sedunov
2016-11-11 19:40:54 +03:00
parent 7a38dfadba
commit 0f58e2eef2
6 changed files with 84 additions and 11 deletions
+1
View File
@@ -10,6 +10,7 @@
###### Issues fixed
- [`KT-14693`](https://youtrack.jetbrains.com/issue/KT-14693) Introduce Type Alias: Do not suggest type qualifiers
- [`KT-14696`](https://youtrack.jetbrains.com/issue/KT-14696) Introduce Type Alias: Fix NPE during dialog repaint
- [`KT-14685`](https://youtrack.jetbrains.com/issue/KT-14685) Introduce Type Alias: Replace type usages in constructor calls
## 1.1-M03 (EAP-3)
@@ -333,6 +333,10 @@ class KotlinPsiUnifier(
typeRef2: KtTypeReference? = null
): Status? {
if (type1 != null && type2 != null) {
val unwrappedType1 = type1.unwrap()
val unwrappedType2 = type2.unwrap()
if (unwrappedType1 !== type1 || unwrappedType2 !== type2) return matchTypes(unwrappedType1, unwrappedType2, typeRef1, typeRef2)
if (type1.isError || type2.isError) return null
if (type1 is AbbreviatedType != type2 is AbbreviatedType) return UNMATCHED
if (type1.isExtensionFunctionType != type2.isExtensionFunctionType) return UNMATCHED
@@ -18,11 +18,14 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.containers.LinkedMultiMap
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.compareDescriptors
@@ -42,7 +45,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.utils.SmartList
import java.util.*
sealed class IntroduceTypeAliasAnalysisResult {
class Error(val message: String) : IntroduceTypeAliasAnalysisResult()
@@ -152,6 +155,7 @@ fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithCon
fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
val aliasName = typeAlias.name?.quoteIfNeeded() ?: return emptyMap()
val aliasRange = typeAlias.textRange
val typeAliasDescriptor = typeAlias.resolveToDescriptor() as TypeAliasDescriptor
val unifierParameters = typeAliasDescriptor.declaredTypeParameters.map { UnifierParameter(it, null) }
@@ -159,33 +163,77 @@ fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
val psiFactory = KtPsiFactory(typeAlias)
fun replaceOccurrence(occurrence: KtTypeElement, arguments: List<KtTypeElement>) {
val typeText = if (arguments.isNotEmpty()) "$aliasName<${arguments.joinToString { it.text }}>" else aliasName
occurrence.replace(psiFactory.createType(typeText).typeElement!!)
fun replaceOccurrence(occurrence: PsiElement, arguments: List<KtTypeElement>) {
val typeArgumentsText = if (arguments.isNotEmpty()) "<${arguments.joinToString { it.text }}>" else ""
when (occurrence) {
is KtTypeElement -> {
occurrence.replace(psiFactory.createType("$aliasName$typeArgumentsText").typeElement!!)
}
is KtCallElement -> {
val typeArgumentList = occurrence.typeArgumentList
if (arguments.isNotEmpty()) {
val newTypeArgumentList = psiFactory.createTypeArguments(typeArgumentsText)
typeArgumentList?.replace(newTypeArgumentList) ?: occurrence.addAfter(newTypeArgumentList, occurrence)
}
else {
typeArgumentList?.delete()
}
occurrence.calleeExpression?.replace(psiFactory.createExpression(aliasName))
}
}
}
val aliasRange = typeAlias.textRange
return typeAlias
val rangesWithReplacers = ArrayList<Pair<KotlinPsiRange, () -> Unit>>()
val originalTypePsi = typeAliasDescriptor.underlyingType.constructor.declarationDescriptor?.let {
DescriptorToSourceUtilsIde.getAnyDeclaration(typeAlias.project, it)
}
if (originalTypePsi != null) {
for (reference in ReferencesSearch.search(originalTypePsi, LocalSearchScope(typeAlias.parent))) {
val element = reference.element as? KtSimpleNameExpression ?: continue
if ((element.textRange.intersects(aliasRange))) continue
val arguments: List<KtTypeElement>
val occurrence: KtElement
val callElement = element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
if (callElement != null) {
occurrence = callElement
arguments = callElement.typeArguments.mapNotNull { it.typeReference?.typeElement }
}
else {
val userType = element.getParentOfTypeAndBranch<KtUserType> { referenceExpression }
if (userType != null) {
occurrence = userType
arguments = userType.typeArgumentsAsTypes.mapNotNull { it.typeElement }
}
else continue
}
if (arguments.size != typeAliasDescriptor.declaredTypeParameters.size) continue
rangesWithReplacers += occurrence.toRange() to { replaceOccurrence(occurrence, arguments) }
}
}
typeAlias
.getTypeReference()
?.typeElement
.toRange()
.match(typeAlias.parent, unifier)
.asSequence()
.filter { !(it.range.getTextRange().intersects(aliasRange)) }
.mapNotNull { match ->
val occurrence = match.range.elements.singleOrNull() as? KtTypeElement ?: return@mapNotNull null
.mapNotNullTo(rangesWithReplacers) { match ->
val occurrence = match.range.elements.singleOrNull() as? KtTypeElement ?: return@mapNotNullTo null
val arguments = unifierParameters.mapNotNull { (match.substitution[it] as? KtTypeReference)?.typeElement }
if (arguments.size != unifierParameters.size) return@mapNotNull null
if (arguments.size != unifierParameters.size) return@mapNotNullTo null
match.range to { replaceOccurrence(occurrence, arguments) }
}
.toMap()
return rangesWithReplacers.toMap()
}
private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO"))
fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias {
val originalType = originalData.originalType
val targetSibling = originalData.targetSibling
val psiFactory = KtPsiFactory(originalType)
for (typeParameter in typeParameters)
@@ -0,0 +1,6 @@
// NAME: B
open class A
typealias MyA = A
// SIBLING:
fun fA(p:<selection>A</selection>): MyA = MyA()
fun fB(p:A): A = A()
@@ -0,0 +1,8 @@
// NAME: B
open class A
typealias MyA = B
typealias B = A
// SIBLING:
fun fA(p: B): MyA = MyA()
fun fB(p: B): B = B()
@@ -4076,6 +4076,12 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceTypeAlias"), Pattern.compile("^(.+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("constructorCalls.kt")
public void testConstructorCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt");
doIntroduceTypeAliasTest(fileName);
}
@TestMetadata("emptyName.kt")
public void testEmptyName() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/emptyName.kt");