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
This commit is contained in:
Alexey Sedunov
2017-11-21 17:39:54 +03:00
parent 7e3ca4734d
commit 84c6f9bf45
9 changed files with 104 additions and 15 deletions
@@ -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<String> = 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.
*/
@@ -135,24 +135,25 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
}
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<KtClassOrObject>() as? KtClass ?: return null
val containingClass = originalExpression.getStrictParentOfType<KtClassOrObject>() 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(
@@ -80,6 +80,7 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
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<E : KtElement>(
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<E : KtElement>(
return StringBuilder().apply {
append("Create ")
val receiverInfo = callableInfos.first().receiverTypeInfo
val receiverInfo = receiverTypeInfo
if (!callableInfos.any { it.isAbstract }) {
if (isExtension) {
append("extension ")
@@ -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() {
<caret>bar()
}
}
@@ -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()
}
}
@@ -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() {
<caret>bar()
}
}
@@ -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()
}
}
@@ -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() {
<caret>bar()
}
}
@@ -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");