From f50059a11a54007ade9e0db9e15d5b017eed378f Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 7 Dec 2015 20:21:15 +0300 Subject: [PATCH] Create from Usage: Implement "Create function" on callable references #KT-10283 Fixed --- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 1 + ...ctionFromCallableReferenceActionFactory.kt | 59 +++++++++++++++++++ .../extensionNoReceiverInCallableRef.kt | 8 +++ .../extensionNoReceiverInCallableRef.kt.after | 11 ++++ .../extensionWithReceiverInCallableRef.kt | 8 +++ ...xtensionWithReceiverInCallableRef.kt.after | 12 ++++ .../noExpectedReturnType.kt | 6 ++ .../noExpectedReturnType.kt.after | 9 +++ .../callableReferences/noFunctionalType.kt | 11 ++++ .../withExpectedReturnType.kt | 8 +++ .../withExpectedReturnType.kt.after | 11 ++++ .../localVariable/callableRef.kt | 1 + .../createVariable/parameter/callableRef.kt | 1 + .../createVariable/property/callableRef.kt | 1 + .../idea/quickfix/QuickFixTestGenerated.java | 39 ++++++++++++ 15 files changed, 186 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateFunctionFromCallableReferenceActionFactory.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noFunctionalType.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index a490e513a95..9fd567d6a30 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -252,6 +252,7 @@ public class QuickFixRegistrar : QuickFixContributor { UNRESOLVED_REFERENCE_WRONG_RECEIVER.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES) UNRESOLVED_REFERENCE.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES) + UNRESOLVED_REFERENCE.registerFactory(CreateFunctionFromCallableReferenceActionFactory) NO_VALUE_FOR_PARAMETER.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES) TOO_MANY_ARGUMENTS.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES) EXPRESSION_EXPECTED_PACKAGE_FOUND.registerFactory(*CreateCallableFromCallActionFactory.INSTANCES) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateFunctionFromCallableReferenceActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateFunctionFromCallableReferenceActionFactory.kt new file mode 100644 index 00000000000..84c7780f689 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateFunctionFromCallableReferenceActionFactory.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable + +import com.intellij.util.SmartList +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.core.refactoring.getExtractionContainers +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.* +import org.jetbrains.kotlin.psi.KtCallableReferenceExpression +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.ifEmpty + +object CreateFunctionFromCallableReferenceActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtCallableReferenceExpression? { + return diagnostic.psiElement.getStrictParentOfType() + } + + override fun extractFixData(element: KtCallableReferenceExpression, diagnostic: Diagnostic): List { + val name = element.callableReference.getReferencedName() + val resolutionFacade = element.getResolutionFacade() + val context = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL) + return element + .guessTypes(context, resolutionFacade.moduleDescriptor) + .filter { KotlinBuiltIns.isExactFunctionType(it) || KotlinBuiltIns.isExactExtensionFunctionType(it) } + .mapNotNull { + val receiverTypeInfo = element.typeReference?.let { TypeInfo(it, Variance.IN_VARIANCE) } ?: TypeInfo.Empty + val returnTypeInfo = TypeInfo(KotlinBuiltIns.getReturnTypeFromFunctionType(it), Variance.OUT_VARIANCE) + val containers = element.getExtractionContainers(includeAll = true).ifEmpty { return@mapNotNull null } + val parameterInfos = SmartList().apply { + if (element.typeReference == null) { + KotlinBuiltIns.getReceiverType(it)?.let { add(ParameterInfo(TypeInfo(it, Variance.IN_VARIANCE))) } + } + KotlinBuiltIns + .getParameterTypeProjectionsFromFunctionType(it) + .mapTo(this) { ParameterInfo(TypeInfo(it.type, it.projectionKind)) } + } + + FunctionInfo(name, receiverTypeInfo, returnTypeInfo, containers, parameterInfos) + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt new file mode 100644 index 00000000000..137f2c40942 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt @@ -0,0 +1,8 @@ +// "Create function 'foo'" "true" +fun T.map(f: T.() -> U) = f() + +fun consume(s: String) {} + +fun test() { + consume(1.map(::foo)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt.after new file mode 100644 index 00000000000..83f1eb667cd --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt.after @@ -0,0 +1,11 @@ +// "Create function 'foo'" "true" +fun T.map(f: T.() -> U) = f() + +fun consume(s: String) {} + +fun test() { + fun foo(i: Int): String { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + consume(1.map(::foo)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt new file mode 100644 index 00000000000..a3ee990a22b --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt @@ -0,0 +1,8 @@ +// "Create extension function 'foo'" "true" +fun T.map(f: T.() -> U) = f() + +fun consume(s: String) {} + +fun test() { + consume(1.map(Int::foo)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt.after new file mode 100644 index 00000000000..c2ccef32380 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt.after @@ -0,0 +1,12 @@ +// "Create extension function 'foo'" "true" +fun T.map(f: T.() -> U) = f() + +fun consume(s: String) {} + +fun test() { + consume(1.map(Int::foo)) +} + +fun Int.foo(): String { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. +} diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt new file mode 100644 index 00000000000..91a4bd19cfb --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt @@ -0,0 +1,6 @@ +// "Create function 'foo'" "true" +fun T.map(f: (T) -> U) = f(this) + +fun test() { + 1.map(::foo) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt.after new file mode 100644 index 00000000000..3dcb8260146 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt.after @@ -0,0 +1,9 @@ +// "Create function 'foo'" "true" +fun T.map(f: (T) -> U) = f(this) + +fun test() { + fun foo(i: Int): Any { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + 1.map(::foo) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noFunctionalType.kt b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noFunctionalType.kt new file mode 100644 index 00000000000..72b2e951ace --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noFunctionalType.kt @@ -0,0 +1,11 @@ +// "Create function 'foo'" "false" +// ACTION: Convert to expression body +// ACTION: Rename reference +// ERROR: Unresolved reference: foo +fun bar(n: Int) = "$n" + +fun consume(s: String) {} + +fun test() { + consume(bar(::foo)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt new file mode 100644 index 00000000000..3a3d3908e08 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt @@ -0,0 +1,8 @@ +// "Create function 'foo'" "true" +fun T.map(f: (T) -> U) = f(this) + +fun consume(s: String) {} + +fun test() { + consume(1.map(::foo)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt.after new file mode 100644 index 00000000000..cb02d803ede --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt.after @@ -0,0 +1,11 @@ +// "Create function 'foo'" "true" +fun T.map(f: (T) -> U) = f(this) + +fun consume(s: String) {} + +fun test() { + fun foo(i: Int): String { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + consume(1.map(::foo)) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/localVariable/callableRef.kt b/idea/testData/quickfix/createFromUsage/createVariable/localVariable/callableRef.kt index 91a6d540291..bc906da3a37 100644 --- a/idea/testData/quickfix/createFromUsage/createVariable/localVariable/callableRef.kt +++ b/idea/testData/quickfix/createFromUsage/createVariable/localVariable/callableRef.kt @@ -1,6 +1,7 @@ // "Create local variable 'foo'" "false" // ACTION: Rename reference // ACTION: Add 'f =' to argument +// ACTION: Create function 'foo' // ERROR: Unresolved reference: foo fun test(f: (Int) -> Int) {} diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/callableRef.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/callableRef.kt index 9c89e83c4a9..5742686eb8d 100644 --- a/idea/testData/quickfix/createFromUsage/createVariable/parameter/callableRef.kt +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/callableRef.kt @@ -1,6 +1,7 @@ // "Create parameter 'foo'" "false" // ACTION: Rename reference // ACTION: Add 'f =' to argument +// ACTION: Create function 'foo' // ERROR: Unresolved reference: foo fun test(f: (Int) -> Int) {} diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/callableRef.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/callableRef.kt index 2bbf9b077ca..e0f64a1b08a 100644 --- a/idea/testData/quickfix/createFromUsage/createVariable/property/callableRef.kt +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/callableRef.kt @@ -1,6 +1,7 @@ // "Create property 'foo'" "false" // ACTION: Rename reference // ACTION: Add 'f =' to argument +// ACTION: Create function 'foo' // ERROR: Unresolved reference: foo fun test(f: (Int) -> Int) {} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 688cd7bf742..4a46c411647 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -2157,6 +2157,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/callableReferences") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CallableReferences extends AbstractQuickFixTest { + public void testAllFilesPresentInCallableReferences() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createFunction/callableReferences"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("extensionNoReceiverInCallableRef.kt") + public void testExtensionNoReceiverInCallableRef() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionNoReceiverInCallableRef.kt"); + doTest(fileName); + } + + @TestMetadata("extensionWithReceiverInCallableRef.kt") + public void testExtensionWithReceiverInCallableRef() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/callableReferences/extensionWithReceiverInCallableRef.kt"); + doTest(fileName); + } + + @TestMetadata("noExpectedReturnType.kt") + public void testNoExpectedReturnType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noExpectedReturnType.kt"); + doTest(fileName); + } + + @TestMetadata("noFunctionalType.kt") + public void testNoFunctionalType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/callableReferences/noFunctionalType.kt"); + doTest(fileName); + } + + @TestMetadata("withExpectedReturnType.kt") + public void testWithExpectedReturnType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/callableReferences/withExpectedReturnType.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/createFromUsage/createFunction/component") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)