Create from Usage: Support "Create abstract function/property" inside an abstract class

#KT-7492 Fixed
(cherry picked from commit 75f6b7f)
This commit is contained in:
Alexey Sedunov
2016-06-27 12:05:10 +03:00
parent 3ba776fffa
commit 49b6811b44
24 changed files with 370 additions and 22 deletions
+4
View File
@@ -119,6 +119,10 @@ These artifacts include extensions for the types available in the latter JDKs, s
- [`KT-11115`](https://youtrack.jetbrains.com/issue/KT-11115) Implement Members: Fix base member detection when abstract and non-abstract members with matching signatures are inherited from an interface
###### New features
- [`KT-7492`](https://youtrack.jetbrains.com/issue/KT-7492) Support "Create abstract function/property" inside an abstract class
#### Refactorings
###### Issues fixed
@@ -284,8 +284,7 @@ class QuickFixRegistrar : QuickFixContributor {
TOO_MANY_ARGUMENTS.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES)
EXPRESSION_EXPECTED_PACKAGE_FOUND.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES)
NONE_APPLICABLE.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES)
TYPE_MISMATCH.registerFactory(CreateCallableFromCallActionFactory.Function)
TYPE_MISMATCH.registerFactory(CreateCallableFromCallActionFactory.Constructor)
TYPE_MISMATCH.registerFactory(*CreateCallableFromCallActionFactory.FUNCTIONS)
NO_VALUE_FOR_PARAMETER.registerFactory(CreateConstructorFromDelegationCallActionFactory)
TOO_MANY_ARGUMENTS.registerFactory(CreateConstructorFromDelegationCallActionFactory)
@@ -443,10 +443,12 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val psiFactory = KtPsiFactory(currentFile)
val modifiers =
if (containingElement is KtClassOrObject
&& containingElement.isAncestor(config.originalElement)
&& callableInfo.kind != CallableKind.SECONDARY_CONSTRUCTOR)
"private "
if (callableInfo.isAbstract) {
if (containingElement is KtClass && containingElement.isInterface()) "" else "abstract "
}
else if (containingElement is KtClassOrObject
&& containingElement.isAncestor(config.originalElement)
&& callableInfo.kind != CallableKind.SECONDARY_CONSTRUCTOR) "private "
else ""
val declaration: KtNamedDeclaration = when (callableInfo.kind) {
@@ -454,6 +456,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val body = when {
containingElement is KtClass && containingElement.isInterface() && !config.isExtension -> ""
callableInfo.kind == CallableKind.SECONDARY_CONSTRUCTOR -> ""
callableInfo.isAbstract -> ""
else -> "{}"
}
@Suppress("USELESS_CAST") // KT-10755
@@ -133,10 +133,15 @@ abstract class CallableInfo (
val receiverTypeInfo: TypeInfo,
val returnTypeInfo: TypeInfo,
val possibleContainers: List<KtElement>,
val typeParameterInfos: List<TypeInfo>
val typeParameterInfos: List<TypeInfo>,
val isAbstract: Boolean = false
) {
abstract val kind: CallableKind
abstract val parameterInfos: List<ParameterInfo>
abstract fun copy(receiverTypeInfo: TypeInfo = this.receiverTypeInfo,
possibleContainers: List<KtElement> = this.possibleContainers,
isAbstract: Boolean = this.isAbstract): CallableInfo
}
class FunctionInfo(name: String,
@@ -146,23 +151,40 @@ class FunctionInfo(name: String,
override val parameterInfos: List<ParameterInfo> = Collections.emptyList(),
typeParameterInfos: List<TypeInfo> = Collections.emptyList(),
val isOperator: Boolean = false,
val isInfix: Boolean = false
) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers, typeParameterInfos) {
val isInfix: Boolean = false,
isAbstract: Boolean = false
) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers, typeParameterInfos, isAbstract) {
override val kind: CallableKind get() = CallableKind.FUNCTION
override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List<KtElement>, isAbstract: Boolean) = FunctionInfo(
name,
receiverTypeInfo,
returnTypeInfo,
possibleContainers,
parameterInfos,
typeParameterInfos,
isOperator,
isInfix,
isAbstract
)
}
class PrimaryConstructorInfo(val classInfo: ClassInfo, expectedTypeInfo: TypeInfo): CallableInfo(
classInfo.name, TypeInfo.Empty, expectedTypeInfo.forceNotNull(), Collections.emptyList(), classInfo.typeArguments
classInfo.name, TypeInfo.Empty, expectedTypeInfo.forceNotNull(), Collections.emptyList(), classInfo.typeArguments, false
) {
override val kind: CallableKind get() = CallableKind.CLASS_WITH_PRIMARY_CONSTRUCTOR
override val parameterInfos: List<ParameterInfo> get() = classInfo.parameterInfos
override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List<KtElement>, isAbstract: Boolean) = throw UnsupportedOperationException()
}
class SecondaryConstructorInfo(
override val parameterInfos: List<ParameterInfo>,
val targetClass: PsiElement
): CallableInfo("", TypeInfo.Empty, TypeInfo.Empty, Collections.emptyList(), Collections.emptyList()) {
): CallableInfo("", TypeInfo.Empty, TypeInfo.Empty, Collections.emptyList(), Collections.emptyList(), false) {
override val kind: CallableKind get() = CallableKind.SECONDARY_CONSTRUCTOR
override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List<KtElement>, isAbstract: Boolean) = throw UnsupportedOperationException()
}
class PropertyInfo(name: String,
@@ -170,8 +192,19 @@ class PropertyInfo(name: String,
returnTypeInfo: TypeInfo,
val writable: Boolean,
possibleContainers: List<KtElement> = Collections.emptyList(),
typeParameterInfos: List<TypeInfo> = Collections.emptyList()
) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers, typeParameterInfos) {
typeParameterInfos: List<TypeInfo> = Collections.emptyList(),
isAbstract: Boolean = false
) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers, typeParameterInfos, isAbstract) {
override val kind: CallableKind get() = CallableKind.PROPERTY
override val parameterInfos: List<ParameterInfo> get() = Collections.emptyList()
override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List<KtElement>, isAbstract: Boolean) = PropertyInfo(
name,
receiverTypeInfo,
returnTypeInfo,
writable,
possibleContainers,
typeParameterInfos,
isAbstract
)
}
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
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.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.*
import org.jetbrains.kotlin.idea.refactoring.canRefactor
@@ -127,7 +128,21 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
}
}
object Property: CreateCallableFromCallActionFactory<KtSimpleNameExpression>() {
protected fun getAbstractCallableInfo(mainCallable: CallableInfo, originalExpression: KtExpression): CallableInfo? {
val containingClass = originalExpression.getStrictParentOfType<KtClassOrObject>() as? KtClass ?: return null
if (!containingClass.isAbstract()) return null
val receiverTypeInfo = mainCallable.receiverTypeInfo
if (receiverTypeInfo != TypeInfo.Empty) {
if (receiverTypeInfo !is TypeInfo.ByType) return null
val containingDescriptor = containingClass.resolveToDescriptor() as ClassDescriptor
if (receiverTypeInfo.theType.constructor.declarationDescriptor != containingDescriptor) return null
}
return mainCallable.copy(receiverTypeInfo = TypeInfo.Empty, possibleContainers = listOf(containingClass), isAbstract = true)
}
sealed class Property: CreateCallableFromCallActionFactory<KtSimpleNameExpression>() {
override fun getElementOfInterest(diagnostic: Diagnostic): KtSimpleNameExpression? {
val refExpr = getExpressionOfInterest(diagnostic) as? KtNameReferenceExpression ?: return null
if (refExpr.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference } != null) return null
@@ -149,9 +164,23 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
)
return PropertyInfo(name, receiverType, returnType, varExpected, possibleContainers)
}
object Default : Property()
object Abstract : Property() {
override fun doCreateCallableInfo(
expression: KtSimpleNameExpression,
context: BindingContext,
name: String,
receiverType: TypeInfo,
possibleContainers: List<KtElement>
) = super.doCreateCallableInfo(expression, context, name, receiverType, possibleContainers)?.let {
getAbstractCallableInfo(it, expression)
}
}
}
object Function: CreateCallableFromCallActionFactory<KtCallExpression>() {
sealed class Function: CreateCallableFromCallActionFactory<KtCallExpression>() {
override fun getElementOfInterest(diagnostic: Diagnostic): KtCallExpression? {
return getExpressionOfInterest(diagnostic) as? KtCallExpression
}
@@ -168,6 +197,20 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
val returnType = TypeInfo(expression.getQualifiedExpressionForSelectorOrThis(), Variance.OUT_VARIANCE)
return FunctionInfo(name, receiverType, returnType, possibleContainers, parameters, typeParameters)
}
object Default : Function()
object Abstract : Function() {
override fun doCreateCallableInfo(
expression: KtCallExpression,
context: BindingContext,
name: String,
receiverType: TypeInfo,
possibleContainers: List<KtElement>
) = super.doCreateCallableInfo(expression, context, name, receiverType, possibleContainers)?.let {
getAbstractCallableInfo(it, expression)
}
}
}
object Constructor: CreateCallableFromCallActionFactory<KtCallExpression>() {
@@ -204,6 +247,13 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
}
companion object {
val INSTANCES = arrayOf(Function, Constructor, Property)
val FUNCTIONS = arrayOf(Function.Default,
Function.Abstract,
Constructor)
val INSTANCES = arrayOf(Function.Default,
Function.Abstract,
Constructor,
Property.Default,
Property.Abstract)
}
}
@@ -79,13 +79,23 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
override fun getText(): String {
val renderedCallables = callableInfos.map {
val kind = when (it.kind) {
CallableKind.FUNCTION -> "function"
CallableKind.PROPERTY -> "property"
CallableKind.SECONDARY_CONSTRUCTOR -> "secondary constructor"
else -> throw AssertionError("Unexpected callable info: $it")
buildString {
if (it.isAbstract) {
append("abstract ")
}
val kind = when (it.kind) {
CallableKind.FUNCTION -> "function"
CallableKind.PROPERTY -> "property"
CallableKind.SECONDARY_CONSTRUCTOR -> "secondary constructor"
else -> throw AssertionError("Unexpected callable info: $it")
}
append(kind)
if (it.name.isNotEmpty()) {
append(" '${it.name}'")
}
}
if (it.name.isNotEmpty()) "$kind '${it.name}'" else kind
}
return StringBuilder().apply {
@@ -0,0 +1,8 @@
// "Create abstract function 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
fun test() {
bar(<caret>foo(1, "2"))
}
}
@@ -0,0 +1,10 @@
// "Create abstract function 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
fun test() {
bar(foo(1, "2"))
}
abstract fun foo<caret>(i: Int, s: String): Boolean
}
@@ -0,0 +1,8 @@
// "Create abstract function 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
fun test(a: A) {
bar(a.<caret>foo(1, "2"))
}
}
@@ -0,0 +1,10 @@
// "Create abstract function 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
fun test(a: A) {
bar(a.foo(1, "2"))
}
abstract fun foo<caret>(i: Int, s: String): Boolean
}
@@ -0,0 +1,8 @@
// "Create abstract function 'foo'" "true"
interface A {
fun bar(b: Boolean) {}
fun test() {
bar(<caret>foo(1, "2"))
}
}
@@ -0,0 +1,10 @@
// "Create abstract function 'foo'" "true"
interface A {
fun bar(b: Boolean) {}
fun test() {
bar(foo(1, "2"))
}
fun foo<caret>(i: Int, s: String): Boolean
}
@@ -0,0 +1,12 @@
// "Create abstract function 'foo'" "false"
// ACTION: Convert to expression body
// ACTION: Create function 'foo'
// ACTION: Rename reference
// ERROR: Unresolved reference: foo
class A {
fun bar(b: Boolean) {}
fun test() {
bar(<caret>foo(1, "2"))
}
}
@@ -0,0 +1,17 @@
// "Create abstract function 'foo'" "false"
// ACTION: Convert to expression body
// ACTION: Create extension function 'foo'
// ACTION: Create member function 'foo'
// ACTION: Rename reference
// ERROR: Unresolved reference: foo
abstract class A {
fun bar(b: Boolean) {}
fun test() {
bar(B().<caret>foo(1, "2"))
}
}
class B {
}
@@ -2,6 +2,7 @@
// ACTION: Convert to expression body
// ACTION: Create local variable 'foo'
// ACTION: Create property 'foo'
// ACTION: Create abstract property 'foo'
// ACTION: Rename reference
// ERROR: Unresolved reference: foo
@@ -0,0 +1,8 @@
// "Create abstract property 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
fun test() {
bar(<caret>foo)
}
}
@@ -0,0 +1,10 @@
// "Create abstract property 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
abstract val foo: Boolean<caret>
fun test() {
bar(foo)
}
}
@@ -0,0 +1,8 @@
// "Create abstract property 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
fun test(a: A) {
bar(a.<caret>foo)
}
}
@@ -0,0 +1,10 @@
// "Create abstract property 'foo'" "true"
abstract class A {
fun bar(b: Boolean) {}
abstract val foo: Boolean<caret>
fun test(a: A) {
bar(a.foo)
}
}
@@ -0,0 +1,8 @@
// "Create abstract property 'foo'" "true"
interface A {
fun bar(b: Boolean) {}
fun test() {
bar(<caret>foo)
}
}
@@ -0,0 +1,10 @@
// "Create abstract property 'foo'" "true"
interface A {
fun bar(b: Boolean) {}
val foo: Boolean<caret>
fun test() {
bar(foo)
}
}
@@ -0,0 +1,15 @@
// "Create abstract property 'foo'" "false"
// ACTION: Convert to expression body
// ACTION: Create local variable 'foo'
// ACTION: Create parameter 'foo'
// ACTION: Create property 'foo'
// ACTION: Create property 'foo' as constructor parameter
// ACTION: Rename reference
// ERROR: Unresolved reference: foo
class A {
fun bar(b: Boolean) {}
fun test() {
bar(<caret>foo)
}
}
@@ -0,0 +1,18 @@
// "Create abstract property 'foo'" "false"
// ACTION: Convert to expression body
// ACTION: Create extension property 'foo'
// ACTION: Create member property 'foo'
// ACTION: Create property 'foo' as constructor parameter
// ACTION: Rename reference
// ERROR: Unresolved reference: foo
abstract class A {
fun bar(b: Boolean) {}
fun test() {
bar(B().<caret>foo)
}
}
class B {
}
@@ -2262,6 +2262,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Abstract extends AbstractQuickFixTest {
public void testAllFilesPresentInAbstract() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createFunction/call/abstract"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("classNoExplicitReceiver.kt")
public void testClassNoExplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/classNoExplicitReceiver.kt");
doTest(fileName);
}
@TestMetadata("explicitReceiverOfContainingClass.kt")
public void testExplicitReceiverOfContainingClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/explicitReceiverOfContainingClass.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("notAbstractClass.kt")
public void testNotAbstractClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/notAbstractClass.kt");
doTest(fileName);
}
@TestMetadata("otherExplicitReceiver.kt")
public void testOtherExplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/abstract/otherExplicitReceiver.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -3560,6 +3599,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/varOnUserType.kt");
doTest(fileName);
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/abstract")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Abstract extends AbstractQuickFixTest {
public void testAllFilesPresentInAbstract() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createVariable/property/abstract"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("classNoExplicitReceiver.kt")
public void testClassNoExplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/abstract/classNoExplicitReceiver.kt");
doTest(fileName);
}
@TestMetadata("explicitReceiverOfContainingClass.kt")
public void testExplicitReceiverOfContainingClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/abstract/explicitReceiverOfContainingClass.kt");
doTest(fileName);
}
@TestMetadata("interfaceNoExplicitReceiver.kt")
public void testInterfaceNoExplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/abstract/interfaceNoExplicitReceiver.kt");
doTest(fileName);
}
@TestMetadata("notAbstractClass.kt")
public void testNotAbstractClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/abstract/notAbstractClass.kt");
doTest(fileName);
}
@TestMetadata("otherExplicitReceiver.kt")
public void testOtherExplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/abstract/otherExplicitReceiver.kt");
doTest(fileName);
}
}
}
}
}