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? {
|
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
|
||||||
|
|||||||
+18
-4
@@ -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 -> {
|
||||||
|
|||||||
+2
-1
@@ -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)
|
||||||
|
|||||||
+21
-15
@@ -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>?>) {
|
||||||
|
|
||||||
|
}
|
||||||
+6
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user