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:
@@ -10,6 +10,7 @@
|
|||||||
###### Issues fixed
|
###### Issues fixed
|
||||||
- [`KT-14693`](https://youtrack.jetbrains.com/issue/KT-14693) Introduce Type Alias: Do not suggest type qualifiers
|
- [`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-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)
|
## 1.1-M03 (EAP-3)
|
||||||
|
|
||||||
|
|||||||
+4
@@ -333,6 +333,10 @@ class KotlinPsiUnifier(
|
|||||||
typeRef2: KtTypeReference? = null
|
typeRef2: KtTypeReference? = null
|
||||||
): Status? {
|
): Status? {
|
||||||
if (type1 != null && type2 != null) {
|
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.isError || type2.isError) return null
|
||||||
if (type1 is AbbreviatedType != type2 is AbbreviatedType) return UNMATCHED
|
if (type1 is AbbreviatedType != type2 is AbbreviatedType) return UNMATCHED
|
||||||
if (type1.isExtensionFunctionType != type2.isExtensionFunctionType) return UNMATCHED
|
if (type1.isExtensionFunctionType != type2.isExtensionFunctionType) return UNMATCHED
|
||||||
|
|||||||
+59
-11
@@ -18,11 +18,14 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias
|
|||||||
|
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiElement
|
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.LinkedMultiMap
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
|
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
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.CollectingNameValidator
|
||||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
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.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
||||||
import org.jetbrains.kotlin.utils.SmartList
|
import java.util.*
|
||||||
|
|
||||||
sealed class IntroduceTypeAliasAnalysisResult {
|
sealed class IntroduceTypeAliasAnalysisResult {
|
||||||
class Error(val message: String) : IntroduceTypeAliasAnalysisResult()
|
class Error(val message: String) : IntroduceTypeAliasAnalysisResult()
|
||||||
@@ -152,6 +155,7 @@ fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithCon
|
|||||||
|
|
||||||
fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
|
fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
|
||||||
val aliasName = typeAlias.name?.quoteIfNeeded() ?: return emptyMap()
|
val aliasName = typeAlias.name?.quoteIfNeeded() ?: return emptyMap()
|
||||||
|
val aliasRange = typeAlias.textRange
|
||||||
val typeAliasDescriptor = typeAlias.resolveToDescriptor() as TypeAliasDescriptor
|
val typeAliasDescriptor = typeAlias.resolveToDescriptor() as TypeAliasDescriptor
|
||||||
|
|
||||||
val unifierParameters = typeAliasDescriptor.declaredTypeParameters.map { UnifierParameter(it, null) }
|
val unifierParameters = typeAliasDescriptor.declaredTypeParameters.map { UnifierParameter(it, null) }
|
||||||
@@ -159,33 +163,77 @@ fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
|
|||||||
|
|
||||||
val psiFactory = KtPsiFactory(typeAlias)
|
val psiFactory = KtPsiFactory(typeAlias)
|
||||||
|
|
||||||
fun replaceOccurrence(occurrence: KtTypeElement, arguments: List<KtTypeElement>) {
|
fun replaceOccurrence(occurrence: PsiElement, arguments: List<KtTypeElement>) {
|
||||||
val typeText = if (arguments.isNotEmpty()) "$aliasName<${arguments.joinToString { it.text }}>" else aliasName
|
val typeArgumentsText = if (arguments.isNotEmpty()) "<${arguments.joinToString { it.text }}>" else ""
|
||||||
occurrence.replace(psiFactory.createType(typeText).typeElement!!)
|
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
|
val rangesWithReplacers = ArrayList<Pair<KotlinPsiRange, () -> Unit>>()
|
||||||
return typeAlias
|
|
||||||
|
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()
|
.getTypeReference()
|
||||||
?.typeElement
|
?.typeElement
|
||||||
.toRange()
|
.toRange()
|
||||||
.match(typeAlias.parent, unifier)
|
.match(typeAlias.parent, unifier)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { !(it.range.getTextRange().intersects(aliasRange)) }
|
.filter { !(it.range.getTextRange().intersects(aliasRange)) }
|
||||||
.mapNotNull { match ->
|
.mapNotNullTo(rangesWithReplacers) { match ->
|
||||||
val occurrence = match.range.elements.singleOrNull() as? KtTypeElement ?: return@mapNotNull null
|
val occurrence = match.range.elements.singleOrNull() as? KtTypeElement ?: return@mapNotNullTo null
|
||||||
val arguments = unifierParameters.mapNotNull { (match.substitution[it] as? KtTypeReference)?.typeElement }
|
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) }
|
match.range to { replaceOccurrence(occurrence, arguments) }
|
||||||
}
|
}
|
||||||
.toMap()
|
return rangesWithReplacers.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO"))
|
private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO"))
|
||||||
|
|
||||||
fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias {
|
fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias {
|
||||||
val originalType = originalData.originalType
|
val originalType = originalData.originalType
|
||||||
val targetSibling = originalData.targetSibling
|
|
||||||
val psiFactory = KtPsiFactory(originalType)
|
val psiFactory = KtPsiFactory(originalType)
|
||||||
|
|
||||||
for (typeParameter in typeParameters)
|
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()
|
||||||
+6
@@ -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);
|
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")
|
@TestMetadata("emptyName.kt")
|
||||||
public void testEmptyName() throws Exception {
|
public void testEmptyName() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/emptyName.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/emptyName.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user