Introduce Type Alias: Support type constructor extraction

This commit is contained in:
Alexey Sedunov
2016-07-19 18:18:24 +03:00
parent b5828a5fa1
commit 0769d47f4b
8 changed files with 85 additions and 22 deletions
@@ -467,3 +467,8 @@ fun KtClassOrObject.findPropertyByName(name: String): KtNamedDeclaration? {
return declarations.firstOrNull { it is KtProperty && it.name == name } as KtNamedDeclaration? return declarations.firstOrNull { it is KtProperty && it.name == name } as KtNamedDeclaration?
?: getPrimaryConstructorParameters().firstOrNull { it.hasValOrVar() && it.name == name } ?: 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.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier; import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier;
import org.jetbrains.kotlin.resolve.scopes.receivers.Qualifier; import org.jetbrains.kotlin.resolve.scopes.receivers.Qualifier;
@@ -57,12 +58,18 @@ public class CodeInsightUtils {
break; break;
case TYPE_ELEMENT: elementClass = KtTypeElement.class; case TYPE_ELEMENT: elementClass = KtTypeElement.class;
break; break;
case TYPE_CONSTRUCTOR: elementClass = KtSimpleNameExpression.class;
break;
default: throw new IllegalArgumentException(elementKind.name()); default: throw new IllegalArgumentException(elementKind.name());
} }
PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, elementClass); PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, elementClass);
if (elementKind == ElementKind.TYPE_ELEMENT) return element; if (elementKind == ElementKind.TYPE_ELEMENT) return element;
if (elementKind == ElementKind.TYPE_CONSTRUCTOR) {
return element != null && KtPsiUtilKt.isTypeConstructorReference(element) ? element : null;
}
if (element instanceof KtScriptInitializer) { if (element instanceof KtScriptInitializer) {
element = ((KtScriptInitializer) element).getBody(); element = ((KtScriptInitializer) element).getBody();
} }
@@ -106,7 +113,8 @@ public class CodeInsightUtils {
public enum ElementKind { public enum ElementKind {
EXPRESSION, EXPRESSION,
TYPE_ELEMENT TYPE_ELEMENT,
TYPE_CONSTRUCTOR
} }
@NotNull @NotNull
@@ -145,7 +153,8 @@ public class CodeInsightUtils {
for (PsiElement element : array) { for (PsiElement element : array) {
boolean correctType = kind == ElementKind.EXPRESSION && element instanceof KtExpression 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 if (!(correctType
|| element.getNode().getElementType() == KtTokens.SEMICOLON || element.getNode().getElementType() == KtTokens.SEMICOLON
|| element instanceof PsiWhiteSpace || element instanceof PsiWhiteSpace
@@ -24,6 +24,7 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.refactoring.RefactoringActionHandler 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.codeInsight.CodeInsightUtils.ElementKind.TYPE_ELEMENT
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers 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.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtTypeAlias import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeElement import org.jetbrains.kotlin.psi.KtTypeElement
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isTypeConstructorReference
object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
@JvmField @JvmField
@@ -47,7 +51,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
editor, editor,
file, file,
"Select target code block", "Select target code block",
listOf(TYPE_ELEMENT), listOf(TYPE_ELEMENT, TYPE_CONSTRUCTOR),
{ elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) }, { elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) },
continuation continuation
) )
@@ -69,9 +73,19 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
targetSibling: PsiElement, targetSibling: PsiElement,
descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? = null descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? = null
) { ) {
val typeElement = elements.singleOrNull() as? KtTypeElement val elementToExtract = elements.singleOrNull()
?: return showErrorHint(project, editor, "No type to refactor", REFACTORING_NAME)
val introduceData = IntroduceTypeAliasData(typeElement, targetSibling) 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() val analysisResult = introduceData.analyze()
when (analysisResult) { when (analysisResult) {
is IntroduceTypeAliasAnalysisResult.Error -> { is IntroduceTypeAliasAnalysisResult.Error -> {
@@ -34,7 +34,8 @@ internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDat
class IntroduceTypeAliasData( class IntroduceTypeAliasData(
val originalType: KtTypeElement, val originalType: KtTypeElement,
val targetSibling: PsiElement val targetSibling: PsiElement,
val extractTypeConstructor: Boolean = false
) : Disposable { ) : Disposable {
val resolutionFacade = originalType.getResolutionFacade() val resolutionFacade = originalType.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL) val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL)
@@ -58,42 +58,48 @@ fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult {
val dummyVar = psiFactory.createProperty("val a: Int").apply { val dummyVar = psiFactory.createProperty("val a: Int").apply {
typeReference!!.replace(originalType.parent as? KtTypeReference ?: psiFactory.createType(originalType)) 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 newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
val project = originalType.project val project = originalType.project
val unifier = KotlinPsiUnifier.DEFAULT 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) { for (newReference in newReferences) {
val resolveInfo = newReference.resolveInfo!! val resolveInfo = newReference.resolveInfo!!
val originalDescriptor = resolveInfo.type.constructor.declarationDescriptor if (newReference !in forcedCandidates) {
val newDescriptor = newContext[BindingContext.TYPE, newReference]?.constructor?.declarationDescriptor val originalDescriptor = resolveInfo.type.constructor.declarationDescriptor
if (compareDescriptors(project, originalDescriptor, newDescriptor)) continue val newDescriptor = newContext[BindingContext.TYPE, newReference]?.constructor?.declarationDescriptor
if (compareDescriptors(project, originalDescriptor, newDescriptor)) continue
}
val equivalenceRepresentative = groupedBrokenReferences val equivalenceRepresentative = groupedReferencesToExtract
.keySet() .keySet()
.firstOrNull { unifier.unify(it.reference, resolveInfo.reference).matched } .firstOrNull { unifier.unify(it.reference, resolveInfo.reference).matched }
if (equivalenceRepresentative != null) { if (equivalenceRepresentative != null) {
groupedBrokenReferences.putValue(equivalenceRepresentative, resolveInfo) groupedReferencesToExtract.putValue(equivalenceRepresentative, resolveInfo)
} }
else { else {
groupedBrokenReferences.putValue(resolveInfo, resolveInfo) groupedReferencesToExtract.putValue(resolveInfo, resolveInfo)
} }
val brokenReferenceInfoIterator = groupedBrokenReferences.values().iterator() val referencesToExtractIterator = groupedReferencesToExtract.values().iterator()
while (brokenReferenceInfoIterator.hasNext()) { while (referencesToExtractIterator.hasNext()) {
val brokenReferenceInfo = brokenReferenceInfoIterator.next() val referenceToExtract = referencesToExtractIterator.next()
if (resolveInfo.reference.isAncestor(brokenReferenceInfo.reference, true)) { if (resolveInfo.reference.isAncestor(referenceToExtract.reference, true)) {
brokenReferenceInfoIterator.remove() referencesToExtractIterator.remove()
} }
} }
} }
val typeParameterNameValidator = CollectingNameValidator() 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 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 } }) { 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") 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>?>) {
}
@@ -4084,6 +4084,12 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
doIntroduceTypeAliasTest(fileName); 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") @TestMetadata("typesExtractedWithFunctionalType.kt")
public void testTypesExtractedWithFunctionalType() throws Exception { public void testTypesExtractedWithFunctionalType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt");