From 84c6f9bf45b4aaa2a14ad8e11312cf851e200312 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 21 Nov 2017 17:39:54 +0300 Subject: [PATCH] Create from Usage: Fix choosing superclass for abstract member This applies to cases when non-existing member is called without explicit receiver #KT-21332 Fixed --- .../callableBuilder/CallableInfo.kt | 11 +++++++++++ .../CreateCallableFromCallActionFactory.kt | 15 ++++++++------- .../CreateCallableFromUsageFix.kt | 19 +++++++++++-------- .../call/abstract/inAbstractSuperclass.kt | 10 ++++++++++ .../abstract/inAbstractSuperclass.kt.after | 12 ++++++++++++ .../call/abstract/inBaseInterface.kt | 10 ++++++++++ .../call/abstract/inBaseInterface.kt.after | 12 ++++++++++++ .../call/abstract/noAbstractSuperclass.kt | 12 ++++++++++++ .../idea/quickfix/QuickFixTestGenerated.java | 18 ++++++++++++++++++ 9 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/abstract/noAbstractSuperclass.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt index 5390266cd7f..9dba0510969 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt @@ -84,6 +84,15 @@ abstract class TypeInfo(val variance: Variance) { override val staticContextRequired: Boolean = true } + class OfThis(delegate: TypeInfo) : DelegatingTypeInfo(delegate) + + val isOfThis: Boolean + get() = when (this) { + is OfThis -> true + is DelegatingTypeInfo -> delegate.isOfThis + else -> false + } + open val substitutionsAllowed: Boolean = true open val staticContextRequired: Boolean = false open fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array = ArrayUtil.EMPTY_STRING_ARRAY @@ -139,6 +148,8 @@ fun TypeInfo.forceNotNull(): TypeInfo { return (this as? ForcedNotNull) ?: ForcedNotNull(this) } +fun TypeInfo.ofThis() = TypeInfo.OfThis(this) + /** * Encapsulates information about a function parameter that is going to be created. */ diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt index 720831646b6..73d7e0620a8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt @@ -135,24 +135,25 @@ sealed class CreateCallableFromCallActionFactory( } protected fun getAbstractCallableInfo(mainCallable: CallableInfo, originalExpression: KtExpression): CallableInfo? { - val containingClass: KtClass + val receiverTypeInfo: TypeInfo val receiverType: KotlinType - val receiverTypeInfo = mainCallable.receiverTypeInfo - if (receiverTypeInfo != TypeInfo.Empty) { - if (receiverTypeInfo !is TypeInfo.ByType) return null + val originalReceiverTypeInfo = mainCallable.receiverTypeInfo + if (originalReceiverTypeInfo != TypeInfo.Empty) { + if (originalReceiverTypeInfo !is TypeInfo.ByType) return null + receiverTypeInfo = originalReceiverTypeInfo receiverType = receiverTypeInfo.theType - containingClass = receiverType.constructor.declarationDescriptor?.source?.getPsi() as? KtClass ?: return null } else { - containingClass = originalExpression.getStrictParentOfType() as? KtClass ?: return null + val containingClass = originalExpression.getStrictParentOfType() as? KtClass ?: return null if (containingClass is KtEnumEntry) return null receiverType = (containingClass.unsafeResolveToDescriptor() as ClassDescriptor).defaultType + receiverTypeInfo = TypeInfo(receiverType, Variance.IN_VARIANCE).ofThis() } if (!receiverType.isAbstract() && TypeUtils.getAllSupertypes(receiverType).all { !it.isAbstract() }) return null - return mainCallable.copy(receiverTypeInfo = receiverTypeInfo, possibleContainers = listOf(containingClass), isAbstract = true) + return mainCallable.copy(receiverTypeInfo = receiverTypeInfo, possibleContainers = emptyList(), isAbstract = true) } protected fun getCallableWithReceiverInsideExtension( diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt index 3bc597274dc..0f6f65fa2b5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt @@ -80,6 +80,7 @@ abstract class CreateCallableFromUsageFixBase( override fun getText(): String { val element = element ?: return "" + val receiverTypeInfo = callableInfos.first().receiverTypeInfo val renderedCallables = callableInfos.map { buildString { if (it.isAbstract) { @@ -97,13 +98,15 @@ abstract class CreateCallableFromUsageFixBase( if (it.name.isNotEmpty()) { append(" '") - val callableBuilder = - CallableBuilderConfiguration(callableInfos, element, isExtension = isExtension) - .createBuilder() - val receiverType = callableBuilder - .computeTypeCandidates(callableInfos.first().receiverTypeInfo) - .firstOrNull() - ?.theType + val receiverType = if (!receiverTypeInfo.isOfThis) { + CallableBuilderConfiguration(callableInfos, element, isExtension = isExtension) + .createBuilder() + .computeTypeCandidates(receiverTypeInfo) + .firstOrNull() + ?.theType + } + else null + if (receiverType != null) { if (isExtension) { val receiverTypeText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType) @@ -125,7 +128,7 @@ abstract class CreateCallableFromUsageFixBase( return StringBuilder().apply { append("Create ") - val receiverInfo = callableInfos.first().receiverTypeInfo + val receiverInfo = receiverTypeInfo if (!callableInfos.any { it.isAbstract }) { if (isExtension) { append("extension ") diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt new file mode 100644 index 00000000000..5c7a8862d7c --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt @@ -0,0 +1,10 @@ +// "Create abstract function 'bar'" "true" +// ERROR: Class 'Foo' is not abstract and does not implement abstract base class member public abstract fun bar(): Unit defined in A + +abstract class A + +class Foo : A() { + fun foo() { + bar() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt.after new file mode 100644 index 00000000000..7c6ab4cfd76 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt.after @@ -0,0 +1,12 @@ +// "Create abstract function 'bar'" "true" +// ERROR: Class 'Foo' is not abstract and does not implement abstract base class member public abstract fun bar(): Unit defined in A + +abstract class A { + abstract fun bar() +} + +class Foo : A() { + fun foo() { + bar() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt new file mode 100644 index 00000000000..5fa1ddaba48 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt @@ -0,0 +1,10 @@ +// "Create abstract function 'bar'" "true" +// ERROR: Class 'Foo' is not abstract and does not implement abstract member public abstract fun bar(): Unit defined in I + +interface I + +class Foo : I { + fun foo() { + bar() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt.after new file mode 100644 index 00000000000..9d47a37e085 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt.after @@ -0,0 +1,12 @@ +// "Create abstract function 'bar'" "true" +// ERROR: Class 'Foo' is not abstract and does not implement abstract member public abstract fun bar(): Unit defined in I + +interface I { + fun bar() +} + +class Foo : I { + fun foo() { + bar() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/noAbstractSuperclass.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/noAbstractSuperclass.kt new file mode 100644 index 00000000000..1b3cf957497 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/abstract/noAbstractSuperclass.kt @@ -0,0 +1,12 @@ +// "Create abstract function 'Foo.bar'" "false" +// ACTION: Create function 'bar' +// ACTION: Rename reference +// ERROR: Unresolved reference: bar + +open class A + +class Foo : A() { + fun foo() { + bar() + } +} \ 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 21f460ac3ac..74515ae994d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -3224,12 +3224,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("inAbstractSuperclass.kt") + public void testInAbstractSuperclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inAbstractSuperclass.kt"); + doTest(fileName); + } + + @TestMetadata("inBaseInterface.kt") + public void testInBaseInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/inBaseInterface.kt"); + doTest(fileName); + } + @TestMetadata("interfaceNoExplicitReceiver.kt") public void testInterfaceNoExplicitReceiver() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/interfaceNoExplicitReceiver.kt"); doTest(fileName); } + @TestMetadata("noAbstractSuperclass.kt") + public void testNoAbstractSuperclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/noAbstractSuperclass.kt"); + doTest(fileName); + } + @TestMetadata("notAbstractClass.kt") public void testNotAbstractClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/notAbstractClass.kt");