diff --git a/annotations/com/intellij/codeInsight/folding/annotations.xml b/annotations/com/intellij/codeInsight/folding/annotations.xml new file mode 100644 index 00000000000..8a507eaaf30 --- /dev/null +++ b/annotations/com/intellij/codeInsight/folding/annotations.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/codeInsight/folding/impl/annotations.xml b/annotations/com/intellij/codeInsight/folding/impl/annotations.xml new file mode 100644 index 00000000000..9c7706fb5ee --- /dev/null +++ b/annotations/com/intellij/codeInsight/folding/impl/annotations.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/codeInsight/highlighting/annotations.xml b/annotations/com/intellij/codeInsight/highlighting/annotations.xml new file mode 100644 index 00000000000..55381ff35c0 --- /dev/null +++ b/annotations/com/intellij/codeInsight/highlighting/annotations.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt index 57bf94d911d..9950f6ef136 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt @@ -49,7 +49,5 @@ public class ReturnValueInstruction( return ReturnValueInstruction((element as JetExpression), lexicalScope, newLabel, returnedValue) } - public val resultExpression: JetExpression = - element.let{ if (it is JetReturnExpression) it.getReturnedExpression()!! else element as JetExpression } public val returnExpressionIfAny: JetReturnExpression? = element as? JetReturnExpression } 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 a2dbff11f3e..bc957846a64 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -24,7 +24,6 @@ import org.jetbrains.jet.plugin.refactoring.createTempCopy import org.jetbrains.jet.lang.psi.codeFragmentUtil.skipVisibilityCheck import com.intellij.psi.PsiElement import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionData -import java.util.Collections import org.jetbrains.jet.plugin.refactoring.extractFunction.performAnalysis import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil @@ -37,6 +36,7 @@ import org.jetbrains.jet.lang.psi.* import org.jetbrains.jet.plugin.intentions.InsertExplicitTypeArguments import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions import org.jetbrains.jet.plugin.refactoring.extractFunction.generateDeclaration +import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange import org.jetbrains.jet.plugin.actions.internal.KotlinInternalMode import org.jetbrains.jet.lang.psi.psiUtil.replaced @@ -97,7 +97,7 @@ fun getFunctionForExtractedFragment( if (targetSibling == null) return null val analysisResult = ExtractionData( - tmpFile, Collections.singletonList(newDebugExpression), targetSibling, ExtractionOptions(false, true) + tmpFile, newDebugExpression.toRange(), 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 eb2f1500af0..9339afa88e0 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties @@ -20,6 +20,8 @@ cannot.refactor.expression.should.have.inferred.type=Expression should have infe cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}'' error.types.in.generated.function=Cannot generate function with erroneous return type +0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration={0} has detected {1} code {1,choice,1#fragment|2#fragments} in this file that can be replaced with a call to extracted declaration. Would you like to review and replace {1,choice,1#it|2#them}? + error.wrong.caret.position.function.or.constructor.name=The caret should be positioned at the name of the function or constructor to be refactored. error.cant.refactor.vararg.functions=Can't refactor the function with variable arguments function.name.is.invalid=Function name is invalid 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 c57bd7d30b5..2ff228e1180 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt @@ -66,6 +66,7 @@ import org.jetbrains.jet.lang.descriptors.FunctionDescriptor import org.jetbrains.jet.renderer.DescriptorRenderer import org.jetbrains.jet.lang.psi.JetPropertyAccessor import org.jetbrains.jet.lang.psi.JetClassOrObject +import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange import org.jetbrains.jet.lang.psi.JetMultiDeclaration public open class ExtractKotlinFunctionHandlerHelper { @@ -81,6 +82,15 @@ public open class ExtractKotlinFunctionHandlerHelper { public class ExtractKotlinFunctionHandler( public val allContainersEnabled: Boolean = false, private val helper: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper.DEFAULT) : RefactoringActionHandler { + private fun adjustElements(elements: List): List { + if (elements.size != 1) return elements + + val e = elements.first() + if (e is JetBlockExpression && e.getParent() is JetFunctionLiteral) return e.getStatements() + + return elements + } + fun doInvoke( editor: Editor, file: JetFile, @@ -89,7 +99,9 @@ public class ExtractKotlinFunctionHandler( ) { val project = file.getProject() - val analysisResult = helper.adjustExtractionData(ExtractionData(file, elements, targetSibling)).performAnalysis() + val analysisResult = helper.adjustExtractionData( + ExtractionData(file, adjustElements(elements).toRange(false), targetSibling) + ).performAnalysis() if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != Status.SUCCESS) { throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() }) @@ -98,7 +110,8 @@ public class ExtractKotlinFunctionHandler( fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) { val adjustedDescriptor = helper.adjustDescriptor(descriptor) val adjustedGeneratorOptions = helper.adjustGeneratorOptions(generatorOptions) - project.executeWriteCommand(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) } + val result = project.executeWriteCommand(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) } + processDuplicates(result.duplicateReplacers, project, editor) } fun validateAndRefactor() { 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 7e9f9399d0c..bb95f405865 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt @@ -64,6 +64,8 @@ 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 +import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange +import org.jetbrains.jet.lang.resolve.BindingContext trait Parameter { val argumentText: String @@ -123,29 +125,37 @@ class FqNameReplacement(val fqName: FqName): Replacement { } trait OutputValue { + val originalExpressions: List val valueType: JetType class ExpressionValue( val callSiteReturn: Boolean, + override val originalExpressions: List, override val valueType: JetType ): OutputValue class Jump( - val elementsToReplace: List, + val elementsToReplace: List, val elementToInsertAfterCall: JetElement, val conditional: Boolean ): OutputValue { + override val originalExpressions: List get() = elementsToReplace override val valueType: JetType = with(KotlinBuiltIns.getInstance()) { if (conditional) getBooleanType() else getUnitType() } } - class ParameterUpdate(val parameter: Parameter): OutputValue { + class ParameterUpdate( + val parameter: Parameter, + override val originalExpressions: List + ): OutputValue { override val valueType: JetType get() = parameter.parameterType } class Initializer( val initializedDeclaration: JetProperty, override val valueType: JetType - ): OutputValue + ): OutputValue { + override val originalExpressions: List get() = Collections.singletonList(initializedDeclaration) + } } abstract class OutputValueBoxer(val outputValues: List) { @@ -315,6 +325,7 @@ data class ExtractionGeneratorOptions( data class ExtractionResult( val declaration: JetNamedDeclaration, + val duplicateReplacers: Map Unit>, val nameByOffset: Map ) 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 d510692b9fb..520514d4744 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt @@ -51,6 +51,7 @@ import org.jetbrains.jet.lang.psi.JetFunctionLiteral import org.jetbrains.jet.lang.psi.JetClassInitializer import org.jetbrains.jet.lang.resolve.calls.callUtil.getResolvedCall import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils +import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange data class ExtractionOptions( val inferUnitTypeForUnusedValues: Boolean, @@ -76,11 +77,12 @@ data class ResolvedReferenceInfo( data class ExtractionData( val originalFile: JetFile, - val originalElements: List, + val originalRange: JetPsiRange, val targetSibling: PsiElement, val options: ExtractionOptions = ExtractionOptions.DEFAULT ) { val project: Project = originalFile.getProject() + val originalElements: List = originalRange.elements val insertBefore: Boolean = targetSibling.getParentByType(javaClass(), true)?.let { it is JetDeclarationWithBody || it is JetClassInitializer diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/duplicateUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/duplicateUtil.kt new file mode 100644 index 00000000000..9f8f8563951 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/duplicateUtil.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.refactoring.extractFunction + +import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.project.Project +import com.intellij.openapi.editor.Editor +import java.util.ArrayList +import com.intellij.openapi.editor.markup.RangeHighlighter +import com.intellij.openapi.editor.ScrollType +import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle +import com.intellij.openapi.application.ApplicationNamesInfo +import com.intellij.openapi.util.Ref +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.codeInsight.highlighting.HighlightManager +import com.intellij.codeInsight.folding.CodeFoldingManager +import com.intellij.ui.ReplacePromptDialog +import com.intellij.find.FindManager +import org.jetbrains.jet.plugin.refactoring.executeWriteCommand +import com.intellij.refactoring.util.duplicates.MethodDuplicatesHandler +import com.intellij.refactoring.RefactoringBundle + +public fun JetPsiRange.highlight(project: Project, editor: Editor): RangeHighlighter? { + val textRange = getTextRange() + val highlighters = ArrayList() + val attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!! + HighlightManager.getInstance(project).addRangeHighlight( + editor, textRange.getStartOffset(), textRange.getEndOffset(), attributes, true, highlighters + ) + return highlighters.firstOrNull() +} + +public fun JetPsiRange.preview(project: Project, editor: Editor): RangeHighlighter? { + return highlight(project, editor)?.let { + val startOffset = getTextRange().getStartOffset() + val foldedRegions = + CodeFoldingManager.getInstance(project) + .getFoldRegionsAtOffset(editor, startOffset) + .filter { !it.isExpanded() } + if (!foldedRegions.empty) { + editor.getFoldingModel().runBatchFoldingOperation { foldedRegions.forEach { it.setExpanded(true) } } + } + editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(startOffset), ScrollType.MAKE_VISIBLE) + + it + } +} + +public fun processDuplicates( + duplicateReplacers: Map Unit>, + project: Project, + editor: Editor +) { + val size = duplicateReplacers.size + if (size == 0) return + + if (size == 1) { + duplicateReplacers.keySet().first().preview(project, editor) + } + + val answer = if (ApplicationManager.getApplication()!!.isUnitTestMode()) + Messages.YES + else + Messages.showYesNoDialog( + project, + JetRefactoringBundle.message( + "0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration", + ApplicationNamesInfo.getInstance()!!.getProductName(), + duplicateReplacers.size() + ), + "Process Duplicates", + Messages.getQuestionIcon() + ) + if (answer != Messages.YES) return + + var showAll = false + for ((i, entry) in duplicateReplacers.entrySet().withIndices()) { + val (pattern, replacer) = entry + if (!pattern.isValid()) continue + + val highlighter = pattern.preview(project, editor) + if (!ApplicationManager.getApplication()!!.isUnitTestMode()) { + if (size > 1 && !showAll) { + val promptDialog = ReplacePromptDialog(false, RefactoringBundle.message("process.duplicates.title", i + 1, size), project) + promptDialog.show() + when(promptDialog.getExitCode()) { + FindManager.PromptResult.ALL -> showAll = true + FindManager.PromptResult.SKIP -> continue + FindManager.PromptResult.CANCEL -> return + } + } + } + highlighter?.let { HighlightManager.getInstance(project).removeSegmentHighlighter(editor, it) } + + project.executeWriteCommand(MethodDuplicatesHandler.REFACTORING_NAME, replacer) + } +} 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 41af055c18c..2192a5cd920 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt @@ -37,7 +37,6 @@ 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 @@ -62,24 +61,12 @@ 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() @@ -96,10 +83,17 @@ private fun JetDeclaration.renderForMessage(bindingContext: BindingContext): Str private fun JetType.isDefault(): Boolean = KotlinBuiltIns.getInstance().isUnit(this) -private fun List.getModifiedVarDescriptors(bindingContext: BindingContext): Set { - return this - .map {if (it is WriteValueInstruction) PseudocodeUtil.extractVariableDescriptorIfAny(it, false, bindingContext) else null} - .filterNotNullTo(HashSet()) +private fun List.getModifiedVarDescriptors(bindingContext: BindingContext): Map> { + val result = HashMap>() + for (instruction in filterIsInstance(javaClass())) { + val expression = instruction.element as? JetExpression + val descriptor = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, false, bindingContext) + if (expression != null && descriptor != null) { + result.getOrPut(descriptor) { ArrayList() }.add(expression) + } + } + + return result } private fun List.getVarDescriptorsAccessedAfterwards(bindingContext: BindingContext): Set { @@ -127,17 +121,21 @@ private fun List.getVarDescriptorsAccessedAfterwards(bindingContext private fun List.getExitPoints(): List = filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } } -private fun List.getResultType( +private fun List.getResultTypeAndExpressions( bindingContext: BindingContext, - options: ExtractionOptions): JetType { - fun instructionToType(instruction: Instruction): JetType? { - val expression = when (instruction) { + options: ExtractionOptions): Pair> { + fun instructionToExpression(instruction: Instruction, unwrapReturn: Boolean): JetExpression? { + return when (instruction) { is ReturnValueInstruction -> - instruction.resultExpression + (if (unwrapReturn) null else instruction.returnExpressionIfAny) ?: instruction.returnedValue.element as? JetExpression is InstructionWithValue -> instruction.outputValue?.element as? JetExpression else -> null } + } + + fun instructionToType(instruction: Instruction): JetType? { + val expression = instructionToExpression(instruction, true) if (expression == null) return null if (options.inferUnitTypeForUnusedValues && expression.isUsedAsStatement(bindingContext)) return null @@ -146,7 +144,10 @@ private fun List.getResultType( } val resultTypes = map(::instructionToType).filterNotNull() - return if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE + val resultType = if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE + val expressions = map { instructionToExpression(it, false) }.filterNotNull() + + return resultType to expressions } private fun List.checkEquivalence(checkPsi: Boolean): Boolean { @@ -182,7 +183,7 @@ private fun ExtractionData.analyzeControlFlow( pseudocode: Pseudocode, module: ModuleDescriptor, bindingContext: BindingContext, - modifiedVarDescriptors: Set, + modifiedVarDescriptors: Map>, options: ExtractionOptions, parameters: Set ): Pair { @@ -213,7 +214,10 @@ private fun ExtractionData.analyzeControlFlow( is ReturnValueInstruction -> { val returnExpression = insn.returnExpressionIfAny if (returnExpression == null) { - defaultExits.add(insn) + val containingDeclaration = insn.returnedValue.element?.getParentByType(javaClass()) + if (containingDeclaration == pseudocode.getCorrespondingElement()) { + defaultExits.add(insn) + } } else if (isCurrentFunctionReturn(returnExpression)) { valuedReturnExits.add(insn) @@ -241,8 +245,8 @@ private fun ExtractionData.analyzeControlFlow( val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext) val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is JetProperty && it.isLocal() } - val typeOfDefaultFlow = defaultExits.getResultType(bindingContext, options) - val returnValueType = valuedReturnExits.getResultType(bindingContext, options) + val (typeOfDefaultFlow, defaultResultExpressions) = defaultExits.getResultTypeAndExpressions(bindingContext, options) + val (returnValueType, valuedReturnExpressions) = valuedReturnExits.getResultTypeAndExpressions(bindingContext, options) val emptyControlFlow = ControlFlow(Collections.emptyList(), { OutputValueBoxer.AsTuple(it, module) }, declarationsToCopy) @@ -251,7 +255,7 @@ private fun ExtractionData.analyzeControlFlow( if (defaultReturnType.isError()) return emptyControlFlow to ErrorMessage.ERROR_TYPES val controlFlow = if (defaultReturnType.isMeaningful()) { - emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultReturnType))) + emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultResultExpressions, defaultReturnType))) } else { emptyControlFlow @@ -263,9 +267,9 @@ private fun ExtractionData.analyzeControlFlow( } val outParameters = - parameters.filter { it.mirrorVarName != null && it.originalDescriptor in modifiedVarDescriptors }.sortBy { it.nameForRef } + parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.originalDescriptor] != null }.sortBy { it.nameForRef } val outDeclarations = - declarationsToCopy.filter { bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] in modifiedVarDescriptors } + declarationsToCopy.filter { modifiedVarDescriptors[bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]] != null } val modifiedValueCount = outParameters.size + outDeclarations.size val outputValues = ArrayList() @@ -276,7 +280,7 @@ private fun ExtractionData.analyzeControlFlow( if (typeOfDefaultFlow.isMeaningful()) { if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError - outputValues.add(ExpressionValue(false, typeOfDefaultFlow)) + outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow)) } else if (valuedReturnExits.isNotEmpty()) { if (jumpExits.isNotEmpty()) return multipleExitsError @@ -285,19 +289,19 @@ private fun ExtractionData.analyzeControlFlow( if (modifiedValueCount != 0) return outputAndExitsError if (valuedReturnExits.size != 1) return multipleExitsError - val element = valuedReturnExits.first!!.element + val element = valuedReturnExits.first!!.element as JetExpression return controlFlow.copy(outputValues = Collections.singletonList(Jump(listOf(element), element, true))) to null } if (!valuedReturnExits.checkEquivalence(false)) return multipleExitsError - outputValues.add(ExpressionValue(true, returnValueType)) + outputValues.add(ExpressionValue(true, valuedReturnExpressions, 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) } + outParameters.mapTo(outputValues) { ParameterUpdate(it, modifiedVarDescriptors[it.originalDescriptor]!!) } if (outputValues.isNotEmpty()) { if (jumpExits.isNotEmpty()) return outputAndExitsError @@ -322,7 +326,7 @@ private fun ExtractionData.analyzeControlFlow( if (jumpExits.isNotEmpty()) { if (!jumpExits.checkEquivalence(true)) return multipleExitsError - val elements = jumpExits.map { it.element } + val elements = jumpExits.map { it.element as JetExpression } return controlFlow.copy( outputValues = Collections.singletonList(Jump(elements, elements.first(), defaultExits.isNotEmpty())) ) to null @@ -674,22 +678,24 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val pseudocode = PseudocodeUtil.generatePseudocode(pseudocodeDeclaration, bindingContext) val localInstructions = getLocalInstructions(pseudocode) - val modifiedVarDescriptors = localInstructions.getModifiedVarDescriptors(bindingContext) + val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext) - val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, modifiedVarDescriptors) + val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, modifiedVarDescriptorsWithExpressions.keySet()) if (paramsInfo.errorMessage != null) { return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) } val messages = ArrayList() + val modifiedVarDescriptorsForControlFlow = HashMap(modifiedVarDescriptorsWithExpressions) + modifiedVarDescriptorsForControlFlow.keySet().retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext)) val (controlFlow, controlFlowMessage) = analyzeControlFlow( localInstructions, pseudocode, resolveSession.getModuleDescriptor(), bindingContext, - modifiedVarDescriptors intersect localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext), + modifiedVarDescriptorsForControlFlow, options, paramsInfo.parameters ) 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 3016be27a94..f58107623de 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt @@ -33,7 +33,6 @@ import org.jetbrains.jet.lang.psi.JetNamedDeclaration import org.jetbrains.jet.lang.psi.JetPsiFactory import java.util.LinkedHashMap import java.util.Collections -import org.jetbrains.jet.plugin.codeInsight.ShortenReferences import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.jet.lang.psi.JetFunctionLiteralArgument @@ -53,6 +52,16 @@ import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl import org.jetbrains.jet.plugin.refactoring.JetNameSuggester import org.jetbrains.jet.plugin.refactoring.isMultiLine import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValueBoxer.AsTuple +import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiUnifier +import org.jetbrains.jet.plugin.util.psi.patternMatching.UnifierParameter +import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange +import org.jetbrains.jet.plugin.codeInsight.ShortenReferences +import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange +import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult +import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange.Match +import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.Status +import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.WeaklyMatched +import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.StronglyMatched fun ExtractableCodeDescriptor.getDeclarationText( options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT, @@ -110,6 +119,227 @@ fun createNameCounterpartMap(from: JetElement, to: JetElement): Map +) + +fun ExtractableCodeDescriptor.findDuplicates(): List { + fun processWeakMatch(match: Match, newControlFlow: ControlFlow): Boolean { + val valueCount = controlFlow.outputValues.size + + val weakMatches = HashMap((match.result as WeaklyMatched).weakMatches) + val currentValuesToNew = HashMap() + + fun matchValues(currentValue: OutputValue, newValue: OutputValue): Boolean { + if ((currentValue is Jump) != (newValue is Jump)) return false + if (currentValue.originalExpressions.zip(newValue.originalExpressions).all { weakMatches[it.first] == it.second }) { + currentValuesToNew[currentValue] = newValue + weakMatches.keySet().removeAll(currentValue.originalExpressions) + return true + } + return false + } + + if (valueCount == 1) { + matchValues(controlFlow.outputValues.first(), newControlFlow.outputValues.first()) + } else { + @outer + for (currentValue in controlFlow.outputValues) + for (newValue in newControlFlow.outputValues) { + if ((currentValue is ExpressionValue) != (newValue is ExpressionValue)) continue + if (matchValues(currentValue, newValue)) continue @outer + } + } + + return currentValuesToNew.size == valueCount && weakMatches.isEmpty() + } + + fun getControlFlowIfMatched(match: Match): ControlFlow? { + val analysisResult = extractionData.copy(originalRange = match.range).performAnalysis() + if (analysisResult.status != AnalysisResult.Status.SUCCESS) return null + + val newControlFlow = analysisResult.descriptor!!.controlFlow + if (newControlFlow.outputValues.isEmpty()) return newControlFlow + if (controlFlow.outputValues.size != newControlFlow.outputValues.size) return null + + val matched = when (match.result) { + is StronglyMatched -> true + is WeaklyMatched -> processWeakMatch(match, newControlFlow) + else -> throw AssertionError("Unexpected unification result: ${match.result}") + } + + return if (matched) newControlFlow else null + } + + val unifierParameters = parameters.map { UnifierParameter(it.originalDescriptor, it.parameterType) } + + val unifier = JetPsiUnifier(unifierParameters, true) + + val scopeElement = extractionData.targetSibling.getParent() ?: return Collections.emptyList() + val originalTextRange = extractionData.originalRange.getTextRange() + return extractionData + .originalRange + .match(scopeElement, unifier) + .stream() + .filter { !(it.range.getTextRange() intersects originalTextRange) } + .map { match -> + val controlFlow = getControlFlowIfMatched(match) + controlFlow?.let { DuplicateInfo(match.range, it, unifierParameters.map { match.result.substitution[it]!!.getText()!! }) } + } + .filterNotNull() + .toList() +} + +private fun makeCall( + name: String, + declaration: JetNamedDeclaration, + controlFlow: ControlFlow, + rangeToReplace: JetPsiRange, + arguments: List) { + fun insertCall(anchor: PsiElement, wrappedCall: JetExpression) { + val firstExpression = rangeToReplace.elements.firstOrNull { it is JetExpression } as? JetExpression + if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) { + val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass())!! + //todo use the right binding context + functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, BindingContext.EMPTY) + return + } + anchor.replace(wrappedCall) + } + + if (rangeToReplace !is JetPsiRange.ListRange) return + + val anchor = rangeToReplace.startElement + val anchorParent = anchor.getParent()!! + + anchor.getNextSibling()?.let { from -> + val to = rangeToReplace.endElement + if (to != anchor) { + anchorParent.deleteChildRange(from, to); + } + } + + val callText = when (declaration) { + is JetNamedFunction -> + arguments.joinToString(separator = ", ", prefix = "${name}(", postfix = ")") + else -> name + } + + val anchorInBlock = stream(anchor) { it.getParent() }.firstOrNull { it.getParent() is JetBlockExpression } + val block = (anchorInBlock?.getParent() as? JetBlockExpression) ?: anchorParent + + val psiFactory = JetPsiFactory(anchor.getProject()) + val newLine = psiFactory.createNewLine() + + if (controlFlow.outputValueBoxer is AsTuple && controlFlow.outputValues.size > 1 && controlFlow.outputValues.all { it is Initializer }) { + val declarationsToMerge = controlFlow.outputValues.map { (it as Initializer).initializedDeclaration } + val isVar = declarationsToMerge.first().isVar() + if (declarationsToMerge.all { it.isVar() == isVar }) { + controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach { + block.addBefore(psiFactory.createDeclaration(it.getText()!!), anchorInBlock) as JetDeclaration + block.addBefore(newLine, anchorInBlock) + } + + val entries = declarationsToMerge.map { p -> p.getName() + (p.getTypeRef()?.let { ": ${it.getText()}" } ?: "") } + anchorInBlock?.replace( + psiFactory.createDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText") + ) + + return + } + } + + 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] = block.addBefore(declCopy, anchorInBlock) as JetDeclaration + block.addBefore(newLine, anchorInBlock) + } + + if (controlFlow.outputValues.isEmpty()) { + anchor.replace(psiFactory.createExpression(callText)) + return + } + + 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() + } +} + fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOptions): ExtractionResult{ val psiFactory = JetPsiFactory(extractionData.originalFile) val nameByOffset = HashMap() @@ -275,156 +505,16 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp } } - fun insertCall(anchor: PsiElement, wrappedCall: JetExpression) { - val firstExpression = extractionData.getExpressions().firstOrNull() - if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) { - val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass())!! - //todo use the right binding context - functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, BindingContext.EMPTY) - return - } - anchor.replace(wrappedCall) - } - - fun makeCall(declaration: JetNamedDeclaration) { - val anchor = extractionData.originalElements.first - if (anchor == null) return - - val anchorParent = anchor.getParent()!! - - anchor.getNextSibling()?.let { from -> - val to = extractionData.originalElements.last - if (to != anchor) { - anchorParent.deleteChildRange(from, to); - } - } - - val callText = when (declaration) { - is JetNamedFunction -> - parameters - .map { it.argumentText } - .joinToString(separator = ", ", prefix = "${name}(", postfix = ")") - else -> name - } - - val anchorInBlock = stream(anchor) { it.getParent() }.firstOrNull { it.getParent() is JetBlockExpression } - val block = (anchorInBlock?.getParent() as? JetBlockExpression) ?: anchorParent - - val newLine = psiFactory.createNewLine() - - if (controlFlow.outputValueBoxer is AsTuple && controlFlow.outputValues.size > 1 && controlFlow.outputValues.all { it is Initializer }) { - val declarationsToMerge = controlFlow.outputValues.map { (it as Initializer).initializedDeclaration } - val isVar = declarationsToMerge.first().isVar() - if (declarationsToMerge.all { it.isVar() == isVar }) { - controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach { - block.addBefore(psiFactory.createDeclaration(it.getText()!!), anchorInBlock) as JetDeclaration - block.addBefore(newLine, anchorInBlock) - } - - val entries = declarationsToMerge.map { p -> p.getName() + (p.getTypeRef()?.let { ": ${it.getText()}" } ?: "") } - anchorInBlock?.replace( - psiFactory.createDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText") - ) - - return - } - } - - 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] = block.addBefore(declCopy, anchorInBlock) as JetDeclaration - block.addBefore(newLine, anchorInBlock) - } - - if (controlFlow.outputValues.isEmpty()) { - anchor.replace(psiFactory.createExpression(callText)) - return - } - - 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 duplicates = if (options.inTempFile) Collections.emptyList() else findDuplicates() val declaration = createDeclaration().let { if (options.inTempFile) it else insertDeclaration(it) } adjustDeclarationBody(declaration) - if (options.inTempFile) return ExtractionResult(declaration, nameByOffset) + if (options.inTempFile) return ExtractionResult(declaration, Collections.emptyMap(), nameByOffset) - makeCall(declaration) - ShortenReferences.process(declaration) - return ExtractionResult(declaration, nameByOffset) + makeCall(name, declaration, controlFlow, extractionData.originalRange, parameters.map { it.argumentText }) + ShortenReferences.process(declaration) + + val duplicateReplacers = duplicates.map { it.range to { makeCall(name, declaration, it.controlFlow, it.range, it.arguments) } }.toMap() + return ExtractionResult(declaration, duplicateReplacers, 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 ceaec8cd83c..3425848233d 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 @@ -232,7 +232,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { 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()))); + outputValues.set(i, new OutputValue.ParameterUpdate(oldToNewParameters.get(parameterUpdate.getParameter()), parameterUpdate.getOriginalExpressions())); } } controlFlow = new ControlFlow(outputValues, controlFlow.getBoxerFactory(), controlFlow.getDeclarationsToCopy()); diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index ce3c24218ad..f2acf9cf96b 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -66,6 +66,8 @@ import com.intellij.openapi.editor.Document import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiWhiteSpace +import com.intellij.openapi.util.Computable +import com.intellij.openapi.ui.DialogWrapper /** * Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName @@ -190,6 +192,12 @@ public fun Project.executeWriteCommand(name: String, command: () -> Unit) { CommandProcessor.getInstance().executeCommand(this, { runWriteAction(command) }, name, null) } +public fun Project.executeWriteCommand(name: String, command: () -> T): T { + var result: T? = null + CommandProcessor.getInstance().executeCommand(this, { result = runWriteAction(command) }, name, null) + return result!! +} + public fun getPsiElementPopup( editor: Editor, elements: Array, diff --git a/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after index 18a4e9d87bc..99db87af6e4 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after +++ b/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after @@ -1,6 +1,6 @@ // SIBLING: fun foo(a: Int): Int { - val b: Int = 1 + val b: Int = i() return if (a + b > 0) i() else if (a - b < 0) 2 else b } diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt new file mode 100644 index 00000000000..0283a436a13 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt @@ -0,0 +1,87 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + var b: Int = 1 + val t = if (a > 0) { + b++ + b + a + } + else { + b-- + b - a + } + return t +} + +fun foo1() { + val x = 1 + var y: Int = x + println( + if (x > 0) { + y++ + y + x + } + else { + y-- + y - x + } + ) +} + +fun foo2(x: Int) { + var p: Int = 1 + var q: Int + if (x > 0) { + p++ + q = p + x + } + else { + p-- + q = p - x + } + println(q) +} + +fun foo3(x: Int): Int { + var p: Int = 1 + if (x > 0) { + p++ + return p + x + } + else { + p-- + return p - x + } +} + +fun foo4() { + val t: (Int) -> (Int) = { + var n = it + if (it > 0) { + n++ + n + it + } + else { + n-- + n - it + } + } + println(t(1)) +} + +fun foo5(x: Int): Int { + var p: Int = 1 + if (x > 0) { + p++ + val t = p + x + } + else { + p-- + val u = p - x + } +} diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt.after b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt.after new file mode 100644 index 00000000000..c31c80b774d --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt.after @@ -0,0 +1,70 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + var b: Int = 1 + val t = i(a, b) + return t +} + +private fun i(a: Int, b: Int): Int { + var b1 = b + return if (a > 0) { + b1++ + b1 + a + } else { + b1-- + b1 - a + } +} + +fun foo1() { + val x = 1 + var y: Int = x + println( + i(x, y) + ) +} + +fun foo2(x: Int) { + var p: Int = 1 + var q: Int + if (x > 0) { + p++ + q = p + x + } + else { + p-- + q = p - x + } + println(q) +} + +fun foo3(x: Int): Int { + var p: Int = 1 + if (x > 0) { + p++ + return p + x + } + else { + p-- + return p - x + } +} + +fun foo4() { + val t: (Int) -> (Int) = { + var n = it + i(it, n) + } + println(t(1)) +} + +fun foo5(x: Int): Int { + var p: Int = 1 + i(x, p) +} diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt new file mode 100644 index 00000000000..69eda395179 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt @@ -0,0 +1,74 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + var b: Int = 1 + if (a > 0) { + b = b + a + } + else { + b = b - a + } + return b +} + +fun foo1() { + val x = 1 + var y: Int = x + println( + if (x > 0) { + y + x + } + else { + y - x + } + ) +} + +fun foo2(x: Int) { + var p: Int = 1 + if (x > 0) { + p = p + x + } + else { + p = p - x + } + println(p) +} + +fun foo3(x: Int): Int { + var p: Int = 1 + if (x > 0) { + return p + x + } + else { + return p - x + } +} + +fun foo4() { + val t: (Int) -> (Int) = { + var n = it + if (it > 0) { + n + it + } + else { + n - it + } + } + println(t(1)) +} + +fun foo5(x: Int): Int { + var p: Int = 1 + if (x > 0) { + val t = p + x + } + else { + val u = p - x + } +} diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt.after b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt.after new file mode 100644 index 00000000000..0f5ae56429d --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt.after @@ -0,0 +1,64 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test +// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + var b: Int = 1 + b = i(a, b) + return b +} + +private fun i(a: Int, b: Int): Int { + var b1 = b + if (a > 0) { + b1 = b1 + a + } else { + b1 = b1 - a + } + return b1 +} + +fun foo1() { + val x = 1 + var y: Int = x + println( + if (x > 0) { + y + x + } + else { + y - x + } + ) +} + +fun foo2(x: Int) { + var p: Int = 1 + p = i(x, p) + println(p) +} + +fun foo3(x: Int): Int { + var p: Int = 1 + return i(x, p) +} + +fun foo4() { + val t: (Int) -> (Int) = { + var n = it + if (it > 0) { + n + it + } + else { + n - it + } + } + println(t(1)) +} + +fun foo5(x: Int): Int { + var p: Int = 1 + i(x, p) +} diff --git a/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt new file mode 100644 index 00000000000..818d3297dcd --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt @@ -0,0 +1,20 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo + +// SIBLING: +fun foo(a: Int, b: Int) { + println("a = $a") + println("b = $b") + println(a + b*a) +} + +fun bar() { + val x = 1 + val y = 2 + + println("a = $x") + println("b = $y") + println(x + y*x) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt.after b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt.after new file mode 100644 index 00000000000..2a007b47f4b --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo + +// SIBLING: +fun foo(a: Int, b: Int) { + unit(a, b) +} + +private fun unit(a: Int, b: Int) { + println("a = $a") + println("b = $b") + println(a + b * a) +} + +fun bar() { + val x = 1 + val y = 2 + + unit(x, y) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt new file mode 100644 index 00000000000..ad84426faa6 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt @@ -0,0 +1,30 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + val b = a + 1 + val c = a - 1 + return b*c +} + +fun foo1(a: Int) { + var x = a + 1 + var y = a - 1 + println(x + y) +} + +fun foo2(): Int { + var p: Int = 1 + var q: Int + p = p + 1 + q = p - 1 + return p + q +} + +fun foo4(a: Int) { + var b = a + b = b + 1 + return b - 1 +} diff --git a/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt.after b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt.after new file mode 100644 index 00000000000..5b34f7a97e1 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt.after @@ -0,0 +1,35 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + val (b, c) = pair(a) + return b*c +} + +private fun pair(a: Int): Pair { + val b = a + 1 + val c = a - 1 + return Pair(b, c) +} + +fun foo1(a: Int) { + var (x, y) = pair(a) + println(x + y) +} + +fun foo2(): Int { + var p: Int = 1 + var q: Int + val pair = pair(p) + p = pair.first + q = pair.second + return p + q +} + +fun foo4(a: Int) { + var b = a + b = b + 1 + return b - 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt new file mode 100644 index 00000000000..45ddeecf7c9 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: val z: kotlin.Int defined in test + +// SIBLING: +fun test(): () -> Int { + val z = 1 + return { + println(z) + z + 1 + } +} + +fun foo1(a: Int): Int { + val t = println(a) + return a + 1 +} + +fun foo2(a: Int) { + println(a) + a + 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt.after b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt.after new file mode 100644 index 00000000000..8f62f66b5bd --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt.after @@ -0,0 +1,25 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: val z: kotlin.Int defined in test + +// SIBLING: +fun test(): () -> Int { + val z = 1 + return { + i(z) + } +} + +private fun i(z: Int): Int { + println(z) + return z + 1 +} + +fun foo1(a: Int): Int { + val t = println(a) + return a + 1 +} + +fun foo2(a: Int) { + i(a) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt new file mode 100644 index 00000000000..7965dbc61d1 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt @@ -0,0 +1,41 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + println(a) + return a + 1 +} + +fun foo1(): Int { + val x = 1 + println(x) + val y = x + 1 + return y +} + +fun foo2(): () -> Int { + val z = 1 + return { + println(z) + z + 1 + } +} + +fun foo3() { + var t = 1 + println(t) + t = t + 1 + println(t) +} + +fun foo4(a: Int): Int { + val t = println(a) + return a + 1 +} + +fun foo5(a: Int) { + println(a) + a + 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt.after b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt.after new file mode 100644 index 00000000000..c9e53e2cd13 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt.after @@ -0,0 +1,41 @@ +// WITH_RUNTIME +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test + +// SIBLING: +fun test(a: Int): Int { + return i(a) +} + +private fun i(a: Int): Int { + println(a) + return a + 1 +} + +fun foo1(): Int { + val x = 1 + val y = i(x) + return y +} + +fun foo2(): () -> Int { + val z = 1 + return { + i(z) + } +} + +fun foo3() { + var t = 1 + t = i(t) + println(t) +} + +fun foo4(a: Int): Int { + val t = println(a) + return a + 1 +} + +fun foo5(a: Int) { + i(a) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt new file mode 100644 index 00000000000..67c9d7058a4 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt @@ -0,0 +1,25 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo + +// SIBLING: +fun foo(a: Int, b: Int): Int { + return a + b*a + 1 +} + +fun bar() { + fun f() = 1 + + val a = 1 + val b = 2 + val c = 3 + + c + b*c + b + a*b + a plus a*a + a + a*b + a + c + f() + a*f() + f() + f().times(f()) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt.after b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt.after new file mode 100644 index 00000000000..5fa9b70aa64 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt.after @@ -0,0 +1,29 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo + +// SIBLING: +fun foo(a: Int, b: Int): Int { + return i(a, b) + 1 +} + +private fun i(a: Int, b: Int): Int { + return a + b * a +} + +fun bar() { + fun f() = 1 + + val a = 1 + val b = 2 + val c = 3 + + i(c, b) + i(b, a) + i(a, a) + a + a*b + a + c + i(f(), a) + i(f(), f()) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java index fb0e14e6237..cdac35c08ce 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 @@ -175,7 +175,8 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { @TestMetadata("OccurrencesInStringTemplate.kt") public void testOccurrencesInStringTemplate() throws Exception { - doIntroduceVariableTest("idea/testData/refactoring/introduceVariable/OccurrencesInStringTemplate.kt"); + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/OccurrencesInStringTemplate.kt"); + doIntroduceVariableTest(fileName); } @TestMetadata("OneExplicitReceiver.kt") @@ -216,7 +217,8 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { @TestMetadata("UnresolvedOccurrences.kt") public void testUnresolvedOccurrences() throws Exception { - doIntroduceVariableTest("idea/testData/refactoring/introduceVariable/UnresolvedOccurrences.kt"); + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/UnresolvedOccurrences.kt"); + doIntroduceVariableTest(fileName); } @TestMetadata("WhenAddBlock.kt") @@ -259,7 +261,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { @TestMetadata("idea/testData/refactoring/extractFunction") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({ExtractFunction.AsProperty.class, ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class}) + @InnerTestClasses({ExtractFunction.AsProperty.class, ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Duplicates.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class}) @RunWith(org.jetbrains.jet.JUnit3RunnerWithInners.class) public static class ExtractFunction extends AbstractJetExtractionTest { public void testAllFilesPresentInExtractFunction() throws Exception { @@ -1051,6 +1053,58 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { } + @TestMetadata("idea/testData/refactoring/extractFunction/duplicates") + @TestDataPath("$PROJECT_ROOT") + @RunWith(org.jetbrains.jet.JUnit3RunnerWithInners.class) + public static class Duplicates extends AbstractJetExtractionTest { + public void testAllFilesPresentInDuplicates() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction/duplicates"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("branchingMatch1.kt") + public void testBranchingMatch1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("branchingMatch2.kt") + public void testBranchingMatch2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("defaultCF.kt") + public void testDefaultCF() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("multipleOutputValuesMatching.kt") + public void testMultipleOutputValuesMatching() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("outputValueAndUnitMatching.kt") + public void testOutputValueAndUnitMatching() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("outputValueMatching.kt") + public void testOutputValueMatching() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("singleExpression.kt") + public void testSingleExpression() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt"); + doExtractFunctionTest(fileName); + } + + } + @TestMetadata("idea/testData/refactoring/extractFunction/initializers") @TestDataPath("$PROJECT_ROOT") @InnerTestClasses({Initializers.Accessors.class, Initializers.Classes.class, Initializers.Functions.class, Initializers.Properties.class})