Fix "add <*>" quick-fix for inner class with generic outer one

So #KT-20763 Fixed
This commit is contained in:
Toshiaki Kameyama
2017-10-25 06:04:11 +03:00
committed by Mikhail Glukhikh
parent 7b2a66c006
commit b8c5d1b852
19 changed files with 256 additions and 10 deletions
@@ -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)