Introduce Type Alias: Support callable references/class literals
#KT-14861 Fixed
This commit is contained in:
@@ -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
|
||||
+40
-13
@@ -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
|
||||
}
|
||||
+6
-6
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-10
@@ -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
|
||||
}
|
||||
+1
@@ -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
|
||||
}
|
||||
+14
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user