Introduce Type Alias

#KT-12902 Fixed
This commit is contained in:
Alexey Sedunov
2016-07-12 15:49:08 +03:00
parent ce0e5b4b46
commit 130e4fb745
63 changed files with 1482 additions and 90 deletions
+1
View File
@@ -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)
@@ -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<String>, typeElement: KtTypeElement): KtTypeAlias {
val typeParametersText = if (typeParameters.isNotEmpty()) typeParameters.joinToString(prefix = "<", postfix = ">") else ""
return createDeclaration<KtTypeAlias>("typealias $name$typeParametersText = X")
.apply { getTypeReference()!!.replace(createType(typeElement)) }
}
fun createStar(): PsiElement {
return createType("List<*>").findElementAt(5)!!
}
@@ -41,4 +41,9 @@ public class KtTypeArgumentList extends KtElementImplStub<KotlinPlaceHolderStub<
public List<KtTypeProjection> getArguments() {
return getStubOrPsiChildrenAsList(KtStubElementTypes.TYPE_PROJECTION);
}
@NotNull
public KtTypeProjection addArgument(@NotNull KtTypeProjection argument) {
return EditCommaSeparatedListHelper.INSTANCE.addItem(this, getArguments(), argument);
}
}
@@ -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 <reified T : PsiElement> T.nextSiblingOfSameType() = PsiTreeUtil.getNextSiblingOfType(this, T::class.java)
inline fun <reified T : PsiElement> T.prevSiblingOfSameType() = PsiTreeUtil.getPrevSiblingOfType(this, T::class.java)
@@ -766,6 +766,7 @@ fun main(args: Array<String>) {
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<AbstractPullUpTest>() {
@@ -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<KtDeclaration>()
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() }
}
@@ -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<UnifierParameter, KtExpression>
val substitution: Map<UnifierParameter, KtElement>
override val status: Status get() = MATCHED
}
class StronglyMatched(
override val range: KotlinPsiRange,
override val substitution: Map<UnifierParameter, KtExpression>
override val substitution: Map<UnifierParameter, KtElement>
): Matched
class WeaklyMatched(
override val range: KotlinPsiRange,
override val substitution: Map<UnifierParameter, KtExpression>,
override val substitution: Map<UnifierParameter, KtElement>,
val weakMatches: Map<KtElement, KtElement>
): 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<UnifierParameter, KtExpression>()
val substitution = HashMap<UnifierParameter, KtElement>()
val declarationPatternsToTargets = MultiMap<DeclarationDescriptor, DeclarationDescriptor>()
val weakMatches = HashMap<KtElement, KtElement>()
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<KotlinType>, types2: Collection<KotlinType>): Boolean {
fun sortTypes(types: Collection<KotlinType>) = 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<KtTypeElement>() != 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<KtTypeElement>() != null
&& decl2.getStrictParentOfType<KtTypeElement>() != 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)
}
}
+6
View File
@@ -157,6 +157,12 @@
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunction"/>
</action>
<action id="IntroduceTypeAlias" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasAction"
text="Type _Alias...">
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift A"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunctionToScope"/>
</action>
<!-- Kotlin Console REPL-->
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
text="Kotlin REPL"
@@ -6,6 +6,7 @@ introduce.property=Introduce Property
introduce.parameter=Introduce Parameter
cannot.refactor.no.container=Cannot refactor in this place
cannot.refactor.no.expression=Cannot perform refactoring without an expression
cannot.refactor.no.type=Cannot perform refactoring without a type
cannot.refactor.syntax.errors=Cannot refactor due to erroneous code
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
cannot.refactor.package.expression=Cannot introduce package reference
@@ -24,11 +24,11 @@ import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.codeInsight.KotlinFileReferencesResolver
import org.jetbrains.kotlin.idea.core.compareDescriptors
import org.jetbrains.kotlin.idea.refactoring.getContextForContainingDeclarationBody
import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo
import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis
@@ -114,7 +114,7 @@ data class ExtractionData(
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
val bindingContext: BindingContext? by lazy { commonParent.getContextForContainingDeclarationBody() }
val bindingContext: BindingContext? by lazy { commonParent.analyze() }
private val itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
@@ -0,0 +1,106 @@
/*
* 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.lang.refactoring.RefactoringSupportProvider
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
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
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetSibling
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.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeElement
object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
@JvmField
val REFACTORING_NAME = "Introduce Type Alias"
fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List<PsiElement>, 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<KtTypeAlias>(REFACTORING_NAME) { descriptor.generateTypeAlias() }
val duplicateReplacers = findDuplicates(typeAlias)
if (duplicateReplacers.isNotEmpty()) {
processDuplicates(duplicateReplacers, project, editor)
}
}
fun doInvoke(
project: Project,
editor: Editor,
elements: List<PsiElement>,
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<out PsiElement>, dataContext: DataContext?) {
throw AssertionError("$REFACTORING_NAME can only be invoked from editor")
}
}
class IntroduceTypeAliasAction : AbstractIntroduceAction() {
override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler
}
@@ -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<KtTypeReference> { it.resolveInfo = null }
}
}
data class TypeParameter(val name: String, val typeReferenceInfos: Collection<TypeReferenceInfo>)
data class IntroduceTypeAliasDescriptor(
val originalData: IntroduceTypeAliasData,
val name: String,
val visibility: KtModifierKeywordToken?,
val typeParameters: List<TypeParameter>
)
data class IntroduceTypeAliasDescriptorWithConflicts(
val descriptor: IntroduceTypeAliasDescriptor,
val conflicts: MultiMap<PsiElement, String>
)
@@ -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<KtExpression>()!!
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<KtTypeReference> { it.resolveInfo != null }
val newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
val project = originalType.project
val unifier = KotlinPsiUnifier.DEFAULT
val groupedBrokenReferences = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
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<KtModifierKeywordToken>{
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<PsiElement, String>()
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<KotlinPsiRange, () -> 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<KtTypeElement>) {
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<KtTypeReference> {
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<PsiElement>()
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()
}
}
@@ -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<TypeParameter, IntroduceTypeAliasParameterTablePanel.TypeParameterInfo>() {
class TypeParameterInfo(
originalParameter: TypeParameter
) : AbstractParameterTablePanel.AbstractParameterInfo<TypeParameter>(originalParameter) {
init {
name = originalParameter.name
}
override fun toParameter() = originalParameter.copy(name)
}
fun init(parameters: List<TypeParameter>) {
parameterInfos = parameters.mapTo(ArrayList()) { TypeParameterInfo(it) }
super.init()
}
val selectedTypeParameterInfos: List<TypeParameterInfo>
get() = parameterInfos.filter { it.isEnabled }
val selectedTypeParameters: List<TypeParameter>
get() = selectedTypeParameterInfos.map { it.toParameter() }
}
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog">
<grid id="27dc6" binding="contentPane" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="522" height="396"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="34b30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="fa2d1" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="2aa47" class="com.intellij.ui.TitledSeparator" binding="inputParametersPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints border-constraint="Center"/>
<properties>
<text value=""/>
</properties>
<border type="none" title="Parameters"/>
<children/>
</grid>
</children>
</grid>
</children>
</grid>
<grid id="b45d1" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="200"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="4266d" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none" title="Signature Preview"/>
<children>
<component id="7722b" class="org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent" binding="signaturePreviewField" custom-create="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
<minimum-size width="500" height="100"/>
<preferred-size width="500" height="100"/>
</grid>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<grid id="5fe96" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<visible value="true"/>
</properties>
<border type="empty"/>
<children>
<component id="d1d09" class="javax.swing.JLabel" binding="aliasNameLabel">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Name:"/>
</properties>
</component>
<component id="52aa7" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="114" height="16"/>
</grid>
</constraints>
<properties>
<labelFor value="71ee7"/>
<text value="&amp;Visibility:"/>
<visible value="true"/>
</properties>
</component>
<grid id="d0f58" binding="aliasNamePanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
<component id="71ee7" class="javax.swing.JComboBox" binding="visibilityBox">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="114" height="26"/>
</grid>
</constraints>
<properties>
<visible value="true"/>
</properties>
</component>
</children>
</grid>
<vspacer id="cd10f">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
</children>
</grid>
</form>
@@ -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<KotlinIntroduceTypeAliasDialog, Unit> onAccept;
public KotlinIntroduceTypeAliasDialog(
@NotNull Project project,
@NotNull IntroduceTypeAliasDescriptor originalDescriptor,
@NotNull Function1<KotlinIntroduceTypeAliasDialog, Unit> 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<KtModifierKeywordToken> 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<PsiElement, String> conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts();
KotlinRefactoringUtilKt.checkConflictsInteractively(
project,
conflicts,
new Function0<Unit>() {
@Override
public Unit invoke() {
close(OK_EXIT_CODE);
return Unit.INSTANCE;
}
},
new Function0<Unit>() {
@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.<TypeParameter>emptyList()
);
}
public IntroduceTypeAliasDescriptor getCurrentDescriptor() {
return currentDescriptor;
}
}
@@ -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') }
}
}
@@ -0,0 +1,7 @@
// NAME:
class A<X, Y>
// SIBLING:
fun foo() {
val a: <caret>A<Int, String>
}
@@ -0,0 +1 @@
No name provided for type alias
@@ -0,0 +1,8 @@
// NAME: C
// SIBLING:
fun foo() {
class A<X, Y>
val a: <caret>A<Int, String>
}
@@ -0,0 +1 @@
Type alias cannot refer to types which aren't accessible in the scope where it's defined
@@ -0,0 +1,9 @@
// NAME: C
class B<X>
// SIBLING:
fun foo() {
class A<X>
val a: <caret>(B<A<Int>>, A<Int>, A<String>) -> A<Int>
}
@@ -0,0 +1,11 @@
// NAME: C
class B<X>
typealias C<T, U> = (B<T>, T, U) -> T
// SIBLING:
fun foo() {
class A<X>
val a: C<A<Int>, A<String>>
}
@@ -0,0 +1,9 @@
// NAME: B
class A<X, Y>
class B
// SIBLING:
fun foo() {
val a: <caret>A<Int, String>
}
@@ -0,0 +1 @@
Type B already exists in the target scope
@@ -0,0 +1,10 @@
// NAME: S
// SIBLING:
fun foo() {
val t1: <caret>(Int) -> Boolean
val t2: (Int) -> Boolean
val t3: Int.() -> Boolean
val t4: (Int, Int) -> Boolean
val t5: (Boolean) -> Int
val t6: Function1<Int, Boolean>
}
@@ -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
}
@@ -0,0 +1,16 @@
// NAME: S
class A<X>
// SIBLING:
fun foo() {
class B<T>
val t1: <caret>(B<Int>) -> ((B<Int>) -> Boolean)
val t2: (B<Int>) -> ((B<Int>) -> Boolean)
val t3: ((B<Int>) -> B<Int>) -> Boolean
val t4: Function1<B<Int>, Function1<B<Int>, Boolean>>
val t5: (String) -> ((String) -> Boolean)
val t6: (Int) -> ((String) -> Boolean)
val t7: (A<B<String>>) -> ((A<B<String>>) -> Boolean)
}
@@ -0,0 +1,18 @@
// NAME: S
class A<X>
typealias S<T> = (T) -> ((T) -> Boolean)
// SIBLING:
fun foo() {
class B<T>
val t1: S<B<Int>>
val t2: S<B<Int>>
val t3: ((B<Int>) -> B<Int>) -> Boolean
val t4: S<B<Int>>
val t5: S<String>
val t6: (Int) -> ((String) -> Boolean)
val t7: S<A<B<String>>>
}
@@ -0,0 +1,7 @@
// NAME: F
class A<X, Y>
// SIBLING:
fun foo() {
val a: A<<caret>(Int) -> String, String>
}
@@ -0,0 +1,9 @@
// NAME: F
class A<X, Y>
typealias F = (Int) -> String
// SIBLING:
fun foo() {
val a: A<F, String>
}
@@ -0,0 +1,7 @@
// NAME: B
class A<X, Y>
fun foo() {
// SIBLING:
val a: <caret>A<Int, String>
}
@@ -0,0 +1,9 @@
// NAME: B
class A<X, Y>
fun foo() {
// SIBLING:
typealias B = A<Int, String>
val a: B
}
@@ -0,0 +1,9 @@
// NAME: C
class A<X, Y>
// SIBLING:
fun foo() {
class B<X, Y>
val a: <caret>A<B<Int, String>, B<String, Int>>
}
@@ -0,0 +1,11 @@
// NAME: C
class A<X, Y>
typealias C<T, U> = A<T, U>
// SIBLING:
fun foo() {
class B<X, Y>
val a: C<B<Int, String>, B<String, Int>>
}
@@ -0,0 +1,8 @@
// NAME: B
// VISIBILITY: private
class A<X, Y>
fun foo() {
// SIBLING:
val a: <caret>A<Int, String>
}
@@ -0,0 +1 @@
'private' is not allowed in the target context
@@ -0,0 +1,7 @@
// NAME: B
class A<X, Y>
class Test {
// SIBLING
val a: <caret>A<Int, String>
}
@@ -0,0 +1,9 @@
// NAME: B
class A<X, Y>
class Test {
typealias B = A<Int, String>
// SIBLING
val a: B
}
@@ -0,0 +1,9 @@
// NAME: C
class A<X, Y>
// SIBLING:
fun foo() {
class B<X, Y>
val a: <caret>A<B<B<Int, String>, String>, B<String, B<Int, String>>>
}
@@ -0,0 +1,11 @@
// NAME: C
class A<X, Y>
typealias C<T, U> = A<T, U>
// SIBLING:
fun foo() {
class B<X, Y>
val a: C<B<B<Int, String>, String>, B<String, B<Int, String>>>
}
@@ -0,0 +1,4 @@
// NAME: A
fun foo() {
val x = <caret>1 + 2
}
@@ -0,0 +1 @@
Cannot perform refactoring without a type
@@ -0,0 +1,7 @@
// NAME: 12
class A<X, Y>
// SIBLING:
fun foo() {
val a: <caret>A<Int, String>
}
@@ -0,0 +1 @@
Type alias name must be a valid identifier: 12
@@ -0,0 +1,7 @@
// NAME: AN
class A<X, Y>
// SIBLING:
fun foo() {
val a: A<Int, String>?<caret>
}
@@ -0,0 +1,9 @@
// NAME: AN
class A<X, Y>
typealias AN = A<Int, String>?
// SIBLING:
fun foo() {
val a: AN
}
@@ -0,0 +1,8 @@
// NAME: B
// VISIBILITY: private
class A<X, Y>
// SIBLING:
fun foo() {
val a: <caret>A<Int, String>
}
@@ -0,0 +1,10 @@
// NAME: B
// VISIBILITY: private
class A<X, Y>
private typealias B = A<Int, String>
// SIBLING:
fun foo() {
val a: B
}
@@ -0,0 +1,8 @@
// NAME: B
// VISIBILITY: protected
class A<X, Y>
// SIBLING:
fun foo() {
val a: <caret>A<Int, String>
}
@@ -0,0 +1 @@
'protected' is not allowed in the target context
@@ -0,0 +1,7 @@
// NAME: B
class A<X, Y>
// SIBLING:
fun foo() {
val a: <caret>A<Int, String>
}
@@ -0,0 +1,9 @@
// NAME: B
class A<X, Y>
typealias B = A<Int, String>
// SIBLING:
fun foo() {
val a: B
}
@@ -0,0 +1,9 @@
// NAME: F
class A<X, Y>
// SIBLING:
fun foo() {
class B<X>
val a: A<<caret>(B<Int>) -> B<String>, String>
}
@@ -0,0 +1,11 @@
// NAME: F
class A<X, Y>
typealias F<T, U> = (T) -> U
// SIBLING:
fun foo() {
class B<X>
val a: A<F<B<Int>, B<String>>, String>
}
@@ -0,0 +1,9 @@
// NAME: S
class A<X, Y>
// SIBLING:
fun foo() {
val t1: <caret>A<Int, Boolean>
val t2: A<Int, Boolean>
val t3: A<Boolean, Int>
}
@@ -0,0 +1,11 @@
// NAME: S
class A<X, Y>
typealias S = A<Int, Boolean>
// SIBLING:
fun foo() {
val t1: S
val t2: S
val t3: A<Boolean, Int>
}
@@ -0,0 +1,13 @@
// NAME: S
class A<X, Y>
// SIBLING:
fun foo() {
class B<T>
val t1: <caret>A<B<Int>, A<B<Int>, Boolean>>
val t2: A<B<Int>, A<B<Int>, Boolean>>
val t3: A<String, A<String, Boolean>>
val t4: A<Int, A<String, Boolean>>
val t5: A<(Int) -> Int, A<() -> Int, Boolean>>
}
@@ -0,0 +1,15 @@
// NAME: S
class A<X, Y>
typealias S<T> = A<T, A<T, Boolean>>
// SIBLING:
fun foo() {
class B<T>
val t1: S<B<Int>>
val t2: S<B<Int>>
val t3: S<String>
val t4: A<Int, A<String, Boolean>>
val t5: A<(Int) -> Int, A<() -> Int, Boolean>>
}
+1 -5
View File
@@ -6,8 +6,4 @@ kotlin.Function1<kotlin.Int, kotlin.String>
(n: Int) -> kotlin.String
Int.() -> String
(m: Int) -> kotlin.String
@ExtensionFunctionType Function1<Int, String>
(m: Int) -> kotlin.String
+1 -5
View File
@@ -6,8 +6,4 @@ kotlin.Function2<kotlin.Any, kotlin.Int, kotlin.String>
(a: Any, n: Int) -> kotlin.String
Any.(Int) -> String
(t: Any, u: Int) -> kotlin.String
@ExtensionFunctionType Function2<Any, Int, String>
(t: Any, u: Int) -> kotlin.String
@@ -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")
@@ -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);
}
}
}