Introduce Type Alias: Support type constructor extraction
This commit is contained in:
@@ -466,4 +466,9 @@ fun KtElement.containingClass(): KtClass? = getStrictParentOfType<KtClass>()
|
||||
fun KtClassOrObject.findPropertyByName(name: String): KtNamedDeclaration? {
|
||||
return declarations.firstOrNull { it is KtProperty && it.name == name } as KtNamedDeclaration?
|
||||
?: getPrimaryConstructorParameters().firstOrNull { it.hasValOrVar() && it.name == name }
|
||||
}
|
||||
|
||||
fun isTypeConstructorReference(e: PsiElement): Boolean {
|
||||
val parent = e.parent
|
||||
return parent is KtUserType && parent.referenceExpression == e
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind;
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
|
||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.Qualifier;
|
||||
@@ -57,12 +58,18 @@ public class CodeInsightUtils {
|
||||
break;
|
||||
case TYPE_ELEMENT: elementClass = KtTypeElement.class;
|
||||
break;
|
||||
case TYPE_CONSTRUCTOR: elementClass = KtSimpleNameExpression.class;
|
||||
break;
|
||||
default: throw new IllegalArgumentException(elementKind.name());
|
||||
}
|
||||
PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, elementClass);
|
||||
|
||||
if (elementKind == ElementKind.TYPE_ELEMENT) return element;
|
||||
|
||||
if (elementKind == ElementKind.TYPE_CONSTRUCTOR) {
|
||||
return element != null && KtPsiUtilKt.isTypeConstructorReference(element) ? element : null;
|
||||
}
|
||||
|
||||
if (element instanceof KtScriptInitializer) {
|
||||
element = ((KtScriptInitializer) element).getBody();
|
||||
}
|
||||
@@ -106,7 +113,8 @@ public class CodeInsightUtils {
|
||||
|
||||
public enum ElementKind {
|
||||
EXPRESSION,
|
||||
TYPE_ELEMENT
|
||||
TYPE_ELEMENT,
|
||||
TYPE_CONSTRUCTOR
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -145,7 +153,8 @@ public class CodeInsightUtils {
|
||||
|
||||
for (PsiElement element : array) {
|
||||
boolean correctType = kind == ElementKind.EXPRESSION && element instanceof KtExpression
|
||||
|| kind == ElementKind.TYPE_ELEMENT && element instanceof KtTypeElement;
|
||||
|| kind == ElementKind.TYPE_ELEMENT && element instanceof KtTypeElement
|
||||
|| kind == ElementKind.TYPE_CONSTRUCTOR && KtPsiUtilKt.isTypeConstructorReference(element);
|
||||
if (!(correctType
|
||||
|| element.getNode().getElementType() == KtTokens.SEMICOLON
|
||||
|| element instanceof PsiWhiteSpace
|
||||
|
||||
+18
-4
@@ -24,6 +24,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.RefactoringActionHandler
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_CONSTRUCTOR
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_ELEMENT
|
||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
|
||||
@@ -34,8 +35,11 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetS
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.KtTypeAlias
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isTypeConstructorReference
|
||||
|
||||
object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
|
||||
@JvmField
|
||||
@@ -47,7 +51,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
|
||||
editor,
|
||||
file,
|
||||
"Select target code block",
|
||||
listOf(TYPE_ELEMENT),
|
||||
listOf(TYPE_ELEMENT, TYPE_CONSTRUCTOR),
|
||||
{ elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) },
|
||||
continuation
|
||||
)
|
||||
@@ -69,9 +73,19 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
|
||||
targetSibling: PsiElement,
|
||||
descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? = null
|
||||
) {
|
||||
val typeElement = elements.singleOrNull() as? KtTypeElement
|
||||
?: return showErrorHint(project, editor, "No type to refactor", REFACTORING_NAME)
|
||||
val introduceData = IntroduceTypeAliasData(typeElement, targetSibling)
|
||||
val elementToExtract = elements.singleOrNull()
|
||||
|
||||
val errorMessage = when (elementToExtract) {
|
||||
is KtSimpleNameExpression -> if (!isTypeConstructorReference(elementToExtract)) "Type reference is expected" else null
|
||||
!is KtTypeElement -> "No type to refactor"
|
||||
else -> null
|
||||
}
|
||||
if (errorMessage != null) return showErrorHint(project, editor, errorMessage, REFACTORING_NAME)
|
||||
|
||||
val introduceData = when (elementToExtract) {
|
||||
is KtTypeElement -> IntroduceTypeAliasData(elementToExtract, targetSibling)
|
||||
else -> IntroduceTypeAliasData(elementToExtract!!.getStrictParentOfType<KtTypeElement>()!!, targetSibling, true)
|
||||
}
|
||||
val analysisResult = introduceData.analyze()
|
||||
when (analysisResult) {
|
||||
is IntroduceTypeAliasAnalysisResult.Error -> {
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@ internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDat
|
||||
|
||||
class IntroduceTypeAliasData(
|
||||
val originalType: KtTypeElement,
|
||||
val targetSibling: PsiElement
|
||||
val targetSibling: PsiElement,
|
||||
val extractTypeConstructor: Boolean = false
|
||||
) : Disposable {
|
||||
val resolutionFacade = originalType.getResolutionFacade()
|
||||
val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL)
|
||||
|
||||
+21
-15
@@ -58,42 +58,48 @@ fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult {
|
||||
val dummyVar = psiFactory.createProperty("val a: Int").apply {
|
||||
typeReference!!.replace(originalType.parent as? KtTypeReference ?: psiFactory.createType(originalType))
|
||||
}
|
||||
val newReferences = dummyVar.typeReference!!.collectDescendantsOfType<KtTypeReference> { it.resolveInfo != null }
|
||||
val newTypeReference = dummyVar.typeReference!!
|
||||
val newReferences = newTypeReference.collectDescendantsOfType<KtTypeReference> { it.resolveInfo != null }
|
||||
val newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
|
||||
val project = originalType.project
|
||||
|
||||
val unifier = KotlinPsiUnifier.DEFAULT
|
||||
val groupedBrokenReferences = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
|
||||
val groupedReferencesToExtract = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
|
||||
|
||||
val forcedCandidates = if (extractTypeConstructor) newTypeReference.typeElement!!.typeArgumentsAsTypes else emptyList()
|
||||
|
||||
for (newReference in newReferences) {
|
||||
val resolveInfo = newReference.resolveInfo!!
|
||||
|
||||
val originalDescriptor = resolveInfo.type.constructor.declarationDescriptor
|
||||
val newDescriptor = newContext[BindingContext.TYPE, newReference]?.constructor?.declarationDescriptor
|
||||
if (compareDescriptors(project, originalDescriptor, newDescriptor)) continue
|
||||
if (newReference !in forcedCandidates) {
|
||||
val originalDescriptor = resolveInfo.type.constructor.declarationDescriptor
|
||||
val newDescriptor = newContext[BindingContext.TYPE, newReference]?.constructor?.declarationDescriptor
|
||||
if (compareDescriptors(project, originalDescriptor, newDescriptor)) continue
|
||||
}
|
||||
|
||||
val equivalenceRepresentative = groupedBrokenReferences
|
||||
val equivalenceRepresentative = groupedReferencesToExtract
|
||||
.keySet()
|
||||
.firstOrNull { unifier.unify(it.reference, resolveInfo.reference).matched }
|
||||
if (equivalenceRepresentative != null) {
|
||||
groupedBrokenReferences.putValue(equivalenceRepresentative, resolveInfo)
|
||||
groupedReferencesToExtract.putValue(equivalenceRepresentative, resolveInfo)
|
||||
}
|
||||
else {
|
||||
groupedBrokenReferences.putValue(resolveInfo, resolveInfo)
|
||||
groupedReferencesToExtract.putValue(resolveInfo, resolveInfo)
|
||||
}
|
||||
|
||||
val brokenReferenceInfoIterator = groupedBrokenReferences.values().iterator()
|
||||
while (brokenReferenceInfoIterator.hasNext()) {
|
||||
val brokenReferenceInfo = brokenReferenceInfoIterator.next()
|
||||
if (resolveInfo.reference.isAncestor(brokenReferenceInfo.reference, true)) {
|
||||
brokenReferenceInfoIterator.remove()
|
||||
val referencesToExtractIterator = groupedReferencesToExtract.values().iterator()
|
||||
while (referencesToExtractIterator.hasNext()) {
|
||||
val referenceToExtract = referencesToExtractIterator.next()
|
||||
if (resolveInfo.reference.isAncestor(referenceToExtract.reference, true)) {
|
||||
referencesToExtractIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val typeParameterNameValidator = CollectingNameValidator()
|
||||
val brokenReferences = groupedBrokenReferences.keySet().filter { groupedBrokenReferences[it].isNotEmpty() }
|
||||
val brokenReferences = groupedReferencesToExtract.keySet().filter { groupedReferencesToExtract[it].isNotEmpty() }
|
||||
val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator)
|
||||
val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedBrokenReferences[it.second]) }
|
||||
val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedReferencesToExtract[it.second]) }
|
||||
|
||||
if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalType } }) {
|
||||
return IntroduceTypeAliasAnalysisResult.Error("Type alias cannot refer to types which aren't accessible in the scope where it's defined")
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// NAME: S
|
||||
|
||||
class A<X, Y>
|
||||
|
||||
class B<T>
|
||||
|
||||
// SIBLING:
|
||||
fun foo(a: <selection>A</selection><(Int) -> B<Int>, B<String>?>) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// NAME: S
|
||||
|
||||
class A<X, Y>
|
||||
|
||||
class B<T>
|
||||
|
||||
typealias S<T, U> = A<T, U>
|
||||
|
||||
// SIBLING:
|
||||
fun foo(a: S<(Int) -> B<Int>, B<String>?>) {
|
||||
|
||||
}
|
||||
+6
@@ -4084,6 +4084,12 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("typeConstructor.kt")
|
||||
public void testTypeConstructor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("typesExtractedWithFunctionalType.kt")
|
||||
public void testTypesExtractedWithFunctionalType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt");
|
||||
|
||||
Reference in New Issue
Block a user