From b8c5d1b8529edc8a7fdc8861f6641838ff41d105 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Wed, 25 Oct 2017 06:04:11 +0300 Subject: [PATCH] Fix "add <*>" quick-fix for inner class with generic outer one So #KT-20763 Fixed --- .../idea/quickfix/AddStarProjectionsFix.kt | 83 +++++++++++++++++-- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 +- .../addStarProjections/inner/inner1.kt | 5 ++ .../addStarProjections/inner/inner1.kt.after | 5 ++ .../addStarProjections/inner/inner2.kt | 5 ++ .../addStarProjections/inner/inner2.kt.after | 5 ++ .../addStarProjections/inner/inner3.kt | 9 ++ .../addStarProjections/inner/inner3.kt.after | 9 ++ .../addStarProjections/inner/inner4.kt | 9 ++ .../addStarProjections/inner/inner4.kt.after | 9 ++ .../addStarProjections/inner/inner5.kt | 9 ++ .../addStarProjections/inner/inner5.kt.after | 9 ++ .../addStarProjections/inner/inner6.kt | 9 ++ .../addStarProjections/inner/inner6.kt.after | 9 ++ .../addStarProjections/inner/inner7.kt | 9 ++ .../addStarProjections/inner/inner7.kt.after | 9 ++ .../addStarProjections/inner/inner8.kt | 7 ++ .../addStarProjections/inner/inner8.kt.after | 7 ++ .../idea/quickfix/QuickFixTestGenerated.java | 57 +++++++++++++ 19 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner1.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner1.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner2.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner2.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner3.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner3.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner4.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner4.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner5.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner5.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner6.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner6.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner7.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner7.kt.after create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner8.kt create mode 100644 idea/testData/quickfix/addStarProjections/inner/inner8.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddStarProjectionsFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddStarProjectionsFix.kt index 54687df1403..00261f0c117 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddStarProjectionsFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddStarProjectionsFix.kt @@ -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(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(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(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? { + 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): 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 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index eb6d45514ad..54c16cc6a4c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -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) diff --git a/idea/testData/quickfix/addStarProjections/inner/inner1.kt b/idea/testData/quickfix/addStarProjections/inner/inner1.kt new file mode 100644 index 00000000000..2536ddd8d8f --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner1.kt @@ -0,0 +1,5 @@ +// "Add star projections" "true" +class A { + inner class B + fun test(x: Any) = x is B +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner1.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner1.kt.after new file mode 100644 index 00000000000..d694c9717f4 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner1.kt.after @@ -0,0 +1,5 @@ +// "Add star projections" "true" +class A { + inner class B + fun test(x: Any) = x is B<*> +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner2.kt b/idea/testData/quickfix/addStarProjections/inner/inner2.kt new file mode 100644 index 00000000000..884069cbcfe --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner2.kt @@ -0,0 +1,5 @@ +// "Add star projections" "true" +class A { + inner class B + fun test(x: Any) = x is B +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner2.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner2.kt.after new file mode 100644 index 00000000000..23711bd6220 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner2.kt.after @@ -0,0 +1,5 @@ +// "Add star projections" "true" +class A { + inner class B + fun test(x: Any) = x is A<*>.B +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner3.kt b/idea/testData/quickfix/addStarProjections/inner/inner3.kt new file mode 100644 index 00000000000..ec1e0db614a --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner3.kt @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + fun test(x: Any) = x is D + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner3.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner3.kt.after new file mode 100644 index 00000000000..68bec746731 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner3.kt.after @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + fun test(x: Any) = x is C<*>.D + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner4.kt b/idea/testData/quickfix/addStarProjections/inner/inner4.kt new file mode 100644 index 00000000000..8ecf9f72b61 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner4.kt @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + fun test(x: Any) = x is D + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner4.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner4.kt.after new file mode 100644 index 00000000000..fe280007084 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner4.kt.after @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + fun test(x: Any) = x is B<*>.C<*>.D + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner5.kt b/idea/testData/quickfix/addStarProjections/inner/inner5.kt new file mode 100644 index 00000000000..930bee80fa8 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner5.kt @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + } + } + fun test(x: Any) = x is B.C.D +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner5.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner5.kt.after new file mode 100644 index 00000000000..e42820fafa9 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner5.kt.after @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + } + } + fun test(x: Any) = x is B<*>.C<*>.D +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner6.kt b/idea/testData/quickfix/addStarProjections/inner/inner6.kt new file mode 100644 index 00000000000..6c6c6dbc1e5 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner6.kt @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + } + } + fun test(x: Any) = x is B.C.D +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner6.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner6.kt.after new file mode 100644 index 00000000000..c279dbacad1 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner6.kt.after @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + inner class C { + inner class D + } + } + fun test(x: Any) = x is B<*>.C<*>.D<*> +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner7.kt b/idea/testData/quickfix/addStarProjections/inner/inner7.kt new file mode 100644 index 00000000000..e4eef41f456 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner7.kt @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + class C { + inner class D + } + } + fun test(x: Any) = x is B.C.D +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner7.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner7.kt.after new file mode 100644 index 00000000000..5f8b1161712 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner7.kt.after @@ -0,0 +1,9 @@ +// "Add star projections" "true" +class A { + class B { + class C { + inner class D + } + } + fun test(x: Any) = x is B.C<*>.D +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner8.kt b/idea/testData/quickfix/addStarProjections/inner/inner8.kt new file mode 100644 index 00000000000..a30a987db5e --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner8.kt @@ -0,0 +1,7 @@ +// "Add star projections" "true" +class A { + inner class B { + inner class C + fun test(x: Any) = x is C + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/addStarProjections/inner/inner8.kt.after b/idea/testData/quickfix/addStarProjections/inner/inner8.kt.after new file mode 100644 index 00000000000..7264eaffa12 --- /dev/null +++ b/idea/testData/quickfix/addStarProjections/inner/inner8.kt.after @@ -0,0 +1,7 @@ +// "Add star projections" "true" +class A { + inner class B { + inner class C + fun test(x: Any) = x is A<*, *>.B<*, *>.C<*, *> + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 1cd5ba20f2a..ae511fdb100 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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)