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-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)
@@ -473,4 +473,6 @@ fun isTypeConstructorReference(e: PsiElement): Boolean {
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_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<PsiElement>, targetSibling: PsiElement) -> Unit) {
selectElementsWithTargetSibling(
@@ -66,7 +67,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
}
}
fun doInvoke(
open fun doInvoke(
project: Project,
editor: Editor,
elements: List<PsiElement>,
@@ -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<KtTypeElement>()!!, targetSibling, true)
else -> IntroduceTypeAliasData(elementToExtract!!.getStrictParentOfType<KtTypeElement>() ?: 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<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) }
}
@@ -116,5 +143,5 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
}
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"))
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<KtTypeReference> { it.resolveInfo = null }
if (!originalTypeElement.isValid) return
originalTypeElement.forEachDescendantOfType<KtTypeReference> { it.resolveInfo = null }
}
}
@@ -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<KtExpression>()!!
val contextExpression = originalTypeElement.getStrictParentOfType<KtExpression>()!!
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<KtTypeReference> { it.resolveInfo != null }
val newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
val project = originalType.project
val project = originalTypeElement.project
val unifier = KotlinPsiUnifier.DEFAULT
val groupedReferencesToExtract = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
@@ -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<KtModifierKeywordTo
fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithConflicts {
val conflicts = MultiMap<PsiElement, String>()
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<KotlinPsiRange, () -> Unit> {
}
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"))
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() {
@@ -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.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<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);
}
@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");