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
|
###### New features
|
||||||
|
|
||||||
- [`KT-7492`](https://youtrack.jetbrains.com/issue/KT-7492) Support "Create abstract function/property" inside an abstract class
|
- [`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
|
#### Refactorings
|
||||||
|
|
||||||
|
|||||||
@@ -156,10 +156,18 @@ fun <T : PsiElement> T.getIfChildIsInBranch(element: PsiElement, branch: T.() ->
|
|||||||
return if (branch().isAncestor(element)) this else null
|
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? {
|
inline fun <reified T : PsiElement> PsiElement.getParentOfTypeAndBranch(strict: Boolean = false, noinline branch: T.() -> PsiElement?): T? {
|
||||||
return getParentOfType<T>(strict)?.getIfChildIsInBranch(this, branch)
|
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? {
|
tailrec fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? {
|
||||||
val parent = parent
|
val parent = parent
|
||||||
return if (parent == container) this else parent?.getOutermostParentContainedIn(container)
|
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.openapi.project.Project
|
||||||
import com.intellij.psi.PsiClass
|
import com.intellij.psi.PsiClass
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
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)
|
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>() {
|
sealed class Property: CreateCallableFromCallActionFactory<KtSimpleNameExpression>() {
|
||||||
override fun getElementOfInterest(diagnostic: Diagnostic): KtSimpleNameExpression? {
|
override fun getElementOfInterest(diagnostic: Diagnostic): KtSimpleNameExpression? {
|
||||||
val refExpr = getExpressionOfInterest(diagnostic) as? KtNameReferenceExpression ?: return null
|
val refExpr = getExpressionOfInterest(diagnostic) as? KtNameReferenceExpression ?: return null
|
||||||
@@ -178,6 +196,18 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
|
|||||||
getAbstractCallableInfo(it, expression)
|
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>() {
|
sealed class Function: CreateCallableFromCallActionFactory<KtCallExpression>() {
|
||||||
@@ -211,6 +241,18 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
|
|||||||
getAbstractCallableInfo(it, expression)
|
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>() {
|
object Constructor: CreateCallableFromCallActionFactory<KtCallExpression>() {
|
||||||
@@ -249,11 +291,14 @@ sealed class CreateCallableFromCallActionFactory<E : KtExpression>(
|
|||||||
companion object {
|
companion object {
|
||||||
val FUNCTIONS = arrayOf(Function.Default,
|
val FUNCTIONS = arrayOf(Function.Default,
|
||||||
Function.Abstract,
|
Function.Abstract,
|
||||||
|
Function.ByImplicitExtensionReceiver,
|
||||||
Constructor)
|
Constructor)
|
||||||
val INSTANCES = arrayOf(Function.Default,
|
val INSTANCES = arrayOf(Function.Default,
|
||||||
Function.Abstract,
|
Function.Abstract,
|
||||||
|
Function.ByImplicitExtensionReceiver,
|
||||||
Constructor,
|
Constructor,
|
||||||
Property.Default,
|
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: Convert to expression body
|
||||||
// ACTION: Create local variable 'foo'
|
// ACTION: Create local variable 'foo'
|
||||||
// ACTION: Create property 'foo'
|
// ACTION: Create property 'foo'
|
||||||
|
// ACTION: Create extension property 'foo'
|
||||||
// ACTION: Rename reference
|
// ACTION: Rename reference
|
||||||
// ERROR: Unresolved reference: foo
|
// ERROR: Unresolved reference: foo
|
||||||
|
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@
|
|||||||
// ACTION: Convert to expression body
|
// ACTION: Convert to expression body
|
||||||
// ACTION: Create local variable 'foo'
|
// ACTION: Create local variable 'foo'
|
||||||
// ACTION: Create property 'foo'
|
// ACTION: Create property 'foo'
|
||||||
|
// ACTION: Create extension property 'foo'
|
||||||
// ACTION: Rename reference
|
// ACTION: Rename reference
|
||||||
// ERROR: Unresolved reference: foo
|
// 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");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.Main.kt");
|
||||||
doTestWithExtraFile(fileName);
|
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")
|
@TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user