diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index 22c65364306..a244ddbaf0c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -466,4 +466,9 @@ fun KtElement.containingClass(): KtClass? = getStrictParentOfType() 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 } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java b/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java index 506951488d5..68d61823ccd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt index 1934afca6f4..04e2cfd0f6b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt @@ -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()!!, targetSibling, true) + } val analysisResult = introduceData.analyze() when (analysisResult) { is IntroduceTypeAliasAnalysisResult.Error -> { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt index 12fe1eaa413..1bf82e8c8bd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt @@ -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) 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 bdd4716919f..4d9bb0ea764 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 @@ -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 { it.resolveInfo != null } + val newTypeReference = dummyVar.typeReference!! + val newReferences = newTypeReference.collectDescendantsOfType { it.resolveInfo != null } val newContext = dummyVar.analyzeInContext(targetScope, contextExpression) val project = originalType.project val unifier = KotlinPsiUnifier.DEFAULT - val groupedBrokenReferences = LinkedMultiMap() + val groupedReferencesToExtract = LinkedMultiMap() + + 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") diff --git a/idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt b/idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt new file mode 100644 index 00000000000..3258e250fbf --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt @@ -0,0 +1,10 @@ +// NAME: S + +class A + +class B + +// SIBLING: +fun foo(a: A<(Int) -> B, B?>) { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt.after b/idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt.after new file mode 100644 index 00000000000..7d37e2bd4c5 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/typeConstructor.kt.after @@ -0,0 +1,12 @@ +// NAME: S + +class A + +class B + +typealias S = A + +// SIBLING: +fun foo(a: S<(Int) -> 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 2edcf4eb99e..625f983dd8e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -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");