From 2abcd17713a152cafed097328b95d82126788402 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 28 Jun 2016 17:17:27 +0300 Subject: [PATCH] Create from Usage: Support "Create member/extension" corresponding to the extension receiver of enclosing function #KT-10668 Fixed (cherry picked from commit bc4c013) --- ChangeLog.md | 1 + .../jetbrains/kotlin/psi/psiUtil/psiUtils.kt | 8 ++++ .../CreateCallableFromCallActionFactory.kt | 47 ++++++++++++++++++- .../extensionFunction.kt | 10 ++++ .../extensionFunction.kt.after | 14 ++++++ .../extensionLambda.kt | 13 +++++ .../extensionLambda.kt.after | 17 +++++++ .../extensionPropertyGetter.kt | 7 +++ .../extensionPropertyGetter.kt.after | 9 ++++ .../extensionPropertySetter.kt | 11 +++++ .../extensionPropertySetter.kt.after | 13 +++++ .../notExtensionCallable.kt | 12 +++++ .../parameter/inGenAccessorInClass.kt | 1 + .../parameter/inGenAccessorInGenClass.kt | 1 + .../QuickFixMultiFileTestGenerated.java | 1 + .../idea/quickfix/QuickFixTestGenerated.java | 39 +++++++++++++++ 16 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/notExtensionCallable.kt diff --git a/ChangeLog.md b/ChangeLog.md index 95b37f6481b..fb292e29c2a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -122,6 +122,7 @@ These artifacts include extensions for the types available in the latter JDKs, s ###### New features - [`KT-7492`](https://youtrack.jetbrains.com/issue/KT-7492) Support "Create abstract function/property" inside an abstract class +- [`KT-10668`](https://youtrack.jetbrains.com/issue/KT-10668) Support "Create member/extension" corresponding to the extension receiver of enclosing function #### Refactorings diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt index 93800bf9e11..41f0cc24c14 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt @@ -156,10 +156,18 @@ fun T.getIfChildIsInBranch(element: PsiElement, branch: T.() -> return if (branch().isAncestor(element)) this else null } +fun T.getIfChildIsInBranches(element: PsiElement, branches: T.() -> Iterable): T? { + return if (branches().any { it.isAncestor(element) }) this else null +} + inline fun PsiElement.getParentOfTypeAndBranch(strict: Boolean = false, noinline branch: T.() -> PsiElement?): T? { return getParentOfType(strict)?.getIfChildIsInBranch(this, branch) } +inline fun PsiElement.getParentOfTypeAndBranches(strict: Boolean = false, noinline branches: T.() -> Iterable): T? { + return getParentOfType(strict)?.getIfChildIsInBranches(this, branches) +} + tailrec fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? { val parent = parent return if (parent == container) this else parent?.getOutermostParentContainedIn(container) 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 dbe0ce0d02a..b6b0041d1dd 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 @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic @@ -142,6 +143,23 @@ sealed class CreateCallableFromCallActionFactory( return mainCallable.copy(receiverTypeInfo = TypeInfo.Empty, possibleContainers = listOf(containingClass), isAbstract = true) } + protected fun getCallableWithReceiverInsideExtension( + mainCallable: CallableInfo, + originalExpression: KtExpression, + context: BindingContext, + receiverType: TypeInfo + ): CallableInfo? { + if (receiverType != TypeInfo.Empty) return null + val callable = (originalExpression.getParentOfTypeAndBranch { bodyExpression } + ?: originalExpression.getParentOfTypeAndBranches { listOf(getter, setter) }) + ?: return null + if (callable !is KtFunctionLiteral && callable.receiverTypeReference == null) return null + val callableDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, callable] as? CallableDescriptor ?: return null + val extensionReceiverType = callableDescriptor.extensionReceiverParameter?.type ?: return null + val newReceiverTypeInfo = TypeInfo(extensionReceiverType, Variance.IN_VARIANCE) + return mainCallable.copy(receiverTypeInfo = newReceiverTypeInfo, possibleContainers = emptyList()) + } + sealed class Property: CreateCallableFromCallActionFactory() { override fun getElementOfInterest(diagnostic: Diagnostic): KtSimpleNameExpression? { val refExpr = getExpressionOfInterest(diagnostic) as? KtNameReferenceExpression ?: return null @@ -178,6 +196,18 @@ sealed class CreateCallableFromCallActionFactory( getAbstractCallableInfo(it, expression) } } + + object ByImplicitExtensionReceiver : Property() { + override fun doCreateCallableInfo( + expression: KtSimpleNameExpression, + context: BindingContext, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ) = super.doCreateCallableInfo(expression, context, name, receiverType, possibleContainers)?.let { + ByImplicitExtensionReceiver.getCallableWithReceiverInsideExtension(it, expression, context, receiverType) + } + } } sealed class Function: CreateCallableFromCallActionFactory() { @@ -211,6 +241,18 @@ sealed class CreateCallableFromCallActionFactory( getAbstractCallableInfo(it, expression) } } + + object ByImplicitExtensionReceiver : Function() { + override fun doCreateCallableInfo( + expression: KtCallExpression, + context: BindingContext, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ) = super.doCreateCallableInfo(expression, context, name, receiverType, possibleContainers)?.let { + getCallableWithReceiverInsideExtension(it, expression, context, receiverType) + } + } } object Constructor: CreateCallableFromCallActionFactory() { @@ -249,11 +291,14 @@ sealed class CreateCallableFromCallActionFactory( companion object { val FUNCTIONS = arrayOf(Function.Default, Function.Abstract, + Function.ByImplicitExtensionReceiver, Constructor) val INSTANCES = arrayOf(Function.Default, Function.Abstract, + Function.ByImplicitExtensionReceiver, Constructor, Property.Default, - Property.Abstract) + Property.Abstract, + Property.ByImplicitExtensionReceiver) } } diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt new file mode 100644 index 00000000000..913a2f9726f --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt @@ -0,0 +1,10 @@ +// "Create extension function 'foo'" "true" +fun bar(b: Boolean) { + +} + +class A(val n: Int) + +fun A.test() { + bar(foo(n)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt.after new file mode 100644 index 00000000000..e83a4f52567 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt.after @@ -0,0 +1,14 @@ +// "Create extension function 'foo'" "true" +fun bar(b: Boolean) { + +} + +class A(val n: Int) + +fun A.test() { + bar(foo(n)) +} + +fun A.foo(n: Int): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +} diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt new file mode 100644 index 00000000000..c4e610b552d --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt @@ -0,0 +1,13 @@ +// "Create extension function 'foo'" "true" +// WITH_RUNTIME +fun bar(b: Boolean) { + +} + +class A(val n: Int) + +fun test() { + with(A(1)) { + bar(foo(n)) + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt.after new file mode 100644 index 00000000000..4774937adbc --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt.after @@ -0,0 +1,17 @@ +// "Create extension function 'foo'" "true" +// WITH_RUNTIME +fun bar(b: Boolean) { + +} + +class A(val n: Int) + +fun test() { + with(A(1)) { + bar(foo(n)) + } +} + +fun A.foo(n: Int): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +} diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt new file mode 100644 index 00000000000..57f96828791 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt @@ -0,0 +1,7 @@ +// "Create extension property 'foo'" "true" +// ERROR: Property must be initialized +class A(val n: Int) + +class B { + val A.test: Boolean get() = foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt.after new file mode 100644 index 00000000000..57e9b119a80 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt.after @@ -0,0 +1,9 @@ +// "Create extension property 'foo'" "true" +// ERROR: Property must be initialized +class A(val n: Int) + +val A.foo: Boolean + +class B { + val A.test: Boolean get() = foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt new file mode 100644 index 00000000000..69442c43820 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt @@ -0,0 +1,11 @@ +// "Create extension property 'foo'" "true" +// ERROR: Property must be initialized +class A(val n: Int) + +class B { + var A.test: Boolean + get() = foo + set(v: Boolean) { + foo = v + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt.after new file mode 100644 index 00000000000..480d7d85bf8 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt.after @@ -0,0 +1,13 @@ +// "Create extension property 'foo'" "true" +// ERROR: Property must be initialized +class A(val n: Int) + +var A.foo: Boolean + +class B { + var A.test: Boolean + get() = foo + set(v: Boolean) { + foo = v + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/notExtensionCallable.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/notExtensionCallable.kt new file mode 100644 index 00000000000..f2f0aed292c --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/notExtensionCallable.kt @@ -0,0 +1,12 @@ +// "Create extension function 'foo'" "false" +// ACTION: Convert to expression body +// ACTION: Create function 'foo' +// ACTION: Rename reference +// ERROR: Unresolved reference: foo +fun bar(b: Boolean) { + +} + +fun test() { + bar(foo(1)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInClass.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInClass.kt index b7cbd1e3d09..1aa6648afd9 100644 --- a/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInClass.kt +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInClass.kt @@ -2,6 +2,7 @@ // ACTION: Convert to expression body // ACTION: Create local variable 'foo' // ACTION: Create property 'foo' +// ACTION: Create extension property 'foo' // ACTION: Rename reference // ERROR: Unresolved reference: foo diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInGenClass.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInGenClass.kt index 4287402dfaa..510d6f72d6f 100644 --- a/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInGenClass.kt +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/inGenAccessorInGenClass.kt @@ -2,6 +2,7 @@ // ACTION: Convert to expression body // ACTION: Create local variable 'foo' // ACTION: Create property 'foo' +// ACTION: Create extension property 'foo' // ACTION: Rename reference // ERROR: Unresolved reference: foo diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 6fed634c53b..71f2af679b4 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -1171,6 +1171,7 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.Main.kt"); doTestWithExtraFile(fileName); } + } } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index c574f5207c2..bdc910bba8e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -2301,6 +2301,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ExtensionByExtensionReceiver extends AbstractQuickFixTest { + public void testAllFilesPresentInExtensionByExtensionReceiver() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("extensionFunction.kt") + public void testExtensionFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionFunction.kt"); + doTest(fileName); + } + + @TestMetadata("extensionLambda.kt") + public void testExtensionLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionLambda.kt"); + doTest(fileName); + } + + @TestMetadata("extensionPropertyGetter.kt") + public void testExtensionPropertyGetter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertyGetter.kt"); + doTest(fileName); + } + + @TestMetadata("extensionPropertySetter.kt") + public void testExtensionPropertySetter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/extensionPropertySetter.kt"); + doTest(fileName); + } + + @TestMetadata("notExtensionCallable.kt") + public void testNotExtensionCallable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/extensionByExtensionReceiver/notExtensionCallable.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)