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:
@@ -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)
|
||||
|
||||
+46
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -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))
|
||||
}
|
||||
+14
@@ -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.
|
||||
}
|
||||
+13
@@ -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))
|
||||
}
|
||||
}
|
||||
+17
@@ -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.
|
||||
}
|
||||
+7
@@ -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
|
||||
}
|
||||
+9
@@ -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
|
||||
}
|
||||
+11
@@ -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
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
}
|
||||
+12
@@ -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))
|
||||
}
|
||||
+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
|
||||
|
||||
|
||||
+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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user