From 130e4fb7458e844dc7cf656b929ca3079c47b970 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 12 Jul 2016 15:49:08 +0300 Subject: [PATCH] Introduce Type Alias #KT-12902 Fixed --- ChangeLog.md | 1 + .../org/jetbrains/kotlin/psi/KtPsiFactory.kt | 8 + .../kotlin/psi/KtTypeArgumentList.java | 5 + .../jetbrains/kotlin/psi/psiUtil/psiUtils.kt | 4 + .../kotlin/generators/tests/GenerateTests.kt | 1 + .../idea/refactoring/ktRefactoringUtil.kt | 40 --- .../psi/patternMatching/KotlinPsiUnifier.kt | 133 ++++++--- idea/src/META-INF/plugin.xml | 6 + .../KotlinRefactoringBundle.properties | 1 + .../extractionEngine/ExtractionData.kt | 4 +- .../IntroduceTypeAliasHandler.kt | 106 ++++++++ .../introduceTypeAlias/extractionModel.kt | 80 ++++++ .../introduceTypeAliasImpl.kt | 235 ++++++++++++++++ .../IntroduceTypeAliasParameterTablePanel.kt | 44 +++ .../ui/KotlinIntroduceTypeAliasDialog.form | 131 +++++++++ .../ui/KotlinIntroduceTypeAliasDialog.java | 256 ++++++++++++++++++ .../refactoring/introduce/introduceUtil.kt | 8 +- .../introduceTypeAlias/emptyName.kt | 7 + .../introduceTypeAlias/emptyName.kt.conflicts | 1 + .../introduceTypeAlias/entireTypeExtracted.kt | 8 + .../entireTypeExtracted.kt.conflicts | 1 + .../equivalentNestedTypeElements.kt | 9 + .../equivalentNestedTypeElements.kt.after | 11 + .../introduceTypeAlias/existingTypeClash.kt | 9 + .../existingTypeClash.kt.conflicts | 1 + .../functionTypeDuplicatesNoTypeParameters.kt | 10 + ...ionTypeDuplicatesNoTypeParameters.kt.after | 12 + ...unctionTypeDuplicatesWithTypeParameters.kt | 16 ++ ...nTypeDuplicatesWithTypeParameters.kt.after | 18 ++ .../introduceTypeAlias/functionalType.kt | 7 + .../functionalType.kt.after | 9 + .../introduceTypeAlias/localTypeAlias.kt | 7 + .../localTypeAlias.kt.after | 9 + .../introduceTypeAlias/localTypeExtracted.kt | 9 + .../localTypeExtracted.kt.after | 11 + .../introduceTypeAlias/localWithVisibility.kt | 8 + .../localWithVisibility.kt.conflicts | 1 + .../introduceTypeAlias/memberTypeAlias.kt | 7 + .../memberTypeAlias.kt.after | 9 + .../nestedTypesExtracted.kt | 9 + .../nestedTypesExtracted.kt.after | 11 + .../introduceTypeAlias/noTypeElement.kt | 4 + .../noTypeElement.kt.conflicts | 1 + .../introduceTypeAlias/nonIdentifierName.kt | 7 + .../nonIdentifierName.kt.conflicts | 1 + .../introduceTypeAlias/nullableType.kt | 7 + .../introduceTypeAlias/nullableType.kt.after | 9 + .../introduceTypeAlias/privateTypeAlias.kt | 8 + .../privateTypeAlias.kt.after | 10 + .../introduceTypeAlias/protectedInFile.kt | 8 + .../protectedInFile.kt.conflicts | 1 + .../introduceTypeAlias/topLevelTypeAlias.kt | 7 + .../topLevelTypeAlias.kt.after | 9 + .../typesExtractedWithFunctionalType.kt | 9 + .../typesExtractedWithFunctionalType.kt.after | 11 + .../userTypeDuplicatesNoTypeParameters.kt | 9 + ...serTypeDuplicatesNoTypeParameters.kt.after | 11 + .../userTypeDuplicatesWithTypeParameters.kt | 13 + ...rTypeDuplicatesWithTypeParameters.kt.after | 15 + .../equivalence/types/function1.kt.match | 6 +- .../equivalence/types/function2.kt.match | 6 +- .../introduce/AbstractExtractionTest.kt | 22 ++ .../introduce/ExtractionTestGenerated.java | 135 +++++++++ 63 files changed, 1482 insertions(+), 90 deletions(-) delete mode 100644 idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/ktRefactoringUtil.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java create mode 100644 idea/testData/refactoring/introduceTypeAlias/emptyName.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/emptyName.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/functionalType.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/functionalType.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/nullableType.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/nullableType.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt.after diff --git a/ChangeLog.md b/ChangeLog.md index 97a18d4fc76..82d26fabf4a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,6 +9,7 @@ ###### New features - [`KT-12903`](https://youtrack.jetbrains.com/issue/KT-12903) Implement "Inline type alias" refactoring +- [`KT-12902`](https://youtrack.jetbrains.com/issue/KT-12902) Implement "Introduce type alias" refactoring ## 1.1-M01 (EAP-1) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index 59734969cca..38f4883a08e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -98,11 +98,19 @@ class KtPsiFactory(private val project: Project) { return typeReference } + fun createType(typeElement: KtTypeElement) = createType("X").apply { this.typeElement!!.replace(typeElement) } + fun createTypeIfPossible(type: String): KtTypeReference? { val typeReference = createProperty("val x : $type").typeReference return if (typeReference?.text == type) typeReference else null } + fun createTypeAlias(name: String, typeParameters: List, typeElement: KtTypeElement): KtTypeAlias { + val typeParametersText = if (typeParameters.isNotEmpty()) typeParameters.joinToString(prefix = "<", postfix = ">") else "" + return createDeclaration("typealias $name$typeParametersText = X") + .apply { getTypeReference()!!.replace(createType(typeElement)) } + } + fun createStar(): PsiElement { return createType("List<*>").findElementAt(5)!! } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtTypeArgumentList.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtTypeArgumentList.java index cc5739ce942..5bc710f17db 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtTypeArgumentList.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtTypeArgumentList.java @@ -41,4 +41,9 @@ public class KtTypeArgumentList extends KtElementImplStub getArguments() { return getStubOrPsiChildrenAsList(KtStubElementTypes.TYPE_PROJECTION); } + + @NotNull + public KtTypeProjection addArgument(@NotNull KtTypeProjection argument) { + return EditCommaSeparatedListHelper.INSTANCE.addItem(this, getArguments(), argument); + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt index b079065a251..93800bf9e11 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt @@ -140,6 +140,10 @@ fun PsiElement.getNextSiblingIgnoringWhitespaceAndComments(): PsiElement? { return siblings(withItself = false).filter { it !is PsiWhiteSpace && it !is PsiComment }.firstOrNull() } +fun PsiElement.getPrevSiblingIgnoringWhitespaceAndComments(): PsiElement? { + return siblings(withItself = false, forward = false).filter { it !is PsiWhiteSpace && it !is PsiComment }.firstOrNull() +} + inline fun T.nextSiblingOfSameType() = PsiTreeUtil.getNextSiblingOfType(this, T::class.java) inline fun T.prevSiblingOfSameType() = PsiTreeUtil.getPrevSiblingOfType(this, T::class.java) diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 3662b4d3d52..f0a2a77af75 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -766,6 +766,7 @@ fun main(args: Array) { model("refactoring/introduceParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceSimpleParameterTest") model("refactoring/introduceLambdaParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceLambdaParameterTest") model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest") + model("refactoring/introduceTypeAlias", pattern = kotlinFileOrScript, testMethod = "doIntroduceTypeAliasTest") } testClass() { diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/ktRefactoringUtil.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/ktRefactoringUtil.kt deleted file mode 100644 index 924afd41e8b..00000000000 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/ktRefactoringUtil.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.refactoring - -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.isAncestor -import org.jetbrains.kotlin.resolve.BindingContext - -fun KtElement.getContextForContainingDeclarationBody(): BindingContext? { - val enclosingDeclaration = getStrictParentOfType() - val bodyElement = when (enclosingDeclaration) { - is KtDeclarationWithBody -> enclosingDeclaration.getBodyExpression() - is KtWithExpressionInitializer -> enclosingDeclaration.getInitializer() - is KtDestructuringDeclaration -> enclosingDeclaration.getInitializer() - is KtParameter -> enclosingDeclaration.getDefaultValue() - is KtAnonymousInitializer -> enclosingDeclaration.body - is KtClass -> { - val delegationSpecifierList = enclosingDeclaration.getSuperTypeList() - if (delegationSpecifierList.isAncestor(this)) this else null - } - else -> null - } - return bodyElement?.let { it.analyze() } -} \ No newline at end of file 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 eaa52cfb8c8..d1b96df69b5 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 @@ -20,8 +20,9 @@ import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.builtins.isExtensionFunctionType import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.idea.refactoring.getContextForContainingDeclarationBody +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers @@ -32,6 +33,7 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.Stat import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils @@ -45,9 +47,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver import org.jetbrains.kotlin.resolve.source.getPsi -import org.jetbrains.kotlin.types.ErrorUtils -import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.types.expressions.OperatorConventions import java.util.* @@ -71,18 +71,18 @@ interface UnificationResult { interface Matched: UnificationResult { val range: KotlinPsiRange - val substitution: Map + val substitution: Map override val status: Status get() = MATCHED } class StronglyMatched( override val range: KotlinPsiRange, - override val substitution: Map + override val substitution: Map ): Matched class WeaklyMatched( override val range: KotlinPsiRange, - override val substitution: Map, + override val substitution: Map, val weakMatches: Map ): Matched @@ -92,7 +92,7 @@ interface UnificationResult { class UnifierParameter( val descriptor: DeclarationDescriptor, - val expectedType: KotlinType + val expectedType: KotlinType? ) class KotlinPsiUnifier( @@ -109,7 +109,7 @@ class KotlinPsiUnifier( ) { val patternContext: BindingContext = originalPattern.getBindingContext() val targetContext: BindingContext = originalTarget.getBindingContext() - val substitution = HashMap() + val substitution = HashMap() val declarationPatternsToTargets = MultiMap() val weakMatches = HashMap() var checkEquivalence: Boolean = false @@ -118,7 +118,7 @@ class KotlinPsiUnifier( private fun KotlinPsiRange.getBindingContext(): BindingContext { val element = (this as? KotlinPsiRange.ListRange)?.startElement as? KtElement if ((element?.containingFile as? KtFile)?.doNotAnalyze != null) return BindingContext.EMPTY - return element?.getContextForContainingDeclarationBody() ?: BindingContext.EMPTY + return element?.analyze() ?: BindingContext.EMPTY } private fun matchDescriptors(d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean { @@ -322,13 +322,20 @@ class KotlinPsiUnifier( } private fun KtTypeReference.getType(): KotlinType? { - val t = bindingContext[BindingContext.TYPE, this] + val t = bindingContext.let { it[BindingContext.ABBREVIATED_TYPE, this] ?: it[BindingContext.TYPE, this] } return if (t == null || t.isError) null else t } - private fun matchTypes(type1: KotlinType?, type2: KotlinType?): Status? { + private fun matchTypes( + type1: KotlinType?, + type2: KotlinType?, + typeRef1: KtTypeReference? = null, + typeRef2: KtTypeReference? = null + ): Status? { if (type1 != null && type2 != null) { if (type1.isError || type2.isError) return null + if (type1 is AbbreviatedType != type2 is AbbreviatedType) return UNMATCHED + if (type1.isExtensionFunctionType != type2.isExtensionFunctionType) return UNMATCHED if (TypeUtils.equalTypes(type1, type2)) return MATCHED if (type1.isMarkedNullable != type2.isMarkedNullable) return UNMATCHED @@ -339,9 +346,11 @@ class KotlinPsiUnifier( val args1 = type1.arguments val args2 = type2.arguments if (args1.size != args2.size) return UNMATCHED - if (!args1.zip(args2).all { - it.first.projectionKind == it.second.projectionKind && matchTypes(it.first.type, it.second.type) == MATCHED } - ) return UNMATCHED + if (!args1.withIndex().all { p -> + val (i, arg1) = p + val arg2 = args2[i] + matchTypeArguments(i, arg1, arg2, typeRef1, typeRef2) + }) return UNMATCHED return MATCHED } @@ -349,6 +358,32 @@ class KotlinPsiUnifier( return if (type1 == null && type2 == null) null else UNMATCHED } + private fun matchTypeArguments( + argIndex: Int, + arg1: TypeProjection, + arg2: TypeProjection, + typeRef1: KtTypeReference?, + typeRef2: KtTypeReference? + ): Boolean { + val typeArgRef1 = typeRef1?.typeElement?.typeArgumentsAsTypes?.getOrNull(argIndex) + val typeArgRef2 = typeRef2?.typeElement?.typeArgumentsAsTypes?.getOrNull(argIndex) + + if (arg1.projectionKind != arg2.projectionKind) return false + val argType1 = arg1.type + val argType2 = arg2.type + // Substitution attempt using either arg1, or arg2 as a pattern type. Falls back to exact matching if substitution is not possible + val status = if (!checkEquivalence && typeRef1 != null && typeRef2 != null) { + val typePsi1 = argType1.constructor.declarationDescriptor?.source?.getPsi() + val typePsi2 = argType2.constructor.declarationDescriptor?.source?.getPsi() + descriptorToParameter[typePsi1]?.let { substitute(it, typeArgRef2) } ?: + descriptorToParameter[typePsi2]?.let { substitute(it, typeArgRef1) } ?: + matchTypes(argType1, argType2, typeArgRef1, typeArgRef2) + } + else matchTypes(argType1, argType2, typeArgRef1, typeArgRef2) + + return status == MATCHED + } + private fun matchTypes(types1: Collection, types2: Collection): Boolean { fun sortTypes(types: Collection) = types.sortedBy { DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it) } @@ -367,6 +402,7 @@ class KotlinPsiUnifier( } is KtBinaryExpression -> operationReference.getReferencedNameElementType() == KtTokens.ELVIS is KtThisExpression -> true + is KtSimpleNameExpression -> getStrictParentOfType() != null else -> false } @@ -641,7 +677,14 @@ class KotlinPsiUnifier( desc2: DeclarationDescriptor?): Status? { if (decl1.javaClass != decl2.javaClass) return UNMATCHED - if (desc1 == null || desc2 == null || ErrorUtils.isError(desc1) || ErrorUtils.isError(desc2)) return UNMATCHED + if (desc1 == null || desc2 == null) { + if (decl1 is KtParameter + && decl2 is KtParameter + && decl1.getStrictParentOfType() != null + && decl2.getStrictParentOfType() != null) return null + return UNMATCHED + } + if (ErrorUtils.isError(desc1) || ErrorUtils.isError(desc2)) return UNMATCHED if (desc1.javaClass != desc2.javaClass) return UNMATCHED declarationPatternsToTargets.putValue(desc1, desc2) @@ -682,8 +725,11 @@ class KotlinPsiUnifier( e2 is KtDeclaration -> e2.matchDeclarations(e1) + e1 is KtTypeElement && e2 is KtTypeElement && e1.parent is KtTypeReference && e2.parent is KtTypeReference -> + matchResolvedInfo(e1.parent, e2.parent) + e1 is KtTypeReference && e2 is KtTypeReference -> - matchTypes(e1.getType(), e2.getType()) + matchTypes(e1.getType(), e2.getType(), e1, e2) KtPsiUtil.isAssignment(e1) -> (e1 as KtBinaryExpression).matchAssignment(e2) @@ -708,8 +754,9 @@ class KotlinPsiUnifier( } private fun PsiElement.checkType(parameter: UnifierParameter): Boolean { + val expectedType = parameter.expectedType ?: return true val targetElementType = (this as? KtExpression)?.let { it.bindingContext.getType(it) } - return targetElementType != null && KotlinTypeChecker.DEFAULT.isSubtypeOf(targetElementType, parameter.expectedType) + return targetElementType != null && KotlinTypeChecker.DEFAULT.isSubtypeOf(targetElementType, expectedType) } private fun doUnifyStringTemplateFragments(target: KtStringTemplateExpression, pattern: ExtractableSubstringInfo): Status { @@ -820,6 +867,23 @@ class KotlinPsiUnifier( return status } + private fun substitute(parameter: UnifierParameter, targetElement: PsiElement?): Status { + val existingArgument = substitution[parameter] + return when { + existingArgument == null -> { + substitution[parameter] = targetElement as KtElement + MATCHED + } + else -> { + checkEquivalence = true + val status = doUnify(existingArgument, targetElement) + checkEquivalence = false + + status + } + } + } + fun doUnify( targetElement: PsiElement?, patternElement: PsiElement? @@ -831,29 +895,26 @@ class KotlinPsiUnifier( if (targetElementUnwrapped == null || patternElementUnwrapped == null) return UNMATCHED if (!checkEquivalence && targetElementUnwrapped !is KtBlockExpression) { - val referencedPatternDescriptor = (patternElementUnwrapped as? KtReferenceExpression)?.let { - it.bindingContext[BindingContext.REFERENCE_TARGET, it] + val referencedPatternDescriptor = when (patternElementUnwrapped) { + is KtReferenceExpression -> { + if (targetElementUnwrapped !is KtExpression) return UNMATCHED + patternElementUnwrapped.bindingContext[BindingContext.REFERENCE_TARGET, patternElementUnwrapped] + } + is KtUserType -> { + if (targetElementUnwrapped !is KtUserType) return UNMATCHED + patternElementUnwrapped.bindingContext[BindingContext.REFERENCE_TARGET, patternElementUnwrapped.referenceExpression] + } + else -> null } val referencedPatternDeclaration = (referencedPatternDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() val parameter = descriptorToParameter[referencedPatternDeclaration] if (referencedPatternDeclaration != null && parameter != null) { - if (targetElementUnwrapped !is KtExpression) return UNMATCHED - if (!targetElementUnwrapped.checkType(parameter)) return UNMATCHED - - val existingArgument = substitution[parameter] - return when { - existingArgument == null -> { - substitution[parameter] = targetElementUnwrapped - MATCHED - } - else -> { - checkEquivalence = true - val status = doUnify(existingArgument, targetElementUnwrapped) - checkEquivalence = false - - status - } + if (targetElementUnwrapped is KtExpression) { + if (!targetElementUnwrapped.checkType(parameter)) return UNMATCHED } + else if (targetElementUnwrapped !is KtUserType) return UNMATCHED + + return substitute(parameter, targetElementUnwrapped) } } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 0fc01886895..be92105d3ef 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -157,6 +157,12 @@ + + + + + , targetSibling: PsiElement) -> Unit) { + selectElementsWithTargetSibling( + REFACTORING_NAME, + editor, + file, + "Select target code block", + CodeInsightUtils.ElementKind.TYPE_ELEMENT, + { elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) }, + continuation + ) + } + + private fun runRefactoring(descriptor: IntroduceTypeAliasDescriptor, project: Project, editor: Editor) { + val typeAlias = project.executeWriteCommand(REFACTORING_NAME) { descriptor.generateTypeAlias() } + + val duplicateReplacers = findDuplicates(typeAlias) + if (duplicateReplacers.isNotEmpty()) { + processDuplicates(duplicateReplacers, project, editor) + } + } + + fun doInvoke( + project: Project, + editor: Editor, + elements: List, + 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 analysisResult = introduceData.analyze() + when (analysisResult) { + is IntroduceTypeAliasAnalysisResult.Error -> { + return showErrorHint(project, editor, analysisResult.message, REFACTORING_NAME) + } + + is IntroduceTypeAliasAnalysisResult.Success -> { + val originalDescriptor = analysisResult.descriptor + if (ApplicationManager.getApplication().isUnitTestMode) { + val (descriptor, conflicts) = descriptorSubstitutor!!(originalDescriptor).validate() + project.checkConflictsInteractively(conflicts) { runRefactoring(descriptor, project, editor) } + } + else { + KotlinIntroduceTypeAliasDialog(project, originalDescriptor) { runRefactoring(it.currentDescriptor, project, editor) }.show() + } + } + } + } + + override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { + if (file !is KtFile) return + selectElements(editor, file) { elements, targetSibling -> doInvoke(project, editor, elements, targetSibling) } + } + + override fun invoke(project: Project, elements: Array, dataContext: DataContext?) { + throw AssertionError("$REFACTORING_NAME can only be invoked from editor") + } +} + +class IntroduceTypeAliasAction : AbstractIntroduceAction() { + override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler +} \ No newline at end of file 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 new file mode 100644 index 00000000000..12fe1eaa413 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType + +class TypeReferenceInfo(val reference: KtTypeReference, val type: KotlinType) + +internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDataProperty(Key.create("RESOLVE_INFO")) + +class IntroduceTypeAliasData( + val originalType: KtTypeElement, + val targetSibling: PsiElement +) : Disposable { + val resolutionFacade = originalType.getResolutionFacade() + val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL) + + init { + markReferences() + } + + private fun markReferences() { + val visitor = object : KtTreeVisitorVoid() { + override fun visitTypeReference(typeReference: KtTypeReference) { + val typeElement = typeReference.typeElement ?: return + + val kotlinType = bindingContext[BindingContext.ABBREVIATED_TYPE, typeReference] ?: + bindingContext[BindingContext.TYPE, typeReference] ?: + return + typeReference.resolveInfo = TypeReferenceInfo(typeReference, kotlinType) + + typeElement.typeArgumentsAsTypes.forEach { it.accept(this) } + } + } + (originalType.parent as? KtTypeReference ?: originalType).accept(visitor) + } + + override fun dispose() { + if (!originalType.isValid) return + originalType.forEachDescendantOfType { it.resolveInfo = null } + } +} + +data class TypeParameter(val name: String, val typeReferenceInfos: Collection) + +data class IntroduceTypeAliasDescriptor( + val originalData: IntroduceTypeAliasData, + val name: String, + val visibility: KtModifierKeywordToken?, + val typeParameters: List +) + +data class IntroduceTypeAliasDescriptorWithConflicts( + val descriptor: IntroduceTypeAliasDescriptor, + val conflicts: MultiMap +) \ No newline at end of file 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 new file mode 100644 index 00000000000..bdd4716919f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt @@ -0,0 +1,235 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias + +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +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.core.CollectingNameValidator +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.compareDescriptors +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange +import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier +import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter +import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.lexer.KtTokens.* +import org.jetbrains.kotlin.name.Name +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 + +sealed class IntroduceTypeAliasAnalysisResult { + class Error(val message: String) : IntroduceTypeAliasAnalysisResult() + class Success(val descriptor: IntroduceTypeAliasDescriptor) : IntroduceTypeAliasAnalysisResult() +} + +private fun IntroduceTypeAliasData.getTargetScope() = targetSibling.getResolutionScope(bindingContext, resolutionFacade) + +fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult { + val psiFactory = KtPsiFactory(originalType) + + val contextExpression = originalType.getStrictParentOfType()!! + val targetScope = getTargetScope() + + 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 newContext = dummyVar.analyzeInContext(targetScope, contextExpression) + val project = originalType.project + + val unifier = KotlinPsiUnifier.DEFAULT + val groupedBrokenReferences = LinkedMultiMap() + 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 + + val equivalenceRepresentative = groupedBrokenReferences + .keySet() + .firstOrNull { unifier.unify(it.reference, resolveInfo.reference).matched } + if (equivalenceRepresentative != null) { + groupedBrokenReferences.putValue(equivalenceRepresentative, resolveInfo) + } + else { + groupedBrokenReferences.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 typeParameterNameValidator = CollectingNameValidator() + val brokenReferences = groupedBrokenReferences.keySet().filter { groupedBrokenReferences[it].isNotEmpty() } + val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator) + val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedBrokenReferences[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") + } + + return IntroduceTypeAliasAnalysisResult.Success(IntroduceTypeAliasDescriptor(this, "", null, typeParameters)) +} + +fun IntroduceTypeAliasData.getApplicableVisibilities(): List{ + val parent = targetSibling.parent + return when (parent) { + is KtClassBody -> listOf(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD) + is KtFile -> listOf(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD) + else -> emptyList() + } +} + +fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithConflicts { + val conflicts = MultiMap() + + val originalType = originalData.originalType + if (name.isEmpty()) { + conflicts.putValue(originalType, "No name provided for type alias") + } + else if (!KotlinNameSuggester.isIdentifier(name)) { + conflicts.putValue(originalType, "Type alias name must be a valid identifier: $name") + } + else if (originalData.getTargetScope().findClassifier(Name.identifier(name), NoLookupLocation.FROM_IDE) != null) { + conflicts.putValue(originalType, "Type $name already exists in the target scope") + } + + if (typeParameters.distinctBy { it.name }.size != typeParameters.size) { + conflicts.putValue(originalType, "Type parameter names must be distinct") + } + + if (visibility != null && visibility !in originalData.getApplicableVisibilities()) { + conflicts.putValue(originalType, "'$visibility' is not allowed in the target context") + } + + return IntroduceTypeAliasDescriptorWithConflicts(this, conflicts) +} + +fun findDuplicates(typeAlias: KtTypeAlias): Map Unit> { + val aliasName = typeAlias.name ?: return emptyMap() + val typeAliasDescriptor = typeAlias.resolveToDescriptor() as TypeAliasDescriptor + + val unifierParameters = typeAliasDescriptor.declaredTypeParameters.map { UnifierParameter(it, null) } + val unifier = KotlinPsiUnifier(unifierParameters) + + 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!!) + } + + val aliasRange = typeAlias.textRange + return 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 + val arguments = unifierParameters.mapNotNull { (match.substitution[it] as? KtTypeReference)?.typeElement } + if (arguments.size != unifierParameters.size) return@mapNotNull null + match.range to { replaceOccurrence(occurrence, arguments) } + } + .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) + for (it in typeParameter.typeReferenceInfos) { + it.reference.typeParameterInfo = typeParameter + } + + val typeAlias = psiFactory.createTypeAlias(name, typeParameters.map { it.name }, originalType) + if (visibility != null && visibility != KtTokens.DEFAULT_VISIBILITY_KEYWORD) { + typeAlias.addModifier(visibility) + } + + for (typeParameter in typeParameters) + for (it in typeParameter.typeReferenceInfos) { + it.reference.typeParameterInfo = null + } + + fun replaceUsage() { + val aliasInstanceText = if (typeParameters.isNotEmpty()) { + "$name<${typeParameters.joinToString { it.typeReferenceInfos.first().reference.text }}>" + } + else { + name + } + originalType.replace(psiFactory.createType(aliasInstanceText).typeElement!!) + } + + fun introduceTypeParameters() { + typeAlias.getTypeReference()!!.forEachDescendantOfType { + val typeParameter = it.typeParameterInfo ?: return@forEachDescendantOfType + val typeParameterReference = psiFactory.createType(typeParameter.name) + it.replace(typeParameterReference) + } + } + + fun insertDeclaration(): KtTypeAlias { + val targetParent = originalData.targetSibling.parent + + val anchorCandidates = SmartList() + anchorCandidates.add(targetSibling) + if (targetSibling is KtEnumEntry) { + anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry }) + } + + val anchor = anchorCandidates.minBy { it.startOffset }!!.parentsWithSelf.first { it.parent == targetParent } + val targetContainer = anchor.parent!! + return (targetContainer.addBefore(typeAlias, anchor) as KtTypeAlias).apply { + targetContainer.addBefore(psiFactory.createWhiteSpace("\n\n"), anchor) + } + } + + return if (previewOnly) { + introduceTypeParameters() + typeAlias + } + else { + replaceUsage() + introduceTypeParameters() + insertDeclaration() + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt new file mode 100644 index 00000000000..40df3013872 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui + +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.TypeParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.ui.AbstractParameterTablePanel +import java.util.* + +open class IntroduceTypeAliasParameterTablePanel : AbstractParameterTablePanel() { + class TypeParameterInfo( + originalParameter: TypeParameter + ) : AbstractParameterTablePanel.AbstractParameterInfo(originalParameter) { + init { + name = originalParameter.name + } + + override fun toParameter() = originalParameter.copy(name) + } + + fun init(parameters: List) { + parameterInfos = parameters.mapTo(ArrayList()) { TypeParameterInfo(it) } + super.init() + } + + val selectedTypeParameterInfos: List + get() = parameterInfos.filter { it.isEnabled } + + val selectedTypeParameters: List + get() = selectedTypeParameterInfos.map { it.toParameter() } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form new file mode 100644 index 00000000000..4ca17db3728 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form @@ -0,0 +1,131 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java new file mode 100644 index 00000000000..6cd80654c25 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java @@ -0,0 +1,256 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.ui.NameSuggestionsField; +import com.intellij.ui.TitledSeparator; +import com.intellij.util.containers.MultiMap; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.idea.KotlinFileType; +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester; +import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt; +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasDescriptor; +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasImplKt; +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler; +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.TypeParameter; +import org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent; +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken; +import org.jetbrains.kotlin.lexer.KtTokens; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.*; +import java.util.List; + +public class KotlinIntroduceTypeAliasDialog extends DialogWrapper { + private JPanel contentPane; + private TitledSeparator inputParametersPanel; + private JComboBox visibilityBox; + private KotlinSignatureComponent signaturePreviewField; + private JPanel aliasNamePanel; + private NameSuggestionsField aliasNameField; + private JLabel aliasNameLabel; + private IntroduceTypeAliasParameterTablePanel parameterTablePanel; + + private final Project project; + + private final IntroduceTypeAliasDescriptor originalDescriptor; + private IntroduceTypeAliasDescriptor currentDescriptor; + + private final Function1 onAccept; + + public KotlinIntroduceTypeAliasDialog( + @NotNull Project project, + @NotNull IntroduceTypeAliasDescriptor originalDescriptor, + @NotNull Function1 onAccept) { + super(project, true); + + this.project = project; + this.originalDescriptor = originalDescriptor; + this.currentDescriptor = originalDescriptor; + this.onAccept = onAccept; + + setModal(true); + setTitle(KotlinIntroduceTypeAliasHandler.REFACTORING_NAME); + init(); + update(); + } + + private void createUIComponents() { + this.signaturePreviewField = new KotlinSignatureComponent("", project); + } + + private boolean isVisibilitySectionAvailable() { + return !getApplicableVisibilities().isEmpty(); + } + + @NotNull + private List getApplicableVisibilities() { + return IntroduceTypeAliasImplKt.getApplicableVisibilities(originalDescriptor.getOriginalData()); + } + + private String getAliasName() { + return aliasNameField.getEnteredName(); + } + + @Nullable + private KtModifierKeywordToken getVisibility() { + if (!isVisibilitySectionAvailable()) return null; + return (KtModifierKeywordToken) visibilityBox.getSelectedItem(); + } + + private boolean checkNames() { + if (!KotlinNameSuggester.INSTANCE.isIdentifier(getAliasName())) return false; + if (parameterTablePanel != null) { + for (IntroduceTypeAliasParameterTablePanel.TypeParameterInfo parameterInfo : parameterTablePanel.getSelectedTypeParameterInfos()) { + if (!KotlinNameSuggester.INSTANCE.isIdentifier(parameterInfo.getName())) return false; + } + } + return true; + } + + private void update() { + this.currentDescriptor = createDescriptor(); + + setOKActionEnabled(checkNames()); + signaturePreviewField.setText(IntroduceTypeAliasImplKt.generateTypeAlias(currentDescriptor, true).getText()); + } + + @Override + protected void init() { + super.init(); + + //noinspection unchecked + visibilityBox.setModel(new DefaultComboBoxModel(getApplicableVisibilities().toArray())); + //noinspection unchecked + visibilityBox.setRenderer( + new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus + ) { + String tokenValue = ((KtModifierKeywordToken) value).getValue(); + return super.getListCellRendererComponent(list, tokenValue, index, isSelected, cellHasFocus); + } + } + ); + + aliasNameField = new NameSuggestionsField(ArrayUtil.EMPTY_STRING_ARRAY, project, KotlinFileType.INSTANCE); + aliasNameField.addDataChangedListener( + new NameSuggestionsField.DataChanged() { + @Override + public void dataChanged() { + update(); + } + } + ); + aliasNamePanel.add(aliasNameField, BorderLayout.CENTER); + aliasNameLabel.setLabelFor(aliasNameField); + + boolean enableVisibility = isVisibilitySectionAvailable(); + visibilityBox.setEnabled(enableVisibility); + if (enableVisibility) { + KtModifierKeywordToken defaultVisibility = originalDescriptor.getVisibility(); + if (defaultVisibility == null) { + defaultVisibility = KtTokens.PUBLIC_KEYWORD; + } + visibilityBox.setSelectedItem(defaultVisibility); + } + visibilityBox.addItemListener( + new ItemListener() { + @Override + public void itemStateChanged(@NotNull ItemEvent e) { + update(); + } + } + ); + + if (!originalDescriptor.getTypeParameters().isEmpty()) { + parameterTablePanel = new IntroduceTypeAliasParameterTablePanel() { + @Override + protected void updateSignature() { + KotlinIntroduceTypeAliasDialog.this.update(); + } + + @Override + protected void onEnterAction() { + doOKAction(); + } + + @Override + protected void onCancelAction() { + doCancelAction(); + } + }; + parameterTablePanel.init(originalDescriptor.getTypeParameters()); + + inputParametersPanel.setText("Type &Parameters"); + inputParametersPanel.setLabelFor(parameterTablePanel.getTable()); + inputParametersPanel.add(parameterTablePanel); + } + else { + inputParametersPanel.setVisible(false); + } + } + + @SuppressWarnings("SuspiciousMethodCalls") + @Override + protected void doOKAction() { + MultiMap conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts(); + KotlinRefactoringUtilKt.checkConflictsInteractively( + project, + conflicts, + new Function0() { + @Override + public Unit invoke() { + close(OK_EXIT_CODE); + return Unit.INSTANCE; + } + }, + new Function0() { + @Override + public Unit invoke() { + KotlinIntroduceTypeAliasDialog.super.doOKAction(); + return onAccept.invoke(KotlinIntroduceTypeAliasDialog.this); + } + } + ); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return aliasNameField; + } + + @Override + protected JComponent createCenterPanel() { + return contentPane; + } + + @NotNull + @Override + protected JComponent createContentPane() { + return contentPane; + } + + @NotNull + private IntroduceTypeAliasDescriptor createDescriptor() { + return originalDescriptor.copy( + originalDescriptor.getOriginalData(), + getAliasName(), + getVisibility(), + parameterTablePanel != null ? parameterTablePanel.getSelectedTypeParameters() : Collections.emptyList() + ); + } + + public IntroduceTypeAliasDescriptor getCurrentDescriptor() { + return currentDescriptor; + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt index 7f4782d2509..33009bd4555 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt @@ -111,7 +111,11 @@ fun selectElementsWithTargetParent( val elements = CodeInsightUtils.findElements(file, startOffset, endOffset, elementKind) if (elements.isEmpty()) { - showErrorHintByKey("cannot.refactor.no.expression") + val messageKey = when (elementKind) { + CodeInsightUtils.ElementKind.EXPRESSION -> "cannot.refactor.no.expression" + CodeInsightUtils.ElementKind.TYPE_ELEMENT -> "cannot.refactor.no.type" + } + showErrorHintByKey(messageKey) return } @@ -212,4 +216,4 @@ fun KtExpression.mustBeParenthesizedInInitializerPosition(): Boolean { if (left?.mustBeParenthesizedInInitializerPosition() ?: false) return true return PsiChildRange(left, operationReference).any { (it is PsiWhiteSpace) && it.textContains('\n') } -} \ No newline at end of file +} diff --git a/idea/testData/refactoring/introduceTypeAlias/emptyName.kt b/idea/testData/refactoring/introduceTypeAlias/emptyName.kt new file mode 100644 index 00000000000..4ceff440d81 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/emptyName.kt @@ -0,0 +1,7 @@ +// NAME: +class A + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/emptyName.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/emptyName.kt.conflicts new file mode 100644 index 00000000000..f8e765b5538 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/emptyName.kt.conflicts @@ -0,0 +1 @@ +No name provided for type alias \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt b/idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt new file mode 100644 index 00000000000..752dc327d0e --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt @@ -0,0 +1,8 @@ +// NAME: C + +// SIBLING: +fun foo() { + class A + + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt.conflicts new file mode 100644 index 00000000000..5d4f5407121 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt.conflicts @@ -0,0 +1 @@ +Type alias cannot refer to types which aren't accessible in the scope where it's defined \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt b/idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt new file mode 100644 index 00000000000..6af36b0a5e3 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt @@ -0,0 +1,9 @@ +// NAME: C +class B + +// SIBLING: +fun foo() { + class A + + val a: (B>, A, A) -> A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt.after b/idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt.after new file mode 100644 index 00000000000..ce76bcad2e8 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt.after @@ -0,0 +1,11 @@ +// NAME: C +class B + +typealias C = (B, T, U) -> T + +// SIBLING: +fun foo() { + class A + + val a: C, A> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt b/idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt new file mode 100644 index 00000000000..8756f83aa4d --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt @@ -0,0 +1,9 @@ +// NAME: B +class A + +class B + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt.conflicts new file mode 100644 index 00000000000..6819d31a79a --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt.conflicts @@ -0,0 +1 @@ +Type B already exists in the target scope \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt new file mode 100644 index 00000000000..df2c43780cf --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt @@ -0,0 +1,10 @@ +// NAME: S +// SIBLING: +fun foo() { + val t1: (Int) -> Boolean + val t2: (Int) -> Boolean + val t3: Int.() -> Boolean + val t4: (Int, Int) -> Boolean + val t5: (Boolean) -> Int + val t6: Function1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt.after b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt.after new file mode 100644 index 00000000000..105167ecfb7 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt.after @@ -0,0 +1,12 @@ +typealias S = (Int) -> Boolean + +// NAME: S +// SIBLING: +fun foo() { + val t1: S + val t2: S + val t3: Int.() -> Boolean + val t4: (Int, Int) -> Boolean + val t5: (Boolean) -> Int + val t6: S +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt new file mode 100644 index 00000000000..220e1039f96 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt @@ -0,0 +1,16 @@ +// NAME: S + +class A + +// SIBLING: +fun foo() { + class B + + val t1: (B) -> ((B) -> Boolean) + val t2: (B) -> ((B) -> Boolean) + val t3: ((B) -> B) -> Boolean + val t4: Function1, Function1, Boolean>> + val t5: (String) -> ((String) -> Boolean) + val t6: (Int) -> ((String) -> Boolean) + val t7: (A>) -> ((A>) -> Boolean) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt.after b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt.after new file mode 100644 index 00000000000..78a2a68cfe4 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt.after @@ -0,0 +1,18 @@ +// NAME: S + +class A + +typealias S = (T) -> ((T) -> Boolean) + +// SIBLING: +fun foo() { + class B + + val t1: S> + val t2: S> + val t3: ((B) -> B) -> Boolean + val t4: S> + val t5: S + val t6: (Int) -> ((String) -> Boolean) + val t7: S>> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/functionalType.kt b/idea/testData/refactoring/introduceTypeAlias/functionalType.kt new file mode 100644 index 00000000000..f2141c97c22 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/functionalType.kt @@ -0,0 +1,7 @@ +// NAME: F +class A + +// SIBLING: +fun foo() { + val a: A<(Int) -> String, String> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/functionalType.kt.after b/idea/testData/refactoring/introduceTypeAlias/functionalType.kt.after new file mode 100644 index 00000000000..96032dca375 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/functionalType.kt.after @@ -0,0 +1,9 @@ +// NAME: F +class A + +typealias F = (Int) -> String + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt b/idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt new file mode 100644 index 00000000000..1b333416344 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt @@ -0,0 +1,7 @@ +// NAME: B +class A + +fun foo() { + // SIBLING: + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt.after b/idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt.after new file mode 100644 index 00000000000..753180ed140 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt.after @@ -0,0 +1,9 @@ +// NAME: B +class A + +fun foo() { + // SIBLING: + typealias B = A + + val a: B +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt b/idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt new file mode 100644 index 00000000000..f78497d4dfd --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt @@ -0,0 +1,9 @@ +// NAME: C +class A + +// SIBLING: +fun foo() { + class B + + val a: A, B> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt.after b/idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt.after new file mode 100644 index 00000000000..4bc547ab944 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt.after @@ -0,0 +1,11 @@ +// NAME: C +class A + +typealias C = A + +// SIBLING: +fun foo() { + class B + + val a: C, B> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt b/idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt new file mode 100644 index 00000000000..f422442d5c7 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt @@ -0,0 +1,8 @@ +// NAME: B +// VISIBILITY: private +class A + +fun foo() { + // SIBLING: + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt.conflicts new file mode 100644 index 00000000000..aa192dc0db0 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt.conflicts @@ -0,0 +1 @@ +'private' is not allowed in the target context \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt b/idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt new file mode 100644 index 00000000000..befbb64b83a --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt @@ -0,0 +1,7 @@ +// NAME: B +class A + +class Test { + // SIBLING + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt.after b/idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt.after new file mode 100644 index 00000000000..f59fadb996a --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt.after @@ -0,0 +1,9 @@ +// NAME: B +class A + +class Test { + typealias B = A + + // SIBLING + val a: B +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt b/idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt new file mode 100644 index 00000000000..e84908b15cd --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt @@ -0,0 +1,9 @@ +// NAME: C +class A + +// SIBLING: +fun foo() { + class B + + val a: A, String>, B>> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt.after b/idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt.after new file mode 100644 index 00000000000..aecb61f53c1 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt.after @@ -0,0 +1,11 @@ +// NAME: C +class A + +typealias C = A + +// SIBLING: +fun foo() { + class B + + val a: C, String>, B>> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt b/idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt new file mode 100644 index 00000000000..5ccb3bdd1b2 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt @@ -0,0 +1,4 @@ +// NAME: A +fun foo() { + val x = 1 + 2 +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt.conflicts new file mode 100644 index 00000000000..fb39ce0908a --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt.conflicts @@ -0,0 +1 @@ +Cannot perform refactoring without a type \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt b/idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt new file mode 100644 index 00000000000..59cdd558323 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt @@ -0,0 +1,7 @@ +// NAME: 12 +class A + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt.conflicts new file mode 100644 index 00000000000..6a594e840ba --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt.conflicts @@ -0,0 +1 @@ +Type alias name must be a valid identifier: 12 \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/nullableType.kt b/idea/testData/refactoring/introduceTypeAlias/nullableType.kt new file mode 100644 index 00000000000..494cd96a634 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/nullableType.kt @@ -0,0 +1,7 @@ +// NAME: AN +class A + +// SIBLING: +fun foo() { + val a: A? +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/nullableType.kt.after b/idea/testData/refactoring/introduceTypeAlias/nullableType.kt.after new file mode 100644 index 00000000000..54ea4cba34d --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/nullableType.kt.after @@ -0,0 +1,9 @@ +// NAME: AN +class A + +typealias AN = A? + +// SIBLING: +fun foo() { + val a: AN +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt b/idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt new file mode 100644 index 00000000000..0af6f657301 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt @@ -0,0 +1,8 @@ +// NAME: B +// VISIBILITY: private +class A + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt.after b/idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt.after new file mode 100644 index 00000000000..2185cca81d9 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt.after @@ -0,0 +1,10 @@ +// NAME: B +// VISIBILITY: private +class A + +private typealias B = A + +// SIBLING: +fun foo() { + val a: B +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt b/idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt new file mode 100644 index 00000000000..327e557aedf --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt @@ -0,0 +1,8 @@ +// NAME: B +// VISIBILITY: protected +class A + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt.conflicts new file mode 100644 index 00000000000..e6b516bb4b6 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt.conflicts @@ -0,0 +1 @@ +'protected' is not allowed in the target context \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt b/idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt new file mode 100644 index 00000000000..a4148d9795a --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt @@ -0,0 +1,7 @@ +// NAME: B +class A + +// SIBLING: +fun foo() { + val a: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt.after b/idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt.after new file mode 100644 index 00000000000..5bd1f87bfe7 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt.after @@ -0,0 +1,9 @@ +// NAME: B +class A + +typealias B = A + +// SIBLING: +fun foo() { + val a: B +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt b/idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt new file mode 100644 index 00000000000..d6bdee23da3 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt @@ -0,0 +1,9 @@ +// NAME: F +class A + +// SIBLING: +fun foo() { + class B + + val a: A<(B) -> B, String> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt.after b/idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt.after new file mode 100644 index 00000000000..24d70357b35 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt.after @@ -0,0 +1,11 @@ +// NAME: F +class A + +typealias F = (T) -> U + +// SIBLING: +fun foo() { + class B + + val a: A, B>, String> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt new file mode 100644 index 00000000000..a3cdcc638ca --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt @@ -0,0 +1,9 @@ +// NAME: S +class A + +// SIBLING: +fun foo() { + val t1: A + val t2: A + val t3: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt.after b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt.after new file mode 100644 index 00000000000..52df1403af6 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt.after @@ -0,0 +1,11 @@ +// NAME: S +class A + +typealias S = A + +// SIBLING: +fun foo() { + val t1: S + val t2: S + val t3: A +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt new file mode 100644 index 00000000000..17e6339fbf3 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt @@ -0,0 +1,13 @@ +// NAME: S +class A + +// SIBLING: +fun foo() { + class B + + val t1: A, A, Boolean>> + val t2: A, A, Boolean>> + val t3: A> + val t4: A> + val t5: A<(Int) -> Int, A<() -> Int, Boolean>> +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt.after b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt.after new file mode 100644 index 00000000000..c01517739da --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt.after @@ -0,0 +1,15 @@ +// NAME: S +class A + +typealias S = A> + +// SIBLING: +fun foo() { + class B + + val t1: S> + val t2: S> + val t3: S + val t4: A> + val t5: A<(Int) -> Int, A<() -> Int, Boolean>> +} \ No newline at end of file diff --git a/idea/testData/unifier/equivalence/types/function1.kt.match b/idea/testData/unifier/equivalence/types/function1.kt.match index a75481cc86d..f1d28f35014 100644 --- a/idea/testData/unifier/equivalence/types/function1.kt.match +++ b/idea/testData/unifier/equivalence/types/function1.kt.match @@ -6,8 +6,4 @@ kotlin.Function1 (n: Int) -> kotlin.String -Int.() -> String - -(m: Int) -> kotlin.String - -@ExtensionFunctionType Function1 +(m: Int) -> kotlin.String \ No newline at end of file diff --git a/idea/testData/unifier/equivalence/types/function2.kt.match b/idea/testData/unifier/equivalence/types/function2.kt.match index fd9b5475119..9eee15c239e 100644 --- a/idea/testData/unifier/equivalence/types/function2.kt.match +++ b/idea/testData/unifier/equivalence/types/function2.kt.match @@ -6,8 +6,4 @@ kotlin.Function2 (a: Any, n: Int) -> kotlin.String -Any.(Int) -> String - -(t: Any, u: Int) -> kotlin.String - -@ExtensionFunctionType Function2 +(t: Any, u: Int) -> kotlin.String \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt index ddc343e5681..fa3b90994a0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt @@ -44,14 +44,17 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.* import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.INTRODUCE_PROPERTY import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.KotlinTestUtils @@ -309,6 +312,25 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase( } } + protected fun doIntroduceTypeAliasTest(path: String) { + doTest(path) { file -> + file as KtFile + + val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:") + val fileText = file.text + val aliasName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// NAME:")!! + val aliasVisibility = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// VISIBILITY:")?.let { + KtPsiFactory(project).createModifierList(it).firstChild.node.elementType as KtModifierKeywordToken + } + val editor = fixture.editor + KotlinIntroduceTypeAliasHandler.selectElements(editor, file) { elements, previousSibling -> + KotlinIntroduceTypeAliasHandler.doInvoke(project, editor, elements, explicitPreviousSibling ?: previousSibling) { + it.copy(name = aliasName, visibility = aliasVisibility ?: it.visibility) + } + } + } + } + protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) { val mainFile = File(path) val afterFile = File("$path.after") 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 33addb26663..b7f28c65b33 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -3961,4 +3961,139 @@ public class ExtractionTestGenerated extends AbstractExtractionTest { doIntroduceJavaParameterTest(fileName); } } + + @TestMetadata("idea/testData/refactoring/introduceTypeAlias") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class IntroduceTypeAlias extends AbstractExtractionTest { + public void testAllFilesPresentInIntroduceTypeAlias() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceTypeAlias"), Pattern.compile("^(.+)\\.(kt|kts)$"), true); + } + + @TestMetadata("emptyName.kt") + public void testEmptyName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/emptyName.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("entireTypeExtracted.kt") + public void testEntireTypeExtracted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("equivalentNestedTypeElements.kt") + public void testEquivalentNestedTypeElements() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("existingTypeClash.kt") + public void testExistingTypeClash() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("functionTypeDuplicatesNoTypeParameters.kt") + public void testFunctionTypeDuplicatesNoTypeParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("functionTypeDuplicatesWithTypeParameters.kt") + public void testFunctionTypeDuplicatesWithTypeParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("functionalType.kt") + public void testFunctionalType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/functionalType.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("localTypeAlias.kt") + public void testLocalTypeAlias() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("localTypeExtracted.kt") + public void testLocalTypeExtracted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("localWithVisibility.kt") + public void testLocalWithVisibility() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("memberTypeAlias.kt") + public void testMemberTypeAlias() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("nestedTypesExtracted.kt") + public void testNestedTypesExtracted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("noTypeElement.kt") + public void testNoTypeElement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("nonIdentifierName.kt") + public void testNonIdentifierName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("nullableType.kt") + public void testNullableType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/nullableType.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("privateTypeAlias.kt") + public void testPrivateTypeAlias() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("protectedInFile.kt") + public void testProtectedInFile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("topLevelTypeAlias.kt") + public void testTopLevelTypeAlias() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("typesExtractedWithFunctionalType.kt") + public void testTypesExtractedWithFunctionalType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("userTypeDuplicatesNoTypeParameters.kt") + public void testUserTypeDuplicatesNoTypeParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("userTypeDuplicatesWithTypeParameters.kt") + public void testUserTypeDuplicatesWithTypeParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt"); + doIntroduceTypeAliasTest(fileName); + } + } }