Fix "add <*>" quick-fix for inner class with generic outer one
So #KT-20763 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
7b2a66c006
commit
b8c5d1b852
@@ -19,15 +19,41 @@ package org.jetbrains.kotlin.idea.quickfix
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
||||
import org.jetbrains.kotlin.types.expressions.TypeReconstructionUtil
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
|
||||
open class AddStarProjectionsFix private constructor(element: KtUserType,
|
||||
private val argumentCount: Int) : KotlinQuickFixAction<KtUserType>(element) {
|
||||
override fun getFamilyName() = "Add star projections"
|
||||
object AddStarProjectionsFixFactory : KotlinSingleIntentionActionFactory() {
|
||||
public override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val diagnosticWithParameters = Errors.NO_TYPE_ARGUMENTS_ON_RHS.cast(diagnostic)
|
||||
val typeReference = diagnosticWithParameters.psiElement
|
||||
|
||||
if (typeReference.classDescriptor()?.isInner == true)
|
||||
return AddStartProjectionsForInnerClass(typeReference)
|
||||
else {
|
||||
val typeElement = typeReference.typeElement ?: return null
|
||||
val unwrappedType = generateSequence(typeElement) { (it as? KtNullableType)?.innerType }.lastOrNull() as? KtUserType ?: return null
|
||||
return AddStarProjectionsFix(unwrappedType, diagnosticWithParameters.a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val starProjectionFixFamilyName = "Add star projections"
|
||||
|
||||
class AddStarProjectionsFix(element: KtUserType, private val argumentCount: Int) : KotlinQuickFixAction<KtUserType>(element) {
|
||||
|
||||
override fun getFamilyName() = starProjectionFixFamilyName
|
||||
|
||||
override fun getText() = "Add '${TypeReconstructionUtil.getTypeNameAndStarProjectionsString("", argumentCount)}'"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
@@ -38,13 +64,52 @@ open class AddStarProjectionsFix private constructor(element: KtUserType,
|
||||
val replacement = KtPsiFactory(file).createType(typeString).typeElement.sure { "No type element after parsing " + typeString }
|
||||
element.replace(replacement)
|
||||
}
|
||||
}
|
||||
|
||||
object IsExpressionFactory : KotlinSingleIntentionActionFactory() {
|
||||
public override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val diagnosticWithParameters = Errors.NO_TYPE_ARGUMENTS_ON_RHS.cast(diagnostic)
|
||||
val typeElement: KtTypeElement = diagnosticWithParameters.psiElement.typeElement ?: return null
|
||||
val unwrappedType = generateSequence(typeElement) { (it as? KtNullableType)?.innerType }.lastOrNull() as? KtUserType ?: return null
|
||||
return AddStarProjectionsFix(unwrappedType, diagnosticWithParameters.a)
|
||||
class AddStartProjectionsForInnerClass(element: KtTypeReference) : KotlinQuickFixAction<KtTypeReference>(element) {
|
||||
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun getText() = starProjectionFixFamilyName
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val typeReference = element ?: return
|
||||
val targetClasses = getTargetClasses(typeReference) ?: return
|
||||
val replaceString = createReplaceString(targetClasses)
|
||||
typeReference.replace(KtPsiFactory(file).createType(replaceString))
|
||||
}
|
||||
|
||||
private fun getTargetClasses(typeReference: KtTypeReference): List<ClassDescriptor>? {
|
||||
val classDescriptor = typeReference.classDescriptor() ?: return null
|
||||
|
||||
val parentWithSelfClasses = classDescriptor.parentsWithSelf.mapNotNull { it as? ClassDescriptor }.toList()
|
||||
|
||||
val scope = typeReference.getResolutionScope()
|
||||
val targets = parentWithSelfClasses.takeWhile { it.isInner || !it.inScope(scope) }
|
||||
|
||||
val last = targets.lastOrNull() ?: return targets
|
||||
val next = parentWithSelfClasses.getOrNull(targets.size) ?: return targets
|
||||
|
||||
return if (last.isInner && next.declaredTypeParameters.isNotEmpty() || !last.inScope(scope)) {
|
||||
targets + next
|
||||
}
|
||||
else {
|
||||
targets
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReplaceString(targetClasses: List<ClassDescriptor>): String {
|
||||
return targetClasses.mapIndexed { index, c ->
|
||||
val name = c.name.asString()
|
||||
val last = targetClasses.getOrNull(index - 1)
|
||||
val size = if (index == 0 || last?.isInner == true) c.declaredTypeParameters.size else 0
|
||||
if (size == 0) name else TypeReconstructionUtil.getTypeNameAndStarProjectionsString(name, size)
|
||||
}.reversed().joinToString(".")
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtTypeReference.classDescriptor(): ClassDescriptor? =
|
||||
this.analyze()[BindingContext.TYPE, this]?.constructor?.declarationDescriptor as? ClassDescriptor
|
||||
|
||||
private fun ClassDescriptor.inScope(scope: LexicalScope): Boolean =
|
||||
scope.findClassifier(this.name, NoLookupLocation.FROM_IDE) != null
|
||||
|
||||
@@ -253,7 +253,7 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
NON_EXHAUSTIVE_WHEN_ON_SEALED_CLASS.registerFactory(AddWhenRemainingBranchesFix)
|
||||
BREAK_OR_CONTINUE_IN_WHEN.registerFactory(AddLoopLabelFix)
|
||||
|
||||
NO_TYPE_ARGUMENTS_ON_RHS.registerFactory(AddStarProjectionsFix.IsExpressionFactory)
|
||||
NO_TYPE_ARGUMENTS_ON_RHS.registerFactory(AddStarProjectionsFixFactory)
|
||||
|
||||
TYPE_ARGUMENTS_REDUNDANT_IN_SUPER_QUALIFIER.registerFactory(RemovePsiElementSimpleFix.RemoveTypeArgumentsFactory)
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
inner class B<T>
|
||||
fun test(x: Any) = x is B<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
inner class B<T>
|
||||
fun test(x: Any) = x is B<*>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add star projections" "true"
|
||||
class A<T> {
|
||||
inner class B
|
||||
fun test(x: Any) = x is B<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add star projections" "true"
|
||||
class A<T> {
|
||||
inner class B
|
||||
fun test(x: Any) = x is A<*>.B
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B {
|
||||
inner class C<T> {
|
||||
inner class D
|
||||
fun test(x: Any) = x is D<caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B {
|
||||
inner class C<T> {
|
||||
inner class D
|
||||
fun test(x: Any) = x is C<*>.D
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
inner class C<U> {
|
||||
inner class D
|
||||
fun test(x: Any) = x is D<caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
inner class C<U> {
|
||||
inner class D
|
||||
fun test(x: Any) = x is B<*>.C<*>.D
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
inner class C<U> {
|
||||
inner class D
|
||||
}
|
||||
}
|
||||
fun test(x: Any) = x is B.C.D<caret>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
inner class C<U> {
|
||||
inner class D
|
||||
}
|
||||
}
|
||||
fun test(x: Any) = x is B<*>.C<*>.D
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
inner class C<U> {
|
||||
inner class D<V>
|
||||
}
|
||||
}
|
||||
fun test(x: Any) = x is B.C<caret>.D
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
inner class C<U> {
|
||||
inner class D<V>
|
||||
}
|
||||
}
|
||||
fun test(x: Any) = x is B<*>.C<*>.D<*>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
class C<U> {
|
||||
inner class D
|
||||
}
|
||||
}
|
||||
fun test(x: Any) = x is B.C.D<caret>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add star projections" "true"
|
||||
class A {
|
||||
class B<T> {
|
||||
class C<U> {
|
||||
inner class D
|
||||
}
|
||||
}
|
||||
fun test(x: Any) = x is B.C<*>.D
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Add star projections" "true"
|
||||
class A<T, U> {
|
||||
inner class B<V, W> {
|
||||
inner class C<X, Y>
|
||||
fun test(x: Any) = x is C<caret>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Add star projections" "true"
|
||||
class A<T, U> {
|
||||
inner class B<V, W> {
|
||||
inner class C<X, Y>
|
||||
fun test(x: Any) = x is A<*, *>.B<*, *>.C<*, *>
|
||||
}
|
||||
}
|
||||
@@ -851,6 +851,63 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addStarProjections/inner")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Inner extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInInner() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addStarProjections/inner"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inner1.kt")
|
||||
public void testInner1() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner1.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner2.kt")
|
||||
public void testInner2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner3.kt")
|
||||
public void testInner3() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner3.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner4.kt")
|
||||
public void testInner4() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner4.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner5.kt")
|
||||
public void testInner5() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner5.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner6.kt")
|
||||
public void testInner6() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner6.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner7.kt")
|
||||
public void testInner7() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner7.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner8.kt")
|
||||
public void testInner8() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addStarProjections/inner/inner8.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addStarProjections/when")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user