diff --git a/ChangeLog.md b/ChangeLog.md index afb4dc5a7d0..71805baa75a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -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) diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/psi/patternMatching/KotlinPsiUnifier.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/psi/patternMatching/KotlinPsiUnifier.kt index f185aac3736..cb11edfac1a 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/psi/patternMatching/KotlinPsiUnifier.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/psi/patternMatching/KotlinPsiUnifier.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt index e676b2d954c..d1141670719 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt @@ -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 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 Unit> { val psiFactory = KtPsiFactory(typeAlias) - fun replaceOccurrence(occurrence: KtTypeElement, arguments: List) { - val typeText = if (arguments.isNotEmpty()) "$aliasName<${arguments.joinToString { it.text }}>" else aliasName - occurrence.replace(psiFactory.createType(typeText).typeElement!!) + fun replaceOccurrence(occurrence: PsiElement, arguments: List) { + 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 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 + val occurrence: KtElement + + val callElement = element.getParentOfTypeAndBranch { calleeExpression } + if (callElement != null) { + occurrence = callElement + arguments = callElement.typeArguments.mapNotNull { it.typeReference?.typeElement } + } + else { + val userType = element.getParentOfTypeAndBranch { 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) diff --git a/idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt b/idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt new file mode 100644 index 00000000000..04f4dec5e45 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt @@ -0,0 +1,6 @@ +// NAME: B +open class A +typealias MyA = A +// SIBLING: +fun fA(p:A): MyA = MyA() +fun fB(p:A): A = A() \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt.after b/idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt.after new file mode 100644 index 00000000000..38509fd00d1 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt.after @@ -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() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index 2c31d670b46..af9a236a959 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -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");