Create from Usage: Support "Create member/extension" corresponding to the extension receiver of enclosing function

#KT-10668 Fixed
(cherry picked from commit bc4c013)
This commit is contained in:
Alexey Sedunov
2016-06-28 17:17:27 +03:00
parent 49b6811b44
commit 2abcd17713
16 changed files with 203 additions and 1 deletions
+1
View File
@@ -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
@@ -156,10 +156,18 @@ fun <T : PsiElement> T.getIfChildIsInBranch(element: PsiElement, branch: T.() ->
return if (branch().isAncestor(element)) this else null
}
fun <T : PsiElement> T.getIfChildIsInBranches(element: PsiElement, branches: T.() -> Iterable<PsiElement?>): T? {
return if (branches().any { it.isAncestor(element) }) this else null
}
inline fun <reified T : PsiElement> PsiElement.getParentOfTypeAndBranch(strict: Boolean = false, noinline branch: T.() -> PsiElement?): T? {
return getParentOfType<T>(strict)?.getIfChildIsInBranch(this, branch)
}
inline fun <reified T : PsiElement> PsiElement.getParentOfTypeAndBranches(strict: Boolean = false, noinline branches: T.() -> Iterable<PsiElement?>): T? {
return getParentOfType<T>(strict)?.getIfChildIsInBranches(this, branches)
}
tailrec fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? {
val parent = parent
return if (parent == container) this else parent?.getOutermostParentContainedIn(container)
@@ -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<E : KtExpression>(
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<KtFunction> { bodyExpression }
?: originalExpression.getParentOfTypeAndBranches<KtProperty> { 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<KtSimpleNameExpression>() {
override fun getElementOfInterest(diagnostic: Diagnostic): KtSimpleNameExpression? {
val refExpr = getExpressionOfInterest(diagnostic) as? KtNameReferenceExpression ?: return null
@@ -178,6 +196,18 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
getAbstractCallableInfo(it, expression)
}
}
object ByImplicitExtensionReceiver : Property() {
override fun doCreateCallableInfo(
expression: KtSimpleNameExpression,
context: BindingContext,
name: String,
receiverType: TypeInfo,
possibleContainers: List<KtElement>
) = super.doCreateCallableInfo(expression, context, name, receiverType, possibleContainers)?.let {
ByImplicitExtensionReceiver.getCallableWithReceiverInsideExtension(it, expression, context, receiverType)
}
}
}
sealed class Function: CreateCallableFromCallActionFactory<KtCallExpression>() {
@@ -211,6 +241,18 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
getAbstractCallableInfo(it, expression)
}
}
object ByImplicitExtensionReceiver : Function() {
override fun doCreateCallableInfo(
expression: KtCallExpression,
context: BindingContext,
name: String,
receiverType: TypeInfo,
possibleContainers: List<KtElement>
) = super.doCreateCallableInfo(expression, context, name, receiverType, possibleContainers)?.let {
getCallableWithReceiverInsideExtension(it, expression, context, receiverType)
}
}
}
object Constructor: CreateCallableFromCallActionFactory<KtCallExpression>() {
@@ -249,11 +291,14 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
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)
}
}
@@ -0,0 +1,10 @@
// "Create extension function 'foo'" "true"
fun bar(b: Boolean) {
}
class A(val n: Int)
fun A.test() {
bar(<caret>foo(n))
}
@@ -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.
}
@@ -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(<caret>foo(n))
}
}
@@ -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.
}
@@ -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() = <caret>foo
}
@@ -0,0 +1,9 @@
// "Create extension property 'foo'" "true"
// ERROR: Property must be initialized
class A(val n: Int)
val A.foo: Boolean<caret>
class B {
val A.test: Boolean get() = foo
}
@@ -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) {
<caret>foo = v
}
}
@@ -0,0 +1,13 @@
// "Create extension property 'foo'" "true"
// ERROR: Property must be initialized
class A(val n: Int)
var A.foo: Boolean<caret>
class B {
var A.test: Boolean
get() = foo
set(v: Boolean) {
foo = v
}
}
@@ -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(<caret>foo(1))
}
@@ -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
@@ -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
@@ -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);
}
}
}
}
@@ -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)