From 752ae87591ea93f834936df049bbdacd90fb16f2 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 21 Nov 2016 16:34:41 +0300 Subject: [PATCH] Introduce Type Alias: Support callable references/class literals #KT-14861 Fixed --- ChangeLog.md | 1 + .../jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt | 4 +- .../IntroduceTypeAliasHandler.kt | 53 ++++++++++++++----- .../introduceTypeAlias/extractionModel.kt | 12 ++--- .../introduceTypeAliasImpl.kt | 33 ++++++++---- .../introduceTypeAlias/callableReference.kt | 9 ++++ .../callableReference.kt.after | 11 ++++ .../callableReferenceSelector.kt | 9 ++++ .../callableReferenceSelector.kt.conflicts | 1 + .../introduceTypeAlias/classLiteral.kt | 9 ++++ .../introduceTypeAlias/classLiteral.kt.after | 11 ++++ .../introduce/AbstractExtractionTest.kt | 18 +++++-- .../introduce/ExtractionTestGenerated.java | 18 +++++++ 13 files changed, 155 insertions(+), 34 deletions(-) create mode 100644 idea/testData/refactoring/introduceTypeAlias/callableReference.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/callableReference.kt.after create mode 100644 idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt.conflicts create mode 100644 idea/testData/refactoring/introduceTypeAlias/classLiteral.kt create mode 100644 idea/testData/refactoring/introduceTypeAlias/classLiteral.kt.after diff --git a/ChangeLog.md b/ChangeLog.md index 71805baa75a..b3fb71706b4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,7 @@ - [`KT-14693`](https://youtrack.jetbrains.com/issue/KT-14693) Introduce Type Alias: Do not suggest type qualifiers - [`KT-14696`](https://youtrack.jetbrains.com/issue/KT-14696) Introduce Type Alias: Fix NPE during dialog repaint - [`KT-14685`](https://youtrack.jetbrains.com/issue/KT-14685) Introduce Type Alias: Replace type usages in constructor calls +- [`KT-14861`](https://youtrack.jetbrains.com/issue/KT-14861) Introduce Type Alias: Support callable references/class literals ## 1.1-M03 (EAP-3) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index 7effa552691..d80a95ccf24 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -473,4 +473,6 @@ fun isTypeConstructorReference(e: PsiElement): Boolean { return parent is KtUserType && parent.referenceExpression == e } -fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar() \ No newline at end of file +fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar() + +fun isDoubleColonReceiver(expression: KtExpression) = expression.getParentOfTypeAndBranch { this.receiverExpression } != null \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt index 04e2cfd0f6b..d17624cd63b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/IntroduceTypeAliasHandler.kt @@ -27,6 +27,7 @@ import com.intellij.refactoring.RefactoringActionHandler import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_CONSTRUCTOR import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_ELEMENT import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively +import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates @@ -34,16 +35,16 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui.Kot 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.KtSimpleNameExpression -import org.jetbrains.kotlin.psi.KtTypeAlias -import org.jetbrains.kotlin.psi.KtTypeElement -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.isTypeConstructorReference +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* -object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { - @JvmField - val REFACTORING_NAME = "Introduce Type Alias" +open class KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { + companion object { + @JvmField + val REFACTORING_NAME = "Introduce Type Alias" + + val INSTANCE = KotlinIntroduceTypeAliasHandler() + } fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List, targetSibling: PsiElement) -> Unit) { selectElementsWithTargetSibling( @@ -66,7 +67,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { } } - fun doInvoke( + open fun doInvoke( project: Project, editor: Editor, elements: List, @@ -76,7 +77,9 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { val elementToExtract = elements.singleOrNull() val errorMessage = when (elementToExtract) { - is KtSimpleNameExpression -> if (!isTypeConstructorReference(elementToExtract)) "Type reference is expected" else null + is KtSimpleNameExpression -> { + if (!(isTypeConstructorReference(elementToExtract) || isDoubleColonReceiver(elementToExtract))) "Type reference is expected" else null + } !is KtTypeElement -> "No type to refactor" else -> null } @@ -84,7 +87,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { val introduceData = when (elementToExtract) { is KtTypeElement -> IntroduceTypeAliasData(elementToExtract, targetSibling) - else -> IntroduceTypeAliasData(elementToExtract!!.getStrictParentOfType()!!, targetSibling, true) + else -> IntroduceTypeAliasData(elementToExtract!!.getStrictParentOfType() ?: elementToExtract as KtElement, targetSibling, true) } val analysisResult = introduceData.analyze() when (analysisResult) { @@ -107,6 +110,30 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { if (file !is KtFile) return + + val offset = if (editor.selectionModel.hasSelection()) editor.selectionModel.selectionStart else editor.caretModel.offset + + val refExpression = file.findElementAt(offset)?.getNonStrictParentOfType() + if (refExpression != null && isDoubleColonReceiver(refExpression)) { + val containers = refExpression.getExtractionContainers(strict = true, includeAll = true) + + chooseContainerElementIfNecessary( + containers, + editor, + if (containers.first() is KtFile) "Select target file" else "Select target code block / file", + true, + { it }, + { + val targetSibling = refExpression.getOutermostParentContainedIn(it) + if (targetSibling != null) { + doInvoke(project, editor, listOf(refExpression), targetSibling) + } + } + ) + + return + } + selectElements(editor, file) { elements, targetSibling -> doInvoke(project, editor, elements, targetSibling) } } @@ -116,5 +143,5 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { } class IntroduceTypeAliasAction : AbstractIntroduceAction() { - override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler + override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler.INSTANCE } \ 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 index 1bf82e8c8bd..3862bc4fec3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/extractionModel.kt @@ -33,12 +33,12 @@ 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 originalTypeElement: KtElement, val targetSibling: PsiElement, val extractTypeConstructor: Boolean = false ) : Disposable { - val resolutionFacade = originalType.getResolutionFacade() - val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL) + val resolutionFacade = originalTypeElement.getResolutionFacade() + val bindingContext = resolutionFacade.analyze(originalTypeElement, BodyResolveMode.PARTIAL) init { markReferences() @@ -57,12 +57,12 @@ class IntroduceTypeAliasData( typeElement.typeArgumentsAsTypes.forEach { it.accept(this) } } } - (originalType.parent as? KtTypeReference ?: originalType).accept(visitor) + (originalTypeElement.parent as? KtTypeReference ?: originalTypeElement).accept(visitor) } override fun dispose() { - if (!originalType.isValid) return - originalType.forEachDescendantOfType { it.resolveInfo = null } + if (!originalTypeElement.isValid) return + originalTypeElement.forEachDescendantOfType { it.resolveInfo = null } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt index d1141670719..67bf2bc8702 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/introduceTypeAliasImpl.kt @@ -55,18 +55,20 @@ sealed class IntroduceTypeAliasAnalysisResult { private fun IntroduceTypeAliasData.getTargetScope() = targetSibling.getResolutionScope(bindingContext, resolutionFacade) fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult { - val psiFactory = KtPsiFactory(originalType) + val psiFactory = KtPsiFactory(originalTypeElement) - val contextExpression = originalType.getStrictParentOfType()!! + val contextExpression = originalTypeElement.getStrictParentOfType()!! val targetScope = getTargetScope() val dummyVar = psiFactory.createProperty("val a: Int").apply { - typeReference!!.replace(originalType.parent as? KtTypeReference ?: psiFactory.createType(originalType)) + typeReference!!.replace( + originalTypeElement.parent as? KtTypeReference ?: + if (originalTypeElement is KtTypeElement) psiFactory.createType(originalTypeElement) else psiFactory.createType(originalTypeElement.text)) } val newTypeReference = dummyVar.typeReference!! val newReferences = newTypeReference.collectDescendantsOfType { it.resolveInfo != null } val newContext = dummyVar.analyzeInContext(targetScope, contextExpression) - val project = originalType.project + val project = originalTypeElement.project val unifier = KotlinPsiUnifier.DEFAULT val groupedReferencesToExtract = LinkedMultiMap() @@ -106,7 +108,7 @@ fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult { val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator) val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedReferencesToExtract[it.second]) } - if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalType } }) { + if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalTypeElement } }) { return IntroduceTypeAliasAnalysisResult.Error("Type alias cannot refer to types which aren't accessible in the scope where it's defined") } @@ -131,7 +133,7 @@ fun IntroduceTypeAliasData.getApplicableVisibilities(): List() - val originalType = originalData.originalType + val originalType = originalData.originalTypeElement if (name.isEmpty()) { conflicts.putValue(originalType, "No name provided for type alias") } @@ -181,6 +183,8 @@ fun findDuplicates(typeAlias: KtTypeAlias): Map Unit> { } occurrence.calleeExpression?.replace(psiFactory.createExpression(aliasName)) } + + is KtExpression -> occurrence.replace(psiFactory.createExpression(aliasName)) } } @@ -233,15 +237,21 @@ fun findDuplicates(typeAlias: KtTypeAlias): Map Unit> { private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO")) fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias { - val originalType = originalData.originalType - val psiFactory = KtPsiFactory(originalType) + val originalElement = originalData.originalTypeElement + val psiFactory = KtPsiFactory(originalElement) for (typeParameter in typeParameters) for (it in typeParameter.typeReferenceInfos) { it.reference.typeParameterInfo = typeParameter } - val typeAlias = psiFactory.createTypeAlias(name, typeParameters.map { it.name }, originalType) + val typeParameterNames = typeParameters.map { it.name } + val typeAlias = if (originalElement is KtTypeElement) { + psiFactory.createTypeAlias(name, typeParameterNames, originalElement) + } + else { + psiFactory.createTypeAlias(name, typeParameterNames, originalElement.text) + } if (visibility != null && visibility != KtTokens.DEFAULT_VISIBILITY_KEYWORD) { typeAlias.addModifier(visibility) } @@ -258,7 +268,10 @@ fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false) else { name } - originalType.replace(psiFactory.createType(aliasInstanceText).typeElement!!) + when (originalElement) { + is KtTypeElement -> originalElement.replace(psiFactory.createType(aliasInstanceText).typeElement!!) + is KtExpression -> originalElement.replace(psiFactory.createExpression(aliasInstanceText)) + } } fun introduceTypeParameters() { diff --git a/idea/testData/refactoring/introduceTypeAlias/callableReference.kt b/idea/testData/refactoring/introduceTypeAlias/callableReference.kt new file mode 100644 index 00000000000..fb31e92fdd1 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/callableReference.kt @@ -0,0 +1,9 @@ +// NAME: T +class A { + fun bar() = 1 +} + +// SIBLING: +fun foo() { + val a = A::bar +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/callableReference.kt.after b/idea/testData/refactoring/introduceTypeAlias/callableReference.kt.after new file mode 100644 index 00000000000..4d438de7581 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/callableReference.kt.after @@ -0,0 +1,11 @@ +// NAME: T +class A { + fun bar() = 1 +} + +typealias T = A + +// SIBLING: +fun foo() { + val a = T::bar +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt b/idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt new file mode 100644 index 00000000000..3aded69c709 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt @@ -0,0 +1,9 @@ +// NAME: T +class A { + fun bar() = 1 +} + +// SIBLING: +fun foo() { + val a = A::bar +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt.conflicts b/idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt.conflicts new file mode 100644 index 00000000000..3fa716238f9 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt.conflicts @@ -0,0 +1 @@ +Refactoring can't be performed on the selected code element diff --git a/idea/testData/refactoring/introduceTypeAlias/classLiteral.kt b/idea/testData/refactoring/introduceTypeAlias/classLiteral.kt new file mode 100644 index 00000000000..9f1ecb01c29 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/classLiteral.kt @@ -0,0 +1,9 @@ +// NAME: T +class A { + fun bar() = 1 +} + +// SIBLING: +fun foo() { + val a = A::class +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceTypeAlias/classLiteral.kt.after b/idea/testData/refactoring/introduceTypeAlias/classLiteral.kt.after new file mode 100644 index 00000000000..c6b32d43d25 --- /dev/null +++ b/idea/testData/refactoring/introduceTypeAlias/classLiteral.kt.after @@ -0,0 +1,11 @@ +// NAME: T +class A { + fun bar() = 1 +} + +typealias T = A + +// SIBLING: +fun foo() { + val a = T::class +} \ 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 39f528cc6e5..d76a47d8dcc 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.PsiComment +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiJavaFile import com.intellij.psi.codeStyle.JavaCodeStyleManager @@ -50,6 +51,7 @@ 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.IntroduceTypeAliasDescriptor import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.KotlinIntroduceTypeParameterHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler @@ -346,11 +348,19 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase( 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 ?: it.name, visibility = aliasVisibility ?: it.visibility) + object : KotlinIntroduceTypeAliasHandler() { + override fun doInvoke( + project: Project, + editor: Editor, + elements: List, + targetSibling: PsiElement, + descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? + ) { + super.doInvoke(project, editor, elements, explicitPreviousSibling ?: targetSibling) { + it.copy(name = aliasName ?: it.name, visibility = aliasVisibility ?: it.visibility) + } } - } + }.invoke(project, editor, file, null) } } 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 af9a236a959..c3b971fc554 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -4076,6 +4076,24 @@ public class ExtractionTestGenerated extends AbstractExtractionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceTypeAlias"), Pattern.compile("^(.+)\\.(kt|kts)$"), TargetBackend.ANY, true); } + @TestMetadata("callableReference.kt") + public void testCallableReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/callableReference.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("callableReferenceSelector.kt") + public void testCallableReferenceSelector() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt"); + doIntroduceTypeAliasTest(fileName); + } + + @TestMetadata("classLiteral.kt") + public void testClassLiteral() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/classLiteral.kt"); + doIntroduceTypeAliasTest(fileName); + } + @TestMetadata("constructorCalls.kt") public void testConstructorCalls() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt");