diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt index 10d36b3b800..2d1a60e617f 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt @@ -38,11 +38,13 @@ import org.jetbrains.kotlin.renderer.* import org.jetbrains.kotlin.resolve.descriptorUtil.setSingleOverridden interface OverrideMemberChooserObject : ClassMember { - enum class BodyType { - NO_BODY, - EMPTY, - SUPER, - QUALIFIED_SUPER + sealed class BodyType { + object NO_BODY : BodyType() + object EMPTY : BodyType() + object SUPER : BodyType() + object QUALIFIED_SUPER : BodyType() + + class Delegate(val receiverName: String) : BodyType() } val descriptor: CallableMemberDescriptor @@ -191,10 +193,15 @@ fun generateUnsupportedOrSuperCall( } else { return buildString { - append("super") - if (bodyType == OverrideMemberChooserObject.BodyType.QUALIFIED_SUPER) { - val superClassFqName = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor.containingDeclaration as ClassifierDescriptor) - append("<").append(superClassFqName).append(">") + if (bodyType is OverrideMemberChooserObject.BodyType.Delegate) { + append(bodyType.receiverName) + } + else { + append("super") + if (bodyType == OverrideMemberChooserObject.BodyType.QUALIFIED_SUPER) { + val superClassFqName = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor.containingDeclaration as ClassifierDescriptor) + append("<").append(superClassFqName).append(">") + } } append(".").append(descriptor.name.render()) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 6168148dac3..ce673c3762f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -429,6 +429,7 @@ class QuickFixRegistrar : QuickFixContributor { USAGE_IS_NOT_INLINABLE.registerFactory(AddInlineModifierFix.NoInlineFactory) UNRESOLVED_REFERENCE.registerFactory(MakeConstructorParameterPropertyFix) + DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE.registerFactory(SpecifyOverrideExplicitlyFix) SUPERTYPE_IS_EXTENSION_FUNCTION_TYPE.registerFactory(ConvertExtensionToFunctionTypeFix) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/SpecifyOverrideExplicitlyFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/SpecifyOverrideExplicitlyFix.kt new file mode 100644 index 00000000000..c97f0867b93 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/SpecifyOverrideExplicitlyFix.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject +import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils + +class SpecifyOverrideExplicitlyFix( + element: KtClassOrObject, private val signature: String +) : KotlinQuickFixAction(element) { + + override fun getText() = "Specify override for '$signature' explicitly" + + override fun getFamilyName() = "Specify override explicitly" + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val context = element.analyzeFullyAndGetResult().bindingContext + val delegatedDescriptor = context.diagnostics.forElement(element).mapNotNull { + if (it.factory == Errors.DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE) + Errors.DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE.cast(it).a + else + null + }.firstOrNull { + DescriptorRenderer.ONLY_NAMES_WITH_SHORT_TYPES.render(it) == signature + } ?: return + for (specifier in element.superTypeListEntries) { + if (specifier is KtDelegatedSuperTypeEntry) { + val superType = specifier.typeReference?.let { context[BindingContext.TYPE, it] } ?: continue + val superTypeDescriptor = superType.constructor.declarationDescriptor as? ClassDescriptor ?: continue + val overriddenDescriptor = delegatedDescriptor.overriddenDescriptors.find { + it.containingDeclaration == superTypeDescriptor + } ?: continue + + val delegateExpression = specifier.delegateExpression as? KtNameReferenceExpression + val delegateTargetDescriptor = context[BindingContext.REFERENCE_TARGET, delegateExpression] ?: return + if (delegateTargetDescriptor is ValueParameterDescriptor && + delegateTargetDescriptor.containingDeclaration.let { + it is ConstructorDescriptor && + it.isPrimary && + it.containingDeclaration == delegatedDescriptor.containingDeclaration + }) { + val delegateParameter = DescriptorToSourceUtils.descriptorToDeclaration( + delegateTargetDescriptor) as? KtParameter + if (delegateParameter != null && !delegateParameter.hasValOrVar()) { + val factory = KtPsiFactory(project) + delegateParameter.addModifier(KtTokens.PRIVATE_KEYWORD) + delegateParameter.addAfter(factory.createValKeyword(), delegateParameter.modifierList) + } + } + + val overrideMemberChooserObject = OverrideMemberChooserObject.create( + project, delegatedDescriptor, overriddenDescriptor, + OverrideMemberChooserObject.BodyType.Delegate(delegateTargetDescriptor.name.asString()) + ) + val member = overrideMemberChooserObject.generateMember(project, copyDoc = false) + val insertedMember = element.addDeclaration(member) + ShortenReferences.DEFAULT.process(insertedMember) + return + } + } + + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val hidesOverrideError = Errors.DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE.cast(diagnostic) + val klass = hidesOverrideError.psiElement + if (klass.superTypeListEntries.any { + it is KtDelegatedSuperTypeEntry && it.delegateExpression !is KtNameReferenceExpression + }) { + return null + } + val properOverride = hidesOverrideError.a + return SpecifyOverrideExplicitlyFix(klass, DescriptorRenderer.ONLY_NAMES_WITH_SHORT_TYPES.render(properOverride)) + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/base.kt b/idea/testData/quickfix/specifyOverrideExplicitly/base.kt new file mode 100644 index 00000000000..af83fd6b269 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/base.kt @@ -0,0 +1,11 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +class C(a: A) : B(), A by a \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/base.kt.after b/idea/testData/quickfix/specifyOverrideExplicitly/base.kt.after new file mode 100644 index 00000000000..0a1d4295a30 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/base.kt.after @@ -0,0 +1,15 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +class C(private val a: A) : B(), A by a { + override fun foo() { + a.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/global.kt b/idea/testData/quickfix/specifyOverrideExplicitly/global.kt new file mode 100644 index 00000000000..6d5ed912d9c --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/global.kt @@ -0,0 +1,15 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +fun bar(): A = null!! + +val a: A = bar() + +class C() : B(), A by a \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/global.kt.after b/idea/testData/quickfix/specifyOverrideExplicitly/global.kt.after new file mode 100644 index 00000000000..ca6acc3015b --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/global.kt.after @@ -0,0 +1,19 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +fun bar(): A = null!! + +val a: A = bar() + +class C() : B(), A by a { + override fun foo() { + a.foo() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/lists.kt b/idea/testData/quickfix/specifyOverrideExplicitly/lists.kt new file mode 100644 index 00000000000..c301d6dcc87 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/lists.kt @@ -0,0 +1,6 @@ +// "Specify override for 'isEmpty(): Boolean' explicitly" "true" +// WITH_RUNTIME + +import java.util.* + +class B(f: MutableList): ArrayList(), MutableList by f \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/lists.kt.after b/idea/testData/quickfix/specifyOverrideExplicitly/lists.kt.after new file mode 100644 index 00000000000..ee577e75c01 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/lists.kt.after @@ -0,0 +1,10 @@ +// "Specify override for 'isEmpty(): Boolean' explicitly" "true" +// WITH_RUNTIME + +import java.util.* + +class B(private val f: MutableList): ArrayList(), MutableList by f { + override fun isEmpty(): Boolean { + return f.isEmpty() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt b/idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt new file mode 100644 index 00000000000..9b90444774b --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt @@ -0,0 +1,10 @@ +// "Specify override for 'size: Int' explicitly" "true" +// WITH_RUNTIME + +import java.util.* + +class B(private val f: MutableList): ArrayList(), MutableList by f { + override fun isEmpty(): Boolean { + return f.isEmpty() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt.after b/idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt.after new file mode 100644 index 00000000000..14aafccc564 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt.after @@ -0,0 +1,13 @@ +// "Specify override for 'size: Int' explicitly" "true" +// WITH_RUNTIME + +import java.util.* + +class B(private val f: MutableList): ArrayList(), MutableList by f { + override fun isEmpty(): Boolean { + return f.isEmpty() + } + + override val size: Int + get() = f.size +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/notPossible.kt b/idea/testData/quickfix/specifyOverrideExplicitly/notPossible.kt new file mode 100644 index 00000000000..60fdf2dd7c1 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/notPossible.kt @@ -0,0 +1,19 @@ +// "Specify override for 'foo(): Unit' explicitly" "false" +// ACTION: Convert to secondary constructor +// ACTION: Create test +// ACTION: Make primary constructor internal +// ACTION: Make primary constructor private +// ACTION: Move 'C' to separate file +// ACTION: Rename file to C.kt + +interface A { + fun foo() +} + +class W(val a: A) + +open class B : A { + override fun foo() {} +} + +class C(w: W) : B(), A by w.a \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/outer.kt b/idea/testData/quickfix/specifyOverrideExplicitly/outer.kt new file mode 100644 index 00000000000..ecf96da8c9c --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/outer.kt @@ -0,0 +1,13 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +fun bar(a: A) { + class C : B(), A by a +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/outer.kt.after b/idea/testData/quickfix/specifyOverrideExplicitly/outer.kt.after new file mode 100644 index 00000000000..53b5c4af3e0 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/outer.kt.after @@ -0,0 +1,17 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +fun bar(a: A) { + class C : B(), A by a { + override fun foo() { + a.foo() + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/property.kt b/idea/testData/quickfix/specifyOverrideExplicitly/property.kt new file mode 100644 index 00000000000..6ce6794d886 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/property.kt @@ -0,0 +1,11 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +class C(val a: A) : B(), A by a \ No newline at end of file diff --git a/idea/testData/quickfix/specifyOverrideExplicitly/property.kt.after b/idea/testData/quickfix/specifyOverrideExplicitly/property.kt.after new file mode 100644 index 00000000000..7c9c3240011 --- /dev/null +++ b/idea/testData/quickfix/specifyOverrideExplicitly/property.kt.after @@ -0,0 +1,15 @@ +// "Specify override for 'foo(): Unit' explicitly" "true" + +interface A { + fun foo() +} + +open class B : A { + override fun foo() {} +} + +class C(val a: A) : B(), A by a { + override fun foo() { + a.foo() + } +} \ 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 cafab93460c..7c82afeca97 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -8134,6 +8134,57 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/specifyOverrideExplicitly") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SpecifyOverrideExplicitly extends AbstractQuickFixTest { + public void testAllFilesPresentInSpecifyOverrideExplicitly() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/specifyOverrideExplicitly"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("base.kt") + public void testBase() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/base.kt"); + doTest(fileName); + } + + @TestMetadata("global.kt") + public void testGlobal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/global.kt"); + doTest(fileName); + } + + @TestMetadata("lists.kt") + public void testLists() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/lists.kt"); + doTest(fileName); + } + + @TestMetadata("lists2.kt") + public void testLists2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/lists2.kt"); + doTest(fileName); + } + + @TestMetadata("notPossible.kt") + public void testNotPossible() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/notPossible.kt"); + doTest(fileName); + } + + @TestMetadata("outer.kt") + public void testOuter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/outer.kt"); + doTest(fileName); + } + + @TestMetadata("property.kt") + public void testProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/specifyOverrideExplicitly/property.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/superTypeIsExtensionType") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)