From a3f215fb34d0c758e5283bebdfc6eeb64bd239d1 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 19 Jun 2014 16:17:52 +0400 Subject: [PATCH] Extract Function: Support extraction of "it" parameter from lambdas --- .../extractFunction/ExtractionData.kt | 16 +++++++++- .../extractFunction/extractFunctionUtils.kt | 9 +++--- .../extractFunction/parameters/it/innerIt.kt | 9 ++++++ .../parameters/it/innerIt.kt.after | 13 ++++++++ .../parameters/it/lambdaWithIt.kt | 9 ++++++ .../parameters/it/lambdaWithIt.kt.after | 13 ++++++++ .../extractFunction/parameters/it/outerIt.kt | 9 ++++++ .../parameters/it/outerIt.kt.after | 13 ++++++++ .../extractFunction/parameters/it/simpleIt.kt | 9 ++++++ .../parameters/it/simpleIt.kt.after | 13 ++++++++ .../JetExtractionTestGenerated.java | 31 ++++++++++++++++++- 11 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.after diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt index 4f8681f7cc9..a077d119d24 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt @@ -46,6 +46,11 @@ import org.jetbrains.jet.lang.psi.JetDeclaration import org.jetbrains.jet.lang.psi.JetDeclarationWithBody import org.jetbrains.jet.lang.psi.JetUserType import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall +import org.jetbrains.jet.lang.psi.JetParameter +import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor +import org.jetbrains.jet.lang.psi.JetPsiFactory +import org.jetbrains.jet.lang.resolve.BindingContextUtils +import org.jetbrains.jet.lang.psi.JetFunctionLiteral data class ExtractionOptions(val inferUnitTypeForUnusedValues: Boolean) { class object { @@ -96,9 +101,18 @@ class ExtractionData( val originalStartOffset = originalElements.first?.let { e -> e.getTextRange()!!.getStartOffset() } + private val itFakeDeclaration by Delegates.lazy { JetPsiFactory.createParameter(project, "it", null) } + val refOffsetToDeclaration by Delegates.lazy { + fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean { + if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] ?: false))) return false + val function = BindingContextUtils.descriptorToDeclaration(context, descriptor.getContainingDeclaration()) as? JetFunctionLiteral + return function == null || !function.isInsideOf(originalElements) + } + if (originalStartOffset != null) { val resultMap = HashMap() + for ((ref, context) in JetFileReferencesResolver.resolve(originalFile, getExpressions())) { if (ref !is JetSimpleNameExpression) continue @@ -111,7 +125,7 @@ class ExtractionData( if (descriptor == null) continue val declaration = DescriptorToDeclarationUtil.getDeclaration(project, descriptor, context) as? PsiNamedElement - if (declaration == null) continue + ?: if (isExtractableIt(descriptor, context)) itFakeDeclaration else continue val offset = ref.getTextRange()!!.getStartOffset() - originalStartOffset resultMap[offset] = ResolveResult(ref, declaration, descriptor, resolvedCall) diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt index af8d682135b..c7e2042d439 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt @@ -133,8 +133,8 @@ private fun List.analyzeControlFlow( ): Pair { fun isCurrentFunctionReturn(expression: JetReturnExpression): Boolean { val functionDescriptor = expression.getTargetFunctionDescriptor(bindingContext) - val function = functionDescriptor?.let { BindingContextUtils.descriptorToDeclaration(bindingContext, it) } - return function == pseudocode.getCorrespondingElement() + val currentDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, pseudocode.getCorrespondingElement()] + return currentDescriptor == functionDescriptor } val exitPoints = getExitPoints() @@ -461,13 +461,14 @@ private fun ExtractionData.inferParametersInfo(commonParent: PsiElement, } private fun ExtractionData.checkLocalDeclarationsWithNonLocalUsages( + pseudocode: Pseudocode, localInstructions: List, bindingContext: BindingContext ): ErrorMessage? { // todo: non-locally used declaration can be turned into the output value val declarations = ArrayList() - localInstructions.firstOrNull()?.owner?.traverse(TraversalOrder.FORWARD) { instruction -> + pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> if (instruction !in localInstructions) { PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, bindingContext)?.let { descriptor -> val declaration = DescriptorToDeclarationUtil.getDeclaration(project, descriptor, bindingContext) @@ -562,7 +563,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult { ) } - checkLocalDeclarationsWithNonLocalUsages(localInstructions, bindingContext)?.let { messages.add(it) } + checkLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext)?.let { messages.add(it) } checkDeclarationsMovingOutOfScope(controlFlow)?.let { messages.add(it) } val functionNameValidator = diff --git a/idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt b/idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt new file mode 100644 index 00000000000..a78c1a188cc --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array>) { + if (t.check { it.check{ it + 1 > 1 } }) { + println("OK") + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt.after b/idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt.after new file mode 100644 index 00000000000..68d4a5e2af0 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array>) { + if (t.check { it.check{ i(it) > 1 } }) { + println("OK") + } +} + +fun i(it: Int): Int { + return it + 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt b/idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt new file mode 100644 index 00000000000..5e2a55eec06 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Array +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array) { + if (t.check { it + 1 > 1 }) { + println("OK") + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.after b/idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.after new file mode 100644 index 00000000000..6aa70ab366d --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Array +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array) { + if (b(t)) { + println("OK") + } +} + +fun b(t: Array): Boolean { + return t.check { it + 1 > 1 } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt b/idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt new file mode 100644 index 00000000000..2022e6452c0 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Array +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array>) { + if (t.check { it.check{ it + 1 > 1 } }) { + println("OK") + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt.after b/idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt.after new file mode 100644 index 00000000000..f5c2eaa5e1b --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Array +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array>) { + if (t.check { b(it) }) { + println("OK") + } +} + +fun b(it: Array): Boolean { + return it.check { it + 1 > 1 } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt b/idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt new file mode 100644 index 00000000000..a81b51a5574 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array) { + if (t.check { it + 1 > 1 }) { + println("OK") + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.after b/idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.after new file mode 100644 index 00000000000..bcdc0a3349a --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array) { + if (t.check { i(it) > 1 }) { + println("OK") + } +} + +fun i(it: Int): Int { + return it + 1 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java index 755411bfb9c..747ff5bb6ec 100644 --- a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java @@ -651,7 +651,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { } @TestMetadata("idea/testData/refactoring/extractFunction/parameters") - @InnerTestClasses({Parameters.ExtractSuper.class, Parameters.ExtractThis.class, Parameters.Misc.class, Parameters.NonDenotableTypes.class}) + @InnerTestClasses({Parameters.ExtractSuper.class, Parameters.ExtractThis.class, Parameters.It.class, Parameters.Misc.class, Parameters.NonDenotableTypes.class}) public static class Parameters extends AbstractJetExtractionTest { public void testAllFilesPresentInParameters() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/parameters"), Pattern.compile("^(.+)\\.kt$"), true); @@ -728,6 +728,34 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { } + @TestMetadata("idea/testData/refactoring/extractFunction/parameters/it") + public static class It extends AbstractJetExtractionTest { + public void testAllFilesPresentInIt() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/parameters/it"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("innerIt.kt") + public void testInnerIt() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/it/innerIt.kt"); + } + + @TestMetadata("lambdaWithIt.kt") + public void testLambdaWithIt() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt"); + } + + @TestMetadata("outerIt.kt") + public void testOuterIt() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/it/outerIt.kt"); + } + + @TestMetadata("simpleIt.kt") + public void testSimpleIt() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/it/simpleIt.kt"); + } + + } + @TestMetadata("idea/testData/refactoring/extractFunction/parameters/misc") public static class Misc extends AbstractJetExtractionTest { public void testAllFilesPresentInMisc() throws Exception { @@ -839,6 +867,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { suite.addTestSuite(Parameters.class); suite.addTestSuite(ExtractSuper.class); suite.addTestSuite(ExtractThis.class); + suite.addTestSuite(It.class); suite.addTestSuite(Misc.class); suite.addTestSuite(NonDenotableTypes.class); return suite;