diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 56521bc0064..50d8b3b03f3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -644,6 +644,8 @@ class QuickFixRegistrar : QuickFixContributor { TOO_MANY_ARGUMENTS.registerFactory(RemoveArgumentFix) + AMBIGUOUS_SUPER.registerFactory(SpecifySuperTypeFix) + FUN_INTERFACE_WRONG_COUNT_OF_ABSTRACT_MEMBERS.registerFactory(RemoveModifierFix.createRemoveFunFromInterfaceFactory()) TOPLEVEL_TYPEALIASES_ONLY.registerFactory(MoveTypeAliasToTopLevelFix) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/SpecifySuperTypeFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/SpecifySuperTypeFix.kt new file mode 100644 index 00000000000..ebf382fdeb0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/SpecifySuperTypeFix.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.ui.popup.ListPopupStep +import com.intellij.openapi.ui.popup.PopupStep +import com.intellij.openapi.ui.popup.util.BaseListPopupStep +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.refactoring.fqName.fqName +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf + +class SpecifySuperTypeFix( + superExpression: KtSuperExpression, + private val superTypes: List +) : KotlinQuickFixAction(superExpression) { + + override fun getText() = "Specify supertype" + + override fun getFamilyName() = text + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + if (editor == null) return + val superExpression = element ?: return + CommandProcessor.getInstance().runUndoTransparentAction { + if (superTypes.size == 1) { + superExpression.specifySuperType(superTypes.first()) + } else { + JBPopupFactory + .getInstance() + .createListPopup(createListPopupStep(superExpression, superTypes)) + .showInBestPositionFor(editor) + } + } + } + + private fun KtSuperExpression.specifySuperType(superType: String) { + project.executeWriteCommand("Specify supertype") { + val label = this.labelQualifier?.text ?: "" + replace(KtPsiFactory(this).createExpression("super<$superType>$label")) + } + } + + private fun createListPopupStep(superExpression: KtSuperExpression, superTypes: List): ListPopupStep<*> { + return object : BaseListPopupStep("Choose supertype", superTypes) { + override fun isAutoSelectionEnabled() = false + + override fun onChosen(selectedValue: String, finalChoice: Boolean): PopupStep<*>? { + if (finalChoice) { + superExpression.specifySuperType(selectedValue) + } + return PopupStep.FINAL_CHOICE + } + } + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction? { + val superExpression = diagnostic.psiElement as? KtSuperExpression ?: return null + val qualifiedExpression = superExpression.getQualifiedExpressionForReceiver() ?: return null + val selectorExpression = qualifiedExpression.selectorExpression ?: return null + + val containingClassOrObject = superExpression.getStrictParentOfType() ?: return null + val superTypeListEntries = containingClassOrObject.superTypeListEntries + if (superTypeListEntries.isEmpty()) return null + + val context = superExpression.analyze(BodyResolveMode.PARTIAL) + val superTypes = superTypeListEntries.mapNotNull { + val typeReference = it.typeReference ?: return@mapNotNull null + val typeElement = it.typeReference?.typeElement ?: return@mapNotNull null + val kotlinType = context[BindingContext.TYPE, typeReference] ?: return@mapNotNull null + typeElement to kotlinType + } + if (superTypes.size != superTypeListEntries.size) return null + + val psiFactory = KtPsiFactory(superExpression) + val superTypesForSuperExpression = superTypes.mapNotNull { (typeElement, kotlinType) -> + if (superTypes.any { it.second != kotlinType && it.second.isSubtypeOf(kotlinType) }) return@mapNotNull null + val fqName = kotlinType.fqName ?: return@mapNotNull null + val fqNameAsString = fqName.asString() + val name = if (typeElement.text.startsWith(fqNameAsString)) fqNameAsString else fqName.shortName().asString() + val newQualifiedExpression = psiFactory.createExpressionByPattern("super<$name>.$0", selectorExpression) + val newContext = newQualifiedExpression.analyzeAsReplacement(qualifiedExpression, context) + if (newQualifiedExpression.getResolvedCall(newContext)?.resultingDescriptor == null) return@mapNotNull null + if (newContext.diagnostics.noSuppression().forElement(newQualifiedExpression).isNotEmpty()) return@mapNotNull null + name + } + if (superTypesForSuperExpression.isEmpty()) return null + + return SpecifySuperTypeFix(superExpression, superTypesForSuperExpression) + } + } +} diff --git a/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt new file mode 100644 index 00000000000..9a87660ab45 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +package a.b.c + +interface X + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, a.b.c.Y(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt.after b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt.after new file mode 100644 index 00000000000..9f656241647 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt.after @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +package a.b.c + +interface X + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, a.b.c.Y(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt new file mode 100644 index 00000000000..8e01f67e9ec --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt @@ -0,0 +1,16 @@ +// "Specify supertype" "true" +package a.b.c + +interface Z { + fun foo() {} +} + +open class X { + open fun foo() {} +} + +class Test : (@Suppress("foo") a.b.c.X)(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt.after b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt.after new file mode 100644 index 00000000000..0e1d016d922 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt.after @@ -0,0 +1,16 @@ +// "Specify supertype" "true" +package a.b.c + +interface Z { + fun foo() {} +} + +open class X { + open fun foo() {} +} + +class Test : (@Suppress("foo") a.b.c.X)(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt new file mode 100644 index 00000000000..3e7a4af16af --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +package a.b.c + +interface X {} + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : a.b.c.Y(), X, Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt.after b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt.after new file mode 100644 index 00000000000..a908fadfbc9 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt.after @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +package a.b.c + +interface X {} + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : a.b.c.Y(), X, Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/function.kt b/idea/testData/quickfix/specifySuperType/function.kt new file mode 100644 index 00000000000..e0731cb7250 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/function.kt @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +package a.b.c + +interface X + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, Y(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/function.kt.after b/idea/testData/quickfix/specifySuperType/function.kt.after new file mode 100644 index 00000000000..1327cd2458d --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/function.kt.after @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +package a.b.c + +interface X + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, Y(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/property.kt b/idea/testData/quickfix/specifySuperType/property.kt new file mode 100644 index 00000000000..6e9b37a0d0a --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/property.kt @@ -0,0 +1,17 @@ +// "Specify supertype" "true" +interface X + +open class Y { + open val bar + get() = 1 +} + +interface Z { + val bar + get() = 2 +} + +class Test : X, Z, Y() { + override val bar: Int + get() = super.bar +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/property.kt.after b/idea/testData/quickfix/specifySuperType/property.kt.after new file mode 100644 index 00000000000..ba287a6b506 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/property.kt.after @@ -0,0 +1,17 @@ +// "Specify supertype" "true" +interface X + +open class Y { + open val bar + get() = 1 +} + +interface Z { + val bar + get() = 2 +} + +class Test : X, Z, Y() { + override val bar: Int + get() = super.bar +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt b/idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt new file mode 100644 index 00000000000..a5966864867 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt @@ -0,0 +1,20 @@ +// "Specify supertype" "true" +interface X + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, Y(), Z { + override fun foo() {} + + inner class Boo : Y(), Z { + override fun foo() { + super@Test.foo() + } + } +} diff --git a/idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt.after b/idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt.after new file mode 100644 index 00000000000..8d9509e28b6 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt.after @@ -0,0 +1,20 @@ +// "Specify supertype" "true" +interface X + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, Y(), Z { + override fun foo() {} + + inner class Boo : Y(), Z { + override fun foo() { + super@Test.foo() + } + } +} diff --git a/idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt b/idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt new file mode 100644 index 00000000000..c776432d9fd --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt @@ -0,0 +1,14 @@ +// "Specify supertype" "true" +interface Z { + fun foo() {} +} + +open class X { + open fun foo() {} +} + +class Test : (@Suppress("foo") X)(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt.after b/idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt.after new file mode 100644 index 00000000000..61d469d1f89 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt.after @@ -0,0 +1,14 @@ +// "Specify supertype" "true" +interface Z { + fun foo() {} +} + +open class X { + open fun foo() {} +} + +class Test : (@Suppress("foo") X)(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt b/idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt new file mode 100644 index 00000000000..ce1c2914d58 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt @@ -0,0 +1,16 @@ +// "Specify supertype" "true" +interface X {} + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : Y(), X, Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt.after b/idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt.after new file mode 100644 index 00000000000..fdd554371d4 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt.after @@ -0,0 +1,16 @@ +// "Specify supertype" "true" +interface X {} + +open class Y { + open fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : Y(), X, Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt b/idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt new file mode 100644 index 00000000000..b438e8ee874 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +interface X { + fun foo() {} +} + +open class Y: X { + override fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, Y(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt.after b/idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt.after new file mode 100644 index 00000000000..7a314211eab --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt.after @@ -0,0 +1,18 @@ +// "Specify supertype" "true" +interface X { + fun foo() {} +} + +open class Y: X { + override fun foo() {} +} + +interface Z { + fun foo() {} +} + +class Test : X, Y(), Z { + override fun foo() { + super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/typeMismatch.kt b/idea/testData/quickfix/specifySuperType/typeMismatch.kt new file mode 100644 index 00000000000..0fc7f3b622e --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/typeMismatch.kt @@ -0,0 +1,15 @@ +// "Specify supertype" "true" +// DISABLE-ERRORS +interface Z { + fun foo(): CharSequence = "" +} + +open class Y { + override fun foo(): String = "" +} + +class Test : Z, Y() { + override fun foo(): String { + return super.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifySuperType/typeMismatch.kt.after b/idea/testData/quickfix/specifySuperType/typeMismatch.kt.after new file mode 100644 index 00000000000..d42160bf935 --- /dev/null +++ b/idea/testData/quickfix/specifySuperType/typeMismatch.kt.after @@ -0,0 +1,15 @@ +// "Specify supertype" "true" +// DISABLE-ERRORS +interface Z { + fun foo(): CharSequence = "" +} + +open class Y { + override fun foo(): String = "" +} + +class Test : Z, Y() { + override fun foo(): String { + return super.foo() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 217fce24cc3..607d7243e97 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -4223,6 +4223,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes } } + @TestMetadata("idea/testData/quickfix/specifySuperType") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SpecifySuperType extends AbstractQuickFixMultiFileTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTestWithExtraFile, this, testDataFilePath); + } + + public void testAllFilesPresentInSpecifySuperType() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/specifySuperType"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), null, true); + } + } + @TestMetadata("idea/testData/quickfix/specifyVisibilityInExplicitApiMode") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 8971048e6b1..12413292721 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -12068,6 +12068,69 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/specifySuperType") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SpecifySuperType extends AbstractQuickFixTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInSpecifySuperType() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/specifySuperType"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true); + } + + @TestMetadata("fullyQualifiedSuperType.kt") + public void testFullyQualifiedSuperType() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/fullyQualifiedSuperType.kt"); + } + + @TestMetadata("fullyQualifiedSuperTypeHasAnnotations.kt") + public void testFullyQualifiedSuperTypeHasAnnotations() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasAnnotations.kt"); + } + + @TestMetadata("fullyQualifiedSuperTypeHasTypeArguments.kt") + public void testFullyQualifiedSuperTypeHasTypeArguments() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/fullyQualifiedSuperTypeHasTypeArguments.kt"); + } + + @TestMetadata("function.kt") + public void testFunction() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/function.kt"); + } + + @TestMetadata("property.kt") + public void testProperty() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/property.kt"); + } + + @TestMetadata("superExpressionHasLabel.kt") + public void testSuperExpressionHasLabel() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/superExpressionHasLabel.kt"); + } + + @TestMetadata("superTypeHasAnnotations.kt") + public void testSuperTypeHasAnnotations() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/superTypeHasAnnotations.kt"); + } + + @TestMetadata("superTypeHasTypeArguments.kt") + public void testSuperTypeHasTypeArguments() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/superTypeHasTypeArguments.kt"); + } + + @TestMetadata("superTypeIsExtendedByOtherSuperType.kt") + public void testSuperTypeIsExtendedByOtherSuperType() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/superTypeIsExtendedByOtherSuperType.kt"); + } + + @TestMetadata("typeMismatch.kt") + public void testTypeMismatch() throws Exception { + runTest("idea/testData/quickfix/specifySuperType/typeMismatch.kt"); + } + } + @TestMetadata("idea/testData/quickfix/specifyVisibilityInExplicitApiMode") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)