From 76433571f821aab98d6236bdf5e99a6c21ffa542 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 4 Sep 2014 17:28:25 +0400 Subject: [PATCH] Extract Function: Support multiple output values --- .../extractFunctionForDebuggerUtil.kt | 4 +- .../JetRefactoringBundle.properties | 2 +- .../ExtractKotlinFunctionHandler.kt | 3 +- .../ExtractableCodeDescriptor.kt | 226 ++++++++++++++---- .../extractFunction/ExtractionData.kt | 9 +- .../extractableAnalysisUtil.kt | 187 ++++++++++----- .../extractFunction/extractorUtil.kt | 221 ++++++++++++----- .../ui/KotlinExtractFunctionDialog.java | 15 +- .../testData/debugger/tinyApp/outs/errors.out | 5 + idea/testData/debugger/tinyApp/outs/vars.out | 3 + .../singleBreakpoint/errors.kt.fragment | 2 +- .../src/evaluate/singleBreakpoint/vars.kt | 3 - .../singleBreakpoint/vars.kt.fragment | 4 + .../controlFlow/default/defaultCF.kt | 5 +- .../controlFlow/default/defaultCF.kt.after | 5 +- .../default/defaultCFWithExtraVars.kt | 5 +- .../default/defaultCFWithExtraVars.kt.after | 5 +- .../controlFlow/outputValues/genericPair.kt | 19 ++ .../outputValues/genericPair.kt.after | 28 +++ .../outputValues/initializersAndUpdate.kt | 11 + .../initializersAndUpdate.kt.after | 20 ++ .../outputValues/multipleOutputValues.kt | 12 - .../multipleOutputValues.kt.conflicts | 1 - .../multipleOutputValuesWithIf.kt | 16 -- .../multipleOutputValuesWithIf.kt.conflicts | 1 - .../multipleOutputValuesWithWhen.kt | 18 -- .../multipleOutputValuesWithWhen.kt.conflicts | 1 - .../outputValues/nestedNonInlinableCall.kt | 18 ++ .../nestedNonInlinableCall.kt.after | 24 ++ .../outputValues/outputValueWithExpression.kt | 20 ++ .../outputValueWithExpression.kt.after | 26 ++ .../outputValues/outputValueWithReturn.kt | 13 + .../outputValueWithReturn.kt.after | 18 ++ .../outputValuesWithExpression.kt | 24 ++ .../outputValuesWithExpression.kt.after | 32 +++ .../controlFlow/outputValues/pair.kt | 19 ++ .../controlFlow/outputValues/pair.kt.after | 28 +++ .../pairOfInitalizersWithNonLocalUsages.kt} | 1 + ...irOfInitalizersWithNonLocalUsages.kt.after | 14 ++ .../controlFlow/outputValues/pairWithIf.kt | 23 ++ .../outputValues/pairWithIf.kt.after | 31 +++ .../outputValues/pairWithNameClash.kt | 20 ++ .../outputValues/pairWithNameClash.kt.after | 29 +++ .../controlFlow/outputValues/pairWithWhen.kt | 25 ++ .../outputValues/pairWithWhen.kt.after | 34 +++ .../outputValues/tooManyOutputValues.kt | 23 ++ .../tooManyOutputValues.kt.conflicts | 1 + .../outputValues/tooManyOutputValuesAsList.kt | 28 +++ .../tooManyOutputValuesAsList.kt.after | 41 ++++ .../controlFlow/outputValues/triple.kt | 23 ++ .../controlFlow/outputValues/triple.kt.after | 34 +++ .../outputValues/usedAndUnusedOutputValues.kt | 19 ++ .../usedAndUnusedOutputValues.kt.after | 26 ++ ...InitalizersWithNonLocalUsages.kt.conflicts | 1 - .../outputValueWithExpression.kt | 15 -- .../outputValueWithExpression.kt.conflicts | 1 - .../unextractable/outputValueWithReturn.kt | 8 - .../outputValueWithReturn.kt.conflicts | 1 - .../properties/multiDeclaration.kt | 4 +- .../properties/multiDeclaration.kt.after | 4 +- .../properties/nestedInMultiDeclaration.kt | 4 +- .../nestedInMultiDeclaration.kt.after | 4 +- .../introduceVariable/FunctionLiteral.kt | 1 + .../FunctionLiteral.kt.after | 3 +- .../introduceVariable/NoExplicitReceivers.kt | 5 +- .../NoExplicitReceivers.kt.after | 5 +- .../AbstractJetExtractionTest.kt | 23 +- .../JetExtractionTestGenerated.java | 108 ++++++--- 68 files changed, 1293 insertions(+), 319 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt.fragment create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt.after delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt.conflicts delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt.conflicts delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt.conflicts create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt.after rename idea/testData/refactoring/extractFunction/controlFlow/{unextractable/multipleInitalizersWithNonLocalUsages.kt => outputValues/pairOfInitalizersWithNonLocalUsages.kt} (85%) create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt.conflicts create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt.after delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt.conflicts delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt.conflicts delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt delete mode 100644 idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt.conflicts diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt index 955a9b57529..1a13dabb3c9 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -62,11 +62,11 @@ fun getFunctionForExtractedFragment( ErrorMessage.SUPER_CALL -> "Cannot perform an action for expression with super call" ErrorMessage.DENOTABLE_TYPES -> "Cannot perform an action because following types are unavailable from debugger scope" ErrorMessage.ERROR_TYPES -> "Cannot perform an action because this code fragment contains erroneous types" - ErrorMessage.MULTIPLE_OUTPUT -> "Cannot perform an action because this code fragment changes more than one variable" ErrorMessage.DECLARATIONS_OUT_OF_SCOPE, ErrorMessage.OUTPUT_AND_EXIT_POINT, ErrorMessage.MULTIPLE_EXIT_POINTS, ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE -> "Cannot perform an action for this expression" + else -> throw AssertionError("Unexpected error: $errorMessage") } errorMessage.additionalInfo?.let { "$message: ${it.joinToString(", ")}" } ?: message }.joinToString(", ") @@ -102,7 +102,7 @@ fun getFunctionForExtractedFragment( if (targetSibling == null) return null val analysisResult = ExtractionData( - tmpFile, Collections.singletonList(newDebugExpression), targetSibling, ExtractionOptions(false) + tmpFile, Collections.singletonList(newDebugExpression), targetSibling, ExtractionOptions(false, true) ).performAnalysis() if (analysisResult.status != Status.SUCCESS) { throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult)) diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties index 506747d3320..eb2f1500af0 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties @@ -9,7 +9,7 @@ cannot.refactor.package.expression=Cannot introduce package reference extract.function=Extract Function cannot.extract.method=Cannot find statements to extract cannot.find.class.to.extract=Cannot find class to extract method -selected.code.fragment.has.multiple.output.values=Selected code fragment has multiple output values: +selected.code.fragment.has.multiple.output.values=Selected code fragment has more than 3 output values: declarations.are.used.outside.of.selected.code.fragment=Following declarations are used outside of selected code fragment: selected.code.fragment.has.multiple.exit.points=Selected code fragment has multiple exit points selected.code.fragment.has.output.values.and.exit.points=Selected code fragment has output values as well as alternative exit points diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt index 18b34dae227..c57bd7d30b5 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt @@ -69,6 +69,7 @@ import org.jetbrains.jet.lang.psi.JetClassOrObject import org.jetbrains.jet.lang.psi.JetMultiDeclaration public open class ExtractKotlinFunctionHandlerHelper { + open fun adjustExtractionData(data: ExtractionData): ExtractionData = data open fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions = options open fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor = descriptor @@ -88,7 +89,7 @@ public class ExtractKotlinFunctionHandler( ) { val project = file.getProject() - val analysisResult = ExtractionData(file, elements, targetSibling).performAnalysis() + val analysisResult = helper.adjustExtractionData(ExtractionData(file, elements, targetSibling)).performAnalysis() if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != Status.SUCCESS) { throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() }) diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt index ce45fe3842a..a3e257acad4 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt @@ -41,6 +41,29 @@ import com.intellij.openapi.util.text.StringUtil import org.jetbrains.jet.lang.psi.JetClassBody import org.jetbrains.jet.lang.psi.JetFile import org.jetbrains.jet.lang.psi.JetNamedDeclaration +import org.jetbrains.jet.lang.resolve.name.Name +import org.jetbrains.jet.lang.types.CommonSupertypes +import org.jetbrains.jet.lang.types.TypeSubstitutor +import org.jetbrains.jet.lang.types.JetTypeImpl +import org.jetbrains.jet.lang.descriptors.annotations.Annotations +import java.util.Collections +import org.jetbrains.jet.lang.types.TypeProjectionImpl +import org.jetbrains.jet.lang.types.Variance +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.lang.types.TypeProjection +import org.jetbrains.jet.lang.types.TypeConstructor +import org.jetbrains.jet.lang.psi.JetExpression +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ExpressionValue +import org.jetbrains.jet.lang.psi.JetReturnExpression +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Jump +import org.jetbrains.jet.lang.descriptors.ModuleDescriptor +import org.jetbrains.jet.lang.resolve.lazy.ResolveSessionUtils +import org.jetbrains.jet.lang.types.TypeUtils +import kotlin.properties.Delegates +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.jet.lang.psi.JetCallElement +import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector trait Parameter { val argumentText: String @@ -99,61 +122,170 @@ class FqNameReplacement(val fqName: FqName): Replacement { } } -trait ControlFlow { - val returnType: JetType - val declarationsToCopy: List +trait OutputValue { + val valueType: JetType - fun toDefault(): ControlFlow = DefaultControlFlow(returnType, declarationsToCopy) + class ExpressionValue( + val callSiteReturn: Boolean, + override val valueType: JetType + ): OutputValue + + class Jump( + val elementsToReplace: List, + val elementToInsertAfterCall: JetElement, + val conditional: Boolean + ): OutputValue { + override val valueType: JetType = with(KotlinBuiltIns.getInstance()) { if (conditional) getBooleanType() else getUnitType() } + } + + class ParameterUpdate(val parameter: Parameter): OutputValue { + override val valueType: JetType get() = parameter.parameterType + } + + class Initializer( + val initializedDeclaration: JetProperty, + override val valueType: JetType + ): OutputValue } -class DefaultControlFlow( - override val returnType: JetType = DEFAULT_RETURN_TYPE, - override val declarationsToCopy: List -): ControlFlow +abstract class OutputValueBoxer(val outputValues: List) { + val outputValueTypes: List get() = outputValues.map { it.valueType } -trait JumpBasedControlFlow : ControlFlow { - val elementsToReplace: List - val elementToInsertAfterCall: JetElement + abstract val returnType: JetType + + protected abstract fun getBoxingExpressionText(arguments: List): String? + + fun getReturnExpression(arguments: List, psiFactory: JetPsiFactory): JetReturnExpression? { + return getBoxingExpressionText(arguments)?.let { psiFactory.createReturn(it) } + } + + protected abstract fun extractExpressionByIndex(boxedExpression: JetExpression, index: Int): JetExpression? + + protected fun extractArgumentExpressionByIndex(boxedExpression: JetExpression, index: Int): JetExpression? { + val call: JetCallExpression? = when (boxedExpression) { + is JetCallExpression -> boxedExpression + is JetQualifiedExpression -> boxedExpression.getSelectorExpression() as? JetCallExpression + else -> null + } + val arguments = call?.getValueArguments() + if (arguments == null || arguments.size <= index) return null + + return arguments[index].getArgumentExpression() + } + + fun extractExpressionByValue(boxedExpression: JetExpression, value: OutputValue): JetExpression? { + val index = outputValues.indexOf(value) + if (index < 0) return null + + return extractExpressionByIndex(boxedExpression, index) + } + + abstract fun getUnboxingExpressions(boxedText: String): Map + + class AsTuple( + outputValues: List, + val module: ModuleDescriptor + ) : OutputValueBoxer(outputValues) { + { + assert(outputValues.size <= 3, "At most 3 output values are supported") + } + + class object { + private val selectors = array("first", "second", "third") + } + + override val returnType: JetType by Delegates.lazy { + fun getType(): JetType { + val boxingClass = when (outputValues.size) { + 1 -> return outputValues.first().valueType + 2 -> ResolveSessionUtils.getClassDescriptorsByFqName(module, FqName("kotlin.Pair")).first() + 3 -> ResolveSessionUtils.getClassDescriptorsByFqName(module, FqName("kotlin.Triple")).first() + else -> return DEFAULT_RETURN_TYPE + } + return TypeUtils.substituteParameters(boxingClass, outputValueTypes) + } + + getType() + } + + override fun getBoxingExpressionText(arguments: List): String? { + return when (arguments.size) { + 0 -> null + 1 -> arguments.first() + else -> { + val constructorName = DescriptorUtils.getFqName(returnType.getConstructor().getDeclarationDescriptor()!!).asString() + return arguments.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")") + } + } + } + + override fun extractExpressionByIndex(boxedExpression: JetExpression, index: Int): JetExpression? { + if (outputValues.size() == 1) return boxedExpression + return extractArgumentExpressionByIndex(boxedExpression, index) + } + + override fun getUnboxingExpressions(boxedText: String): Map { + return when (outputValues.size) { + 0 -> Collections.emptyMap() + 1 -> Collections.singletonMap(outputValues.first(), boxedText) + else -> { + var i = 0 + ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText.${selectors[i++]}" } + } + } + } + } + + class AsList(outputValues: List): OutputValueBoxer(outputValues) { + override val returnType: JetType by Delegates.lazy { + if (outputValues.isEmpty()) DEFAULT_RETURN_TYPE + else TypeUtils.substituteParameters( + KotlinBuiltIns.getInstance().getList(), + Collections.singletonList(CommonSupertypes.commonSupertype(outputValues.map { it.valueType })) + ) + } + + override fun getBoxingExpressionText(arguments: List): String? { + if (arguments.isEmpty()) return null + return arguments.joinToString(prefix = "kotlin.listOf(", separator = ", ", postfix = ")") + } + + override fun extractExpressionByIndex(boxedExpression: JetExpression, index: Int): JetExpression? { + return extractArgumentExpressionByIndex(boxedExpression, index) + } + + override fun getUnboxingExpressions(boxedText: String): Map { + var i = 0 + return ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText[${i++}]" } + } + } } -class ConditionalJump( - override val elementsToReplace: List, - override val elementToInsertAfterCall: JetElement, - override val declarationsToCopy: List -): JumpBasedControlFlow { - override val returnType: JetType get() = KotlinBuiltIns.getInstance().getBooleanType() +data class ControlFlow( + val outputValues: List, + val boxerFactory: (List) -> OutputValueBoxer, + val declarationsToCopy: List +) { + val outputValueBoxer = boxerFactory(outputValues) + + val defaultOutputValue: ExpressionValue? = with(outputValues.filterIsInstance(javaClass())) { + if (size > 1) throw IllegalArgumentException("Multiple expression values: ${outputValues.joinToString()}") else firstOrNull() + } + + val jumpOutputValue: Jump? = with(outputValues.filterIsInstance(javaClass())) { + when { + isEmpty() -> + null + outputValues.size > size || size > 1 -> + throw IllegalArgumentException("Jump values must be the only value if it's present: ${outputValues.joinToString()}") + else -> + first() + } + } } -class UnconditionalJump( - override val elementsToReplace: List, - override val elementToInsertAfterCall: JetElement, - override val declarationsToCopy: List -): JumpBasedControlFlow { - override val returnType: JetType get() = KotlinBuiltIns.getInstance().getUnitType() -} - -class ExpressionEvaluation( - override val returnType: JetType, - override val declarationsToCopy: List -): ControlFlow - -class ExpressionEvaluationWithCallSiteReturn( - override val returnType: JetType, - override val declarationsToCopy: List -): ControlFlow - -class ParameterUpdate( - val parameter: Parameter, - override val declarationsToCopy: List -): ControlFlow { - override val returnType: JetType get() = parameter.parameterType -} - -class Initializer( - val initializedDeclaration: JetProperty, - override val returnType: JetType, - override val declarationsToCopy: List -): ControlFlow +fun ControlFlow.toDefault(): ControlFlow = + copy(outputValues = outputValues.filterNot { it is OutputValue.Jump || it is OutputValue.ExpressionValue }) data class ExtractableCodeDescriptor( val extractionData: ExtractionData, 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 938a0865de9..d510692b9fb 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt @@ -52,9 +52,12 @@ import org.jetbrains.jet.lang.psi.JetClassInitializer import org.jetbrains.jet.lang.resolve.calls.callUtil.getResolvedCall import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils -data class ExtractionOptions(val inferUnitTypeForUnusedValues: Boolean) { +data class ExtractionOptions( + val inferUnitTypeForUnusedValues: Boolean, + val enableListBoxing: Boolean +) { class object { - val DEFAULT = ExtractionOptions(true) + val DEFAULT = ExtractionOptions(true, false) } } @@ -71,7 +74,7 @@ data class ResolvedReferenceInfo( val resolveResult: ResolveResult ) -class ExtractionData( +data class ExtractionData( val originalFile: JetFile, val originalElements: List, val targetSibling: PsiElement, diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt index 4967eb711cd..8854488131b 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt @@ -37,6 +37,7 @@ import org.jetbrains.jet.lang.cfg.Label import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil import org.jetbrains.jet.plugin.imports.canBeReferencedViaImport +import org.jetbrains.jet.lang.resolve.DescriptorUtils import com.intellij.psi.PsiNamedElement import org.jetbrains.jet.lang.descriptors.impl.LocalVariableDescriptor import org.jetbrains.jet.utils.DFS @@ -61,6 +62,24 @@ import org.jetbrains.jet.lang.resolve.bindingContextUtil.isUsedAsStatement import org.jetbrains.jet.lang.psi.psiUtil.isAncestor import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils import org.jetbrains.jet.plugin.imports.importableFqNameSafe +import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses +import org.jetbrains.jet.plugin.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith +import org.jetbrains.jet.lang.resolve.name.Name +import gnu.trove.THashSet +import gnu.trove.TObjectHashingStrategy +import gnu.trove.THashMap +import org.jetbrains.jet.lang.resolve.name.FqName +import org.jetbrains.jet.lang.resolve.lazy.KotlinCodeAnalyzer +import org.jetbrains.jet.lang.resolve.lazy.ResolveSessionUtils +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Initializer +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ParameterUpdate +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ExpressionValue +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Jump +import org.jetbrains.jet.lang.cfg.pseudocode.instructions.special.MarkInstruction +import org.jetbrains.jet.lang.cfg.pseudocode.instructions.special.CompilationErrorInstruction +import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.traverseFollowingInstructions +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValueBoxer.AsList +import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.getStartInstruction private val DEFAULT_FUNCTION_NAME = "myFun" private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType() @@ -83,6 +102,28 @@ private fun List.getModifiedVarDescriptors(bindingContext: BindingC .filterNotNullTo(HashSet()) } +private fun List.getVarDescriptorsAccessedAfterwards(bindingContext: BindingContext): Set { + val accessedAfterwards = HashSet() + val visitedInstructions = HashSet() + + fun doTraversal(instruction: Instruction) { + traverseFollowingInstructions(instruction, visitedInstructions, TraversalOrder.FORWARD) { + when { + it is AccessValueInstruction && it !in this -> + PseudocodeUtil.extractVariableDescriptorIfAny(it, false, bindingContext)?.let { accessedAfterwards.add(it) } + + it is LocalFunctionDeclarationInstruction -> + doTraversal(it.body.getEnterInstruction()) + } + + true + } + } + + forEach(::doTraversal) + return accessedAfterwards +} + private fun List.getExitPoints(): List = filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } } @@ -139,6 +180,7 @@ private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages( private fun ExtractionData.analyzeControlFlow( localInstructions: List, pseudocode: Pseudocode, + module: ModuleDescriptor, bindingContext: BindingContext, modifiedVarDescriptors: Set, options: ExtractionOptions, @@ -202,74 +244,91 @@ private fun ExtractionData.analyzeControlFlow( val typeOfDefaultFlow = defaultExits.getResultType(bindingContext, options) val returnValueType = valuedReturnExits.getResultType(bindingContext, options) - val defaultReturnType = if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow - if (defaultReturnType.isError()) return Pair(DefaultControlFlow(DEFAULT_RETURN_TYPE, declarationsToCopy), ErrorMessage.ERROR_TYPES) + val emptyControlFlow = + ControlFlow(Collections.emptyList(), { OutputValueBoxer.AsTuple(it, module) }, declarationsToCopy) - val defaultControlFlow = DefaultControlFlow(defaultReturnType, declarationsToCopy) + val defaultReturnType = if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow + if (defaultReturnType.isError()) return emptyControlFlow to ErrorMessage.ERROR_TYPES + + val controlFlow = if (defaultReturnType.isMeaningful()) { + emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultReturnType))) + } + else { + emptyControlFlow + } if (declarationsToReport.isNotEmpty()) { val localVarStr = declarationsToReport.map { it.renderForMessage(bindingContext)!! }.distinct().sort() - return Pair(defaultControlFlow, ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE.addAdditionalInfo(localVarStr)) + return controlFlow to ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE.addAdditionalInfo(localVarStr) } + val outParameters = + parameters.filter { it.mirrorVarName != null && it.originalDescriptor in modifiedVarDescriptors }.sortBy { it.nameForRef } val outDeclarations = declarationsToCopy.filter { bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] in modifiedVarDescriptors } + val modifiedValueCount = outParameters.size + outDeclarations.size - val outParameters = parameters.filterTo(HashSet()) { it.mirrorVarName != null } - val outValuesCount = outDeclarations.size + outParameters.size - when { - outValuesCount > 1 -> { - val outValuesStr = - (outParameters.map { it.originalDescriptor.renderForMessage() } - + outDeclarations.map { it.renderForMessage(bindingContext)!! }).sort() - return Pair(defaultControlFlow, ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr)) - } + val outputValues = ArrayList() - outValuesCount == 1 -> { - if (returnValueType.isMeaningful() || typeOfDefaultFlow.isMeaningful() || jumpExits.isNotEmpty()) { - return Pair(defaultControlFlow, ErrorMessage.OUTPUT_AND_EXIT_POINT) - } - - val controlFlow = - outDeclarations.firstOrNull()?.let { - val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? CallableDescriptor - Initializer(it as JetProperty, descriptor?.getReturnType() ?: DEFAULT_PARAMETER_TYPE, declarationsToCopy) - } ?: ParameterUpdate(outParameters.first(), declarationsToCopy) - return Pair(controlFlow, null) - } - } - - val multipleExitsError = Pair(defaultControlFlow, ErrorMessage.MULTIPLE_EXIT_POINTS) + val multipleExitsError = controlFlow to ErrorMessage.MULTIPLE_EXIT_POINTS + val outputAndExitsError = controlFlow to ErrorMessage.OUTPUT_AND_EXIT_POINT if (typeOfDefaultFlow.isMeaningful()) { if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError - return Pair(ExpressionEvaluation(typeOfDefaultFlow, declarationsToCopy), null) + outputValues.add(ExpressionValue(false, typeOfDefaultFlow)) } - - if (valuedReturnExits.isNotEmpty()) { + else if (valuedReturnExits.isNotEmpty()) { if (jumpExits.isNotEmpty()) return multipleExitsError if (defaultExits.isNotEmpty()) { + if (modifiedValueCount != 0) return outputAndExitsError if (valuedReturnExits.size != 1) return multipleExitsError val element = valuedReturnExits.first!!.element - return Pair(ConditionalJump(listOf(element), element, declarationsToCopy), null) + return controlFlow.copy(outputValues = Collections.singletonList(Jump(listOf(element), element, true))) to null } if (!valuedReturnExits.checkEquivalence(false)) return multipleExitsError - return Pair(ExpressionEvaluationWithCallSiteReturn(returnValueType, declarationsToCopy), null) + outputValues.add(ExpressionValue(true, returnValueType)) + } + + outDeclarations.mapTo(outputValues) { + val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? CallableDescriptor + Initializer(it as JetProperty, descriptor?.getReturnType() ?: DEFAULT_PARAMETER_TYPE) + } + outParameters.mapTo(outputValues) { ParameterUpdate(it) } + + if (outputValues.isNotEmpty()) { + if (jumpExits.isNotEmpty()) return outputAndExitsError + + val boxerFactory: (List) -> OutputValueBoxer = when { + outputValues.size > 3 -> { + if (!options.enableListBoxing) { + val outValuesStr = + (outParameters.map { it.originalDescriptor.renderForMessage() } + + outDeclarations.map { it.renderForMessage(bindingContext)!! }).sort() + return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr) + } + OutputValueBoxer::AsList + } + + else -> controlFlow.boxerFactory + } + + return controlFlow.copy(outputValues = outputValues, boxerFactory = boxerFactory) to null } if (jumpExits.isNotEmpty()) { if (!jumpExits.checkEquivalence(true)) return multipleExitsError val elements = jumpExits.map { it.element } - if (defaultExits.isNotEmpty()) return Pair(ConditionalJump(elements, elements.first!!, declarationsToCopy), null) - return Pair(UnconditionalJump(elements, elements.first!!, declarationsToCopy), null) + return controlFlow.copy( + outputValues = Collections.singletonList(Jump(elements, elements.first(), defaultExits.isNotEmpty())) + ) to null } - return Pair(defaultControlFlow, null) + return controlFlow to null } fun ExtractionData.createTemporaryDeclaration(functionText: String): JetNamedDeclaration { @@ -545,20 +604,18 @@ private fun ExtractionData.checkDeclarationsMovingOutOfScope( bindingContext: BindingContext ): ErrorMessage? { val declarationsOutOfScope = HashSet() - if (controlFlow is JumpBasedControlFlow) { - controlFlow.elementToInsertAfterCall.accept( - object: JetTreeVisitorVoid() { - override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { - val target = expression.getReference()?.resolve() - if (target is JetNamedDeclaration - && target.isInsideOf(originalElements) - && target.getParentByType(javaClass(), true) == enclosingDeclaration) { - declarationsOutOfScope.add(target) - } + controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( + object : JetTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { + val target = expression.getReference()?.resolve() + if (target is JetNamedDeclaration + && target.isInsideOf(originalElements) + && target.getParentByType(javaClass(), true) == enclosingDeclaration) { + declarationsOutOfScope.add(target) } } - ) - } + } + ) if (declarationsOutOfScope.isNotEmpty()) { val declStr = declarationsOutOfScope.map { it.renderForMessage(bindingContext)!! }.sort() @@ -607,7 +664,8 @@ fun ExtractionData.performAnalysis(): AnalysisResult { } else -> return noContainerError } - val bindingContext = originalFile.getLazyResolveSession().resolveToElement(bodyElement) + val resolveSession = originalFile.getLazyResolveSession() + val bindingContext = resolveSession.resolveToElement(bodyElement) val pseudocodeDeclaration = PsiTreeUtil.getParentOfType( commonParent, javaClass(), javaClass() @@ -626,10 +684,18 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val messages = ArrayList() val (controlFlow, controlFlowMessage) = - analyzeControlFlow(localInstructions, pseudocode, bindingContext, modifiedVarDescriptors, options, paramsInfo.parameters) + analyzeControlFlow( + localInstructions, + pseudocode, + resolveSession.getModuleDescriptor(), + bindingContext, + modifiedVarDescriptors intersect localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext), + options, + paramsInfo.parameters + ) controlFlowMessage?.let { messages.add(it) } - controlFlow.returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes) + controlFlow.outputValueBoxer.returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes) if (paramsInfo.nonDenotableTypes.isNotEmpty()) { val typeStr = paramsInfo.nonDenotableTypes.map {it.renderForMessage()}.sort() @@ -648,17 +714,18 @@ fun ExtractionData.performAnalysis(): AnalysisResult { if (targetSibling is JetClassInitializer) targetSibling.getParent() else targetSibling, JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES ) - val functionName = JetNameSuggester.suggestNames(controlFlow.returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).first() + val functionName = JetNameSuggester.suggestNames( + controlFlow.outputValueBoxer.returnType, + functionNameValidator, DEFAULT_FUNCTION_NAME + ).first() - if (controlFlow is JumpBasedControlFlow) { - controlFlow.elementToInsertAfterCall.accept( - object: JetTreeVisitorVoid() { - override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { - paramsInfo.originalRefToParameter[expression]?.let { it.refCount-- } - } + controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( + object : JetTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { + paramsInfo.originalRefToParameter[expression]?.let { it.refCount-- } } - ) - } + } + ) val adjustedParameters = paramsInfo.parameters.filterTo(HashSet()) { it.refCount > 0 } val receiverCandidates = adjustedParameters.filterTo(HashSet()) { it.receiverCandidate } diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt index 41218680cb6..91243aceaf6 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt @@ -43,6 +43,15 @@ import org.jetbrains.jet.lang.psi.psiUtil.prependElement import org.jetbrains.jet.lang.psi.psiUtil.appendElement import org.jetbrains.jet.lang.psi.psiUtil.replaced import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils +import org.jetbrains.jet.lang.psi.psiUtil.getParentByTypesAndPredicate +import org.jetbrains.jet.lang.psi.JetBlockExpression +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ParameterUpdate +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Jump +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Initializer +import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ExpressionValue +import org.jetbrains.jet.lang.psi.JetReturnExpression +import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl +import org.jetbrains.jet.plugin.refactoring.JetNameSuggester fun ExtractableCodeDescriptor.getDeclarationText( options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT, @@ -67,7 +76,7 @@ fun ExtractableCodeDescriptor.getDeclarationText( builder.param(parameter.name, descriptorRenderer.renderType(parameter.parameterType)) } - with(controlFlow.returnType) { + with(controlFlow.outputValueBoxer.returnType) { if (isDefault() || isError()) builder.noReturnType() else builder.returnType(descriptorRenderer.renderType(this)) } @@ -115,6 +124,40 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp } } + fun getReturnArguments(resultExpression: JetExpression?): List { + return controlFlow.outputValues + .map { + when (it) { + is ExpressionValue -> resultExpression?.getText() + is Jump -> if (it.conditional) "false" else null + is ParameterUpdate -> it.parameter.nameForRef + is Initializer -> it.initializedDeclaration.getName() + else -> throw IllegalArgumentException("Unknown output value: $it") + } + } + .filterNotNull() + } + + fun replaceWithReturn( + originalExpression: JetExpression, + replacingExpression: JetReturnExpression + ) { + val currentResultExpression = + if (originalExpression is JetReturnExpression) originalExpression.getReturnedExpression() else originalExpression + if (currentResultExpression == null) return + + val newResultExpression = controlFlow.defaultOutputValue?.let { + val boxedExpression = originalExpression.replaced(replacingExpression).getReturnedExpression()!! + controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it) + } + if (newResultExpression == null) { + throw AssertionError("Can' replace '${originalExpression.getText()}' with '${replacingExpression.getText()}'") + } + + val counterpartMap = createNameCounterpartMap(currentResultExpression, newResultExpression) + nameByOffset.entrySet().forEach { e -> counterpartMap[e.getValue()]?.let { e.setValue(it) } } + } + fun adjustDeclarationBody(declaration: JetNamedDeclaration) { val body = declaration.getGeneratedBlockBody() @@ -143,9 +186,11 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp val replacingReturn: JetExpression? val expressionsToReplaceWithReturn: List - if (controlFlow is JumpBasedControlFlow) { - replacingReturn = psiFactory.createExpression(if (controlFlow is ConditionalJump) "return true" else "return") - expressionsToReplaceWithReturn = controlFlow.elementsToReplace.map { jumpElement -> + + val jumpValue = controlFlow.jumpOutputValue + if (jumpValue != null) { + replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return") + expressionsToReplaceWithReturn = jumpValue.elementsToReplace.map { jumpElement -> val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - extractionData.originalStartOffset!! val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(jumpElement.javaClass) assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'") @@ -170,32 +215,29 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp } } - for (param in parameters) { - param.mirrorVarName?.let { varName -> - body.prependElement(psiFactory.createProperty(varName, null, true, param.name)) + val firstExpression = body.getStatements().firstOrNull() + if (firstExpression != null) { + for (param in parameters) { + param.mirrorVarName?.let { varName -> + body.addBefore(psiFactory.createProperty(varName, null, true, param.name), firstExpression) + body.addBefore(psiFactory.createNewLine(), firstExpression) + } } } - when (controlFlow) { - is ParameterUpdate -> - body.appendElement(psiFactory.createReturn(controlFlow.parameter.nameForRef)) + val lastExpression = body.getStatements().lastOrNull() as? JetExpression + if (lastExpression is JetReturnExpression) return - is Initializer -> - body.appendElement(psiFactory.createReturn(controlFlow.initializedDeclaration.getName()!!)) + val returnExpression = controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(lastExpression), psiFactory) + if (returnExpression == null) return - is ConditionalJump -> - body.appendElement(psiFactory.createReturn("false")) + val defaultValue = controlFlow.defaultOutputValue + when { + defaultValue == null -> + body.appendElement(returnExpression) - is ExpressionEvaluation -> - body.getStatements().last?.let { - val newExpr = it.replaced( - psiFactory.createReturn( - it.getText() ?: throw AssertionError("Return expression shouldn't be empty: code fragment = ${body.getText()}") - ) - ).getReturnedExpression()!! - val counterpartMap = createNameCounterpartMap(it, newExpr) - nameByOffset.entrySet().forEach { e -> counterpartMap[e.getValue()]?.let { e.setValue(it) } } - } + !defaultValue.callSiteReturn -> + replaceWithReturn(lastExpression!!, returnExpression) } } @@ -218,12 +260,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp } } - fun insertCall(anchor: PsiElement, wrappedCall: JetExpression?) { - if (wrappedCall == null) { - anchor.delete() - return - } - + fun insertCall(anchor: PsiElement, wrappedCall: JetExpression) { val firstExpression = extractionData.getExpressions().firstOrNull() if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) { val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass())!! @@ -234,9 +271,9 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp anchor.replace(wrappedCall) } - fun makeCall(declaration: JetNamedDeclaration): JetNamedDeclaration { + fun makeCall(declaration: JetNamedDeclaration) { val anchor = extractionData.originalElements.first - if (anchor == null) return declaration + if (anchor == null) return val anchorParent = anchor.getParent()!! @@ -255,45 +292,98 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp else -> name } + val anchorInBlock = stream(anchor) { it.getParent() }.firstOrNull { it.getParent() is JetBlockExpression } + val block = (anchorInBlock?.getParent() as? JetBlockExpression) ?: anchorParent + + val newLine = psiFactory.createNewLine() + + val inlinableCall = controlFlow.outputValues.size <= 1 + val unboxingExpressions = + if (inlinableCall) { + controlFlow.outputValueBoxer.getUnboxingExpressions(callText) + } + else { + val varNameValidator = JetNameValidatorImpl(block, anchorInBlock, JetNameValidatorImpl.Target.PROPERTIES) + val resultVal = JetNameSuggester.suggestNames(controlFlow.outputValueBoxer.returnType, varNameValidator, null).first() + block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) + block.addBefore(newLine, anchorInBlock) + controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) + } + val copiedDeclarations = HashMap() for (decl in controlFlow.declarationsToCopy) { val declCopy = psiFactory.createDeclaration(decl.getText()!!) - copiedDeclarations[decl] = anchorParent.addBefore(declCopy, anchor) as JetDeclaration - anchorParent.addBefore(psiFactory.createNewLine(), anchor) + copiedDeclarations[decl] = block.addBefore(declCopy, anchorInBlock) as JetDeclaration + block.addBefore(newLine, anchorInBlock) } - val wrappedCall = when (controlFlow) { - is ExpressionEvaluationWithCallSiteReturn -> - psiFactory.createReturn(callText) - - is ParameterUpdate -> - psiFactory.createExpression("${controlFlow.parameter.argumentText} = $callText") - - is Initializer -> { - val newDecl = copiedDeclarations[controlFlow.initializedDeclaration] as JetProperty - newDecl.replace(DeclarationUtils.changePropertyInitializer(newDecl, psiFactory.createExpression(callText))) - null - } - - is ConditionalJump -> - psiFactory.createExpression("if ($callText) ${controlFlow.elementToInsertAfterCall.getText()}") - - is UnconditionalJump -> { - anchorParent.addAfter( - psiFactory.createExpression(controlFlow.elementToInsertAfterCall.getText()!!), - anchor - ) - anchorParent.addAfter(psiFactory.createNewLine(), anchor) - - psiFactory.createExpression(callText) - } - - else -> - psiFactory.createExpression(callText) + if (controlFlow.outputValues.isEmpty()) { + anchor.replace(psiFactory.createExpression(callText)) + return } - insertCall(anchor, wrappedCall) - return declaration + fun wrapCall(outputValue: OutputValue, callText: String): List { + return when (outputValue) { + is OutputValue.ExpressionValue -> + Collections.singletonList( + if (outputValue.callSiteReturn) psiFactory.createReturn(callText) else psiFactory.createExpression(callText) + ) + + is ParameterUpdate -> + Collections.singletonList( + psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText") + ) + + is Jump -> { + if (outputValue.conditional) { + Collections.singletonList( + psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.getText()}") + ) + } + else { + listOf( + psiFactory.createExpression(callText), + newLine, + psiFactory.createExpression(outputValue.elementToInsertAfterCall.getText()!!) + ) + } + } + + is Initializer -> { + val newProperty = copiedDeclarations[outputValue.initializedDeclaration] as JetProperty + newProperty.replace(DeclarationUtils.changePropertyInitializer(newProperty, psiFactory.createExpression(callText))) + Collections.emptyList() + } + + else -> throw IllegalArgumentException("Unknown output value: $outputValue") + } + } + + val defaultValue = controlFlow.defaultOutputValue + + controlFlow.outputValues + .filter { it != defaultValue } + .flatMap { wrapCall(it, unboxingExpressions[it]!!) } + .withIndices() + .forEach { + val (i, e) = it + + if (i > 0) { + block.addBefore(newLine, anchorInBlock) + } + block.addBefore(e, anchorInBlock) + } + + defaultValue?.let { + if (!inlinableCall) { + block.addBefore(newLine, anchorInBlock) + } + insertCall(anchor, wrapCall(it, unboxingExpressions[it]!!).first() as JetExpression) + } + + if (anchor.isValid()) { + anchor.delete() + } } val declaration = createDeclaration() @@ -301,7 +391,8 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp if (options.inTempFile) return ExtractionResult(declaration, nameByOffset) - val declarationInPlace = makeCall(insertDeclaration(declaration)) + val declarationInPlace = insertDeclaration(declaration) + makeCall(declarationInPlace) ShortenReferences.process(declarationInPlace) return ExtractionResult(declaration, nameByOffset) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java index 7e77b7214af..ceaec8cd83c 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java @@ -40,6 +40,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -226,13 +227,15 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { } ControlFlow controlFlow = descriptor.getControlFlow(); - if (controlFlow instanceof ParameterUpdate) { - ParameterUpdate parameterUpdate = (ParameterUpdate) controlFlow; - controlFlow = new ParameterUpdate( - oldToNewParameters.get(parameterUpdate.getParameter()), - parameterUpdate.getDeclarationsToCopy() - ); + List outputValues = new ArrayList(controlFlow.getOutputValues()); + for (int i = 0; i < outputValues.size(); i++) { + OutputValue outputValue = outputValues.get(i); + if (outputValue instanceof OutputValue.ParameterUpdate) { + OutputValue.ParameterUpdate parameterUpdate = (OutputValue.ParameterUpdate) outputValue; + outputValues.set(i, new OutputValue.ParameterUpdate(oldToNewParameters.get(parameterUpdate.getParameter()))); + } } + controlFlow = new ControlFlow(outputValues, controlFlow.getBoxerFactory(), controlFlow.getDeclarationsToCopy()); Map replacementMap = ContainerUtil.newHashMap(); for (Map.Entry e : descriptor.getReplacementMap().entrySet()) { diff --git a/idea/testData/debugger/tinyApp/outs/errors.out b/idea/testData/debugger/tinyApp/outs/errors.out index b11f131e890..1f5592dea14 100644 --- a/idea/testData/debugger/tinyApp/outs/errors.out +++ b/idea/testData/debugger/tinyApp/outs/errors.out @@ -2,6 +2,11 @@ LineBreakpoint created at errors.kt:13 !JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! errors.ErrorsPackage Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' errors.kt:12 +Compile bytecode for prop += 1 +prop2 +=2 +prop + prop2 + +// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple; Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/vars.out b/idea/testData/debugger/tinyApp/outs/vars.out index d7ab9217393..cc800cc3fc2 100644 --- a/idea/testData/debugger/tinyApp/outs/vars.out +++ b/idea/testData/debugger/tinyApp/outs/vars.out @@ -4,6 +4,9 @@ Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socke vars.kt:6 Compile bytecode for a Compile bytecode for a += 1 +a + +// RESULT: 3: I Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/errors.kt.fragment b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/errors.kt.fragment index 1f25bd696e0..3f2c2cdddd0 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/errors.kt.fragment +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/errors.kt.fragment @@ -2,4 +2,4 @@ prop += 1 prop2 +=2 prop + prop2 -// RESULT: Cannot perform an action because this code fragment changes more than one variable: var prop2: Int, var prop: Int \ No newline at end of file +// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple; \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt index ade36005e61..a4b1bd4cd51 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt @@ -10,8 +10,5 @@ fun main(args: Array) { // EXPRESSION: a // RESULT: 2: I -// EXPRESSION: a += 1 -// RESULT: 3: I - // EXPRESSION: a // RESULT: 2: I diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt.fragment b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt.fragment new file mode 100644 index 00000000000..6a46a52ed48 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt.fragment @@ -0,0 +1,4 @@ +a += 1 +a + +// RESULT: 3: I \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt index 7301c63ad33..327f3a92d23 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt +++ b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt @@ -1,7 +1,8 @@ -// PARAM_TYPES: kotlin.Int, Comparable -// PARAM_TYPES: kotlin.Int, Number, Comparable, Any +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int // PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo // PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// WITH_RUNTIME // SIBLING: fun foo(a: Int) { val b: Int = 1 diff --git a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.after index 807d1075aa9..dbdcceae1e7 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.after +++ b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.after @@ -1,7 +1,8 @@ -// PARAM_TYPES: kotlin.Int, Comparable -// PARAM_TYPES: kotlin.Int, Number, Comparable, Any +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int // PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo // PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// WITH_RUNTIME // SIBLING: fun foo(a: Int) { val b: Int = 1 diff --git a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt index aae00c677f6..573fbeeea92 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt +++ b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt @@ -1,7 +1,8 @@ -// PARAM_TYPES: kotlin.Int, Comparable -// PARAM_TYPES: kotlin.Int, Number, Comparable, Any +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int // PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo // PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// WITH_RUNTIME // SIBLING: fun foo(a: Int) { val b: Int = 1 diff --git a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt.after index 9b9ac1011cd..512205af878 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt.after +++ b/idea/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt.after @@ -1,7 +1,8 @@ -// PARAM_TYPES: kotlin.Int, Comparable -// PARAM_TYPES: kotlin.Int, Number, Comparable, Any +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int // PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo // PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// WITH_RUNTIME // SIBLING: fun foo(a: Int) { val b: Int = 1 diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt new file mode 100644 index 00000000000..72a80993350 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME +// PARAM_TYPES: A?, kotlin.Any? +// PARAM_TYPES: B, A +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: var a: A? defined in foo +// PARAM_DESCRIPTOR: value-parameter val b: B defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(b: B): Int { + var a: A? = null + var c: Int = 1 + + a = b + c += 2 + println(a) + println(c) + + return a.hashCode() ?: 0 + c +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt.after new file mode 100644 index 00000000000..668f719a5d7 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt.after @@ -0,0 +1,28 @@ +// WITH_RUNTIME +// PARAM_TYPES: A?, kotlin.Any? +// PARAM_TYPES: B, A +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: var a: A? defined in foo +// PARAM_DESCRIPTOR: value-parameter val b: B defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(b: B): Int { + var a: A? = null + var c: Int = 1 + + val pair = pair(a, b, c) + a = pair.first + c = pair.second + + return a.hashCode() ?: 0 + c +} + +private fun pair(a: A?, b: B, c: Int): Pair { + var a1 = a + var c1 = c + a1 = b + c1 += 2 + println(a1) + println(c1) + return Pair(a1, c1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt new file mode 100644 index 00000000000..57c740a2207 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: var k: kotlin.Int defined in foo +// SIBLING: +fun foo() { + var k = 0 + val a = 1 + k++ + val b = 2 + println(a + b - k) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt.after new file mode 100644 index 00000000000..eeccb7b9874 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt.after @@ -0,0 +1,20 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: var k: kotlin.Int defined in foo +// SIBLING: +fun foo() { + var k = 0 + val triple = triple(k) + val a = triple.first + val b = triple.second + k = triple.third + println(a + b - k) +} + +private fun triple(k: Int): Triple { + var k1 = k + val a = 1 + k1++ + val b = 2 + return Triple(a, b, k1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt deleted file mode 100644 index 10a49088446..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt +++ /dev/null @@ -1,12 +0,0 @@ -// SIBLING: -fun foo(a: Int): Int { - var b: Int = 1 - var c: Int = 1 - - b += a - c -= a - println(b) - println(c) - - return b -} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt.conflicts deleted file mode 100644 index c53e0fcc3ef..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt.conflicts +++ /dev/null @@ -1 +0,0 @@ -Selected code fragment has multiple output values: var b: Int var c: Int \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt deleted file mode 100644 index 45e6e7e491a..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt +++ /dev/null @@ -1,16 +0,0 @@ -// SIBLING: -fun foo(a: Int): Int { - var b: Int = 1 - var c: Int = 1 - - if (a > 0) { - b += a - } - else { - c -= a - } - println(b) - println(c) - - return b -} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt.conflicts deleted file mode 100644 index c53e0fcc3ef..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt.conflicts +++ /dev/null @@ -1 +0,0 @@ -Selected code fragment has multiple output values: var b: Int var c: Int \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt deleted file mode 100644 index b56079d4b46..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt +++ /dev/null @@ -1,18 +0,0 @@ -// SIBLING: -fun foo(a: Int): Int { - var b: Int = 1 - var c: Int = 1 - - when { - a > 0 -> { - b += a - } - else -> { - c -= a - } - } - println(b) - println(c) - - return b -} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt.conflicts deleted file mode 100644 index c53e0fcc3ef..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt.conflicts +++ /dev/null @@ -1 +0,0 @@ -Selected code fragment has multiple output values: var b: Int var c: Int \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt new file mode 100644 index 00000000000..d869fcee202 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + val t = 1 + if (a > 0) { + b += a + a + 1 + } + else a + + return b + c +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt.after new file mode 100644 index 00000000000..1b250dcb776 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt.after @@ -0,0 +1,24 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + val pair = pair(a, b) + b = pair.second + val t = 1 + pair.first + + return b + c +} + +private fun pair(a: Int, b: Int): Pair { + var b1 = b + return Pair(if (a > 0) { + b1 += a + a + 1 + } else a, b1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt new file mode 100644 index 00000000000..f99364a56a4 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt @@ -0,0 +1,20 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + + val t = if (a > 0) { + b += a + b + } + else { + a + } + println(b) + + return t +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt.after new file mode 100644 index 00000000000..08ebcaea695 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt.after @@ -0,0 +1,26 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + + val pair = pair(a, b) + b = pair.second + val t = pair.first + println(b) + + return t +} + +private fun pair(a: Int, b: Int): Pair { + var b1 = b + return Pair(if (a > 0) { + b1 += a + b1 + } else { + a + }, b1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt new file mode 100644 index 00000000000..1c0dab80484 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + + b += a + println(b) + return b +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt.after new file mode 100644 index 00000000000..461cdca63eb --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt.after @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + + return i(a, b) +} + +private fun i(a: Int, b: Int): Int { + var b1 = b + b1 += a + println(b1) + return b1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt new file mode 100644 index 00000000000..4a264a7a773 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt @@ -0,0 +1,24 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 2 + + val t = if (a > 0) { + b += a + c -= b + b + } + else { + a + } + println(b + c) + + return t +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt.after new file mode 100644 index 00000000000..72d95a93c9c --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt.after @@ -0,0 +1,32 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 2 + + val triple = triple(a, b, c) + b = triple.second + c = triple.third + val t = triple.first + println(b + c) + + return t +} + +private fun triple(a: Int, b: Int, c: Int): Triple { + var b1 = b + var c1 = c + return Triple(if (a > 0) { + b1 += a + c1 -= b1 + b1 + } else { + a + }, b1, c1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt new file mode 100644 index 00000000000..3e5a2db94bb --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + b += a + c -= a + println(b) + println(c) + + return b + c +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt.after new file mode 100644 index 00000000000..e843cd9128b --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt.after @@ -0,0 +1,28 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + val pair = pair(a, b, c) + b = pair.first + c = pair.second + + return b + c +} + +private fun pair(a: Int, b: Int, c: Int): Pair { + var b1 = b + var c1 = c + b1 += a + c1 -= a + println(b1) + println(c1) + return Pair(b1, c1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt similarity index 85% rename from idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt rename to idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt index 2cb993c53cb..4659d72eb19 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt @@ -1,3 +1,4 @@ +// WITH_RUNTIME // SIBLING: fun foo() { val a = 1 diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt.after new file mode 100644 index 00000000000..df721a0addb --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt.after @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// SIBLING: +fun foo() { + val pair = pair() + val a = pair.first + val b = pair.second + println(a + b) +} + +private fun pair(): Pair { + val a = 1 + val b = 2 + return Pair(a, b) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt new file mode 100644 index 00000000000..14cf0f4f683 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt @@ -0,0 +1,23 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + if (a > 0) { + b += a + } + else { + c -= a + } + println(b) + println(c) + + return b + c +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt.after new file mode 100644 index 00000000000..4af0155fa8f --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt.after @@ -0,0 +1,31 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + val pair = pair(a, b, c) + b = pair.first + c = pair.second + + return b + c +} + +private fun pair(a: Int, b: Int, c: Int): Pair { + var b1 = b + var c1 = c + if (a > 0) { + b1 += a + } else { + c1 -= a + } + println(b1) + println(c1) + return Pair(b1, c1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt new file mode 100644 index 00000000000..5ca93f892ea --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt @@ -0,0 +1,20 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + val pair = 2 + + b += a + c -= a + println(b) + println(c) + + return b + c +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt.after new file mode 100644 index 00000000000..443615a30ce --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt.after @@ -0,0 +1,29 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + val pair = 2 + + val pair1 = pair(a, b, c) + b = pair1.first + c = pair1.second + + return b + c +} + +private fun pair(a: Int, b: Int, c: Int): Pair { + var b1 = b + var c1 = c + b1 += a + c1 -= a + println(b1) + println(c1) + return Pair(b1, c1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt new file mode 100644 index 00000000000..726909a3e3c --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt @@ -0,0 +1,25 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + when { + a > 0 -> { + b += a + } + else -> { + c -= a + } + } + println(b) + println(c) + + return b + c +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt.after new file mode 100644 index 00000000000..44cbf5dbe31 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt.after @@ -0,0 +1,34 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + val pair = pair(a, b, c) + b = pair.first + c = pair.second + + return b + c +} + +private fun pair(a: Int, b: Int, c: Int): Pair { + var b1 = b + var c1 = c + when { + a > 0 -> { + b1 += a + } + else -> { + c1 -= a + } + } + println(b1) + println(c1) + return Pair(b1, c1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt new file mode 100644 index 00000000000..f5e42a4823c --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt @@ -0,0 +1,23 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + var d: Int = 1 + var e: Int = 1 + + b += a + c -= a + d += c + e -= d + println(b) + println(c) + + return b + c + d + e +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt.conflicts new file mode 100644 index 00000000000..cc6e91b09be --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt.conflicts @@ -0,0 +1 @@ +Selected code fragment has more than 3 output values: var b: Int var c: Int var d: Int var e: Int \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt new file mode 100644 index 00000000000..959ae017971 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt @@ -0,0 +1,28 @@ +// WITH_RUNTIME +// OPTIONS: true, true +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var d: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var e: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + var d: Int = 1 + var e: Int = 1 + + b += a + c -= a + d += c + e -= d + println(b) + println(c) + + return b + c + d + e +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt.after new file mode 100644 index 00000000000..0809754d63a --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt.after @@ -0,0 +1,41 @@ +// WITH_RUNTIME +// OPTIONS: true, true +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var d: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var e: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + var d: Int = 1 + var e: Int = 1 + + val list = list(a, b, c, d, e) + b = list[0] + c = list[1] + d = list[2] + e = list[3] + + return b + c + d + e +} + +private fun list(a: Int, b: Int, c: Int, d: Int, e: Int): List { + var b1 = b + var c1 = c + var d1 = d + var e1 = e + b1 += a + c1 -= a + d1 += c1 + e1 -= d1 + println(b1) + println(c1) + return listOf(b1, c1, d1, e1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt new file mode 100644 index 00000000000..a1b5ca2ea42 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt @@ -0,0 +1,23 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var d: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + var d: Int = 1 + + b += a + c -= a + d += c + println(b) + println(c) + + return b + c + d +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt.after new file mode 100644 index 00000000000..d28f07a2ea7 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt.after @@ -0,0 +1,34 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var d: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + var d: Int = 1 + + val triple = triple(a, b, c, d) + b = triple.first + c = triple.second + d = triple.third + + return b + c + d +} + +private fun triple(a: Int, b: Int, c: Int, d: Int): Triple { + var b1 = b + var c1 = c + var d1 = d + b1 += a + c1 -= a + d1 += c1 + println(b1) + println(c1) + return Triple(b1, c1, d1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt new file mode 100644 index 00000000000..e3a6a6143c7 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + b += a + c -= a + println(b) + println(c) + + return b +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt.after new file mode 100644 index 00000000000..4f8cd8659bb --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt.after @@ -0,0 +1,26 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: var c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + var b: Int = 1 + var c: Int = 1 + + b = i(a, b, c) + + return b +} + +private fun i(a: Int, b: Int, c: Int): Int { + var b1 = b + var c1 = c + b1 += a + c1 -= a + println(b1) + println(c1) + return b1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt.conflicts deleted file mode 100644 index 140c9346d58..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt.conflicts +++ /dev/null @@ -1 +0,0 @@ -Selected code fragment has multiple output values: val a: Int val b: Int \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt deleted file mode 100644 index d0696b844e2..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt +++ /dev/null @@ -1,15 +0,0 @@ -// SIBLING: -fun foo(a: Int): Int { - var b: Int = 1 - - val t = if (a > 0) { - b += a - b - } - else { - a - } - println(b) - - return t -} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt.conflicts deleted file mode 100644 index a3cb60bc6a1..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt.conflicts +++ /dev/null @@ -1 +0,0 @@ -Selected code fragment has output values as well as alternative exit points \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt deleted file mode 100644 index 55c0ff5fdf0..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt +++ /dev/null @@ -1,8 +0,0 @@ -// SIBLING: -fun foo(a: Int): Int { - var b: Int = 1 - - b += a - println(b) - return b -} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt.conflicts deleted file mode 100644 index a3cb60bc6a1..00000000000 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt.conflicts +++ /dev/null @@ -1 +0,0 @@ -Selected code fragment has output values as well as alternative exit points \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt b/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt index 39ad63948bb..67c124b6fc5 100644 --- a/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt +++ b/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt @@ -1,6 +1,4 @@ -data class Pair(val a: A, val b: B) -fun A.to(b: B) = Pair(this, b) - +// WITH_RUNTIME // SIBLING: fun foo() { val (a, b) = 1 to 2 diff --git a/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.after b/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.after index eac90dcbcd2..29ae7bace4b 100644 --- a/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.after +++ b/idea/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.after @@ -1,6 +1,4 @@ -data class Pair(val a: A, val b: B) -fun A.to(b: B) = Pair(this, b) - +// WITH_RUNTIME // SIBLING: fun foo() { val (a, b) = pair() diff --git a/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt b/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt index 68b412cd7ab..62699456320 100644 --- a/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt +++ b/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt @@ -1,6 +1,4 @@ -data class Pair(val a: A, val b: B) -fun A.to(b: B) = Pair(this, b) - +// WITH_RUNTIME // SIBLING: fun foo() { val (a, b) = diff --git a/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt.after b/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt.after index 125bffcbb24..afefca832d0 100644 --- a/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt.after +++ b/idea/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt.after @@ -1,6 +1,4 @@ -data class Pair(val a: A, val b: B) -fun A.to(b: B) = Pair(this, b) - +// WITH_RUNTIME // SIBLING: fun foo() { val (a, b) = diff --git a/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt b/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt index 651cf248267..97416523608 100644 --- a/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt +++ b/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt @@ -1,3 +1,4 @@ +// WITH_RUNTIME fun foo(c : Collection){ c.filter{it; false} } \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt.after b/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt.after index 05f4b0cac1c..81fd82b8f6b 100644 --- a/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt.after +++ b/idea/testData/refactoring/introduceVariable/FunctionLiteral.kt.after @@ -1,4 +1,5 @@ +// WITH_RUNTIME fun foo(c : Collection){ - val function = { it; false } + val function: (String) -> Boolean = { it; false } c.filter(function) } \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt b/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt index 25aaada9d5c..617c593d7bd 100644 --- a/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt +++ b/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt @@ -1,3 +1,4 @@ +// WITH_RUNTIME fun main(args: Array) { with(A()) { println(prop) @@ -7,6 +8,4 @@ fun main(args: Array) { class A { val prop = 1 -} - -public inline fun with(receiver: T, f: T.() -> R): R = receiver.f() \ No newline at end of file +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt.after b/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt.after index f7a5564a17e..9ec9e3de118 100644 --- a/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt.after +++ b/idea/testData/refactoring/introduceVariable/NoExplicitReceivers.kt.after @@ -1,3 +1,4 @@ +// WITH_RUNTIME fun main(args: Array) { with(A()) { val i = prop @@ -8,6 +9,4 @@ fun main(args: Array) { class A { val prop = 1 -} - -public inline fun with(receiver: T, f: T.() -> R): R = receiver.f() \ No newline at end of file +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt index 0f0bbdf9400..265d4304ac0 100644 --- a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt @@ -41,6 +41,11 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandlerHelper import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractableCodeDescriptor +import com.intellij.testFramework.LightPlatformTestCase +import org.jetbrains.jet.testing.ConfigLibraryUtil +import org.jetbrains.jet.plugin.PluginTestCaseBase +import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionData +import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionOptions public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() { override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST @@ -76,19 +81,31 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe } ) - val fileText = file.getText() + val fileText = file.getText() ?: "" val expectedDescriptors = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString() val expectedTypes = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString() val extractAsProperty = InTextDirectivesUtils.isDirectiveDefined(fileText, "// EXTRACT_AS_PROPERTY") + val extractionOptions = InTextDirectivesUtils.findListWithPrefixes(fileText, "// OPTIONS: ").let { + if (it.isNotEmpty()) { + [suppress("CAST_NEVER_SUCCEEDS")] + val args = it.map { it.toBoolean() }.copyToArray() as Array + javaClass().getConstructors()[0].newInstance(*args) as ExtractionOptions + } else ExtractionOptions.DEFAULT + } + val renderer = DescriptorRenderer.DEBUG_TEXT val editor = fixture.getEditor() selectElements(editor, file) {(elements, previousSibling) -> ExtractKotlinFunctionHandler( helper = object : ExtractKotlinFunctionHandlerHelper() { + override fun adjustExtractionData(data: ExtractionData): ExtractionData { + return data.copy(options = extractionOptions) + } + override fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions { return options.copy(extractAsProperty = extractAsProperty) } @@ -120,6 +137,10 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe val file = fixture.configureByFile(mainFile.getName()) as JetFile + if (InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// WITH_RUNTIME") != null) { + ConfigLibraryUtil.configureKotlinRuntime(myModule, PluginTestCaseBase.fullJdk()) + } + try { action(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 550c6364e71..3a159a12730 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 @@ -715,21 +715,69 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction/controlFlow/outputValues"), Pattern.compile("^(.+)\\.kt$"), true); } - @TestMetadata("multipleOutputValues.kt") - public void testMultipleOutputValues() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValues.kt"); + @TestMetadata("genericPair.kt") + public void testGenericPair() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt"); doExtractFunctionTest(fileName); } - @TestMetadata("multipleOutputValuesWithIf.kt") - public void testMultipleOutputValuesWithIf() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithIf.kt"); + @TestMetadata("initializersAndUpdate.kt") + public void testInitializersAndUpdate() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt"); doExtractFunctionTest(fileName); } - @TestMetadata("multipleOutputValuesWithWhen.kt") - public void testMultipleOutputValuesWithWhen() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/multipleOutputValuesWithWhen.kt"); + @TestMetadata("nestedNonInlinableCall.kt") + public void testNestedNonInlinableCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("outputValueWithExpression.kt") + public void testOutputValueWithExpression() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("outputValueWithReturn.kt") + public void testOutputValueWithReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("outputValuesWithExpression.kt") + public void testOutputValuesWithExpression() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("pair.kt") + public void testPair() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("pairOfInitalizersWithNonLocalUsages.kt") + public void testPairOfInitalizersWithNonLocalUsages() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("pairWithIf.kt") + public void testPairWithIf() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("pairWithNameClash.kt") + public void testPairWithNameClash() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("pairWithWhen.kt") + public void testPairWithWhen() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt"); doExtractFunctionTest(fileName); } @@ -763,6 +811,30 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("tooManyOutputValues.kt") + public void testTooManyOutputValues() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("tooManyOutputValuesAsList.kt") + public void testTooManyOutputValuesAsList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("triple.kt") + public void testTriple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("usedAndUnusedOutputValues.kt") + public void testUsedAndUnusedOutputValues() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("valuesUsedInLambdaOnly.kt") public void testValuesUsedInLambdaOnly() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/outputValues/valuesUsedInLambdaOnly.kt"); @@ -849,12 +921,6 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } - @TestMetadata("multipleInitalizersWithNonLocalUsages.kt") - public void testMultipleInitalizersWithNonLocalUsages() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleInitalizersWithNonLocalUsages.kt"); - doExtractFunctionTest(fileName); - } - @TestMetadata("multipleJumps.kt") public void testMultipleJumps() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/unextractable/multipleJumps.kt"); @@ -867,18 +933,6 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } - @TestMetadata("outputValueWithExpression.kt") - public void testOutputValueWithExpression() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithExpression.kt"); - doExtractFunctionTest(fileName); - } - - @TestMetadata("outputValueWithReturn.kt") - public void testOutputValueWithReturn() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/unextractable/outputValueWithReturn.kt"); - doExtractFunctionTest(fileName); - } - @TestMetadata("variablesOutOfScope.kt") public void testVariablesOutOfScope() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/unextractable/variablesOutOfScope.kt");