diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties
index cf25fb161d79..d89a19c92cea 100644
--- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties
+++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties
@@ -2649,4 +2649,5 @@ progress.title.searching.for.expected.actual=Searching for expect/actual\u2026
button.rename.current=Rename Current
button.rename.base=Rename Base
-progress.title.calculate.occurrences=Calculate occurrences\u2026
\ No newline at end of file
+progress.title.calculate.occurrences=Calculate occurrences\u2026
+progress.title.check.for.conflicts=Check for conflicts\u2026
\ No newline at end of file
diff --git a/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt b/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt
index bfeaf285ab42..bf0904cca9ec 100644
--- a/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt
+++ b/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt
@@ -11,5 +11,8 @@ object FqNames {
val OLD_USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
val OPT_IN_FQ_NAMES = setOf(OLD_USE_EXPERIMENTAL_FQ_NAME, OptInNames.OPT_IN_FQ_NAME)
+
+ fun isRequiresOptInFqName(fqName: FqName?) : Boolean =
+ fqName == OptInNames.REQUIRES_OPT_IN_FQ_NAME || fqName == OLD_EXPERIMENTAL_FQ_NAME
}
}
\ No newline at end of file
diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java
index 5a8e92cfef79..2897e597e78a 100644
--- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java
+++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java
@@ -2657,6 +2657,11 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
runTest("../../../idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/assignment.kt");
}
+ @TestMetadata("block.kt")
+ public void testBlock() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt");
+ }
+
@TestMetadata("constructorWithEmptyBody.kt")
public void testConstructorWithEmptyBody() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/constructorWithEmptyBody.kt");
diff --git a/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml b/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml
index 1ac95efc78f9..733b54f37c1e 100644
--- a/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml
+++ b/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml
@@ -47,5 +47,6 @@
+
\ No newline at end of file
diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt
index b621afb886f2..f9b64a97e6a0 100644
--- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt
+++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix
+import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.convertInfixCallToOrdinary
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -53,21 +54,4 @@ internal class InfixCallToOrdinaryInspection : KotlinApplicableInspectionBase.Si
context(KtAnalysisSession)
override fun prepareContext(element: KtBinaryExpression) {
}
-}
-
-fun convertInfixCallToOrdinary(element: KtBinaryExpression): KtExpression {
- val argument = KtPsiUtil.safeDeparenthesize(element.right!!)
- val pattern = "$0.$1" + when (argument) {
- is KtLambdaExpression -> " $2:'{}'"
- else -> "($2)"
- }
-
- val replacement = KtPsiFactory(element.project).createExpressionByPattern(
- pattern,
- element.left!!,
- element.operationReference,
- argument
- )
-
- return element.replace(replacement) as KtExpression
}
\ No newline at end of file
diff --git a/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt b/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt
index 6072c4e6b827..e7ce1faf1cb1 100644
--- a/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt
+++ b/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMultiplatformSymbol
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
-import org.jetbrains.kotlin.idea.base.util.names.FqNames
+import org.jetbrains.kotlin.idea.base.util.names.FqNames.OptInFqNames.isRequiresOptInFqName
import org.jetbrains.kotlin.idea.core.TemplateKind
import org.jetbrains.kotlin.idea.core.getFunctionBodyTextFromTemplate
import org.jetbrains.kotlin.idea.j2k.IdeaDocCommentConverter
@@ -35,7 +35,6 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.findDocComment.findDocComment
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
import org.jetbrains.kotlin.renderer.render
-import org.jetbrains.kotlin.resolve.checkers.OptInNames
import org.jetbrains.kotlin.types.Variance
import javax.swing.Icon
@@ -249,8 +248,7 @@ private fun keepAnnotation(annotation: KtAnnotationApplication, file: KtFile?):
context(KtAnalysisSession)
private fun KtClassOrObjectSymbol.hasRequiresOptInAnnotation(): Boolean = annotations.any { annotation ->
- val fqName = annotation.classId?.asSingleFqName()
- fqName == OptInNames.REQUIRES_OPT_IN_FQ_NAME || fqName == FqNames.OptInFqNames.OLD_EXPERIMENTAL_FQ_NAME
+ isRequiresOptInFqName(annotation.classId?.asSingleFqName())
}
context(KtAnalysisSession)
diff --git a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt
index e4623806e819..670fd9cb5c34 100644
--- a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt
+++ b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt
@@ -20,8 +20,11 @@ fun KtDeclarationWithBody.isConvertableToExpressionBody(): Boolean {
// Check if either property accessor or named function
if (this !is KtNamedFunction && this !is KtPropertyAccessor) return false
- // Check if function has block with single non-empty KtReturnExpression
- val returnedExpression = bodyBlockExpression?.statements?.singleOrNull() ?: return true
+ // Check if function has block with single non-empty KtReturnExpression or the block is empty
+ val statements = bodyBlockExpression?.statements ?: return true
+ if (statements.isEmpty()) return true
+
+ val returnedExpression = statements.singleOrNull() ?: return false
// Check if the returnedExpression actually always returns (early return is possible)
// TODO: take into consideration other cases (???)
diff --git a/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt b/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt
index 96797fd77ad5..e7970982c9c6 100644
--- a/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt
+++ b/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt
@@ -208,22 +208,6 @@ private fun FunctionDescriptor.allowsMoveOfLastParameterOutsideParentheses(
return movableParametersOfCandidateCount == lambdaAndCallableReferencesInOriginalCallCount
}
-fun KtBlockExpression.appendElement(element: KtElement, addNewLine: Boolean = false): KtElement {
- val rBrace = rBrace
- val newLine = KtPsiFactory(project).createNewLine()
- val anchor = if (rBrace == null) {
- val lastChild = lastChild
- lastChild as? PsiWhiteSpace ?: addAfter(newLine, lastChild)!!
- } else {
- rBrace.prevSibling!!
- }
- val addedElement = addAfter(element, anchor)!! as KtElement
- if (addNewLine) {
- addAfter(newLine, addedElement)
- }
- return addedElement
-}
-
//TODO: git rid of this method
fun PsiElement.deleteElementAndCleanParent() {
val parent = parent
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt
index 735d552b7571..722896aebc60 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.*
+import org.jetbrains.kotlin.idea.refactoring.appendElement
import org.jetbrains.kotlin.idea.resolve.languageVersionSettings
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt
index a77b471ad963..3d80c94e9c54 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt
@@ -23,9 +23,9 @@ import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.shorten.runRefactoringAndKeepDelayedRequests
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
-import org.jetbrains.kotlin.idea.core.appendElement
import org.jetbrains.kotlin.idea.core.getOrCreateBody
import org.jetbrains.kotlin.idea.refactoring.CompositeRefactoringRunner
+import org.jetbrains.kotlin.idea.refactoring.appendElement
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
import org.jetbrains.kotlin.idea.util.getDefaultInitializer
import org.jetbrains.kotlin.psi.*
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt
index 5ff4a3d66a14..b478e284d49d 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt
@@ -2,40 +2,25 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction
-import com.intellij.codeInsight.template.impl.TemplateManagerImpl
-import com.intellij.injected.editor.EditorWindow
import com.intellij.java.refactoring.JavaRefactoringBundle
-import com.intellij.openapi.command.WriteCommandAction
-import com.intellij.openapi.command.impl.FinishMarkAction
-import com.intellij.openapi.command.impl.StartMarkAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
-import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiFile
-import com.intellij.psi.SmartPointerManager
import com.intellij.psi.util.PsiTreeUtil
-import com.intellij.refactoring.RefactoringBundle
-import com.intellij.refactoring.extractMethod.newImpl.inplace.EditorState
-import com.intellij.refactoring.extractMethod.newImpl.inplace.ExtractMethodTemplateBuilder
-import com.intellij.refactoring.extractMethod.newImpl.inplace.InplaceExtractUtils
-import com.intellij.refactoring.extractMethod.newImpl.inplace.TemplateField
-import org.jetbrains.annotations.Nls
+import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange
import org.jetbrains.kotlin.idea.base.psi.unifier.toRange
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.refactoring.KotlinNamesValidator
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog
-import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.util.nonBlocking
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class ExtractKotlinFunctionHandler(
@@ -62,7 +47,7 @@ class ExtractKotlinFunctionHandler(
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
onFinish: (ExtractionResult) -> Unit
) {
- fun afterFinish(extraction: ExtractionResult){
+ fun afterFinish(extraction: ExtractionResult) {
processDuplicates(extraction.duplicateReplacers, project, editor)
onFinish(extraction)
}
@@ -72,102 +57,40 @@ class ExtractKotlinFunctionHandler(
}
}
- open class InplaceExtractionHelper(private val allContainersEnabled: Boolean) : ExtractionEngineHelper(EXTRACT_FUNCTION) {
+ open class InplaceExtractionHelper(
+ val allContainersEnabled: Boolean
+ ) : ExtractionEngineHelper(EXTRACT_FUNCTION),
+ AbstractInplaceExtractionHelper {
override fun configureAndRun(
project: Project,
editor: Editor,
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
onFinish: (ExtractionResult) -> Unit
) {
- val activeTemplateState = TemplateManagerImpl.getTemplateState(editor)
- if (activeTemplateState != null) {
- activeTemplateState.gotoEnd(true)
- ExtractKotlinFunctionHandler(allContainersEnabled, InteractiveExtractionHelper)
- .invoke(project, editor, descriptorWithConflicts.descriptor.extractionData.originalFile, null)
- }
- val suggestedNames = descriptorWithConflicts.descriptor.suggestedNames.takeIf { it.isNotEmpty() } ?: listOf("extracted")
- val descriptor = descriptorWithConflicts.descriptor.copy(suggestedNames = suggestedNames)
- val elements = descriptor.extractionData.originalElements
- val file = descriptor.extractionData.originalFile
- val callTextRange = TextRange(rangeOf(elements.first()).startOffset, rangeOf(elements.last()).endOffset)
-
- val commonParent = descriptor.extractionData.commonParent
- val container = commonParent.takeIf { commonParent != elements.firstOrNull() } ?: commonParent.parent
- val callRangeProvider: () -> TextRange? = createSmartRangeProvider(container, callTextRange)
- val editorState = EditorState(project, editor)
- val disposable = Disposer.newDisposable()
- WriteCommandAction.writeCommandAction(project).run {
- val startMarkAction = StartMarkAction.start(editor, project, EXTRACT_FUNCTION)
- Disposer.register(disposable) { FinishMarkAction.finish(project, editor, startMarkAction) }
- }
- fun afterFinish(extraction: ExtractionResult) {
- // Templates do not work well in injected editors, see InlayModelWindow
- if (editor is EditorWindow) {
- Disposer.dispose(disposable)
- return
- }
- val callRange: TextRange = callRangeProvider.invoke() ?: throw IllegalStateException()
- val callIdentifier = findSingleCallExpression(file, callRange)?.calleeExpression ?: throw IllegalStateException()
- val methodIdentifier = extraction.declaration.nameIdentifier ?: throw IllegalStateException()
- val methodRange = extraction.declaration.textRange
- val methodOffset = extraction.declaration.navigationElement.textRange.endOffset
- val callOffset = callIdentifier.textRange.endOffset
- val preview = InplaceExtractUtils.createPreview(editor, methodRange, methodOffset, callRange, callOffset)
- Disposer.register(disposable, preview)
- val templateField = TemplateField(callIdentifier.textRange, listOf(methodIdentifier.textRange))
- .withCompletionNames(descriptor.suggestedNames)
- .withCompletionHint(getDialogAdvertisement())
- .withValidation { variableRange ->
- val error = getIdentifierError(file, variableRange)
- if (error != null) {
- InplaceExtractUtils.showErrorHint(editor, variableRange.endOffset, error)
- }
- error == null
- }
- ExtractMethodTemplateBuilder(editor, EXTRACT_FUNCTION)
- .enableRestartForHandler(ExtractKotlinFunctionHandler::class.java)
- .onBroken {
- editorState.revert()
- }
- .onSuccess {
- processDuplicates(extraction.duplicateReplacers, file.project, editor)
- }
- .disposeWithTemplate(disposable)
- .createTemplate(file, listOf(templateField))
- onFinish(extraction)
- }
- try {
- val configuration = ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT)
- doRefactor(configuration, ::afterFinish)
- } catch (e: Throwable) {
- Disposer.dispose(disposable)
- throw e
- }
+ super.configureAndRun(project, editor, descriptorWithConflicts, onFinish)
}
- @Nls
- private fun getDialogAdvertisement(): String {
- val shortcut = KeymapUtil.getPrimaryShortcut("ExtractFunction") ?: throw IllegalStateException("Action is not found")
- return RefactoringBundle.message("inplace.refactoring.advertisement.text", KeymapUtil.getShortcutText(shortcut))
+ override fun createRestartHandler(): AbstractExtractKotlinFunctionHandler = ExtractKotlinFunctionHandler(
+ allContainersEnabled, InteractiveExtractionHelper
+ )
+
+ override fun extractDuplicates(
+ duplicateReplacers: Map Unit>,
+ project: Project,
+ editor: Editor
+ ) {
+ processDuplicates(duplicateReplacers, project, editor)
}
- protected fun rangeOf(element: PsiElement): TextRange {
- return (element as? KtExpression)?.extractableSubstringInfo?.contentRange ?: element.textRange
+ override fun doRefactor(descriptor: IExtractableCodeDescriptor, onFinish: (ExtractionResult) -> Unit) {
+ val configuration = ExtractionGeneratorConfiguration(descriptor as ExtractableCodeDescriptor, ExtractionGeneratorOptions.DEFAULT)
+ doRefactor(configuration, onFinish)
}
- protected fun createSmartRangeProvider(container: PsiElement, range: TextRange): () -> TextRange? {
- val offsetFromStart = range.startOffset - container.textRange.startOffset
- val offsetFromEnd = container.textRange.endOffset - range.endOffset
- val pointer = SmartPointerManager.createPointer(container)
- fun findRange(): TextRange? {
- val containerRange = pointer.range ?: return null
- return TextRange(containerRange.startOffset + offsetFromStart, containerRange.endOffset - offsetFromEnd)
- }
- return ::findRange
- }
-
- @Nls
- private fun getIdentifierError(file: PsiFile, variableRange: TextRange): String? {
+ override fun getIdentifierError(
+ file: KtFile,
+ variableRange: TextRange
+ ): String? {
val call = PsiTreeUtil.findElementOfClassAtOffset(file, variableRange.startOffset, KtCallExpression::class.java, false)
val name = file.viewProvider.document.getText(variableRange)
return if (! KotlinNamesValidator().isIdentifier(name, file.project)) {
@@ -178,13 +101,6 @@ class ExtractKotlinFunctionHandler(
null
}
}
-
- protected fun findSingleCallExpression(file: KtFile, range: TextRange?): KtCallExpression? {
- if (range == null) return null
- val container = PsiTreeUtil.findCommonParent(file.findElementAt(range.startOffset), file.findElementAt(range.endOffset))
- val callExpressions = PsiTreeUtil.findChildrenOfType(container, KtCallExpression::class.java)
- return callExpressions.singleOrNull { it.textRange in range }
- }
}
override fun doInvoke(
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt
index 782f5a19c5c7..b79ace9bbb29 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt
@@ -30,7 +30,10 @@ open class ExtractFunctionParameterTablePanel : AbstractParameterTablePanel conflicts = ((ExtractableCodeDescriptorWithConflicts) result).getConflicts();
conflicts.values().removeAll(originalDescriptor.getConflicts().values());
- KotlinRefactoringUtilKt.checkConflictsInteractively(
+ checkConflictsInteractively(
project,
conflicts,
new Function0<>() {
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt
index e66a39e3cf46..e6b397745bb0 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt
@@ -2,319 +2,33 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
-import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.annotations.ApiStatus
-import org.jetbrains.annotations.Nls
-import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
-import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange
-import org.jetbrains.kotlin.idea.base.psi.replaced
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.*
-import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode
-import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
-import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
-import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.DescriptorUtils
-import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
-import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.types.typeUtil.nullability
import org.jetbrains.kotlin.utils.IDEAPlatforms
import org.jetbrains.kotlin.utils.IDEAPluginsCompatibilityAPI
-import java.util.*
-interface Parameter {
- val argumentText: String
+interface Parameter: IParameter {
val originalDescriptor: DeclarationDescriptor
- val name: String
- val mirrorVarName: String?
- val receiverCandidate: Boolean
-
- val parameterType: KotlinType
-
- fun getParameterTypeCandidates(): List
-
- fun copy(name: String, parameterType: KotlinType): Parameter
}
-val Parameter.nameForRef: String get() = mirrorVarName ?: name
-
-data class TypeParameter(
- val originalDeclaration: KtTypeParameter,
- val originalConstraints: List
-)
-
-interface Replacement : Function2
-
-interface ParameterReplacement : Replacement {
- val parameter: Parameter
- fun copy(parameter: Parameter): ParameterReplacement
-}
-
-class RenameReplacement(override val parameter: Parameter) : ParameterReplacement {
- override fun copy(parameter: Parameter) = RenameReplacement(parameter)
-
- override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
- val expressionToReplace = (e.parent as? KtThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it }
- val parameterName = KtPsiUtil.unquoteIdentifier(parameter.nameForRef)
- val replacingName =
- if (e.text.startsWith('`') || !parameterName.isIdentifier()) "`$parameterName`" else parameterName
- val psiFactory = KtPsiFactory(e.project)
- val replacement = when {
- parameter == descriptor.receiverParameter -> psiFactory.createExpression("this")
- expressionToReplace is KtOperationReferenceExpression -> psiFactory.createOperationName(replacingName)
- else -> psiFactory.createSimpleName(replacingName)
- }
- return expressionToReplace.replaced(replacement)
- }
-}
-
-abstract class WrapInWithReplacement : Replacement {
- abstract val argumentText: String
-
- override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
- val call = (e as? KtSimpleNameExpression)?.getQualifiedElement() ?: return e
- val replacingExpression = KtPsiFactory(e.project).createExpressionByPattern("with($0) { $1 }", argumentText, call)
- val replace = call.replace(replacingExpression)
- return (replace as KtCallExpression).lambdaArguments.first().getLambdaExpression()!!.bodyExpression!!.statements.first()
- }
-}
-
-class WrapParameterInWithReplacement(override val parameter: Parameter) : WrapInWithReplacement(), ParameterReplacement {
- override val argumentText: String
- get() = parameter.name
-
- override fun copy(parameter: Parameter) = WrapParameterInWithReplacement(parameter)
-}
-
-class WrapObjectInWithReplacement(val descriptor: ClassDescriptor) : WrapInWithReplacement() {
- override val argumentText: String
- get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor)
-}
-
-class AddPrefixReplacement(override val parameter: Parameter) : ParameterReplacement {
- override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter)
-
- override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
- if (descriptor.receiverParameter == parameter) return e
-
- val selector = (e.parent as? KtCallExpression) ?: e
- val replacingExpression = KtPsiFactory(e.project).createExpressionByPattern("${parameter.nameForRef}.$0", selector)
- val newExpr = (selector.replace(replacingExpression) as KtQualifiedExpression).selectorExpression!!
- return (newExpr as? KtCallExpression)?.calleeExpression ?: newExpr
- }
-}
-
-class FqNameReplacement(val fqName: FqName) : Replacement {
- override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
- val thisExpr = e.parent as? KtThisExpression
- if (thisExpr != null) {
- return thisExpr.replaced(KtPsiFactory(e.project).createExpression(fqName.asString())).getQualifiedElementSelector()!!
- }
-
- val newExpr = (e as? KtSimpleNameExpression)?.mainReference?.bindToFqName(fqName, ShorteningMode.NO_SHORTENING) as KtElement
- return if (newExpr is KtQualifiedExpression) newExpr.selectorExpression!! else newExpr
- }
-}
-
-interface OutputValue {
- val originalExpressions: List
- val valueType: KotlinType
-
- class ExpressionValue(
- val callSiteReturn: Boolean,
- override val originalExpressions: List,
- override val valueType: KotlinType
- ) : OutputValue
-
- class Jump(
- val elementsToReplace: List,
- val elementToInsertAfterCall: KtElement?,
- val conditional: Boolean,
- builtIns: KotlinBuiltIns
- ) : OutputValue {
- override val originalExpressions: List get() = elementsToReplace
- override val valueType: KotlinType = with(builtIns) { if (conditional) booleanType else unitType }
- }
-
- class ParameterUpdate(
- val parameter: Parameter,
- override val originalExpressions: List
- ) : OutputValue {
- override val valueType: KotlinType get() = parameter.parameterType
- }
-
- class Initializer(
- val initializedDeclaration: KtProperty,
- override val valueType: KotlinType
- ) : OutputValue {
- override val originalExpressions: List get() = Collections.singletonList(initializedDeclaration)
- }
-}
-
-abstract class OutputValueBoxer(val outputValues: List) {
- val outputValueTypes: List get() = outputValues.map { it.valueType }
-
- abstract val returnType: KotlinType
-
- protected abstract fun getBoxingExpressionPattern(arguments: List): String?
-
- abstract val boxingRequired: Boolean
-
- fun getReturnExpression(arguments: List, psiFactory: KtPsiFactory): KtReturnExpression? {
- val expressionPattern = getBoxingExpressionPattern(arguments) ?: return null
- return psiFactory.createExpressionByPattern("return $expressionPattern", *arguments.toTypedArray()) as KtReturnExpression
- }
-
- protected abstract fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression?
-
- protected fun extractArgumentExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? {
- val call: KtCallExpression? = when (boxedExpression) {
- is KtCallExpression -> boxedExpression
- is KtQualifiedExpression -> boxedExpression.selectorExpression as? KtCallExpression
- else -> null
- }
- val arguments = call?.valueArguments
- if (arguments == null || arguments.size <= index) return null
-
- return arguments[index].getArgumentExpression()
- }
-
- fun extractExpressionByValue(boxedExpression: KtExpression, value: OutputValue): KtExpression? {
- 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) {
- init {
- assert(outputValues.size <= 3) { "At most 3 output values are supported" }
- }
-
- companion object {
- private val selectors = arrayOf("first", "second", "third")
- }
-
- override val returnType: KotlinType by lazy {
- fun getType(): KotlinType {
- val boxingClass = when (outputValues.size) {
- 1 -> return outputValues.first().valueType
- 2 -> module.resolveTopLevelClass(FqName("kotlin.Pair"), NoLookupLocation.FROM_IDE)!!
- 3 -> module.resolveTopLevelClass(FqName("kotlin.Triple"), NoLookupLocation.FROM_IDE)!!
- else -> return module.builtIns.defaultReturnType
- }
- return TypeUtils.substituteParameters(boxingClass, outputValueTypes)
- }
-
- getType()
- }
-
- override val boxingRequired: Boolean = outputValues.size > 1
-
- override fun getBoxingExpressionPattern(arguments: List): String? {
- return when (arguments.size) {
- 0 -> null
- 1 -> "$0"
- else -> {
- val constructorName = DescriptorUtils.getFqName(returnType.constructor.declarationDescriptor!!).asString()
- return arguments.indices.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")") { "\$$it" }
- }
- }
- }
-
- override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? {
- 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: KotlinType by lazy {
- assert(outputValues.isNotEmpty())
- val builtIns = outputValues.first().valueType.builtIns
- TypeUtils.substituteParameters(
- builtIns.list,
- Collections.singletonList(CommonSupertypes.commonSupertype(outputValues.map { it.valueType }))
- )
- }
-
- override val boxingRequired: Boolean = outputValues.isNotEmpty()
-
- override fun getBoxingExpressionPattern(arguments: List): String? {
- if (arguments.isEmpty()) return null
- return arguments.indices.joinToString(prefix = "kotlin.collections.listOf(", separator = ", ", postfix = ")") { "\$$it" }
- }
-
- override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? {
- return extractArgumentExpressionByIndex(boxedExpression, index)
- }
-
- override fun getUnboxingExpressions(boxedText: String): Map {
- var i = 0
- return ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText[${i++}]" }
- }
- }
-}
-
-data class ControlFlow(
- val outputValues: List,
- val boxerFactory: (List) -> OutputValueBoxer,
- val declarationsToCopy: List
-) {
- val outputValueBoxer = boxerFactory(outputValues)
-
- val defaultOutputValue: ExpressionValue? = with(outputValues.filterIsInstance()) {
- if (size > 1) throw IllegalArgumentException("Multiple expression values: ${outputValues.joinToString()}") else firstOrNull()
- }
-
- val jumpOutputValue: Jump? = with(outputValues.filterIsInstance()) {
- val jumpCount = size
- when {
- isEmpty() ->
- null
- outputValues.size > jumpCount || jumpCount > 1 ->
- throw IllegalArgumentException("Jump values must be the only value if it's present: ${outputValues.joinToString()}")
- else ->
- first()
- }
- }
-}
-
-val ControlFlow.possibleReturnTypes: List
+val ControlFlow.possibleReturnTypes: List
get() {
val returnType = outputValueBoxer.returnType
return when {
@@ -327,33 +41,46 @@ val ControlFlow.possibleReturnTypes: List
}
}
-fun ControlFlow.toDefault(): ControlFlow =
- copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue })
-fun ControlFlow.copy(oldToNewParameters: Map): ControlFlow {
+fun ControlFlow.copy(oldToNewParameters: Map): ControlFlow {
val newOutputValues = outputValues.map {
if (it is ParameterUpdate) ParameterUpdate(oldToNewParameters[it.parameter]!!, it.originalExpressions) else it
}
return copy(outputValues = newOutputValues)
}
+
+class WrapObjectInWithReplacement(val descriptor: ClassDescriptor) : WrapInWithReplacement() {
+ override val argumentText: String
+ get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor)
+}
+
data class ExtractableCodeDescriptor(
- val extractionData: ExtractionData,
+ override val extractionData: ExtractionData,
val originalContext: BindingContext,
- val suggestedNames: List,
- val visibility: KtModifierKeywordToken?,
- val parameters: List,
- val receiverParameter: Parameter?,
- val typeParameters: List,
- val replacementMap: MultiMap,
- val controlFlow: ControlFlow,
- val returnType: KotlinType,
- val modifiers: List = emptyList(),
+ override val suggestedNames: List,
+ override val visibility: KtModifierKeywordToken?,
+ override val parameters: List,
+ override val receiverParameter: Parameter?,
+ override val typeParameters: List,
+ override val replacementMap: MultiMap>,
+ override val controlFlow: ControlFlow,
+ override val returnType: KotlinType,
+ override val modifiers: List = emptyList(),
val annotations: List = emptyList(),
- val optInMarkers: List = emptyList()
-) {
- val name: String get() = suggestedNames.firstOrNull() ?: ""
- val duplicates: List by lazy { findDuplicates() }
+ override val optInMarkers: List = emptyList()
+): IExtractableCodeDescriptor {
+ override val name: String get() = suggestedNames.firstOrNull() ?: ""
+ override val duplicates: List> by lazy { findDuplicates() }
+ override fun isUnitReturnType(): Boolean {
+ return returnType.isUnit()
+ }
+
+ override val annotationsText: String
+ get() {
+ if (annotations.isEmpty()) return ""
+ return annotations.map { IdeDescriptorRenderers.SOURCE_CODE.renderAnnotation(it) }.joinToString("\n", postfix = "\n")
+ }
}
/**
@@ -382,18 +109,18 @@ fun ExtractableCodeDescriptor.withVisibility(
plugins = "Jetpack Compose plugin in IDEA"
)
fun ExtractableCodeDescriptor.copy(
- extractionData: ExtractionData = this.extractionData,
- originalContext: BindingContext = this.originalContext,
- suggestedNames: List = this.suggestedNames,
- visibility: KtModifierKeywordToken? = this.visibility,
- parameters: List = this.parameters,
- receiverParameter: Parameter? = this.receiverParameter,
- typeParameters: List = this.typeParameters,
- replacementMap: MultiMap = this.replacementMap,
- controlFlow: ControlFlow = this.controlFlow,
- returnType: KotlinType = this.returnType,
- modifiers: List = this.modifiers,
- annotations: List = this.annotations
+ extractionData: ExtractionData = this.extractionData,
+ originalContext: BindingContext = this.originalContext,
+ suggestedNames: List = this.suggestedNames,
+ visibility: KtModifierKeywordToken? = this.visibility,
+ parameters: List = this.parameters,
+ receiverParameter: Parameter? = this.receiverParameter,
+ typeParameters: List = this.typeParameters,
+ replacementMap: MultiMap> = this.replacementMap,
+ controlFlow: ControlFlow = this.controlFlow,
+ returnType: KotlinType = this.returnType,
+ modifiers: List = this.modifiers,
+ annotations: List = this.annotations
) = copy(
extractionData,
originalContext,
@@ -417,7 +144,7 @@ fun ExtractableCodeDescriptor.copy(
newReceiver: Parameter?,
returnType: KotlinType?
): ExtractableCodeDescriptor {
- val newReplacementMap = MultiMap.create()
+ val newReplacementMap = MultiMap.create>()
for ((ref, replacements) in replacementMap.entrySet()) {
val newReplacements = replacements.map {
if (it is ParameterReplacement) {
@@ -446,165 +173,20 @@ fun ExtractableCodeDescriptor.copy(
)
}
-enum class ExtractionTarget(val targetName: String) {
- FUNCTION(KotlinBundle.message("text.function")) {
- override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true
- },
-
- FAKE_LAMBDALIKE_FUNCTION(KotlinBundle.message("text.lambda.parameter")) {
- override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
- return checkSimpleControlFlow(descriptor) || descriptor.controlFlow.outputValues.isEmpty()
- }
- },
-
- PROPERTY_WITH_INITIALIZER(KotlinBundle.message("text.property.with.initializer")) {
- override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
- return checkSignatureAndParent(descriptor)
- && checkSimpleControlFlow(descriptor)
- && checkSimpleBody(descriptor)
- && checkNotTrait(descriptor)
- && descriptor.receiverParameter == null
- }
- },
-
- PROPERTY_WITH_GETTER(KotlinBundle.message("text.property.with.getter")) {
- override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
- return checkSignatureAndParent(descriptor)
- }
- },
-
- LAZY_PROPERTY(KotlinBundle.message("text.lazy.property")) {
- override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
- return checkSignatureAndParent(descriptor)
- && checkSimpleControlFlow(descriptor)
- && checkNotTrait(descriptor)
- && descriptor.receiverParameter == null
- }
- };
-
- abstract fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean
-
- companion object {
- fun checkNotTrait(descriptor: ExtractableCodeDescriptor): Boolean {
- val parent = descriptor.extractionData.targetSibling.getStrictParentOfType()
- return !(parent is KtClass && parent.isInterface())
- }
-
- fun checkSimpleBody(descriptor: ExtractableCodeDescriptor): Boolean {
- val expression = descriptor.extractionData.expressions.singleOrNull()
- return expression != null && expression !is KtDeclaration && expression !is KtBlockExpression
- }
-
- fun checkSimpleControlFlow(descriptor: ExtractableCodeDescriptor): Boolean {
- val outputValue = descriptor.controlFlow.outputValues.singleOrNull()
- return (outputValue is ExpressionValue && !outputValue.callSiteReturn) || outputValue is Initializer
- }
-
- fun checkSignatureAndParent(descriptor: ExtractableCodeDescriptor): Boolean {
- if (!descriptor.parameters.isEmpty()) return false
- if (descriptor.returnType.isUnit()) return false
-
- val parent = descriptor.extractionData.targetSibling.parent
- return (parent is KtFile || parent is KtClassBody)
- }
- }
-}
-
-val propertyTargets: List = listOf(
- ExtractionTarget.PROPERTY_WITH_INITIALIZER,
- ExtractionTarget.PROPERTY_WITH_GETTER,
- ExtractionTarget.LAZY_PROPERTY
-)
-
-data class ExtractionGeneratorOptions(
- val inTempFile: Boolean = false,
- val target: ExtractionTarget = ExtractionTarget.FUNCTION,
- val dummyName: String? = null,
- val allowExpressionBody: Boolean = true,
- val delayInitialOccurrenceReplacement: Boolean = false,
- val isConst: Boolean = false
-) {
- companion object {
- @JvmField
- val DEFAULT = ExtractionGeneratorOptions()
- }
-}
-
data class ExtractionGeneratorConfiguration(
- val descriptor: ExtractableCodeDescriptor,
- val generatorOptions: ExtractionGeneratorOptions
-)
+ override val descriptor: ExtractableCodeDescriptor,
+ override val generatorOptions: ExtractionGeneratorOptions
+): IExtractionGeneratorConfiguration
data class ExtractionResult(
- val config: ExtractionGeneratorConfiguration,
- val declaration: KtNamedDeclaration,
- val duplicateReplacers: Map Unit>
-) : Disposable {
+ override val config: ExtractionGeneratorConfiguration,
+ override val declaration: KtNamedDeclaration,
+ override val duplicateReplacers: Map Unit>
+) : IExtractionResult {
override fun dispose() = unmarkReferencesInside(declaration)
}
-class AnalysisResult(
- val descriptor: ExtractableCodeDescriptor?,
- val status: Status,
- val messages: List
-) {
- enum class Status {
- SUCCESS,
- NON_CRITICAL_ERROR,
- CRITICAL_ERROR
- }
-
- enum class ErrorMessage {
- NO_EXPRESSION,
- NO_CONTAINER,
- SYNTAX_ERRORS,
- SUPER_CALL,
- DENOTABLE_TYPES,
- ERROR_TYPES,
- MULTIPLE_OUTPUT,
- OUTPUT_AND_EXIT_POINT,
- MULTIPLE_EXIT_POINTS,
- DECLARATIONS_ARE_USED_OUTSIDE,
- DECLARATIONS_OUT_OF_SCOPE;
-
- var additionalInfo: List? = null
-
- fun addAdditionalInfo(info: List): ErrorMessage {
- additionalInfo = info
- return this
- }
-
- @Nls
- fun renderMessage(): String {
- val message = KotlinBundle.message(
- when (this) {
- NO_EXPRESSION -> "cannot.refactor.no.expression"
- NO_CONTAINER -> "cannot.refactor.no.container"
- SYNTAX_ERRORS -> "cannot.refactor.syntax.errors"
- SUPER_CALL -> "cannot.extract.super.call"
- DENOTABLE_TYPES -> "parameter.types.are.not.denotable"
- ERROR_TYPES -> "error.types.in.generated.function"
- MULTIPLE_OUTPUT -> "selected.code.fragment.has.multiple.output.values"
- OUTPUT_AND_EXIT_POINT -> "selected.code.fragment.has.output.values.and.exit.points"
- MULTIPLE_EXIT_POINTS -> "selected.code.fragment.has.multiple.exit.points"
- DECLARATIONS_ARE_USED_OUTSIDE -> "declarations.are.used.outside.of.selected.code.fragment"
- DECLARATIONS_OUT_OF_SCOPE -> "declarations.will.move.out.of.scope"
- }
- )
-
- return additionalInfo?.let { "$message\n\n${it.joinToString("\n") { msg ->
- @Suppress("HardCodedStringLiteral")
- StringUtil.htmlEmphasize(msg)
- }}" } ?: message
- }
- }
-}
-
-sealed class ExtractableCodeDescriptorWithConflictsResult
-
data class ExtractableCodeDescriptorWithConflicts(
- val descriptor: ExtractableCodeDescriptor,
- val conflicts: MultiMap
-): ExtractableCodeDescriptorWithConflictsResult()
-
-data class ExtractableCodeDescriptorWithException(val exception: RuntimeException): ExtractableCodeDescriptorWithConflictsResult()
+ override val descriptor: ExtractableCodeDescriptor,
+ override val conflicts: MultiMap
+): ExtractableCodeDescriptorWithConflictsResult(), IExtractableCodeDescriptorWithConflicts
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt
index d8a56e95e71f..6e7f776b044d 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt
@@ -2,10 +2,7 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.util.PsiTreeUtil
@@ -28,55 +25,38 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter
-import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
-import org.jetbrains.kotlin.resolve.calls.util.getImplicitReceiverValue
-import org.jetbrains.kotlin.resolve.calls.util.hasBothReceivers
import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke
+import org.jetbrains.kotlin.resolve.calls.util.getImplicitReceiverValue
+import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
+import org.jetbrains.kotlin.resolve.calls.util.hasBothReceivers
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.types.KotlinType
-data class ResolveResult(
- val originalRefExpr: KtSimpleNameExpression,
- val declaration: PsiElement,
- val descriptor: DeclarationDescriptor,
- val resolvedCall: ResolvedCall<*>?
-)
-
-data class ResolvedReferenceInfo(
- val refExpr: KtSimpleNameExpression,
- val resolveResult: ResolveResult,
- val smartCast: KotlinType?,
- val possibleTypes: Set,
- val shouldSkipPrimaryReceiver: Boolean
-)
-
-internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyablePsiUserDataProperty(Key.create("RESOLVE_RESULT"))
-
data class ExtractionData(
- val originalFile: KtFile,
- val originalRange: KotlinPsiRange,
- val targetSibling: PsiElement,
- val duplicateContainer: PsiElement? = null,
- val options: ExtractionOptions = ExtractionOptions.DEFAULT
-) : Disposable {
- val project: Project = originalFile.project
- val originalElements: List = originalRange.elements
- val physicalElements = originalElements.map { it.substringContextOrThis }
+ override val originalFile: KtFile,
+ override val originalRange: KotlinPsiRange,
+ override val targetSibling: PsiElement,
+ override val duplicateContainer: PsiElement? = null,
+ override val options: ExtractionOptions = ExtractionOptions.DEFAULT
+) : IExtractionData {
+ override val project: Project = originalFile.project
+ override val originalElements: List = originalRange.elements
+ override val physicalElements = originalElements.map { it.substringContextOrThis }
- val substringInfo: K1ExtractableSubstringInfo?
+ override val substringInfo: K1ExtractableSubstringInfo?
get() = (originalElements.singleOrNull() as? KtExpression)?.extractableSubstringInfo as? K1ExtractableSubstringInfo
- val insertBefore: Boolean = options.extractAsProperty
+ override val insertBefore: Boolean = options.extractAsProperty
|| targetSibling.getStrictParentOfType()?.let {
it is KtDeclarationWithBody || it is KtAnonymousInitializer
} ?: false
- val expressions = originalElements.filterIsInstance()
+ override val expressions: List = originalElements.filterIsInstance()
- val codeFragmentText: String by lazy {
+ override val codeFragmentText: String by lazy {
val originalElements = originalElements
when (originalElements.size) {
0 -> ""
@@ -85,15 +65,22 @@ data class ExtractionData(
}
}
- val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
+ override val commonParent: KtElement = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
- val bindingContext: BindingContext? by lazy { commonParent.analyze() }
+ val bindingContext: BindingContext by lazy { commonParent.analyze() }
private val itFakeDeclaration by lazy { KtPsiFactory(project).createParameter("it: Any?") }
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(project).createFunction("fun invoke() {}") }
init {
- markReferences()
+ encodeReferences>({ bindingContext[BindingContext.SMARTCAST, it] != null }) { physicalRef ->
+ val resolvedCall = physicalRef.getResolvedCall(bindingContext)
+ val descriptor =
+ bindingContext[BindingContext.REFERENCE_TARGET, physicalRef]
+ val declaration = descriptor?.let { getDeclaration(descriptor, bindingContext) }
+
+ declaration?.let { ResolveResult>(physicalRef, declaration, descriptor, resolvedCall) }
+ }
}
private fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean {
@@ -116,44 +103,6 @@ data class ExtractionData(
}
}
- private fun markReferences() {
- val context = bindingContext ?: return
- val visitor = object : KtTreeVisitorVoid() {
- override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
- if (context[BindingContext.SMARTCAST, expression] != null) {
- expression.selectorExpression?.accept(this)
- return
- }
-
- super.visitQualifiedExpression(expression)
- }
-
- override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) {
- if (ref.parent is KtValueArgumentName) return
-
- val physicalRef = substringInfo?.let {
- // If substring contains some references it must be extracted as a string template
- val physicalExpression = expressions.single() as KtStringTemplateExpression
- val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset
- val offsetInExtracted = ref.startOffset - extractedContentOffset
- val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted
- it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType()
- } ?: ref
-
- val resolvedCall = physicalRef.getResolvedCall(context)
- val descriptor = context[BindingContext.REFERENCE_TARGET, physicalRef] ?: return
- val declaration = getDeclaration(descriptor, context) ?: return
-
- val resolveResult = ResolveResult(physicalRef, declaration, descriptor, resolvedCall)
- physicalRef.resolveResult = resolveResult
- if (ref != physicalRef) {
- ref.resolveResult = resolveResult
- }
- }
- }
- expressions.forEach { it.accept(visitor) }
- }
-
private fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set {
val dataFlowValueFactory = expression.getResolutionFacade().dataFlowValueFactory
val dataFlowInfo = context.getDataFlowInfoAfter(expression)
@@ -171,20 +120,19 @@ data class ExtractionData(
return dataFlowInfo.getCollectedTypes(dataFlowValue, expression.languageVersionSettings)
}
- fun getBrokenReferencesInfo(body: KtBlockExpression): List {
- val originalContext = bindingContext ?: return listOf()
+ fun getBrokenReferencesInfo(body: KtBlockExpression): List, KotlinType>> {
+ val originalContext = bindingContext
val newReferences = body.collectDescendantsOfType { it.resolveResult != null }
val context = body.analyze()
- val referencesInfo = ArrayList()
+ val referencesInfo = ArrayList, KotlinType>>()
for (newRef in newReferences) {
- val originalResolveResult = newRef.resolveResult ?: continue
+ val originalResolveResult = newRef.resolveResult as? ResolveResult> ?: continue
val smartCast: KotlinType?
val possibleTypes: Set
- val shouldSkipPrimaryReceiver: Boolean
// Qualified property reference: a.b
val qualifiedExpression = newRef.getQualifiedExpressionForSelector()
@@ -194,7 +142,7 @@ data class ExtractionData(
possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext)
val receiverDescriptor =
(originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor
- shouldSkipPrimaryReceiver = smartCast == null
+ val shouldSkipPrimaryReceiver = smartCast == null
&& !DescriptorUtils.isCompanionObject(receiverDescriptor)
&& qualifiedExpression.receiverExpression !is KtSuperExpression
if (shouldSkipPrimaryReceiver && originalResolveResult.resolvedCall?.hasBothReceivers() != true) continue
@@ -202,7 +150,6 @@ data class ExtractionData(
if (newRef.getParentOfTypeAndBranch { callableReference } != null) continue
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]?.defaultType
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
- shouldSkipPrimaryReceiver = false
}
val parent = newRef.parent
@@ -238,7 +185,6 @@ data class ExtractionData(
variableResolveResult,
smartCast,
possibleTypes,
- shouldSkipPrimaryReceiver
)
)
referencesInfo.add(
@@ -247,7 +193,6 @@ data class ExtractionData(
functionResolveResult,
smartCast,
possibleTypes,
- shouldSkipPrimaryReceiver
)
)
} else {
@@ -257,7 +202,6 @@ data class ExtractionData(
originalResolveResult,
smartCast,
possibleTypes,
- shouldSkipPrimaryReceiver
)
)
}
@@ -270,11 +214,4 @@ data class ExtractionData(
override fun dispose() {
expressions.forEach(::unmarkReferencesInside)
}
-}
-
-fun unmarkReferencesInside(root: PsiElement) {
- runReadAction {
- if (!root.isValid) return@runReadAction
- root.forEachDescendantOfType { it.resolveResult = null }
- }
}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt
index db9c13e892c2..e976d5adae0a 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt
@@ -2,116 +2,23 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
-import com.intellij.openapi.actionSystem.ex.ActionUtil
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.MessageType
-import com.intellij.openapi.ui.popup.Balloon
-import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts
-import com.intellij.openapi.util.NlsSafe
-import com.intellij.refactoring.BaseRefactoringProcessor
-import com.intellij.ui.awt.RelativePoint
-import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
-import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
-import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
-import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
-import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
-import org.jetbrains.kotlin.idea.util.nonBlocking
-import org.jetbrains.kotlin.utils.addToStdlib.cast
-import org.jetbrains.kotlin.utils.addToStdlib.safeAs
-import javax.swing.event.HyperlinkEvent
+import org.jetbrains.kotlin.types.KotlinType
-abstract class ExtractionEngineHelper(@NlsContexts.DialogTitle val operationName: String) {
- open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
+abstract class ExtractionEngineHelper(@NlsContexts.DialogTitle operationName: String)
+ : IExtractionEngineHelper(operationName) {
- fun doRefactor(config: ExtractionGeneratorConfiguration, onFinish: (ExtractionResult) -> Unit = {}) {
- val project = config.descriptor.extractionData.project
- onFinish(project.executeWriteCommand(operationName) { config.generateDeclaration() })
+ override fun generateDeclaration(config: ExtractionGeneratorConfiguration): ExtractionResult {
+ return config.generateDeclaration()
}
- open fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate()
-
- abstract fun configureAndRun(
- project: Project,
- editor: Editor,
- descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
- onFinish: (ExtractionResult) -> Unit = {}
- )
+ override fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate()
}
class ExtractionEngine(
- val helper: ExtractionEngineHelper
-) {
- fun run(
- editor: Editor,
- extractionData: ExtractionData,
- onFinish: (ExtractionResult) -> Unit = {}
- ) {
- val project = extractionData.project
-
- val adjustExtractionData = helper.adjustExtractionData(extractionData)
- val analysisResult = ActionUtil.underModalProgress(project, KotlinBundle.message("progress.title.analyze.extraction.data")) {
- adjustExtractionData.performAnalysis()
- }
-
- if (isUnitTestMode() && analysisResult.status != AnalysisResult.Status.SUCCESS) {
- throw BaseRefactoringProcessor.ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
- }
-
- fun validateAndRefactor() {
- nonBlocking(project, {
- try {
- helper.validate(analysisResult.descriptor!!)
- } catch (e: RuntimeException) {
- ExtractableCodeDescriptorWithException(e)
- }
- }) { result ->
- result.safeAs()?.let { throw it.exception }
- val validationResult = result.cast()
- project.checkConflictsInteractively(validationResult.conflicts) {
- helper.configureAndRun(project, editor, validationResult) {
- try {
- onFinish(it)
- } finally {
- it.dispose()
- extractionData.dispose()
- }
- }
- }
- }
- }
-
- val message = analysisResult.messages.joinToString("\n") { it.renderMessage() }
- when (analysisResult.status) {
- AnalysisResult.Status.CRITICAL_ERROR -> {
- showErrorHint(project, editor, message, helper.operationName)
- }
-
- AnalysisResult.Status.NON_CRITICAL_ERROR -> {
- val anchorPoint = RelativePoint(
- editor.contentComponent,
- editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!)
- )
- @NlsSafe val htmlContent =
- "$message
${KotlinBundle.message("text.proceed.with.extraction")}"
- JBPopupFactory.getInstance()!!
- .createHtmlTextBalloonBuilder(
- htmlContent,
- MessageType.WARNING
- ) { event ->
- if (event?.eventType == HyperlinkEvent.EventType.ACTIVATED) {
- validateAndRefactor()
- }
- }
- .setHideOnClickOutside(true)
- .setHideOnFrameResize(false)
- .setHideOnLinkClick(true)
- .createBalloon()
- .show(anchorPoint, Balloon.Position.below)
- }
-
- AnalysisResult.Status.SUCCESS -> validateAndRefactor()
- }
+ helper: ExtractionEngineHelper
+): IExtractionEngine(helper) {
+ override fun performAnalysis(extractionData: ExtractionData): AnalysisResult {
+ return extractionData.performAnalysis()
}
}
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt
index eacb7a4e05aa..8218486ef9ee 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt
@@ -4,10 +4,10 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
-import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.cfg.containingDeclarationForPseudocode
import org.jetbrains.kotlin.cfg.pseudocode.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
-import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider
import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester
@@ -36,15 +35,11 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
-import org.jetbrains.kotlin.idea.core.OLD_EXPERIMENTAL_FQ_NAME
import org.jetbrains.kotlin.idea.core.OPT_IN_FQ_NAMES
import org.jetbrains.kotlin.idea.core.compareDescriptors
-import org.jetbrains.kotlin.idea.refactoring.createTempCopy
-import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage
-import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
-import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.*
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.*
+import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
@@ -54,17 +49,16 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement
-import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.checkers.OptInNames
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
+import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.typeUtil.makeNullable
-import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
-import org.jetbrains.kotlin.utils.DFS.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.util.*
@@ -196,10 +190,6 @@ private fun getCommonNonTrivialSuccessorIfAny(instructions: List):
return singleSuccessorCheckingVisitor.target ?: instructions.firstOrNull()?.owner?.sinkInstruction
}
-private fun KotlinType.isMeaningful(): Boolean {
- return !KotlinBuiltIns.isUnit(this) && !KotlinBuiltIns.isNothing(this)
-}
-
private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages(
pseudocode: Pseudocode,
localInstructions: List,
@@ -219,256 +209,33 @@ private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages(
return declarations.sortedBy { it.textRange!!.startOffset }
}
-private fun ExtractionData.analyzeControlFlow(
- localInstructions: List,
- pseudocode: Pseudocode,
- module: ModuleDescriptor,
- bindingContext: BindingContext,
- modifiedVarDescriptors: Map>,
- options: ExtractionOptions,
- targetScope: LexicalScope?,
- parameters: Set
-): Pair {
- val exitPoints = localInstructions.getExitPoints()
-
- val valuedReturnExits = ArrayList()
- val defaultExits = ArrayList()
- val jumpExits = ArrayList()
- exitPoints.forEach {
- val e = (it as? UnconditionalJumpInstruction)?.element
-
- when (val inst = when {
- it !is ReturnValueInstruction && it !is ReturnNoValueInstruction && it.owner != pseudocode -> null
- it is UnconditionalJumpInstruction && it.targetLabel.isJumpToError -> it
- e != null && e !is KtBreakExpression && e !is KtContinueExpression -> it.previousInstructions.firstOrNull()
- else -> it
- }) {
- is ReturnValueInstruction -> if (inst.owner == pseudocode) {
- if (inst.returnExpressionIfAny == null) {
- defaultExits.add(inst)
- } else {
- valuedReturnExits.add(inst)
- }
- }
-
- is AbstractJumpInstruction -> {
- val element = inst.element
- if ((element is KtReturnExpression && inst.owner == pseudocode)
- || element is KtBreakExpression
- || element is KtContinueExpression
- ) jumpExits.add(inst) else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) defaultExits.add(inst)
- }
-
- else -> if (inst != null && inst !is LocalFunctionDeclarationInstruction) defaultExits.add(inst)
- }
- }
-
- val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext)
- val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is KtProperty && it.isLocal }
-
- val (typeOfDefaultFlow, defaultResultExpressions) = getResultTypeAndExpressions(
- defaultExits,
- bindingContext,
- targetScope,
- options,
- module
- )
-
- val (returnValueType, valuedReturnExpressions) = getResultTypeAndExpressions(
- valuedReturnExits,
- bindingContext,
- targetScope,
- options,
- module
- )
-
- val emptyControlFlow =
- ControlFlow(Collections.emptyList(), { OutputValueBoxer.AsTuple(it, module) }, 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, defaultResultExpressions, defaultReturnType)))
- } else emptyControlFlow
-
- if (declarationsToReport.isNotEmpty()) {
- val localVarStr = declarationsToReport.map { it.renderForMessage(bindingContext)!! }.distinct().sorted()
- return controlFlow to ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE.addAdditionalInfo(localVarStr)
- }
-
- val outParameters =
- parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.originalDescriptor] != null }.sortedBy { it.nameForRef }
- val outDeclarations =
- declarationsToCopy.filter { modifiedVarDescriptors[bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]] != null }
- val modifiedValueCount = outParameters.size + outDeclarations.size
-
- val outputValues = ArrayList()
-
- 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
-
- outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow))
- } 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 as KtExpression
- return controlFlow.copy(outputValues = Collections.singletonList(Jump(listOf(element), element, true, module.builtIns))) to null
- }
-
- if (getCommonNonTrivialSuccessorIfAny(valuedReturnExits) == null) return multipleExitsError
- outputValues.add(ExpressionValue(true, valuedReturnExpressions, returnValueType))
- }
-
- outDeclarations.mapTo(outputValues) {
- val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? CallableDescriptor
- Initializer(it as KtProperty, descriptor?.returnType ?: module.builtIns.defaultParameterType)
- }
- outParameters.mapTo(outputValues) { ParameterUpdate(it, modifiedVarDescriptors[it.originalDescriptor]!!) }
-
- 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)!! }).sorted()
- return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr)
- }
- { values -> OutputValueBoxer.AsList(values) }
- }
-
- else -> controlFlow.boxerFactory
- }
-
- return controlFlow.copy(outputValues = outputValues, boxerFactory = boxerFactory) to null
- }
-
- if (jumpExits.isNotEmpty()) {
- val jumpTarget = getCommonNonTrivialSuccessorIfAny(jumpExits) ?: return multipleExitsError
-
- val singleExit = getCommonNonTrivialSuccessorIfAny(defaultExits) == jumpTarget
- val conditional = !singleExit && defaultExits.isNotEmpty()
- val elements = jumpExits.map { it.element as KtExpression }
- val elementToInsertAfterCall = if (singleExit) null else elements.first()
- return controlFlow.copy(
- outputValues = Collections.singletonList(
- Jump(
- elements,
- elementToInsertAfterCall,
- conditional,
- module.builtIns
- )
- )
- ) to null
- }
-
- return controlFlow to null
-}
-
-fun ExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclaration {
- val targetSiblingMarker = Any()
- PsiTreeUtil.mark(targetSibling, targetSiblingMarker)
- val tmpFile = originalFile.createTempCopy("")
- tmpFile.deleteChildRange(tmpFile.firstChild, tmpFile.lastChild)
- tmpFile.addRange(originalFile.firstChild, originalFile.lastChild)
- val newTargetSibling = PsiTreeUtil.releaseMark(tmpFile, targetSiblingMarker)!!
- val newTargetParent = newTargetSibling.parent
-
- val declaration = KtPsiFactory(project).createDeclarationByPattern(
- pattern,
- PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
- )
- return if (insertBefore) {
- newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration
- } else {
- newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration
- }
-}
-
-internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression {
- if (options.extractAsProperty) {
- return ((createTemporaryDeclaration("val = {\n$0\n}\n") as KtProperty).initializer as KtLambdaExpression).bodyExpression!!
- }
- return (createTemporaryDeclaration("fun() {\n$0\n}\n") as KtNamedFunction).bodyBlockExpression!!
-}
-
-private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List {
- if (!processTypeArguments) return Collections.singletonList(this)
- return dfsFromNode(
- this,
- Neighbors { current -> current.arguments.map { it.type } },
- VisitedWithSet(),
- object : CollectingNodeHandler>(ArrayList()) {
- override fun afterChildren(current: KotlinType) {
- result.add(current)
- }
- }
- )!!
-}
-
-fun KtTypeParameter.collectRelevantConstraints(): List {
- val typeConstraints = getNonStrictParentOfType()?.typeConstraints ?: return Collections.emptyList()
- return typeConstraints.filter { it.subjectTypeParameterName?.mainReference?.resolve() == this }
-}
-
-fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List {
- val typeRefs = ArrayList()
- originalDeclaration.extendsBound?.let { typeRefs.add(it) }
- originalConstraints.mapNotNullTo(typeRefs) { it.boundTypeReference }
-
- return typeRefs.mapNotNull { bindingContext[BindingContext.TYPE, it] }
-}
-
private fun KotlinType.isExtractable(targetScope: LexicalScope?): Boolean {
- return collectReferencedTypes(true).fold(true) { extractable, typeToCheck ->
- val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor
- val typeParameter = parameterTypeDescriptor?.let {
- DescriptorToSourceUtils.descriptorToDeclaration(it)
- } as? KtTypeParameter
-
- extractable && (typeParameter != null || typeToCheck.isResolvableInScope(targetScope, false))
- }
+ return processTypeIfExtractable(
+ typeParameters = mutableSetOf(),
+ nonDenotableTypes = mutableSetOf(),
+ targetScope = targetScope,
+ processTypeArguments = true
+ )
}
internal fun KotlinType.processTypeIfExtractable(
typeParameters: MutableSet,
nonDenotableTypes: MutableSet,
- options: ExtractionOptions,
targetScope: LexicalScope?,
processTypeArguments: Boolean = true
): Boolean {
- return collectReferencedTypes(processTypeArguments).fold(true) { extractable, typeToCheck ->
+ return processTypeIfExtractable(typeParameters, nonDenotableTypes, processTypeArguments, { type -> type.arguments.map { it.type } } ) { typeToCheck, typeParameters ->
val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor
val typeParameter = parameterTypeDescriptor?.let {
DescriptorToSourceUtils.descriptorToDeclaration(it)
} as? KtTypeParameter
-
when {
- typeToCheck.isResolvableInScope(targetScope, true) ->
- extractable
-
+ typeToCheck.isResolvableInScope(targetScope, true) -> true
typeParameter != null -> {
typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints()))
- extractable
- }
-
- typeToCheck.isError ->
- false
-
- else -> {
- nonDenotableTypes.add(typeToCheck)
- false
+ true
}
+ else -> false
}
}
}
@@ -480,13 +247,13 @@ internal class MutableParameter(
private val targetScope: LexicalScope?,
private val originalType: KotlinType,
private val possibleTypes: Set
-) : Parameter {
+) : Parameter, IMutableParameter {
// All modifications happen in the same thread
private var writable: Boolean = true
private val defaultTypes = LinkedHashSet()
private val typePredicates = HashSet()
- var refCount: Int = 0
+ override var refCount: Int = 0
fun addDefaultType(kotlinType: KotlinType) {
assert(writable) { "Can't add type to non-writable parameter $currentName" }
@@ -546,46 +313,10 @@ internal class MutableParameter(
override val parameterType: KotlinType
get() = getParameterTypeCandidates().firstOrNull() ?: defaultType
- override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(this, name, parameterType)
}
-private class DelegatingParameter(
- val original: Parameter,
- override val name: String,
- override val parameterType: KotlinType
-) : Parameter by original {
- override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(original, name, parameterType)
-}
-
-private fun ExtractionData.checkDeclarationsMovingOutOfScope(
- enclosingDeclaration: KtDeclaration,
- controlFlow: ControlFlow,
- bindingContext: BindingContext
-): ErrorMessage? {
- val declarationsOutOfScope = HashSet()
- controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(
- object : KtTreeVisitorVoid() {
- override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
- val target = expression.mainReference.resolve()
- if (target is KtNamedDeclaration
- && target.isInsideOf(physicalElements)
- && target.getStrictParentOfType() == enclosingDeclaration
- ) {
- declarationsOutOfScope.add(target)
- }
- }
- }
- )
-
- if (declarationsOutOfScope.isNotEmpty()) {
- val declStr = declarationsOutOfScope.map { it.renderForMessage(bindingContext)!! }.sorted()
- return ErrorMessage.DECLARATIONS_OUT_OF_SCOPE.addAdditionalInfo(declStr)
- }
-
- return null
-}
-
-private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode): List {
+private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode?): List? {
+ if (pseudocode == null) return null
val instructions = ArrayList()
pseudocode.traverse(TraversalOrder.FORWARD) {
if (it is KtElementInstruction && it.element.isInsideOf(physicalElements)) {
@@ -595,29 +326,6 @@ private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode): List()
- if (parent is KtClass) {
- if (parent.isInterface()) return null
- if (parent.isEnum() && commonParent.getNonStrictParentOfType()?.getStrictParentOfType() == parent) return null
- }
-
- return KtTokens.PRIVATE_KEYWORD
-}
-
private data class ExperimentalMarkers(
val propagatingMarkerDescriptors: List,
val optInMarkers: List
@@ -686,162 +394,281 @@ private fun ExtractionData.getExperimentalMarkers(): ExperimentalMarkers {
)
}
-fun ExtractionData.performAnalysis(): AnalysisResult {
- if (originalElements.isEmpty()) return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_EXPRESSION))
+class KotlinTypeDescriptor(private val data: ExtractionData) : TypeDescriptor {
+ private val module = data.commonParent.containingKtFile.findModuleDescriptor()
- val noContainerError = AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_CONTAINER))
+ override fun KotlinType.isMeaningful(): Boolean = !KotlinBuiltIns.isUnit(this) && !KotlinBuiltIns.isNothing(this)
- val bindingContext = bindingContext ?: return noContainerError
+ override fun KotlinType.isError(): Boolean = isError
- val declaration = commonParent.containingDeclarationForPseudocode ?: return noContainerError
- val pseudocode = declaration.getContainingPseudocode(bindingContext)
- ?: return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.SYNTAX_ERRORS))
- val localInstructions = getLocalInstructions(pseudocode)
+ override val booleanType: KotlinType = module.builtIns.booleanType
+ override val unitType: KotlinType = module.builtIns.unitType
+ override val nullableAnyType: KotlinType = module.builtIns.nullableAnyType
- val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext)
+ override fun createListType(argTypes: List): KotlinType {
+ return TypeUtils.substituteParameters(
+ module.builtIns.list,
+ Collections.singletonList(CommonSupertypes.commonSupertype(argTypes))
+ )
+ }
- val virtualBlock = createTemporaryCodeBlock()
+ override fun createTuple(outputValues: List>): KotlinType {
+ val boxingClass = when (outputValues.size) {
+ 1 -> return outputValues.first().valueType
+ 2 -> module.resolveTopLevelClass(FqName("kotlin.Pair"), NoLookupLocation.FROM_IDE)!!
+ 3 -> module.resolveTopLevelClass(FqName("kotlin.Triple"), NoLookupLocation.FROM_IDE)!!
+ else -> return module.builtIns.defaultReturnType
+ }
+ return TypeUtils.substituteParameters(boxingClass, outputValues.map { it.valueType })
+ }
- val targetScope = targetSibling.getResolutionScope(bindingContext, commonParent.getResolutionFacade())
- val paramsInfo = inferParametersInfo(
- virtualBlock,
- commonParent,
- pseudocode,
+ override fun returnType(ktNamedDeclaration: KtNamedDeclaration): KotlinType? {
+ val descriptor = data.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, ktNamedDeclaration] as? CallableDescriptor
+ return descriptor?.returnType
+ }
+
+ override fun typeArguments(kotlinType: KotlinType): List {
+ return kotlinType.arguments.map { it.type }
+ }
+
+ override fun renderType(
+ kotlinType: KotlinType,
+ isReceiver: Boolean,
+ variance: Variance
+ ): String {
+ val renderType = IdeDescriptorRenderers.SOURCE_CODE.renderType(kotlinType)
+ return if (kotlinType.isFunctionType && isReceiver) "($renderType)" else renderType
+ }
+
+ override fun renderTypeWithoutApproximation(kotlinType: KotlinType): String {
+ return kotlinType.renderForMessage()
+ }
+
+ override fun renderForMessage(ktNamedDeclaration: KtNamedDeclaration): String? {
+ return data.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, ktNamedDeclaration]?.renderForMessage()
+ }
+
+ override fun renderForMessage(param: IParameter): String {
+ return (param as Parameter).originalDescriptor.renderForMessage()
+ }
+ private val targetScope = data.targetSibling.getResolutionScope(
+ data.bindingContext,
+ data.commonParent.getResolutionFacade()
+ )
+
+ override fun isResolvableInScope(
+ typeToCheck: KotlinType,
+ typeParameters: MutableSet
+ ): Boolean {
+ val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor
+ val typeParameter = parameterTypeDescriptor?.let {
+ DescriptorToSourceUtils.descriptorToDeclaration(it)
+ } as? KtTypeParameter
+ return when {
+ typeToCheck.isResolvableInScope(targetScope, true) -> true
+ typeParameter != null -> {
+ typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints()))
+ true
+ }
+ else -> false
+ }
+ }
+}
+
+object ExtractNameSuggester: IExtractionNameSuggester {
+ override fun suggestNamesByType(
+ kotlinType: KotlinType,
+ container: KtElement,
+ validator: (String) -> Boolean,
+ defaultName: String?
+ ): List {
+ if (KotlinBuiltIns.isUnit(kotlinType)) return emptyList()
+ return Fe10KotlinNameSuggester.suggestNamesByType(kotlinType, validator, defaultName)
+ }
+
+ override fun createNameValidator(
+ container: KtElement,
+ anchor: PsiElement?,
+ validatorType: KotlinNameSuggestionProvider.ValidatorTarget
+ ): (String) -> Boolean {
+ return Fe10KotlinNewDeclarationNameValidator(container, anchor, validatorType)
+ }
+
+ override fun suggestNameByName(
+ name: String,
+ container: KtElement,
+ anchor: PsiElement?
+ ): String {
+ return Fe10KotlinNameSuggester.suggestNameByName(name, createNameValidator(container, anchor, KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE))
+ }
+}
+
+private class ExtractionDataAnalyzer(private val extractionData: ExtractionData): AbstractExtractionDataAnalyzer(extractionData) {
+
+ private val pseudocode = extractionData.commonParent.containingDeclarationForPseudocode?.getContainingPseudocode(extractionData.bindingContext)
+
+ override fun hasSyntaxErrors(): Boolean {
+ return pseudocode == null
+ }
+
+ private val localInstructions = extractionData.getLocalInstructions(pseudocode)
+
+ private val targetScope = extractionData.targetSibling.getResolutionScope(
+ extractionData.bindingContext,
+ extractionData.commonParent.getResolutionFacade()
+ )
+
+ private val modifiedVarDescriptorsWithExpressions = localInstructions?.getModifiedVarDescriptors(extractionData.bindingContext)
+
+ override fun getLocalDeclarationsWithNonLocalUsages(): List {
+ return extractionData.getLocalDeclarationsWithNonLocalUsages(pseudocode!!, localInstructions!!, extractionData.bindingContext)
+ }
+
+ override fun getVarDescriptorsAccessedAfterwards(): Set {
+ return localInstructions!!.getVarDescriptorsAccessedAfterwards(extractionData.bindingContext).map { it.name.asString() }.toSet()
+ }
+
+ override fun getModifiedVars(): Map> {
+ return localInstructions!!.getModifiedVarDescriptors(extractionData.bindingContext).mapKeys { it.key.name.asString() }
+ }
+
+ override fun createOutputDescriptor(): OutputDescriptor {
+ return extractionData.createOutputDescriptor(pseudocode!!, localInstructions!!, targetScope)
+ }
+
+ override val nameSuggester: IExtractionNameSuggester = ExtractNameSuggester
+
+ override val typeDescriptor: TypeDescriptor = KotlinTypeDescriptor(extractionData)
+
+ override fun inferParametersInfo(
+ virtualBlock: KtBlockExpression,
+ modifiedVariables: Set
+ ): ParametersInfo {
+ return extractionData.inferParametersInfo(
+ virtualBlock,
+ pseudocode!!,
+ extractionData.bindingContext,
+ targetScope,
+ modifiedVarDescriptorsWithExpressions!!.keys
+ )
+ }
+
+ override fun createDescriptor(
+ suggestedFunctionNames: List,
+ defaultVisibility: KtModifierKeywordToken?,
+ parameters: List,
+ receiverParameter: MutableParameter?,
+ typeParameters: List,
+ replacementMap: MultiMap>,
+ flow: ControlFlow,
+ returnType: KotlinType
+ ): IExtractableCodeDescriptor {
+ val experimentalMarkers = extractionData.getExperimentalMarkers()
+ var descriptor = ExtractableCodeDescriptor(
+ extractionData,
+ extractionData.bindingContext,
+ suggestedFunctionNames,
+ defaultVisibility,
+ parameters,
+ receiverParameter as Parameter?,
+ typeParameters,
+ replacementMap,
+ flow,
+ returnType,
+ emptyList(),
+ annotations = experimentalMarkers.propagatingMarkerDescriptors,
+ optInMarkers = experimentalMarkers.optInMarkers
+ )
+
+ val generatedDeclaration = ExtractionGeneratorConfiguration(
+ descriptor,
+ ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false)
+ ).generateDeclaration().declaration
+ val virtualContext = generatedDeclaration.analyzeWithContent()
+ if (virtualContext.diagnostics.all()
+ .any { it.factory == Errors.ILLEGAL_SUSPEND_FUNCTION_CALL || it.factory == Errors.ILLEGAL_SUSPEND_PROPERTY_ACCESS }
+ ) {
+ descriptor = descriptor.copy(modifiers = listOf(KtTokens.SUSPEND_KEYWORD))
+ }
+
+ for (analyser in AdditionalExtractableAnalyser.EP_NAME.extensions) {
+ descriptor = analyser.amendDescriptor(descriptor)
+ }
+ return descriptor
+ }
+}
+
+fun ExtractionData.performAnalysis(): AnalysisResult {
+ return ExtractionDataAnalyzer(this).performAnalysis()
+}
+
+private fun ExtractionData.createOutputDescriptor(pseudocode: Pseudocode, localInstructions: List, targetScope: LexicalScope): OutputDescriptor {
+
+ val valuedReturnExits = ArrayList()
+ val defaultExits = ArrayList()
+ val jumpExits = ArrayList()
+ localInstructions.getExitPoints().forEach {
+ val e = (it as? UnconditionalJumpInstruction)?.element
+
+ when (val inst = when {
+ it !is ReturnValueInstruction && it !is ReturnNoValueInstruction && it.owner != pseudocode -> null
+ it is UnconditionalJumpInstruction && it.targetLabel.isJumpToError -> it
+ e != null && e !is KtBreakExpression && e !is KtContinueExpression -> it.previousInstructions.firstOrNull()
+ else -> it
+ }) {
+ is ReturnValueInstruction -> if (inst.owner == pseudocode) {
+ if (inst.returnExpressionIfAny == null) {
+ defaultExits.add(inst)
+ } else {
+ valuedReturnExits.add(inst)
+ }
+ }
+
+ is AbstractJumpInstruction -> {
+ val element = inst.element
+ if ((element is KtReturnExpression && inst.owner == pseudocode)
+ || element is KtBreakExpression
+ || element is KtContinueExpression
+ ) {
+ jumpExits.add(inst)
+ } else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) defaultExits.add(inst)
+ }
+
+ else -> if (inst != null && inst !is LocalFunctionDeclarationInstruction) defaultExits.add(inst)
+ }
+ }
+
+ val module = originalFile.findModuleDescriptor()
+ val (typeOfDefaultFlow, defaultResultExpressions) = getResultTypeAndExpressions(
+ defaultExits,
bindingContext,
targetScope,
- modifiedVarDescriptorsWithExpressions.keys
+ options,
+ module
)
- if (paramsInfo.errorMessage != null) {
- return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!))
- }
- val messages = ArrayList()
-
- val modifiedVarDescriptorsForControlFlow = HashMap(modifiedVarDescriptorsWithExpressions)
- modifiedVarDescriptorsForControlFlow.keys.retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext))
- val (controlFlow, controlFlowMessage) =
- analyzeControlFlow(
- localInstructions,
- pseudocode,
- originalFile.findModuleDescriptor(),
- bindingContext,
- modifiedVarDescriptorsForControlFlow,
- options,
- targetScope,
- paramsInfo.parameters
- )
- controlFlowMessage?.let { messages.add(it) }
-
- val returnType = controlFlow.outputValueBoxer.returnType
- returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options, targetScope)
-
- if (paramsInfo.nonDenotableTypes.isNotEmpty()) {
- val typeStr = paramsInfo.nonDenotableTypes.map { it.renderForMessage() }.sorted()
- return AnalysisResult(
- null,
- Status.CRITICAL_ERROR,
- listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr))
- )
- }
-
- commonParent.getStrictParentOfType()?.let { enclosingDeclaration ->
- checkDeclarationsMovingOutOfScope(enclosingDeclaration, controlFlow, bindingContext)?.let { messages.add(it) }
- }
-
- controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(
- object : KtTreeVisitorVoid() {
- override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
- paramsInfo.originalRefToParameter[expression].firstOrNull()?.let { it.refCount-- }
- }
- }
- )
- val adjustedParameters = paramsInfo.parameters.filterTo(LinkedHashSet()) { it.refCount > 0 }
-
- val receiverCandidates = adjustedParameters.filterTo(hashSetOf()) { it.receiverCandidate }
- val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null
- receiverParameter?.let { adjustedParameters.remove(it) }
-
- val experimentalMarkers = getExperimentalMarkers()
- var descriptor = ExtractableCodeDescriptor(
- this,
+ val (returnValueType, valuedReturnExpressions) = getResultTypeAndExpressions(
+ valuedReturnExits,
bindingContext,
- suggestFunctionNames(returnType),
- getDefaultVisibility(),
- adjustedParameters.toList(),
- receiverParameter,
- paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! },
- paramsInfo.replacementMap,
- if (messages.isEmpty()) controlFlow else controlFlow.toDefault(),
- returnType,
- emptyList(),
- annotations = experimentalMarkers.propagatingMarkerDescriptors,
- optInMarkers = experimentalMarkers.optInMarkers
+ targetScope,
+ options,
+ module
)
- val generatedDeclaration = ExtractionGeneratorConfiguration(
- descriptor,
- ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false)
- ).generateDeclaration().declaration
- val virtualContext = generatedDeclaration.analyzeWithContent()
- if (virtualContext.diagnostics.all()
- .any { it.factory == Errors.ILLEGAL_SUSPEND_FUNCTION_CALL || it.factory == Errors.ILLEGAL_SUSPEND_PROPERTY_ACCESS }
- ) {
- descriptor = descriptor.copy(modifiers = listOf(KtTokens.SUSPEND_KEYWORD))
- }
+ val jumpTarget = getCommonNonTrivialSuccessorIfAny(jumpExits)
- for (analyser in AdditionalExtractableAnalyser.EP_NAME.extensions) {
- descriptor = analyser.amendDescriptor(descriptor)
- }
-
- return AnalysisResult(
- descriptor,
- if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR,
- messages
+ return OutputDescriptor(
+ defaultResultExpression = defaultResultExpressions.singleOrNull(),
+ typeOfDefaultFlow = typeOfDefaultFlow,
+ valuedReturnExpressions = valuedReturnExpressions,
+ returnValueType = returnValueType,
+ jumpExpressions = jumpExits.map { it.element as KtExpression },
+ hasSingleTarget = valuedReturnExits.isNotEmpty() && getCommonNonTrivialSuccessorIfAny(valuedReturnExits) != null ||
+ jumpExits.isNotEmpty() && jumpTarget != null,
+ sameExitForDefaultAndJump = getCommonNonTrivialSuccessorIfAny(defaultExits) == jumpTarget
)
}
-private fun ExtractionData.suggestFunctionNames(returnType: KotlinType): List {
- val functionNames = LinkedHashSet()
-
- val validator =
- Fe10KotlinNewDeclarationNameValidator(
- targetSibling.parent,
- if (targetSibling is KtAnonymousInitializer) targetSibling.parent else targetSibling,
- when {
- options.extractAsProperty -> KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE
- else -> KotlinNameSuggestionProvider.ValidatorTarget.FUNCTION
- }
- )
- if (!KotlinBuiltIns.isUnit(returnType)) {
- functionNames.addAll(Fe10KotlinNameSuggester.suggestNamesByType(returnType, validator))
- }
-
- expressions.singleOrNull()?.let { expr ->
- val property = expr.getStrictParentOfType()
- if (property?.initializer == expr) {
- property.name?.let { functionNames.add(KotlinNameSuggester.suggestNameByName("get" + it.capitalizeAsciiOnly(), validator)) }
- }
- }
-
- return functionNames.toList()
-}
-
-internal fun KtNamedDeclaration.getGeneratedBody() =
- when (this) {
- is KtNamedFunction -> bodyExpression
- else -> {
- val property = this as KtProperty
-
- property.getter?.bodyExpression?.let { return it }
- property.initializer?.let { return it }
- // We assume lazy property here with delegate expression 'by Delegates.lazy { body }'
- property.delegateExpression?.let {
- val call = it.getCalleeExpressionIfAny()?.parent as? KtCallExpression
- call?.lambdaArguments?.singleOrNull()?.getLambdaExpression()?.bodyExpression
- }
- }
- } ?: throw AssertionError("Couldn't get block body for this declaration: ${getElementTextWithContext()}")
-
@JvmOverloads
fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarget.FUNCTION): ExtractableCodeDescriptorWithConflicts {
fun getDeclarationMessage(declaration: PsiElement, messageKey: String, capitalize: Boolean = true): String {
@@ -863,7 +690,7 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg
val bindingContext = generatedDeclaration.analyzeWithContent()
fun processReference(currentRefExpr: KtSimpleNameExpression) {
- val resolveResult = currentRefExpr.resolveResult ?: return
+ val resolveResult = currentRefExpr.resolveResult as? ResolveResult>?: return
if (currentRefExpr.parent is KtThisExpression) return
val diagnostics = bindingContext.diagnostics.forElement(currentRefExpr)
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt
index 6ba14b214ef9..9f1db0b4eb43 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt
@@ -3,44 +3,23 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.codeHighlighting.HighlightDisplayLevel
-import com.intellij.openapi.util.Key
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager
-import com.intellij.psi.PsiElement
-import com.intellij.psi.codeStyle.CodeStyleManager
-import com.intellij.psi.search.LocalSearchScope
-import com.intellij.psi.search.searches.ReferencesSearch
-import com.intellij.refactoring.BaseRefactoringProcessor
-import org.jetbrains.kotlin.builtins.KotlinBuiltIns
-import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester
-import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider
-import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester
-import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNewDeclarationNameValidator
-import org.jetbrains.kotlin.idea.base.psi.isMultiLine
-import org.jetbrains.kotlin.idea.base.psi.replaced
import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange
import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult
import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult.StrictSuccess
import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult.WeakSuccess
import org.jetbrains.kotlin.idea.base.psi.unifier.toRange
-import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
-import org.jetbrains.kotlin.idea.codeInsight.inspections.shared.convertInfixCallToOrdinary
-import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.OperatorToFunctionConverter
-import org.jetbrains.kotlin.idea.core.ShortenReferences
-import org.jetbrains.kotlin.idea.core.appendElement
-import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.idea.core.toVisibility
import org.jetbrains.kotlin.idea.inspections.PublicApiImplicitTypeInspection
import org.jetbrains.kotlin.idea.inspections.UseExpressionBodyInspection
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.refactoring.introduce.*
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.*
-import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValueBoxer.AsTuple
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
-import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getAllAccessibleVariables
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
@@ -49,137 +28,13 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.match
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
-import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.psiUtil.*
-import org.jetbrains.kotlin.renderer.DescriptorRenderer
-import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.DescriptorUtils
-import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny
-import org.jetbrains.kotlin.resolve.checkers.OptInNames
import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.isFlexible
import java.util.*
-private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: DescriptorRenderer): CallableBuilder {
- val extractionTarget = config.generatorOptions.target
- if (!extractionTarget.isAvailable(config.descriptor)) {
- val message = KotlinBundle.message("error.text.can.t.generate.0.1",
- extractionTarget.targetName,
- config.descriptor.extractionData.codeFragmentText
- )
- throw BaseRefactoringProcessor.ConflictsInTestsException(listOf(message))
- }
-
- val builderTarget = when (extractionTarget) {
- ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION
- else -> CallableBuilder.Target.READ_ONLY_PROPERTY
- }
- return CallableBuilder(builderTarget).apply {
- val visibility = config.descriptor.visibility?.value ?: ""
-
- fun TypeParameter.isReified() = originalDeclaration.hasModifier(KtTokens.REIFIED_KEYWORD)
- val shouldBeInline = config.descriptor.typeParameters.any { it.isReified() }
-
- val optInAnnotation = if (config.generatorOptions.target != ExtractionTarget.FUNCTION || config.descriptor.optInMarkers.isEmpty()) {
- ""
- } else {
- val innerText = config.descriptor.optInMarkers.joinToString(separator = ", ") { "${it.shortName().render()}::class" }
- "@${OptInNames.OPT_IN_FQ_NAME.shortName().render()}($innerText)\n"
- }
-
- val annotations = if (config.descriptor.annotations.isEmpty()) {
- ""
- } else {
- config.descriptor.annotations.joinToString(separator = "\n", postfix = "\n") { renderer.renderAnnotation(it) }
- }
- val extraModifiers = config.descriptor.modifiers.map { it.value } +
- listOfNotNull(if (shouldBeInline) KtTokens.INLINE_KEYWORD.value else null) +
- listOfNotNull(if (config.generatorOptions.isConst) KtTokens.CONST_KEYWORD.value else null)
- val modifiers = if (visibility.isNotEmpty()) listOf(visibility) + extraModifiers else extraModifiers
- modifier(annotations + optInAnnotation + modifiers.joinToString(separator = " "))
-
- typeParams(
- config.descriptor.typeParameters.map {
- val typeParameter = it.originalDeclaration
- val bound = typeParameter.extendsBound
-
- buildString {
- if (it.isReified()) {
- append(KtTokens.REIFIED_KEYWORD.value)
- append(' ')
- }
- append(typeParameter.name)
- if (bound != null) {
- append(" : ")
- append(bound.text)
- }
- }
- }
- )
-
- fun KotlinType.typeAsString() = renderer.renderType(this)
-
- config.descriptor.receiverParameter?.let {
- val receiverType = it.parameterType
- val receiverTypeAsString = receiverType.typeAsString()
- receiver(if (receiverType.isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString)
- }
-
- name(config.generatorOptions.dummyName ?: config.descriptor.name)
-
- config.descriptor.parameters.forEach { parameter ->
- param(parameter.name, parameter.parameterType.typeAsString())
- }
-
- with(config.descriptor.returnType) {
- if (KotlinBuiltIns.isUnit(this) || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
- noReturnType()
- } else {
- returnType(typeAsString())
- }
- }
-
- typeConstraints(config.descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! })
- }
-}
-
-fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString()
-
-fun ExtractionGeneratorConfiguration.getDeclarationPattern(
- descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE
-): String {
- val extractionTarget = generatorOptions.target
- if (!extractionTarget.isAvailable(descriptor)) {
- throw BaseRefactoringProcessor.ConflictsInTestsException(
- listOf(
- KotlinBundle.message("error.text.can.t.generate.0.1",
- extractionTarget.targetName,
- descriptor.extractionData.codeFragmentText
- )
- )
- )
- }
-
- return buildSignature(this, descriptorRenderer).let { builder ->
- builder.transform {
- for (i in generateSequence(indexOf('$')) { indexOf('$', it + 2) }) {
- if (i < 0) break
- insert(i + 1, '$')
- }
- }
-
- when (extractionTarget) {
- ExtractionTarget.FUNCTION,
- ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
- ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody("$0")
- ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer("$0")
- ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody("$0")
- }
-
- builder.asString()
- }
-}
+fun ExtractionGeneratorConfiguration.getSignaturePreview() = Generator.getSignaturePreview(this)
fun KotlinType.isSpecial(): Boolean {
val classDescriptor = this.constructor.declarationDescriptor as? ClassDescriptor ?: return false
@@ -190,20 +45,16 @@ fun createNameCounterpartMap(from: KtElement, to: KtElement): Map().zip(to.collectDescendantsOfType()).toMap()
}
-class DuplicateInfo(
- val range: KotlinPsiRange,
- val controlFlow: ControlFlow,
- val arguments: List
-)
-fun ExtractableCodeDescriptor.findDuplicates(): List {
- fun processWeakMatch(match: WeakSuccess<*>, newControlFlow: ControlFlow): Boolean {
+
+fun ExtractableCodeDescriptor.findDuplicates(): List> {
+ fun processWeakMatch(match: WeakSuccess<*>, newControlFlow: ControlFlow): Boolean {
val valueCount = controlFlow.outputValues.size
val weakMatches = HashMap(match.weakMatches)
- val currentValuesToNew = HashMap()
+ val currentValuesToNew = HashMap, OutputValue>()
- fun matchValues(currentValue: OutputValue, newValue: OutputValue): Boolean {
+ 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
@@ -227,7 +78,7 @@ fun ExtractableCodeDescriptor.findDuplicates(): List {
return currentValuesToNew.size == valueCount && weakMatches.isEmpty()
}
- fun getControlFlowIfMatched(match: KotlinPsiUnificationResult.Success<*>): ControlFlow? {
+ fun getControlFlowIfMatched(match: KotlinPsiUnificationResult.Success<*>): ControlFlow? {
val analysisResult = extractionData.copy(originalRange = match.range).performAnalysis()
if (analysisResult.status != AnalysisResult.Status.SUCCESS) return null
@@ -270,237 +121,23 @@ fun ExtractableCodeDescriptor.findDuplicates(): List {
.toList()
}
-private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? {
- return extractionData.duplicateContainer ?: extractionData.targetSibling.parent
-}
+private object Generator: ExtractFunctionGenerator() {
+ override val nameGenerator: IExtractionNameSuggester = ExtractNameSuggester
-private fun makeCall(
- extractableDescriptor: ExtractableCodeDescriptor,
- declaration: KtNamedDeclaration,
- controlFlow: ControlFlow,
- rangeToReplace: KotlinPsiRange,
- arguments: List
-) {
- fun insertCall(anchor: PsiElement, wrappedCall: KtExpression): KtExpression? {
- val firstExpression = rangeToReplace.elements.firstOrNull { it is KtExpression } as? KtExpression
- if (firstExpression?.isLambdaOutsideParentheses() == true) {
- val functionLiteralArgument = firstExpression.getStrictParentOfType()!!
- return functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, extractableDescriptor.originalContext)
- }
-
- if (anchor is KtOperationReferenceExpression) {
- val newNameExpression = when (val operationExpression = anchor.parent as? KtOperationExpression ?: return null) {
- is KtUnaryExpression -> OperatorToFunctionConverter.convert(operationExpression).second
- is KtBinaryExpression -> {
- convertInfixCallToOrdinary(operationExpression).getCalleeExpressionIfAny()
- }
- else -> null
- }
- return newNameExpression?.replaced(wrappedCall)
- }
-
- (anchor as? KtExpression)?.extractableSubstringInfo?.let {
- return it.replaceWith(wrappedCall)
- }
-
- return anchor.replaced(wrappedCall)
+ override fun createTypeDescriptor(data: IExtractionData): TypeDescriptor {
+ return KotlinTypeDescriptor(data as ExtractionData)
}
- if (rangeToReplace.isEmpty) return
-
- val anchor = rangeToReplace.elements.first()
- val anchorParent = anchor.parent!!
-
- anchor.nextSibling?.let { from ->
- val to = rangeToReplace.elements.last()
- if (to != anchor) {
- anchorParent.deleteChildRange(from, to)
+ override fun IExtractionGeneratorConfiguration.collapseBody(body: KtBlockExpression) {
+ val bodyExpression = body.statements.singleOrNull()
+ val bodyOwner = body.parent as KtDeclarationWithBody
+ val useExpressionBodyInspection = UseExpressionBodyInspection()
+ if (bodyExpression != null && useExpressionBodyInspection.isActiveFor(bodyOwner)) {
+ useExpressionBodyInspection.simplify(bodyOwner, !useExplicitReturnType(this))
}
}
- val calleeName = declaration.name?.quoteIfNeeded()
- val callText = when (declaration) {
- is KtNamedFunction -> {
- val argumentsText = arguments.joinToString(separator = ", ", prefix = "(", postfix = ")")
- val typeArguments = extractableDescriptor.typeParameters.map { it.originalDeclaration.name }
- val typeArgumentsText = with(typeArguments) {
- if (isNotEmpty()) joinToString(separator = ", ", prefix = "<", postfix = ">") else ""
- }
- "$calleeName$typeArgumentsText$argumentsText"
- }
- else -> calleeName
- }
-
- val anchorInBlock = generateSequence(anchor) { it.parent }.firstOrNull { it.parent is KtBlockExpression }
- val block = (anchorInBlock?.parent as? KtBlockExpression) ?: anchorParent
-
- val psiFactory = KtPsiFactory(anchor.project)
- 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.text!!), anchorInBlock) as KtDeclaration
- block.addBefore(newLine, anchorInBlock)
- }
-
- val entries = declarationsToMerge.map { p -> p.name + (p.typeReference?.let { ": ${it.text}" } ?: "") }
- anchorInBlock?.replace(
- psiFactory.createDestructuringDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText")
- )
-
- return
- }
- }
-
- val inlinableCall = controlFlow.outputValues.size <= 1
- val unboxingExpressions =
- if (inlinableCall) {
- controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return)
- } else {
- val varNameValidator = Fe10KotlinNewDeclarationNameValidator(block, anchorInBlock, KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE)
- val resultVal = Fe10KotlinNameSuggester.suggestNamesByType(extractableDescriptor.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.text!!)
- copiedDeclarations[decl] = block.addBefore(declCopy, anchorInBlock) as KtDeclaration
- 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 ExpressionValue -> {
- val exprText = if (outputValue.callSiteReturn) {
- val firstReturn = outputValue.originalExpressions.asSequence().filterIsInstance().firstOrNull()
- val label = firstReturn?.getTargetLabel()?.text ?: ""
- "return$label $callText"
- } else {
- callText
- }
- Collections.singletonList(psiFactory.createExpression(exprText))
- }
-
- is ParameterUpdate ->
- Collections.singletonList(
- psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText")
- )
-
- is Jump -> {
- when {
- outputValue.elementToInsertAfterCall == null -> Collections.singletonList(psiFactory.createExpression(callText))
- outputValue.conditional -> Collections.singletonList(
- psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.text}")
- )
- else -> listOf(
- psiFactory.createExpression(callText),
- newLine,
- psiFactory.createExpression(outputValue.elementToInsertAfterCall.text!!)
- )
- }
- }
-
- is Initializer -> {
- val newProperty = copiedDeclarations[outputValue.initializedDeclaration] as KtProperty
- newProperty.initializer = 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.getValue(it)) }
- .withIndex()
- .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.getValue(it)).first() as KtExpression)?.removeTemplateEntryBracesIfPossible()
- }
-
- if (anchor.isValid) {
- anchor.delete()
- }
-}
-
-private var KtExpression.isJumpElementToReplace: Boolean
- by NotNullablePsiCopyableUserDataProperty(Key.create("IS_JUMP_ELEMENT_TO_REPLACE"), false)
-
-private var KtReturnExpression.isReturnForLabelRemoval: Boolean
- by NotNullablePsiCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false)
-
-fun ExtractionGeneratorConfiguration.generateDeclaration(
- declarationToReplace: KtNamedDeclaration? = null
-): ExtractionResult {
- val psiFactory = KtPsiFactory(descriptor.extractionData.project)
-
- fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues
- .flatMapTo(arrayListOf()) { it.originalExpressions.filterIsInstance() }
-
- fun createDeclaration(): KtNamedDeclaration {
- descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true }
- getReturnsForLabelRemoval().forEach { it.isReturnForLabelRemoval = true }
-
- return with(descriptor.extractionData) {
- if (generatorOptions.inTempFile) {
- createTemporaryDeclaration("${getDeclarationPattern()}\n")
- } else {
- psiFactory.createDeclarationByPattern(
- getDeclarationPattern(),
- PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
- )
- }
- }
- }
-
- fun getReturnArguments(resultExpression: KtExpression?): List {
- return descriptor.controlFlow.outputValues
- .mapNotNull {
- when (it) {
- is ExpressionValue -> resultExpression
- is Jump -> if (it.conditional) psiFactory.createExpression("false") else null
- is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef)
- is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!)
- else -> throw IllegalArgumentException("Unknown output value: $it")
- }
- }
- }
-
- fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) {
- descriptor.controlFlow.defaultOutputValue?.let {
- val boxedExpression = replaced(replacingExpression).returnedExpression!!
- descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
- }
- }
-
- fun getPublicApiInspectionIfEnabled(): PublicApiImplicitTypeInspection? {
+ private fun getPublicApiInspectionIfEnabled(descriptor: IExtractableCodeDescriptor): PublicApiImplicitTypeInspection? {
val project = descriptor.extractionData.project
val inspectionProfileManager = ProjectInspectionProfileManager.getInstance(project)
val inspectionProfile = inspectionProfileManager.currentProfile
@@ -509,9 +146,10 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
return state.tool.tool as? PublicApiImplicitTypeInspection
}
- fun useExplicitReturnType(): Boolean {
+ private fun useExplicitReturnType(config: IExtractionGeneratorConfiguration): Boolean {
+ val descriptor = config.descriptor
if (descriptor.returnType.isFlexible()) return true
- val inspection = getPublicApiInspectionIfEnabled() ?: return false
+ val inspection = getPublicApiInspectionIfEnabled(descriptor) ?: return false
val targetClass = (descriptor.extractionData.targetSibling.parent as? KtClassBody)?.parent as? KtClassOrObject
if ((targetClass != null && targetClass.isLocal) || descriptor.extractionData.isLocal()) return false
val visibility = (descriptor.visibility ?: KtTokens.DEFAULT_VISIBILITY_KEYWORD).toVisibility()
@@ -523,208 +161,30 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
}
}
- fun adjustDeclarationBody(declaration: KtNamedDeclaration) {
- val body = declaration.getGeneratedBody()
+ override fun checkTypeArgumentsAreRedundant(args: KtTypeArgumentList): Boolean {
+ return RemoveExplicitTypeArgumentsIntention.isApplicableTo(args, false)
+ }
- (body.blockExpressionsOrSingle().singleOrNull() as? KtExpression)?.let {
- if (it.mustBeParenthesizedInInitializerPosition()) {
- it.replace(psiFactory.createExpressionByPattern("($0)", it))
- }
- }
-
- val jumpValue = descriptor.controlFlow.jumpOutputValue
- if (jumpValue != null) {
- val replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
- body.collectDescendantsOfType { it.isJumpElementToReplace }.forEach {
- it.replace(replacingReturn)
- it.isJumpElementToReplace = false
- }
- }
-
- body.collectDescendantsOfType { it.isReturnForLabelRemoval }.forEach {
- it.getTargetLabel()?.delete()
- it.isReturnForLabelRemoval = false
- }
-
- /*
- * Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
- * before calls/types themselves
- */
- val currentRefs = body
- .collectDescendantsOfType { it.resolveResult != null }
- .sortedByDescending { it.startOffset }
-
- currentRefs.forEach {
- val resolveResult = it.resolveResult!!
- val currentRef = if (it.isValid) {
- it
- } else {
- body.findDescendantOfType { expr -> expr.resolveResult == resolveResult } ?: return@forEach
- }
- val originalRef = resolveResult.originalRefExpr
- val newRef = descriptor.replacementMap[originalRef]
- .fold(currentRef as KtElement) { ref, replacement -> replacement(descriptor, ref) }
- (newRef as? KtSimpleNameExpression)?.resolveResult = resolveResult
- }
-
- if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return
-
- if (body !is KtBlockExpression) throw AssertionError("Block body expected: ${descriptor.extractionData.codeFragmentText}")
-
- val firstExpression = body.statements.firstOrNull()
- if (firstExpression != null) {
- for (param in descriptor.parameters) {
- param.mirrorVarName?.let { varName ->
- body.addBefore(psiFactory.createProperty(varName, null, true, param.name), firstExpression)
- body.addBefore(psiFactory.createNewLine(), firstExpression)
- }
- }
- }
-
- val defaultValue = descriptor.controlFlow.defaultOutputValue
-
- val lastExpression = body.statements.lastOrNull()
- if (lastExpression is KtReturnExpression) return
-
- val defaultExpression =
- if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer
- .boxingRequired && lastExpression!!.isMultiLine()
- ) {
- val varNameValidator = Fe10KotlinNewDeclarationNameValidator(body, lastExpression, KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE)
- val resultVal = Fe10KotlinNameSuggester.suggestNamesByType(defaultValue.valueType, varNameValidator, null).first()
- body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression)
- body.addBefore(psiFactory.createNewLine(), lastExpression)
- psiFactory.createExpression(resultVal)
- } else lastExpression
-
- val returnExpression =
- descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return
-
- when (generatorOptions.target) {
- ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> {
- // In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type
- // We just add resulting expressions without return, since returns are prohibited in the body of lazy property
- if (defaultValue == null) {
- body.appendElement(returnExpression.returnedExpression!!)
- }
- return
- }
- else -> {}
- }
-
- when {
- defaultValue == null -> body.appendElement(returnExpression)
- !defaultValue.callSiteReturn -> lastExpression!!.replaceWithReturn(returnExpression)
- }
-
- if (generatorOptions.allowExpressionBody) {
- val bodyExpression = body.statements.singleOrNull()
- val bodyOwner = body.parent as KtDeclarationWithBody
- val useExpressionBodyInspection = UseExpressionBodyInspection()
- if (bodyExpression != null && useExpressionBodyInspection.isActiveFor(bodyOwner)) {
- useExpressionBodyInspection.simplify(bodyOwner, !useExplicitReturnType())
+ override fun resolveNameConflict(property: KtProperty) {
+ if ((property.descriptor as? PropertyDescriptor)?.let { DescriptorUtils.isOverride(it) } == true) {
+ val scope = property.getResolutionScope()
+ val newName = KotlinNameSuggester.suggestNameByName(property.name!!) {
+ it != property.name && scope.getAllAccessibleVariables(Name.identifier(it)).isEmpty()
}
+ property.setName(newName)
}
}
- fun insertDeclaration(declaration: KtNamedDeclaration, anchor: PsiElement): KtNamedDeclaration {
- declarationToReplace?.let { return it.replace(declaration) as KtNamedDeclaration }
-
- return with(descriptor.extractionData) {
- val targetContainer = anchor.parent!!
- // TODO: Get rid of explicit new-lines in favor of formatter rules
- val emptyLines = psiFactory.createWhiteSpace("\n\n")
- if (insertBefore) {
- (targetContainer.addBefore(declaration, anchor) as KtNamedDeclaration).apply {
- targetContainer.addBefore(emptyLines, anchor)
- }
- } else {
- (targetContainer.addAfter(declaration, anchor) as KtNamedDeclaration).apply {
- if (!(targetContainer is KtClassBody && (targetContainer.parent as? KtClass)?.isEnum() == true)) {
- targetContainer.addAfter(emptyLines, anchor)
- }
- val insertedDeclaration = this
- PostInsertDeclarationCallback.EP_NAME.forEachExtensionSafe { extension ->
- extension.declarationInserted(insertedDeclaration, targetContainer, psiFactory)
- }
- }
- }
- }
+ override fun IExtractionGeneratorConfiguration.createExtractionResult(
+ declaration: KtNamedDeclaration,
+ duplicatesReplacer: Map Unit>
+ ): ExtractionResult {
+ return ExtractionResult(this as ExtractionGeneratorConfiguration, declaration, duplicatesReplacer)
}
-
- val duplicates = if (generatorOptions.inTempFile) Collections.emptyList() else descriptor.duplicates
-
- val anchor = with(descriptor.extractionData) {
- val targetParent = targetSibling.parent
-
- val anchorCandidates = duplicates.mapTo(arrayListOf()) { it.range.elements.first().substringContextOrThis }
- anchorCandidates.add(targetSibling)
- if (targetSibling is KtEnumEntry) {
- anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry })
- }
-
- val marginalCandidate = if (insertBefore) {
- anchorCandidates.minByOrNull { it.startOffset }!!
- } else {
- anchorCandidates.maxByOrNull { it.startOffset }!!
- }
-
- // Ascend to the level of targetSibling
- marginalCandidate.parentsWithSelf.first { it.parent == targetParent }
- }
-
- val shouldInsert = !(generatorOptions.inTempFile || generatorOptions.target == ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION)
- val declaration = createDeclaration().let { if (shouldInsert) insertDeclaration(it, anchor) else it }
- adjustDeclarationBody(declaration)
-
- if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap())
-
- val replaceInitialOccurrence = {
- val arguments = descriptor.parameters.map { it.argumentText }
- makeCall(descriptor, declaration, descriptor.controlFlow, descriptor.extractionData.originalRange, arguments)
- }
-
- if (!generatorOptions.delayInitialOccurrenceReplacement) replaceInitialOccurrence()
-
- if (shouldInsert) {
- ShortenReferences.DEFAULT.process(declaration)
- }
-
- val duplicateReplacers = HashMap Unit>().apply {
- if (generatorOptions.delayInitialOccurrenceReplacement) {
- put(descriptor.extractionData.originalRange, replaceInitialOccurrence)
- }
- putAll(duplicates.map {
- val smartListRange = KotlinPsiRange.SmartListRange(it.range.elements)
- smartListRange to { makeCall(descriptor, declaration, it.controlFlow, smartListRange, it.arguments) }
- })
- }
-
- if (descriptor.typeParameters.isNotEmpty()) {
- for (ref in ReferencesSearch.search(declaration, LocalSearchScope(descriptor.getOccurrenceContainer()!!))) {
- val typeArgumentList = (ref.element.parent as? KtCallExpression)?.typeArgumentList ?: continue
- if (RemoveExplicitTypeArgumentsIntention.isApplicableTo(typeArgumentList, false)) {
- typeArgumentList.delete()
- }
- }
- }
-
- if (declaration is KtProperty) {
- if (declaration.isExtensionDeclaration() && !declaration.isTopLevel) {
- val receiverTypeReference = (declaration as? KtCallableDeclaration)?.receiverTypeReference
- receiverTypeReference?.siblings(withItself = false)?.firstOrNull { it.node.elementType == KtTokens.DOT }?.delete()
- receiverTypeReference?.delete()
- }
- if ((declaration.descriptor as? PropertyDescriptor)?.let { DescriptorUtils.isOverride(it) } == true) {
- val scope = declaration.getResolutionScope()
- val newName = KotlinNameSuggester.suggestNameByName(descriptor.name) {
- it != descriptor.name && scope.getAllAccessibleVariables(Name.identifier(it)).isEmpty()
- }
- declaration.setName(newName)
- }
- }
-
- CodeStyleManager.getInstance(descriptor.extractionData.project).reformat(declaration)
-
- return ExtractionResult(this, declaration, duplicateReplacers)
}
+
+fun ExtractionGeneratorConfiguration.generateDeclaration(
+ declarationToReplace: KtNamedDeclaration? = null
+): ExtractionResult {
+ return Generator.generateDeclaration(this, declarationToReplace)
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt
index 06b30fe0366d..8db13f3d894b 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt
@@ -2,9 +2,7 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
-import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
-import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.createFunctionType
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.SingleType
@@ -15,8 +13,6 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider
-import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester
-import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNewDeclarationNameValidator
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
@@ -45,24 +41,14 @@ import org.jetbrains.kotlin.types.CommonSupertypes
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.OperatorConventions
-internal class ParametersInfo {
- var errorMessage: AnalysisResult.ErrorMessage? = null
- val originalRefToParameter = MultiMap.create()
- val parameters = LinkedHashSet()
- val typeParameters = HashSet()
- val nonDenotableTypes = HashSet()
- val replacementMap = MultiMap.create()
-}
-
internal fun ExtractionData.inferParametersInfo(
virtualBlock: KtBlockExpression,
- commonParent: PsiElement,
pseudocode: Pseudocode,
bindingContext: BindingContext,
targetScope: LexicalScope,
modifiedVarDescriptors: Set
-): ParametersInfo {
- val info = ParametersInfo()
+): ParametersInfo {
+ val info = ParametersInfo()
val extractedDescriptorToParameter = LinkedHashMap()
@@ -98,18 +84,16 @@ internal fun ExtractionData.inferParametersInfo(
continue
}
- if (!refInfo.shouldSkipPrimaryReceiver) {
- extractReceiver(
- receiverToExtract,
- info,
- targetScope,
- refInfo,
- extractedDescriptorToParameter,
- pseudocode,
- bindingContext,
- false
- )
- }
+ extractReceiver(
+ receiverToExtract,
+ info,
+ targetScope,
+ refInfo,
+ extractedDescriptorToParameter,
+ pseudocode,
+ bindingContext,
+ false
+ )
if (options.canWrapInWith && twoReceivers) {
extractReceiver(
@@ -125,7 +109,7 @@ internal fun ExtractionData.inferParametersInfo(
}
}
- val varNameValidator = Fe10KotlinNewDeclarationNameValidator(
+ val varNameValidator = ExtractNameSuggester.createNameValidator(
commonParent.getNonStrictParentOfType()!!,
physicalElements.firstOrNull(),
KotlinNameSuggestionProvider.ValidatorTarget.PARAMETER
@@ -135,12 +119,12 @@ internal fun ExtractionData.inferParametersInfo(
for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) {
if (!parameter
.parameterType
- .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope)
+ .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, targetScope)
) continue
with(parameter) {
if (currentName == null) {
- currentName = Fe10KotlinNameSuggester.suggestNamesByType(parameterType, varNameValidator, "p").first()
+ currentName = ExtractNameSuggester.suggestNamesByType(parameterType, commonParent, varNameValidator, "p").first()
}
require(currentName != null)
@@ -162,8 +146,8 @@ internal fun ExtractionData.inferParametersInfo(
}
}
- for (typeToCheck in info.typeParameters.flatMapTo(HashSet()) { it.collectReferencedTypes(bindingContext) }) {
- typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope)
+ for (typeToCheck in info.typeParameters.flatMap { it.collectReferencedTypes() }.mapNotNull { bindingContext[BindingContext.TYPE, it] }) {
+ typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, targetScope)
}
@@ -172,9 +156,9 @@ internal fun ExtractionData.inferParametersInfo(
private fun ExtractionData.extractReceiver(
receiverToExtract: ReceiverValue?,
- info: ParametersInfo,
+ info: ParametersInfo,
targetScope: LexicalScope,
- refInfo: ResolvedReferenceInfo,
+ refInfo: ResolvedReferenceInfo, KotlinType>,
extractedDescriptorToParameter: HashMap,
pseudocode: Pseudocode,
bindingContext: BindingContext,
@@ -211,7 +195,7 @@ private fun ExtractionData.extractReceiver(
if (referencedClassifierDescriptor != null) {
if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable(
- info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor
+ info.typeParameters, info.nonDenotableTypes, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor
)
) return
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt
index 62fdf8792245..8595dee6a442 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt
@@ -11,10 +11,9 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.NameUtil
import com.intellij.refactoring.RefactoringActionHandler
import org.jetbrains.annotations.Nls
-import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.base.psi.unifier.toRange
+import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
-import org.jetbrains.kotlin.idea.util.ElementKind
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinInplacePropertyIntroducer
@@ -22,6 +21,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetS
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey
import org.jetbrains.kotlin.idea.refactoring.introduce.validateExpressionElements
+import org.jetbrains.kotlin.idea.util.ElementKind
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.plainContent
@@ -70,6 +70,7 @@ class KotlinIntroduceConstantHandler(
INTRODUCE_CONSTANT
)
}
+
else -> {
val options = ExtractionOptions(extractAsProperty = true)
val extractionData = ExtractionData(file, adjustedElements.toRange(), target, null, options)
@@ -147,6 +148,7 @@ class KotlinIntroduceConstantHandler(
} -> KotlinBundle.message(
"error.text.can.t.introduce.constant.for.this.expression.because.not.constant"
)
+
else -> null
}
}
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java
index 2f20028d5359..5150ce8f2914 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java
@@ -32,6 +32,8 @@ import java.awt.event.ItemListener;
import java.util.Collections;
import java.util.List;
+import static org.jetbrains.kotlin.idea.refactoring.KotlinCommonRefactoringUtilKt.checkConflictsInteractively;
+
public class KotlinIntroduceTypeAliasDialog extends DialogWrapper {
private JPanel contentPane;
private TitledSeparator inputParametersPanel;
@@ -181,7 +183,7 @@ public class KotlinIntroduceTypeAliasDialog extends DialogWrapper {
@Override
protected void doOKAction() {
MultiMap conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts();
- KotlinRefactoringUtilKt.checkConflictsInteractively(
+ checkConflictsInteractively(
project,
conflicts,
new Function0<>() {
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt
index f8736b1bd910..8cd66b72d963 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt
@@ -124,26 +124,6 @@ fun PsiElement.getUsageContext(): PsiElement {
fun PsiElement.isInKotlinAwareSourceRoot(): Boolean =
!isOutsideKotlinAwareSourceRoot(containingFile)
-fun Project.checkConflictsInteractively(
- conflicts: MultiMap,
- onShowConflicts: () -> Unit = {},
- onAccept: () -> Unit
-) {
- if (!conflicts.isEmpty) {
- if (isUnitTestMode()) throw ConflictsInTestsException(conflicts.values())
-
- val dialog = ConflictsDialog(this, conflicts) { onAccept() }
- dialog.show()
- if (!dialog.isOK) {
- if (dialog.isShowConflicts) {
- onShowConflicts()
- }
- return
- }
- }
-
- onAccept()
-}
fun reportDeclarationConflict(
conflicts: MultiMap,
diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index 420e41bf72f4..29f5c650b506 100644
--- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -17800,6 +17800,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
runTest("testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/assignment.kt");
}
+ @TestMetadata("block.kt")
+ public void testBlock() throws Exception {
+ runTest("testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt");
+ }
+
@TestMetadata("constructorWithEmptyBody.kt")
public void testConstructorWithEmptyBody() throws Exception {
runTest("testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/constructorWithEmptyBody.kt");
diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt
index 621073b145e2..03cbc820253e 100644
--- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt
+++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt
@@ -7,12 +7,14 @@ import com.intellij.codeInsight.completion.JavaCompletionUtil
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.ide.DataManager
+import com.intellij.openapi.application.impl.NonBlockingReadActionImpl
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.DocumentImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
@@ -30,9 +32,7 @@ import com.intellij.refactoring.util.JavaNameSuggestionUtil
import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager
import com.intellij.testFramework.EditorTestUtil
import com.intellij.testFramework.IdeaTestUtil
-import com.intellij.testFramework.fixtures.CodeInsightTestFixture
-import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
-import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
+import com.intellij.testFramework.fixtures.*
import com.intellij.testFramework.runInEdtAndWait
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.idea.KotlinFileType
@@ -68,19 +68,88 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.idea.base.test.IgnoreTests
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils
+import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.AbstractExtractKotlinFunctionHandler
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.junit.Assert
import java.io.File
import java.util.*
import kotlin.test.assertEquals
+private const val DUMMY_FUN_NAME = "__dummyTestFun__"
abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase() {
- override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST
+ override fun getProjectDescriptor() = JAVA_LATEST
+
+ override fun setUp() {
+ super.setUp()
+ Registry.get("kotlin.extract.function.default.name").setValue(DUMMY_FUN_NAME, testRootDisposable)
+ }
val fixture: JavaCodeInsightTestFixture get() = myFixture
protected open fun getIntroduceVariableHandler(): RefactoringActionHandler = K1IntroduceVariableHandler
+ protected open fun getExtractFunctionHandler(
+ explicitPreviousSibling: PsiElement?,
+ expectedNames: List,
+ expectedReturnTypes: List,
+ expectedDescriptors: String,
+ expectedTypes: String,
+ extractionOptions: ExtractionOptions
+ ): AbstractExtractKotlinFunctionHandler {
+ return ExtractKotlinFunctionHandler(
+ helper = object : ExtractionEngineHelper(EXTRACT_FUNCTION) {
+ override fun adjustExtractionData(data: ExtractionData): ExtractionData {
+ return data.copy(options = extractionOptions)
+ }
+
+ override fun configureAndRun(
+ project: Project,
+ editor: Editor,
+ descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
+ onFinish: (ExtractionResult) -> Unit
+ ) {
+ val renderer = DescriptorRenderer.FQ_NAMES_IN_TYPES
+ val descriptor = descriptorWithConflicts.descriptor
+ val actualNames = descriptor.suggestedNames
+ val actualReturnTypes = descriptor.controlFlow.possibleReturnTypes.map {
+ IdeDescriptorRenderers.SOURCE_CODE.renderType(it)
+ }
+ val allParameters = listOfNotNull(descriptor.receiverParameter) + descriptor.parameters
+ val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
+ val actualTypes = allParameters.map { param ->
+ param.getParameterTypeCandidates().joinToString(", ", "[", "]") { type -> renderer.renderType(type) }
+ }.joinToString()
+
+ if (actualNames.size != 1 || expectedNames.isNotEmpty()) {
+ assertEquals(expectedNames, actualNames, "Expected names mismatch.")
+ }
+ if (actualReturnTypes.size != 1 || expectedReturnTypes.isNotEmpty()) {
+ assertEquals(expectedReturnTypes, actualReturnTypes, "Expected return types mismatch.")
+ }
+ KotlinLightCodeInsightFixtureTestCaseBase.assertEquals(
+ "Expected descriptors mismatch.",
+ expectedDescriptors,
+ actualDescriptors
+ )
+ KotlinLightCodeInsightFixtureTestCaseBase.assertEquals("Expected types mismatch.", expectedTypes, actualTypes)
+
+ val newDescriptor = if (descriptor.name == "") {
+ descriptor.copy(suggestedNames = Collections.singletonList(DUMMY_FUN_NAME))
+ } else {
+ descriptor
+ }
+
+ fun afterFinish(extraction: ExtractionResult) {
+ processDuplicates(extraction.duplicateReplacers, project, editor)
+ onFinish(extraction)
+ }
+ doRefactor(ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT), ::afterFinish)
+ }
+ }
+ )
+ }
+
+
protected open fun doIntroduceVariableTest(unused: String) {
doTestIfNotDisabledByFileDirective { file ->
TemplateManagerImpl.setTemplateTesting(getTestRootDisposable())
@@ -134,7 +203,7 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase()
class LambdaHelperImpl : HelperImpl(), KotlinIntroduceLambdaParameterHelper {
override fun configureExtractLambda(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor = with(descriptor) {
- if (name.isEmpty()) copy(suggestedNames = listOf("__dummyTestFun__")) else this
+ if (name.isEmpty()) copy(suggestedNames = listOf(DUMMY_FUN_NAME)) else this
}
}
@@ -275,7 +344,7 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase()
}
protected fun doExtractFunctionTest(unused: String) {
- doTest { file -> doExtractFunction(myFixture, file as KtFile) }
+ doTestIfNotDisabledByFileDirective { file -> doExtractFunction(myFixture, file as KtFile) }
}
protected fun doIntroduceTypeParameterTest(unused: String) {
@@ -448,6 +517,32 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase()
}
}
}
+
+ fun doExtractFunction(fixture: CodeInsightTestFixture, file: KtFile) {
+ val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:")
+ val fileText = file.getText() ?: ""
+ val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ")
+ val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ")
+ val expectedDescriptors =
+ InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString()
+ val expectedTypes =
+ InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString()
+
+ val extractionOptions = InTextDirectivesUtils.findListWithPrefixes(fileText, "// OPTIONS: ").let {
+ if (it.isNotEmpty()) {
+ @Suppress("UNCHECKED_CAST")
+ val args = it.map { it.toBoolean() }.toTypedArray() as Array
+ ExtractionOptions::class.java.constructors.first { it.parameterTypes.size == args.size }.newInstance(*args) as ExtractionOptions
+ } else ExtractionOptions.DEFAULT
+ }
+
+ val editor = fixture.editor
+ val handler = getExtractFunctionHandler(explicitPreviousSibling, expectedNames, expectedReturnTypes, expectedDescriptors, expectedTypes, extractionOptions)
+ handler.selectElements(editor, file) { elements, previousSibling ->
+ handler.doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling)
+ }
+ }
+
}
class ExtractTestFiles(
@@ -457,11 +552,21 @@ class ExtractTestFiles(
val extraFilesToPsi: Map = emptyMap()
) {
constructor(path: String, mainFile: PsiFile, extraFilesToPsi: Map = emptyMap(), isFirPlugin: Boolean) :
- this(mainFile, getAfterFile(path, isFirPlugin), File("$path.conflicts"), extraFilesToPsi)
+ this(mainFile, getAfterFile(path, isFirPlugin), getConflictsFile(path, isFirPlugin), extraFilesToPsi)
}
+private fun getConflictsFile(path: String, isFirPlugin: Boolean): File {
+ if (isFirPlugin) {
+ val firSpecific = File("$path.fir.conflicts")
+ if (firSpecific.exists()) {
+ return firSpecific
+ }
+ }
+ return File("$path.conflicts")
+}
+
private fun getAfterFile(path: String, isFirPlugin: Boolean): File {
var file = File("$path.after")
if (isFirPlugin) {
@@ -518,79 +623,3 @@ private fun assertEqualsToFile(expectedFile: File, actualText: String, generateM
}
KotlinTestUtils.assertEqualsToFile(expectedFile, actualText)
}
-
-fun doExtractFunction(fixture: CodeInsightTestFixture, file: KtFile) {
- val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:")
- val fileText = file.getText() ?: ""
- val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ")
- val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ")
- val expectedDescriptors =
- InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString()
- val expectedTypes =
- InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString()
-
- val extractionOptions = InTextDirectivesUtils.findListWithPrefixes(fileText, "// OPTIONS: ").let {
- if (it.isNotEmpty()) {
- @Suppress("UNCHECKED_CAST")
- val args = it.map { it.toBoolean() }.toTypedArray() as Array
- ExtractionOptions::class.java.constructors.first { it.parameterTypes.size == args.size }.newInstance(*args) as ExtractionOptions
- } else ExtractionOptions.DEFAULT
- }
-
- val renderer = DescriptorRenderer.FQ_NAMES_IN_TYPES
-
- val editor = fixture.editor
- val handler = ExtractKotlinFunctionHandler(
- helper = object : ExtractionEngineHelper(EXTRACT_FUNCTION) {
- override fun adjustExtractionData(data: ExtractionData): ExtractionData {
- return data.copy(options = extractionOptions)
- }
-
- override fun configureAndRun(
- project: Project,
- editor: Editor,
- descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
- onFinish: (ExtractionResult) -> Unit
- ) {
- val descriptor = descriptorWithConflicts.descriptor
- val actualNames = descriptor.suggestedNames
- val actualReturnTypes = descriptor.controlFlow.possibleReturnTypes.map {
- IdeDescriptorRenderers.SOURCE_CODE.renderType(it)
- }
- val allParameters = listOfNotNull(descriptor.receiverParameter) + descriptor.parameters
- val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
- val actualTypes = allParameters.map {
- it.getParameterTypeCandidates().map { renderer.renderType(it) }.joinToString(", ", "[", "]")
- }.joinToString()
-
- if (actualNames.size != 1 || expectedNames.isNotEmpty()) {
- assertEquals(expectedNames, actualNames, "Expected names mismatch.")
- }
- if (actualReturnTypes.size != 1 || expectedReturnTypes.isNotEmpty()) {
- assertEquals(expectedReturnTypes, actualReturnTypes, "Expected return types mismatch.")
- }
- KotlinLightCodeInsightFixtureTestCaseBase.assertEquals(
- "Expected descriptors mismatch.",
- expectedDescriptors,
- actualDescriptors
- )
- KotlinLightCodeInsightFixtureTestCaseBase.assertEquals("Expected types mismatch.", expectedTypes, actualTypes)
-
- val newDescriptor = if (descriptor.name == "") {
- descriptor.copy(suggestedNames = Collections.singletonList("__dummyTestFun__"))
- } else {
- descriptor
- }
-
- fun afterFinish(extraction: ExtractionResult) {
- processDuplicates(extraction.duplicateReplacers, project, editor)
- onFinish(extraction)
- }
- doRefactor(ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT), ::afterFinish)
- }
- }
- )
- handler.selectElements(editor, file) { elements, previousSibling ->
- handler.doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling)
- }
-}
diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java
index 07a447f51620..b5b9477d1802 100644
--- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java
+++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java
@@ -1010,6 +1010,11 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest {
runTest("testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt");
}
+ @TestMetadata("noConflictWithInnerFunction1.kt")
+ public void testNoConflictWithInnerFunction1() throws Exception {
+ runTest("testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt");
+ }
+
@TestMetadata("objectQualifier.kt")
public void testObjectQualifier() throws Exception {
runTest("testData/refactoring/extractFunction/basic/objectQualifier.kt");
@@ -2010,6 +2015,11 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest {
runTest("testData/refactoring/extractFunction/optIn/filterPropagatingMarkers.kt");
}
+ @TestMetadata("filterPropagatingMarkersWitArgs.kt")
+ public void testFilterPropagatingMarkersWitArgs() throws Exception {
+ runTest("testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt");
+ }
+
@TestMetadata("noExperimentalApi.kt")
public void testNoExperimentalApi() throws Exception {
runTest("testData/refactoring/extractFunction/optIn/noExperimentalApi.kt");
@@ -2195,6 +2205,11 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest {
KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath);
}
+ @TestMetadata("bothReceivers.kt")
+ public void testBothReceivers() throws Exception {
+ runTest("testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt");
+ }
+
@TestMetadata("explicitLabeledThisInMember.kt")
public void testExplicitLabeledThisInMember() throws Exception {
runTest("testData/refactoring/extractFunction/parameters/extractThis/explicitLabeledThisInMember.kt");
diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt
new file mode 100644
index 000000000000..a177dfd8d20b
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt
@@ -0,0 +1,8 @@
+// PROBLEM: none
+
+private fun bool(a: Int, b: Int): Boolean {
+ if (a > 0) throw Exception("")
+ if (a + b > 0) return true
+ println(a - b)
+ return false
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.fir.after
new file mode 100644
index 000000000000..5270af3a25b6
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.fir.after
@@ -0,0 +1,9 @@
+// WITH_STDLIB
+// SUGGESTED_RETURN_TYPES: kotlin.Boolean?, kotlin.Boolean
+// PARAM_DESCRIPTOR: value-parameter it: kotlin.collections.Map.Entry defined in test.``
+// PARAM_TYPES: kotlin.collections.Map.Entry
+fun test() {
+ J.getMap().filter { __dummyTestFun__(it) }
+}
+
+private fun __dummyTestFun__(entry: Map.Entry): Boolean? = entry.key
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt.fir.after
new file mode 100644
index 000000000000..7839d6fd3b96
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt.fir.after
@@ -0,0 +1,16 @@
+// WITH_STDLIB
+// SUGGESTED_NAMES: map, stringStringMap, stringMap
+// PARAM_DESCRIPTOR: value-parameter arg: kotlin.String defined in Foo.Companion.bar
+// PARAM_TYPES: kotlin.String
+
+class Foo {
+ var arguments: Map? = null
+
+ companion object {
+ fun bar(arg: String) = Foo().apply {
+ arguments = map(arg)
+ }
+
+ private fun map(arg: String): Map = mapOf(Foo::arguments.name to arg)
+ }
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt
index b8b1a1a7b621..0f838badd351 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt
@@ -3,4 +3,6 @@
fun foo(x : String, y : String.() -> Unit) {
x.y()
-}
\ No newline at end of file
+}
+
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after
index 1a4d720e61af..2768ad3144cd 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after
@@ -5,4 +5,6 @@ fun foo(x : String, y : String.() -> Unit) {
x.(function(y))()
}
-private fun function(y: String.() -> Unit) = y
\ No newline at end of file
+private fun function(y: String.() -> Unit) = y
+
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt
index 10b9f850cb6b..419240367fc0 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt
@@ -8,4 +8,6 @@ class A {
fun bar() {
A.foo()
-}
\ No newline at end of file
+}
+
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt
index 31ad42b97e89..64a586443c43 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt
@@ -3,4 +3,6 @@
fun foo(x : String.(String) -> Unit) {
"A" x "B"
-}
\ No newline at end of file
+}
+
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after
index 0725f0e80cda..b4d7fc2c6906 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after
@@ -5,4 +5,6 @@ fun foo(x : String.(String) -> Unit) {
"A".(function(x))("B")
}
-private fun function(x: String.(String) -> Unit) = x
\ No newline at end of file
+private fun function(x: String.(String) -> Unit) = x
+
+// IGNORE_K2
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt.fir.after
new file mode 100644
index 000000000000..695d5cae5f68
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt.fir.after
@@ -0,0 +1,7 @@
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo, value-parameter b: kotlin.Int defined in foo
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+
+private inline fun foo(a: Int, b: Int, f: (Int) -> Int) = f(i(a, b))
+
+private fun i(a: Int, b: Int): Int = a + b
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt.fir.after
new file mode 100644
index 000000000000..ccafec2e6936
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt.fir.after
@@ -0,0 +1,7 @@
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo, value-parameter b: kotlin.Int defined in foo
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+
+inline fun foo(a: Int, b: Int, f: (Int) -> Int) = f(i(a, b))
+
+fun i(a: Int, b: Int): Int = a + b
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt.fir.after
new file mode 100644
index 000000000000..431631bcb8f9
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt.fir.after
@@ -0,0 +1,11 @@
+// PARAM_DESCRIPTOR: public suspend inline fun ExtendMe.receive(): T defined in root package in file extractSuspend.kt
+// PARAM_TYPES: ExtendMe
+suspend inline fun ExtendMe.receive(): T = any() as T
+
+suspend fun ExtendMe.any(): Any = receive("")
+
+class ExtendMe {
+ suspend fun receive(s: String): Any {
+ return Any()
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt.fir.after
new file mode 100644
index 000000000000..a055a58f0844
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt.fir.after
@@ -0,0 +1,12 @@
+// PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in E.A.foo
+// PARAM_TYPES: kotlin.Int
+enum class E {
+ // SIBLING:
+ A {
+ fun foo(n: Int) = i(n)
+ },
+ B,
+ C;
+
+ fun i(n: Int): Int = n + 1
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.fir.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.fir.kt.after
new file mode 100644
index 000000000000..3db244cb6f53
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.fir.kt.after
@@ -0,0 +1,14 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
+// SIBLING:
+fun foo(a: Int): Int {
+ if (bool(a)) return a
+ return -a
+}
+
+private fun bool(a: Int): Boolean {
+ // test
+ println(a)
+ if (a > 0) return true
+ return false
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt.fir.after
new file mode 100644
index 000000000000..3db244cb6f53
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt.fir.after
@@ -0,0 +1,14 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
+// SIBLING:
+fun foo(a: Int): Int {
+ if (bool(a)) return a
+ return -a
+}
+
+private fun bool(a: Int): Boolean {
+ // test
+ println(a)
+ if (a > 0) return true
+ return false
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.fir.after
new file mode 100644
index 000000000000..36ae5d95ff30
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.fir.after
@@ -0,0 +1,16 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
+// SIBLING:
+fun foo(a: Int): Int {
+ if (bool(a)) return a
+ return -a
+}
+
+private fun bool(a: Int): Boolean {
+ /*
+ test
+ */
+ println(a)
+ if (a > 0) return true
+ return false
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt.fir.after
new file mode 100644
index 000000000000..56964b6bfda7
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt.fir.after
@@ -0,0 +1,11 @@
+// SUGGESTED_NAMES: b, getT
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in test
+// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in test
+// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable, java.io.Serializable, kotlin.Any
+// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable, java.io.Serializable, kotlin.Any
+
+fun test(a: Int, b: Int): Boolean {
+ val t = bool(a, b)
+}
+
+private fun bool(a: Int, b: Int): Boolean = a === b
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt.fir.after
new file mode 100644
index 000000000000..90ca902e3d7f
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt.fir.after
@@ -0,0 +1,7 @@
+interface T{
+ fun foo(): Int {
+ return i()
+ }
+
+ fun i(): Int = 1
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt.fir.conflicts
new file mode 100644
index 000000000000..e8b0919426f5
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt.fir.conflicts
@@ -0,0 +1 @@
+Class A.Inner will become invisible after extraction Function Inner.foo() will become invisible after extraction
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt.fir.conflicts
new file mode 100644
index 000000000000..82377f1b8217
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt.fir.conflicts
@@ -0,0 +1 @@
+Following declarations are used outside of selected code fragment: class A(n: Int) constructor(n: Int) n: Int
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt.fir.after
new file mode 100644
index 000000000000..91aad9c919ca
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt.fir.after
@@ -0,0 +1,6 @@
+fun foo(a: Int): Int {
+ // SIBLING:
+ fun i(): Int = a + 10
+
+ return i()
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt.fir.conflicts
new file mode 100644
index 000000000000..38beeb24649e
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt.fir.conflicts
@@ -0,0 +1 @@
+Following declarations are used outside of selected code fragment: fun bar(): Int
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt.fir.after
new file mode 100644
index 000000000000..e899fee69e86
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt.fir.after
@@ -0,0 +1,14 @@
+// SUGGESTED_NAMES: i, getKm
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: public final val meters: kotlin.Int? defined in A
+class A {
+ val meters: Int? = 1
+}
+
+fun test() {
+ val a = A()
+ if (a.meters == null) return
+ val km = i(a.meters)
+}
+
+private fun i(meters: Int): Int = meters / 10
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt.fir.after
new file mode 100644
index 000000000000..96f9aeed67fe
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt.fir.after
@@ -0,0 +1,17 @@
+// WITH_STDLIB
+// SUGGESTED_NAMES: i, getKm
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: public final val meters: kotlin.Int? defined in A
+class A {
+ val meters: Int? = 1
+}
+
+fun test() {
+ val a = A()
+ with (a) {
+ if (meters == null) return
+ val km = i(meters)
+ }
+}
+
+private fun i(meters: Int): Int = meters / 10
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt.fir.after
new file mode 100644
index 000000000000..6a9bbb4d5e7c
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt.fir.after
@@ -0,0 +1,10 @@
+fun foo() {
+ val a = 1
+ // SIBLING:
+ fun bool(): Boolean = a > 0
+
+ if (bool()) {
+ fun b(): Int { return 0 }
+ println(b())
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt
new file mode 100644
index 000000000000..33845d4a9049
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt
@@ -0,0 +1,8 @@
+fun foo() {
+ val a = 1
+ // SIBLING:
+ if (a > 0) {
+ fun bool(): Int { return 0 }
+ println(bool())
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.after
new file mode 100644
index 000000000000..6f8083b8ccb8
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.after
@@ -0,0 +1,10 @@
+fun foo() {
+ val a = 1
+ // SIBLING:
+ fun b() = a > 0
+
+ if (b()) {
+ fun bool(): Int { return 0 }
+ println(bool())
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.fir.after
new file mode 100644
index 000000000000..7f05e197cf8a
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.fir.after
@@ -0,0 +1,10 @@
+fun foo() {
+ val a = 1
+ // SIBLING:
+ fun b(): Boolean = a > 0
+
+ if (b()) {
+ fun bool(): Int { return 0 }
+ println(bool())
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt
index 7cb42b9d9395..8525520d6372 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt
@@ -6,4 +6,6 @@ object A {
fun bar() {
A.foo()
-}
\ No newline at end of file
+}
+
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after
index ec21b78fbb1b..4b69aa9b6f6e 100644
--- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after
@@ -8,4 +8,6 @@ fun bar() {
a().foo()
}
-private fun a() = A
\ No newline at end of file
+private fun a() = A
+
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt.fir.after
new file mode 100644
index 000000000000..35b45422f632
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt.fir.after
@@ -0,0 +1,13 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
+// SIBLING:
+fun foo(a: Int): Int {
+ if (bool(a)) return a
+ return -a
+}
+
+private fun bool(a: Int): Boolean {
+ println(a)
+ if (a > 0) return true
+ return false
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after
new file mode 100644
index 000000000000..9d66d7a80952
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after
@@ -0,0 +1,23 @@
+// SUGGESTED_NAMES: s, getA
+// PARAM_TYPES: kotlin.String
+// PARAM_TYPES: kotlin.String, kotlin.Comparable, kotlin.CharSequence, java.io.Serializable, kotlin.Any
+// PARAM_TYPES: kotlin.String, kotlin.Comparable, kotlin.CharSequence, java.io.Serializable, kotlin.Any
+// PARAM_DESCRIPTOR: local final fun kotlin.String.``(): kotlin.Unit defined in Foo.foo.``.``, local final fun kotlin.String.``(): kotlin.Unit defined in Foo.foo.`