Introduce Type Alias: Support callable references/class literals

#KT-14861 Fixed
This commit is contained in:
Alexey Sedunov
2016-11-21 16:34:41 +03:00
parent 9967d4c4e2
commit 752ae87591
13 changed files with 155 additions and 34 deletions
+1
View File
@@ -11,6 +11,7 @@
- [`KT-14693`](https://youtrack.jetbrains.com/issue/KT-14693) Introduce Type Alias: Do not suggest type qualifiers - [`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-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-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) ## 1.1-M03 (EAP-3)
@@ -473,4 +473,6 @@ fun isTypeConstructorReference(e: PsiElement): Boolean {
return parent is KtUserType && parent.referenceExpression == e return parent is KtUserType && parent.referenceExpression == e
} }
fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar() fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar()
fun isDoubleColonReceiver(expression: KtExpression) = expression.getParentOfTypeAndBranch<KtDoubleColonExpression> { this.receiverExpression } != null
@@ -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_CONSTRUCTOR
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_ELEMENT import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_ELEMENT
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates 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.selectElementsWithTargetSibling
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtTypeElement
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isTypeConstructorReference
object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { open class KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
@JvmField companion object {
val REFACTORING_NAME = "Introduce Type Alias" @JvmField
val REFACTORING_NAME = "Introduce Type Alias"
val INSTANCE = KotlinIntroduceTypeAliasHandler()
}
fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit) { fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit) {
selectElementsWithTargetSibling( selectElementsWithTargetSibling(
@@ -66,7 +67,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
} }
} }
fun doInvoke( open fun doInvoke(
project: Project, project: Project,
editor: Editor, editor: Editor,
elements: List<PsiElement>, elements: List<PsiElement>,
@@ -76,7 +77,9 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
val elementToExtract = elements.singleOrNull() val elementToExtract = elements.singleOrNull()
val errorMessage = when (elementToExtract) { 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" !is KtTypeElement -> "No type to refactor"
else -> null else -> null
} }
@@ -84,7 +87,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
val introduceData = when (elementToExtract) { val introduceData = when (elementToExtract) {
is KtTypeElement -> IntroduceTypeAliasData(elementToExtract, targetSibling) is KtTypeElement -> IntroduceTypeAliasData(elementToExtract, targetSibling)
else -> IntroduceTypeAliasData(elementToExtract!!.getStrictParentOfType<KtTypeElement>()!!, targetSibling, true) else -> IntroduceTypeAliasData(elementToExtract!!.getStrictParentOfType<KtTypeElement>() ?: elementToExtract as KtElement, targetSibling, true)
} }
val analysisResult = introduceData.analyze() val analysisResult = introduceData.analyze()
when (analysisResult) { when (analysisResult) {
@@ -107,6 +110,30 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
if (file !is KtFile) return if (file !is KtFile) return
val offset = if (editor.selectionModel.hasSelection()) editor.selectionModel.selectionStart else editor.caretModel.offset
val refExpression = file.findElementAt(offset)?.getNonStrictParentOfType<KtSimpleNameExpression>()
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) } selectElements(editor, file) { elements, targetSibling -> doInvoke(project, editor, elements, targetSibling) }
} }
@@ -116,5 +143,5 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
} }
class IntroduceTypeAliasAction : AbstractIntroduceAction() { class IntroduceTypeAliasAction : AbstractIntroduceAction() {
override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler.INSTANCE
} }
@@ -33,12 +33,12 @@ class TypeReferenceInfo(val reference: KtTypeReference, val type: KotlinType)
internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDataProperty(Key.create("RESOLVE_INFO")) internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDataProperty(Key.create("RESOLVE_INFO"))
class IntroduceTypeAliasData( class IntroduceTypeAliasData(
val originalType: KtTypeElement, val originalTypeElement: KtElement,
val targetSibling: PsiElement, val targetSibling: PsiElement,
val extractTypeConstructor: Boolean = false val extractTypeConstructor: Boolean = false
) : Disposable { ) : Disposable {
val resolutionFacade = originalType.getResolutionFacade() val resolutionFacade = originalTypeElement.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL) val bindingContext = resolutionFacade.analyze(originalTypeElement, BodyResolveMode.PARTIAL)
init { init {
markReferences() markReferences()
@@ -57,12 +57,12 @@ class IntroduceTypeAliasData(
typeElement.typeArgumentsAsTypes.forEach { it.accept(this) } typeElement.typeArgumentsAsTypes.forEach { it.accept(this) }
} }
} }
(originalType.parent as? KtTypeReference ?: originalType).accept(visitor) (originalTypeElement.parent as? KtTypeReference ?: originalTypeElement).accept(visitor)
} }
override fun dispose() { override fun dispose() {
if (!originalType.isValid) return if (!originalTypeElement.isValid) return
originalType.forEachDescendantOfType<KtTypeReference> { it.resolveInfo = null } originalTypeElement.forEachDescendantOfType<KtTypeReference> { it.resolveInfo = null }
} }
} }
@@ -55,18 +55,20 @@ sealed class IntroduceTypeAliasAnalysisResult {
private fun IntroduceTypeAliasData.getTargetScope() = targetSibling.getResolutionScope(bindingContext, resolutionFacade) private fun IntroduceTypeAliasData.getTargetScope() = targetSibling.getResolutionScope(bindingContext, resolutionFacade)
fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult { fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult {
val psiFactory = KtPsiFactory(originalType) val psiFactory = KtPsiFactory(originalTypeElement)
val contextExpression = originalType.getStrictParentOfType<KtExpression>()!! val contextExpression = originalTypeElement.getStrictParentOfType<KtExpression>()!!
val targetScope = getTargetScope() val targetScope = getTargetScope()
val dummyVar = psiFactory.createProperty("val a: Int").apply { val dummyVar = psiFactory.createProperty("val a: Int").apply {
typeReference!!.replace(originalType.parent as? KtTypeReference ?: psiFactory.createType(originalType)) typeReference!!.replace(
originalTypeElement.parent as? KtTypeReference ?:
if (originalTypeElement is KtTypeElement) psiFactory.createType(originalTypeElement) else psiFactory.createType(originalTypeElement.text))
} }
val newTypeReference = dummyVar.typeReference!! val newTypeReference = dummyVar.typeReference!!
val newReferences = newTypeReference.collectDescendantsOfType<KtTypeReference> { it.resolveInfo != null } val newReferences = newTypeReference.collectDescendantsOfType<KtTypeReference> { it.resolveInfo != null }
val newContext = dummyVar.analyzeInContext(targetScope, contextExpression) val newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
val project = originalType.project val project = originalTypeElement.project
val unifier = KotlinPsiUnifier.DEFAULT val unifier = KotlinPsiUnifier.DEFAULT
val groupedReferencesToExtract = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>() val groupedReferencesToExtract = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
@@ -106,7 +108,7 @@ fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult {
val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator) val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator)
val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedReferencesToExtract[it.second]) } val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedReferencesToExtract[it.second]) }
if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalType } }) { if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalTypeElement } }) {
return IntroduceTypeAliasAnalysisResult.Error("Type alias cannot refer to types which aren't accessible in the scope where it's defined") return IntroduceTypeAliasAnalysisResult.Error("Type alias cannot refer to types which aren't accessible in the scope where it's defined")
} }
@@ -131,7 +133,7 @@ fun IntroduceTypeAliasData.getApplicableVisibilities(): List<KtModifierKeywordTo
fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithConflicts { fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithConflicts {
val conflicts = MultiMap<PsiElement, String>() val conflicts = MultiMap<PsiElement, String>()
val originalType = originalData.originalType val originalType = originalData.originalTypeElement
if (name.isEmpty()) { if (name.isEmpty()) {
conflicts.putValue(originalType, "No name provided for type alias") conflicts.putValue(originalType, "No name provided for type alias")
} }
@@ -181,6 +183,8 @@ fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
} }
occurrence.calleeExpression?.replace(psiFactory.createExpression(aliasName)) occurrence.calleeExpression?.replace(psiFactory.createExpression(aliasName))
} }
is KtExpression -> occurrence.replace(psiFactory.createExpression(aliasName))
} }
} }
@@ -233,15 +237,21 @@ fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO")) private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO"))
fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias { fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias {
val originalType = originalData.originalType val originalElement = originalData.originalTypeElement
val psiFactory = KtPsiFactory(originalType) val psiFactory = KtPsiFactory(originalElement)
for (typeParameter in typeParameters) for (typeParameter in typeParameters)
for (it in typeParameter.typeReferenceInfos) { for (it in typeParameter.typeReferenceInfos) {
it.reference.typeParameterInfo = typeParameter 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) { if (visibility != null && visibility != KtTokens.DEFAULT_VISIBILITY_KEYWORD) {
typeAlias.addModifier(visibility) typeAlias.addModifier(visibility)
} }
@@ -258,7 +268,10 @@ fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false)
else { else {
name 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() { fun introduceTypeParameters() {
@@ -0,0 +1,9 @@
// NAME: T
class A {
fun bar() = 1
}
// SIBLING:
fun foo() {
val a = <caret>A::bar
}
@@ -0,0 +1,11 @@
// NAME: T
class A {
fun bar() = 1
}
typealias T = A
// SIBLING:
fun foo() {
val a = T::bar
}
@@ -0,0 +1,9 @@
// NAME: T
class A {
fun bar() = 1
}
// SIBLING:
fun foo() {
val a = A::<caret>bar
}
@@ -0,0 +1 @@
Refactoring can't be performed on the selected code element
@@ -0,0 +1,9 @@
// NAME: T
class A {
fun bar() = 1
}
// SIBLING:
fun foo() {
val a = <caret>A::class
}
@@ -0,0 +1,11 @@
// NAME: T
class A {
fun bar() = 1
}
typealias T = A
// SIBLING:
fun foo() {
val a = T::class
}
@@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiComment import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiJavaFile
import com.intellij.psi.codeStyle.JavaCodeStyleManager 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.introduceParameter.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.INTRODUCE_PROPERTY 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.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.introduceTypeAlias.KotlinIntroduceTypeAliasHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.KotlinIntroduceTypeParameterHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.KotlinIntroduceTypeParameterHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler 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 KtPsiFactory(project).createModifierList(it).firstChild.node.elementType as KtModifierKeywordToken
} }
val editor = fixture.editor val editor = fixture.editor
KotlinIntroduceTypeAliasHandler.selectElements(editor, file) { elements, previousSibling -> object : KotlinIntroduceTypeAliasHandler() {
KotlinIntroduceTypeAliasHandler.doInvoke(project, editor, elements, explicitPreviousSibling ?: previousSibling) { override fun doInvoke(
it.copy(name = aliasName ?: it.name, visibility = aliasVisibility ?: it.visibility) project: Project,
editor: Editor,
elements: List<PsiElement>,
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)
} }
} }
@@ -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); 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") @TestMetadata("constructorCalls.kt")
public void testConstructorCalls() throws Exception { public void testConstructorCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/constructorCalls.kt");