mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] k2: extract function refactoring
Refactoring is done in phases: I. Parameters 1. all references in the selected fragment are resolved and marked 2. temp anonymous function is created and the selected fragment is pasted there 3. all references in temp fragment is resolved, and for each reference which resolves differently a parameter is created 4. parameter's type is calculated based on the type of the initial expression, the minimal applicable type is to be calculated (KTIJ-29166) 5. parameter types are checked if they are visible from the new target and iff not, a conflict message is shown II. Return type 1. Jumps & returns are analyzed to check if extraction is possible. Conflict is shown e.g., if jumps point to the different targets or jumps and returns coexist. 2. Return type is detected by the type of return statement, last selected statement or jump statement 3. Return type is checked if it's visible and iff not, a conflict message is shown III. Generation 1. Signature is build based on defined parameters and return type; set of names is generated based on return type 2. Body is created from selected fragment with replacements for jumps/returns IV. Duplicates are processed 1. Not implemented yet (KTIJ-29165) ^KTIJ-26164 fixed GitOrigin-RevId: 1398693c73b2c7d5c7fe08712de0bb8777fa1810
This commit is contained in:
committed by
intellij-monorepo-bot
parent
49298bfdaf
commit
6e52013a66
@@ -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
|
||||
progress.title.calculate.occurrences=Calculate occurrences\u2026
|
||||
progress.title.check.for.conflicts=Check for conflicts\u2026
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+5
@@ -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");
|
||||
|
||||
+1
@@ -47,5 +47,6 @@
|
||||
<orderEntry type="module" module-name="intellij.java.analysis.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.jdom" />
|
||||
<orderEntry type="module" module-name="kotlin.refactorings.common" />
|
||||
</component>
|
||||
</module>
|
||||
+1
-17
@@ -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
|
||||
}
|
||||
+2
-4
@@ -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)
|
||||
|
||||
+5
-2
@@ -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 (???)
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+1
-1
@@ -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.*
|
||||
|
||||
+25
-109
@@ -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<KotlinType, ExtractionResult, ExtractableCodeDescriptorWithConflicts> {
|
||||
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<Throwable> {
|
||||
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<AbstractInplaceExtractionHelper>.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<KotlinPsiRange, () -> 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<KotlinType>, 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(
|
||||
|
||||
+4
-1
@@ -30,7 +30,10 @@ open class ExtractFunctionParameterTablePanel : AbstractParameterTablePanel<Para
|
||||
name = if (isReceiver) KotlinBundle.message("text.receiver") else originalParameter.name
|
||||
}
|
||||
|
||||
override fun toParameter() = originalParameter.copy(name, type)
|
||||
override fun toParameter() = object : Parameter by originalParameter {
|
||||
override val name: String = this@ParameterInfo.name
|
||||
override val parameterType: KotlinType = this@ParameterInfo.type
|
||||
}
|
||||
}
|
||||
|
||||
override fun createTableModel(): TableModelBase = MyTableModel()
|
||||
|
||||
+4
-3
@@ -38,6 +38,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jetbrains.kotlin.idea.refactoring.KotlinCommonRefactoringUtilKt.checkConflictsInteractively;
|
||||
import static org.jetbrains.kotlin.idea.util.NonblockingKt.nonBlocking;
|
||||
|
||||
public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
@@ -81,7 +82,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
}
|
||||
|
||||
private boolean isVisibilitySectionAvailable() {
|
||||
return ExtractableAnalysisUtilKt.isVisibilityApplicable(originalDescriptor.getDescriptor().getExtractionData());
|
||||
return ExtractUtilKt.isVisibilityApplicable(originalDescriptor.getDescriptor().getExtractionData());
|
||||
}
|
||||
|
||||
private String getFunctionName() {
|
||||
@@ -109,7 +110,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
|
||||
setOKActionEnabled(checkNames());
|
||||
signaturePreviewField.setText(
|
||||
ExtractorUtilKt.getSignaturePreview(getCurrentConfiguration(), IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS)
|
||||
ExtractorUtilKt.getSignaturePreview(getCurrentConfiguration())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -222,7 +223,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
MultiMap<PsiElement, String> conflicts = ((ExtractableCodeDescriptorWithConflicts) result).getConflicts();
|
||||
conflicts.values().removeAll(originalDescriptor.getConflicts().values());
|
||||
|
||||
KotlinRefactoringUtilKt.checkConflictsInteractively(
|
||||
checkConflictsInteractively(
|
||||
project,
|
||||
conflicts,
|
||||
new Function0<>() {
|
||||
|
||||
+55
-473
@@ -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<KotlinType> {
|
||||
val originalDescriptor: DeclarationDescriptor
|
||||
val name: String
|
||||
val mirrorVarName: String?
|
||||
val receiverCandidate: Boolean
|
||||
|
||||
val parameterType: KotlinType
|
||||
|
||||
fun getParameterTypeCandidates(): List<KotlinType>
|
||||
|
||||
fun copy(name: String, parameterType: KotlinType): Parameter
|
||||
}
|
||||
|
||||
val Parameter.nameForRef: String get() = mirrorVarName ?: name
|
||||
|
||||
data class TypeParameter(
|
||||
val originalDeclaration: KtTypeParameter,
|
||||
val originalConstraints: List<KtTypeConstraint>
|
||||
)
|
||||
|
||||
interface Replacement : Function2<ExtractableCodeDescriptor, KtElement, KtElement>
|
||||
|
||||
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<KtExpression>
|
||||
val valueType: KotlinType
|
||||
|
||||
class ExpressionValue(
|
||||
val callSiteReturn: Boolean,
|
||||
override val originalExpressions: List<KtExpression>,
|
||||
override val valueType: KotlinType
|
||||
) : OutputValue
|
||||
|
||||
class Jump(
|
||||
val elementsToReplace: List<KtExpression>,
|
||||
val elementToInsertAfterCall: KtElement?,
|
||||
val conditional: Boolean,
|
||||
builtIns: KotlinBuiltIns
|
||||
) : OutputValue {
|
||||
override val originalExpressions: List<KtExpression> get() = elementsToReplace
|
||||
override val valueType: KotlinType = with(builtIns) { if (conditional) booleanType else unitType }
|
||||
}
|
||||
|
||||
class ParameterUpdate(
|
||||
val parameter: Parameter,
|
||||
override val originalExpressions: List<KtExpression>
|
||||
) : OutputValue {
|
||||
override val valueType: KotlinType get() = parameter.parameterType
|
||||
}
|
||||
|
||||
class Initializer(
|
||||
val initializedDeclaration: KtProperty,
|
||||
override val valueType: KotlinType
|
||||
) : OutputValue {
|
||||
override val originalExpressions: List<KtExpression> get() = Collections.singletonList(initializedDeclaration)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
|
||||
val outputValueTypes: List<KotlinType> get() = outputValues.map { it.valueType }
|
||||
|
||||
abstract val returnType: KotlinType
|
||||
|
||||
protected abstract fun getBoxingExpressionPattern(arguments: List<KtExpression>): String?
|
||||
|
||||
abstract val boxingRequired: Boolean
|
||||
|
||||
fun getReturnExpression(arguments: List<KtExpression>, 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<OutputValue, String>
|
||||
|
||||
class AsTuple(
|
||||
outputValues: List<OutputValue>,
|
||||
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<KtExpression>): 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<OutputValue, String> {
|
||||
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<OutputValue>) : 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<KtExpression>): 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<OutputValue, String> {
|
||||
var i = 0
|
||||
return ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText[${i++}]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ControlFlow(
|
||||
val outputValues: List<OutputValue>,
|
||||
val boxerFactory: (List<OutputValue>) -> OutputValueBoxer,
|
||||
val declarationsToCopy: List<KtDeclaration>
|
||||
) {
|
||||
val outputValueBoxer = boxerFactory(outputValues)
|
||||
|
||||
val defaultOutputValue: ExpressionValue? = with(outputValues.filterIsInstance<ExpressionValue>()) {
|
||||
if (size > 1) throw IllegalArgumentException("Multiple expression values: ${outputValues.joinToString()}") else firstOrNull()
|
||||
}
|
||||
|
||||
val jumpOutputValue: Jump? = with(outputValues.filterIsInstance<Jump>()) {
|
||||
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<KotlinType>
|
||||
val ControlFlow<KotlinType>.possibleReturnTypes: List<KotlinType>
|
||||
get() {
|
||||
val returnType = outputValueBoxer.returnType
|
||||
return when {
|
||||
@@ -327,33 +41,46 @@ val ControlFlow.possibleReturnTypes: List<KotlinType>
|
||||
}
|
||||
}
|
||||
|
||||
fun ControlFlow.toDefault(): ControlFlow =
|
||||
copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue })
|
||||
|
||||
fun ControlFlow.copy(oldToNewParameters: Map<Parameter, Parameter>): ControlFlow {
|
||||
fun ControlFlow<KotlinType>.copy(oldToNewParameters: Map<Parameter, Parameter>): ControlFlow<KotlinType> {
|
||||
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<KotlinType>() {
|
||||
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<String>,
|
||||
val visibility: KtModifierKeywordToken?,
|
||||
val parameters: List<Parameter>,
|
||||
val receiverParameter: Parameter?,
|
||||
val typeParameters: List<TypeParameter>,
|
||||
val replacementMap: MultiMap<KtSimpleNameExpression, Replacement>,
|
||||
val controlFlow: ControlFlow,
|
||||
val returnType: KotlinType,
|
||||
val modifiers: List<KtKeywordToken> = emptyList(),
|
||||
override val suggestedNames: List<String>,
|
||||
override val visibility: KtModifierKeywordToken?,
|
||||
override val parameters: List<Parameter>,
|
||||
override val receiverParameter: Parameter?,
|
||||
override val typeParameters: List<TypeParameter>,
|
||||
override val replacementMap: MultiMap<KtSimpleNameExpression, IReplacement<KotlinType>>,
|
||||
override val controlFlow: ControlFlow<KotlinType>,
|
||||
override val returnType: KotlinType,
|
||||
override val modifiers: List<KtKeywordToken> = emptyList(),
|
||||
val annotations: List<AnnotationDescriptor> = emptyList(),
|
||||
val optInMarkers: List<FqName> = emptyList()
|
||||
) {
|
||||
val name: String get() = suggestedNames.firstOrNull() ?: ""
|
||||
val duplicates: List<DuplicateInfo> by lazy { findDuplicates() }
|
||||
override val optInMarkers: List<FqName> = emptyList()
|
||||
): IExtractableCodeDescriptor<KotlinType> {
|
||||
override val name: String get() = suggestedNames.firstOrNull() ?: ""
|
||||
override val duplicates: List<DuplicateInfo<KotlinType>> 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<String> = this.suggestedNames,
|
||||
visibility: KtModifierKeywordToken? = this.visibility,
|
||||
parameters: List<Parameter> = this.parameters,
|
||||
receiverParameter: Parameter? = this.receiverParameter,
|
||||
typeParameters: List<TypeParameter> = this.typeParameters,
|
||||
replacementMap: MultiMap<KtSimpleNameExpression, Replacement> = this.replacementMap,
|
||||
controlFlow: ControlFlow = this.controlFlow,
|
||||
returnType: KotlinType = this.returnType,
|
||||
modifiers: List<KtKeywordToken> = this.modifiers,
|
||||
annotations: List<AnnotationDescriptor> = this.annotations
|
||||
extractionData: ExtractionData = this.extractionData,
|
||||
originalContext: BindingContext = this.originalContext,
|
||||
suggestedNames: List<String> = this.suggestedNames,
|
||||
visibility: KtModifierKeywordToken? = this.visibility,
|
||||
parameters: List<Parameter> = this.parameters,
|
||||
receiverParameter: Parameter? = this.receiverParameter,
|
||||
typeParameters: List<TypeParameter> = this.typeParameters,
|
||||
replacementMap: MultiMap<KtSimpleNameExpression, IReplacement<KotlinType>> = this.replacementMap,
|
||||
controlFlow: ControlFlow<KotlinType> = this.controlFlow,
|
||||
returnType: KotlinType = this.returnType,
|
||||
modifiers: List<KtKeywordToken> = this.modifiers,
|
||||
annotations: List<AnnotationDescriptor> = this.annotations
|
||||
) = copy(
|
||||
extractionData,
|
||||
originalContext,
|
||||
@@ -417,7 +144,7 @@ fun ExtractableCodeDescriptor.copy(
|
||||
newReceiver: Parameter?,
|
||||
returnType: KotlinType?
|
||||
): ExtractableCodeDescriptor {
|
||||
val newReplacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
|
||||
val newReplacementMap = MultiMap.create<KtSimpleNameExpression, IReplacement<KotlinType>>()
|
||||
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<KtDeclaration>()
|
||||
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<ExtractionTarget> = 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<KotlinType>
|
||||
|
||||
data class ExtractionResult(
|
||||
val config: ExtractionGeneratorConfiguration,
|
||||
val declaration: KtNamedDeclaration,
|
||||
val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>
|
||||
) : Disposable {
|
||||
override val config: ExtractionGeneratorConfiguration,
|
||||
override val declaration: KtNamedDeclaration,
|
||||
override val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>
|
||||
) : IExtractionResult<KotlinType> {
|
||||
override fun dispose() = unmarkReferencesInside(declaration)
|
||||
}
|
||||
|
||||
class AnalysisResult(
|
||||
val descriptor: ExtractableCodeDescriptor?,
|
||||
val status: Status,
|
||||
val messages: List<ErrorMessage>
|
||||
) {
|
||||
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<String>? = null
|
||||
|
||||
fun addAdditionalInfo(info: List<String>): 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<PsiElement, String>
|
||||
): ExtractableCodeDescriptorWithConflictsResult()
|
||||
|
||||
data class ExtractableCodeDescriptorWithException(val exception: RuntimeException): ExtractableCodeDescriptorWithConflictsResult()
|
||||
override val descriptor: ExtractableCodeDescriptor,
|
||||
override val conflicts: MultiMap<PsiElement, String>
|
||||
): ExtractableCodeDescriptorWithConflictsResult(), IExtractableCodeDescriptorWithConflicts<KotlinType>
|
||||
|
||||
+31
-94
@@ -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<KotlinType>,
|
||||
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<PsiElement> = 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<PsiElement> = 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<KtDeclaration>()?.let {
|
||||
it is KtDeclarationWithBody || it is KtAnonymousInitializer
|
||||
} ?: false
|
||||
|
||||
val expressions = originalElements.filterIsInstance<KtExpression>()
|
||||
override val expressions: List<KtExpression> = originalElements.filterIsInstance<KtExpression>()
|
||||
|
||||
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<DeclarationDescriptor, ResolvedCall<*>>({ 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<DeclarationDescriptor, ResolvedCall<*>>(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<KtSimpleNameExpression>()
|
||||
} ?: 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<KotlinType> {
|
||||
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<ResolvedReferenceInfo> {
|
||||
val originalContext = bindingContext ?: return listOf()
|
||||
fun getBrokenReferencesInfo(body: KtBlockExpression): List<ResolvedReferenceInfo<DeclarationDescriptor, ResolvedCall<*>, KotlinType>> {
|
||||
val originalContext = bindingContext
|
||||
|
||||
val newReferences = body.collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null }
|
||||
|
||||
val context = body.analyze()
|
||||
|
||||
val referencesInfo = ArrayList<ResolvedReferenceInfo>()
|
||||
val referencesInfo = ArrayList<ResolvedReferenceInfo<DeclarationDescriptor, ResolvedCall<*>, KotlinType>>()
|
||||
for (newRef in newReferences) {
|
||||
val originalResolveResult = newRef.resolveResult ?: continue
|
||||
val originalResolveResult = newRef.resolveResult as? ResolveResult<DeclarationDescriptor, ResolvedCall<*>> ?: continue
|
||||
|
||||
val smartCast: KotlinType?
|
||||
val possibleTypes: Set<KotlinType>
|
||||
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<KtCallableReferenceExpression> { 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<KtSimpleNameExpression> { it.resolveResult = null }
|
||||
}
|
||||
}
|
||||
+10
-103
@@ -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<KotlinType, ExtractionData, ExtractionGeneratorConfiguration, ExtractionResult, ExtractableCodeDescriptor, ExtractableCodeDescriptorWithConflicts>(operationName) {
|
||||
|
||||
fun doRefactor(config: ExtractionGeneratorConfiguration, onFinish: (ExtractionResult) -> Unit = {}) {
|
||||
val project = config.descriptor.extractionData.project
|
||||
onFinish(project.executeWriteCommand<ExtractionResult>(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<ExtractableCodeDescriptorWithException>()?.let { throw it.exception }
|
||||
val validationResult = result.cast<ExtractableCodeDescriptorWithConflicts>()
|
||||
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<br/><br/><a href=\"EXTRACT\">${KotlinBundle.message("text.proceed.with.extraction")}</a>"
|
||||
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<KotlinType, ExtractionData, ExtractionGeneratorConfiguration, ExtractionResult, ExtractableCodeDescriptor, ExtractableCodeDescriptorWithConflicts>(helper) {
|
||||
override fun performAnalysis(extractionData: ExtractionData): AnalysisResult<KotlinType> {
|
||||
return extractionData.performAnalysis()
|
||||
}
|
||||
}
|
||||
|
||||
+277
-450
@@ -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<Instruction>):
|
||||
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<Instruction>,
|
||||
@@ -219,256 +209,33 @@ private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages(
|
||||
return declarations.sortedBy { it.textRange!!.startOffset }
|
||||
}
|
||||
|
||||
private fun ExtractionData.analyzeControlFlow(
|
||||
localInstructions: List<Instruction>,
|
||||
pseudocode: Pseudocode,
|
||||
module: ModuleDescriptor,
|
||||
bindingContext: BindingContext,
|
||||
modifiedVarDescriptors: Map<VariableDescriptor, List<KtExpression>>,
|
||||
options: ExtractionOptions,
|
||||
targetScope: LexicalScope?,
|
||||
parameters: Set<Parameter>
|
||||
): Pair<ControlFlow, ErrorMessage?> {
|
||||
val exitPoints = localInstructions.getExitPoints()
|
||||
|
||||
val valuedReturnExits = ArrayList<ReturnValueInstruction>()
|
||||
val defaultExits = ArrayList<Instruction>()
|
||||
val jumpExits = ArrayList<AbstractJumpInstruction>()
|
||||
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<OutputValue>()
|
||||
|
||||
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<OutputValue>) -> 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<KtNamedDeclaration>(
|
||||
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<KotlinType> {
|
||||
if (!processTypeArguments) return Collections.singletonList(this)
|
||||
return dfsFromNode(
|
||||
this,
|
||||
Neighbors { current -> current.arguments.map { it.type } },
|
||||
VisitedWithSet(),
|
||||
object : CollectingNodeHandler<KotlinType, KotlinType, ArrayList<KotlinType>>(ArrayList()) {
|
||||
override fun afterChildren(current: KotlinType) {
|
||||
result.add(current)
|
||||
}
|
||||
}
|
||||
)!!
|
||||
}
|
||||
|
||||
fun KtTypeParameter.collectRelevantConstraints(): List<KtTypeConstraint> {
|
||||
val typeConstraints = getNonStrictParentOfType<KtTypeParameterListOwner>()?.typeConstraints ?: return Collections.emptyList()
|
||||
return typeConstraints.filter { it.subjectTypeParameterName?.mainReference?.resolve() == this }
|
||||
}
|
||||
|
||||
fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List<KotlinType> {
|
||||
val typeRefs = ArrayList<KtTypeReference>()
|
||||
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<TypeParameter>,
|
||||
nonDenotableTypes: MutableSet<KotlinType>,
|
||||
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<KotlinType>
|
||||
) : Parameter {
|
||||
) : Parameter, IMutableParameter<KotlinType> {
|
||||
// All modifications happen in the same thread
|
||||
private var writable: Boolean = true
|
||||
private val defaultTypes = LinkedHashSet<KotlinType>()
|
||||
private val typePredicates = HashSet<TypePredicate>()
|
||||
|
||||
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<KtNamedDeclaration>()
|
||||
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<KtDeclaration>() == 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<Instruction> {
|
||||
private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode?): List<Instruction>? {
|
||||
if (pseudocode == null) return null
|
||||
val instructions = ArrayList<Instruction>()
|
||||
pseudocode.traverse(TraversalOrder.FORWARD) {
|
||||
if (it is KtElementInstruction && it.element.isInsideOf(physicalElements)) {
|
||||
@@ -595,29 +326,6 @@ private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode): List<In
|
||||
return instructions
|
||||
}
|
||||
|
||||
fun ExtractionData.isLocal(): Boolean {
|
||||
val parent = targetSibling.parent
|
||||
return parent !is KtClassBody && (parent !is KtFile || parent.isScript())
|
||||
}
|
||||
|
||||
fun ExtractionData.isVisibilityApplicable(): Boolean {
|
||||
if (isLocal()) return false
|
||||
if (commonParent.parentsWithSelf.any { it is KtNamedFunction && it.hasModifier(KtTokens.INLINE_KEYWORD) && it.isPublic }) return false
|
||||
return true
|
||||
}
|
||||
|
||||
fun ExtractionData.getDefaultVisibility(): KtModifierKeywordToken? {
|
||||
if (!isVisibilityApplicable()) return null
|
||||
|
||||
val parent = targetSibling.getStrictParentOfType<KtDeclaration>()
|
||||
if (parent is KtClass) {
|
||||
if (parent.isInterface()) return null
|
||||
if (parent.isEnum() && commonParent.getNonStrictParentOfType<KtEnumEntry>()?.getStrictParentOfType<KtClass>() == parent) return null
|
||||
}
|
||||
|
||||
return KtTokens.PRIVATE_KEYWORD
|
||||
}
|
||||
|
||||
private data class ExperimentalMarkers(
|
||||
val propagatingMarkerDescriptors: List<AnnotationDescriptor>,
|
||||
val optInMarkers: List<FqName>
|
||||
@@ -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<KotlinType> {
|
||||
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>): KotlinType {
|
||||
return TypeUtils.substituteParameters(
|
||||
module.builtIns.list,
|
||||
Collections.singletonList(CommonSupertypes.commonSupertype(argTypes))
|
||||
)
|
||||
}
|
||||
|
||||
val virtualBlock = createTemporaryCodeBlock()
|
||||
override fun createTuple(outputValues: List<OutputValue<KotlinType>>): 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<KotlinType> {
|
||||
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<KotlinType>): 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<TypeParameter>
|
||||
): 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<KotlinType> {
|
||||
override fun suggestNamesByType(
|
||||
kotlinType: KotlinType,
|
||||
container: KtElement,
|
||||
validator: (String) -> Boolean,
|
||||
defaultName: String?
|
||||
): List<String> {
|
||||
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<KotlinType, MutableParameter>(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<KtNamedDeclaration> {
|
||||
return extractionData.getLocalDeclarationsWithNonLocalUsages(pseudocode!!, localInstructions!!, extractionData.bindingContext)
|
||||
}
|
||||
|
||||
override fun getVarDescriptorsAccessedAfterwards(): Set<String> {
|
||||
return localInstructions!!.getVarDescriptorsAccessedAfterwards(extractionData.bindingContext).map { it.name.asString() }.toSet()
|
||||
}
|
||||
|
||||
override fun getModifiedVars(): Map<String, List<KtExpression>> {
|
||||
return localInstructions!!.getModifiedVarDescriptors(extractionData.bindingContext).mapKeys { it.key.name.asString() }
|
||||
}
|
||||
|
||||
override fun createOutputDescriptor(): OutputDescriptor<KotlinType> {
|
||||
return extractionData.createOutputDescriptor(pseudocode!!, localInstructions!!, targetScope)
|
||||
}
|
||||
|
||||
override val nameSuggester: IExtractionNameSuggester<KotlinType> = ExtractNameSuggester
|
||||
|
||||
override val typeDescriptor: TypeDescriptor<KotlinType> = KotlinTypeDescriptor(extractionData)
|
||||
|
||||
override fun inferParametersInfo(
|
||||
virtualBlock: KtBlockExpression,
|
||||
modifiedVariables: Set<String>
|
||||
): ParametersInfo<KotlinType, MutableParameter> {
|
||||
return extractionData.inferParametersInfo(
|
||||
virtualBlock,
|
||||
pseudocode!!,
|
||||
extractionData.bindingContext,
|
||||
targetScope,
|
||||
modifiedVarDescriptorsWithExpressions!!.keys
|
||||
)
|
||||
}
|
||||
|
||||
override fun createDescriptor(
|
||||
suggestedFunctionNames: List<String>,
|
||||
defaultVisibility: KtModifierKeywordToken?,
|
||||
parameters: List<MutableParameter>,
|
||||
receiverParameter: MutableParameter?,
|
||||
typeParameters: List<TypeParameter>,
|
||||
replacementMap: MultiMap<KtSimpleNameExpression, IReplacement<KotlinType>>,
|
||||
flow: ControlFlow<KotlinType>,
|
||||
returnType: KotlinType
|
||||
): IExtractableCodeDescriptor<KotlinType> {
|
||||
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<KotlinType> {
|
||||
return ExtractionDataAnalyzer(this).performAnalysis()
|
||||
}
|
||||
|
||||
private fun ExtractionData.createOutputDescriptor(pseudocode: Pseudocode, localInstructions: List<Instruction>, targetScope: LexicalScope): OutputDescriptor<KotlinType> {
|
||||
|
||||
val valuedReturnExits = ArrayList<ReturnValueInstruction>()
|
||||
val defaultExits = ArrayList<Instruction>()
|
||||
val jumpExits = ArrayList<AbstractJumpInstruction>()
|
||||
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<ErrorMessage>()
|
||||
|
||||
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<KtDeclaration>()?.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<Parameter>()) { 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<String> {
|
||||
val functionNames = LinkedHashSet<String>()
|
||||
|
||||
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<KtProperty>()
|
||||
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<DeclarationDescriptor, ResolvedCall<*>>?: return
|
||||
if (currentRefExpr.parent is KtThisExpression) return
|
||||
|
||||
val diagnostics = bindingContext.diagnostics.forElement(currentRefExpr)
|
||||
|
||||
+41
-581
@@ -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<KtSimpleNameEx
|
||||
return from.collectDescendantsOfType<KtSimpleNameExpression>().zip(to.collectDescendantsOfType<KtSimpleNameExpression>()).toMap()
|
||||
}
|
||||
|
||||
class DuplicateInfo(
|
||||
val range: KotlinPsiRange,
|
||||
val controlFlow: ControlFlow,
|
||||
val arguments: List<String>
|
||||
)
|
||||
|
||||
fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> {
|
||||
fun processWeakMatch(match: WeakSuccess<*>, newControlFlow: ControlFlow): Boolean {
|
||||
|
||||
fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo<KotlinType>> {
|
||||
fun processWeakMatch(match: WeakSuccess<*>, newControlFlow: ControlFlow<KotlinType>): Boolean {
|
||||
val valueCount = controlFlow.outputValues.size
|
||||
|
||||
val weakMatches = HashMap(match.weakMatches)
|
||||
val currentValuesToNew = HashMap<OutputValue, OutputValue>()
|
||||
val currentValuesToNew = HashMap<OutputValue<KotlinType>, OutputValue<KotlinType>>()
|
||||
|
||||
fun matchValues(currentValue: OutputValue, newValue: OutputValue): Boolean {
|
||||
fun matchValues(currentValue: OutputValue<KotlinType>, newValue: OutputValue<KotlinType>): 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<DuplicateInfo> {
|
||||
return currentValuesToNew.size == valueCount && weakMatches.isEmpty()
|
||||
}
|
||||
|
||||
fun getControlFlowIfMatched(match: KotlinPsiUnificationResult.Success<*>): ControlFlow? {
|
||||
fun getControlFlowIfMatched(match: KotlinPsiUnificationResult.Success<*>): ControlFlow<KotlinType>? {
|
||||
val analysisResult = extractionData.copy(originalRange = match.range).performAnalysis()
|
||||
if (analysisResult.status != AnalysisResult.Status.SUCCESS) return null
|
||||
|
||||
@@ -270,237 +121,23 @@ fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> {
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? {
|
||||
return extractionData.duplicateContainer ?: extractionData.targetSibling.parent
|
||||
}
|
||||
private object Generator: ExtractFunctionGenerator<KotlinType, ExtractionResult>() {
|
||||
override val nameGenerator: IExtractionNameSuggester<KotlinType> = ExtractNameSuggester
|
||||
|
||||
private fun makeCall(
|
||||
extractableDescriptor: ExtractableCodeDescriptor,
|
||||
declaration: KtNamedDeclaration,
|
||||
controlFlow: ControlFlow,
|
||||
rangeToReplace: KotlinPsiRange,
|
||||
arguments: List<String>
|
||||
) {
|
||||
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<KtLambdaArgument>()!!
|
||||
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<KotlinType> {
|
||||
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<KotlinType>.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<KtDeclaration, KtDeclaration>()
|
||||
for (decl in controlFlow.declarationsToCopy) {
|
||||
val declCopy = psiFactory.createDeclaration<KtDeclaration>(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<PsiElement> {
|
||||
return when (outputValue) {
|
||||
is ExpressionValue -> {
|
||||
val exprText = if (outputValue.callSiteReturn) {
|
||||
val firstReturn = outputValue.originalExpressions.asSequence().filterIsInstance<KtReturnExpression>().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<KtReturnExpression>() }
|
||||
|
||||
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<KtExpression> {
|
||||
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<KotlinType>): 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<KotlinType>): 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<KtExpression> { it.isJumpElementToReplace }.forEach {
|
||||
it.replace(replacingReturn)
|
||||
it.isJumpElementToReplace = false
|
||||
}
|
||||
}
|
||||
|
||||
body.collectDescendantsOfType<KtReturnExpression> { 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<KtSimpleNameExpression> { 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<KotlinType>.createExtractionResult(
|
||||
declaration: KtNamedDeclaration,
|
||||
duplicatesReplacer: Map<KotlinPsiRange, () -> 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<KotlinPsiRange, () -> 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)
|
||||
}
|
||||
+20
-36
@@ -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<KtSimpleNameExpression, MutableParameter>()
|
||||
val parameters = LinkedHashSet<MutableParameter>()
|
||||
val typeParameters = HashSet<TypeParameter>()
|
||||
val nonDenotableTypes = HashSet<KotlinType>()
|
||||
val replacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
|
||||
}
|
||||
|
||||
internal fun ExtractionData.inferParametersInfo(
|
||||
virtualBlock: KtBlockExpression,
|
||||
commonParent: PsiElement,
|
||||
pseudocode: Pseudocode,
|
||||
bindingContext: BindingContext,
|
||||
targetScope: LexicalScope,
|
||||
modifiedVarDescriptors: Set<VariableDescriptor>
|
||||
): ParametersInfo {
|
||||
val info = ParametersInfo()
|
||||
): ParametersInfo<KotlinType, MutableParameter> {
|
||||
val info = ParametersInfo<KotlinType, MutableParameter>()
|
||||
|
||||
val extractedDescriptorToParameter = LinkedHashMap<DeclarationDescriptor, MutableParameter>()
|
||||
|
||||
@@ -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<KtExpression>()!!,
|
||||
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<KotlinType, MutableParameter>,
|
||||
targetScope: LexicalScope,
|
||||
refInfo: ResolvedReferenceInfo,
|
||||
refInfo: ResolvedReferenceInfo<DeclarationDescriptor, ResolvedCall<*>, KotlinType>,
|
||||
extractedDescriptorToParameter: HashMap<DeclarationDescriptor, MutableParameter>,
|
||||
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
|
||||
|
||||
|
||||
+4
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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<PsiElement, String> conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts();
|
||||
KotlinRefactoringUtilKt.checkConflictsInteractively(
|
||||
checkConflictsInteractively(
|
||||
project,
|
||||
conflicts,
|
||||
new Function0<>() {
|
||||
|
||||
-20
@@ -124,26 +124,6 @@ fun PsiElement.getUsageContext(): PsiElement {
|
||||
fun PsiElement.isInKotlinAwareSourceRoot(): Boolean =
|
||||
!isOutsideKotlinAwareSourceRoot(containingFile)
|
||||
|
||||
fun Project.checkConflictsInteractively(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
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<PsiElement, String>,
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+112
-83
@@ -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<String>,
|
||||
expectedReturnTypes: List<String>,
|
||||
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<Any?>
|
||||
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<PsiFile, File> = emptyMap()
|
||||
) {
|
||||
constructor(path: String, mainFile: PsiFile, extraFilesToPsi: Map<PsiFile, File> = 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<Any?>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -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");
|
||||
|
||||
+8
@@ -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)
|
||||
ret<caret>urn false
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// SUGGESTED_RETURN_TYPES: kotlin.Boolean?, kotlin.Boolean
|
||||
// PARAM_DESCRIPTOR: value-parameter it: kotlin.collections.Map.Entry<kotlin.Boolean!, kotlin.Boolean!> defined in test.`<anonymous>`
|
||||
// PARAM_TYPES: kotlin.collections.Map.Entry<kotlin.Boolean!, kotlin.Boolean!>
|
||||
fun test() {
|
||||
J.getMap().filter { __dummyTestFun__(it) }
|
||||
}
|
||||
|
||||
private fun __dummyTestFun__(entry: Map.Entry<Boolean, Boolean>): Boolean? = entry.key
|
||||
+16
@@ -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<String, String>? = null
|
||||
|
||||
companion object {
|
||||
fun bar(arg: String) = Foo().apply {
|
||||
arguments = map(arg)
|
||||
}
|
||||
|
||||
private fun map(arg: String): Map<String, String> = mapOf(Foo::arguments.name to arg)
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -3,4 +3,6 @@
|
||||
|
||||
fun foo(x : String, y : String.() -> Unit) {
|
||||
x.<selection>y</selection>()
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -5,4 +5,6 @@ fun foo(x : String, y : String.() -> Unit) {
|
||||
x.(function(y))()
|
||||
}
|
||||
|
||||
private fun function(y: String.() -> Unit) = y
|
||||
private fun function(y: String.() -> Unit) = y
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -8,4 +8,6 @@ class A {
|
||||
|
||||
fun bar() {
|
||||
<selection>A</selection>.foo()
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -3,4 +3,6 @@
|
||||
|
||||
fun foo(x : String.(String) -> Unit) {
|
||||
"A" <selection>x</selection> "B"
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -5,4 +5,6 @@ fun foo(x : String.(String) -> Unit) {
|
||||
"A".(function(x))("B")
|
||||
}
|
||||
|
||||
private fun function(x: String.(String) -> Unit) = x
|
||||
private fun function(x: String.(String) -> Unit) = x
|
||||
|
||||
// IGNORE_K2
|
||||
|
||||
+7
@@ -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
|
||||
+7
@@ -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
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// PARAM_DESCRIPTOR: public suspend inline fun <reified T> ExtendMe.receive(): T defined in root package in file extractSuspend.kt
|
||||
// PARAM_TYPES: ExtendMe
|
||||
suspend inline fun <reified T> ExtendMe.receive(): T = any() as T
|
||||
|
||||
suspend fun ExtendMe.any(): Any = receive("")
|
||||
|
||||
class ExtendMe {
|
||||
suspend fun receive(s: String): Any {
|
||||
return Any()
|
||||
}
|
||||
}
|
||||
+12
@@ -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
|
||||
}
|
||||
+14
@@ -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
|
||||
}
|
||||
+14
@@ -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
|
||||
}
|
||||
+16
@@ -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
|
||||
}
|
||||
+11
@@ -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<kotlin.Int>, java.io.Serializable, kotlin.Any
|
||||
// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable<kotlin.Int>, 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
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
interface T{
|
||||
fun foo(): Int {
|
||||
return i()
|
||||
}
|
||||
|
||||
fun i(): Int = 1
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Class A.Inner will become invisible after extraction Function Inner.foo() will become invisible after extraction
|
||||
+1
@@ -0,0 +1 @@
|
||||
Following declarations are used outside of selected code fragment: class A(n: Int) constructor(n: Int) n: Int
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fun foo(a: Int): Int {
|
||||
// SIBLING:
|
||||
fun i(): Int = a + 10
|
||||
|
||||
return i()
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Following declarations are used outside of selected code fragment: fun bar(): Int
|
||||
+14
@@ -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
|
||||
+17
@@ -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
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fun foo() {
|
||||
val a = 1
|
||||
// SIBLING:
|
||||
fun bool(): Boolean = a > 0
|
||||
|
||||
if (bool()) {
|
||||
fun b(): Int { return 0 }
|
||||
println(b())
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fun foo() {
|
||||
val a = 1
|
||||
// SIBLING:
|
||||
if (<selection>a > 0</selection>) {
|
||||
fun bool(): Int { return 0 }
|
||||
println(bool())
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fun foo() {
|
||||
val a = 1
|
||||
// SIBLING:
|
||||
fun b() = a > 0
|
||||
|
||||
if (b()) {
|
||||
fun bool(): Int { return 0 }
|
||||
println(bool())
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fun foo() {
|
||||
val a = 1
|
||||
// SIBLING:
|
||||
fun b(): Boolean = a > 0
|
||||
|
||||
if (b()) {
|
||||
fun bool(): Int { return 0 }
|
||||
println(bool())
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -6,4 +6,6 @@ object A {
|
||||
|
||||
fun bar() {
|
||||
<selection>A</selection>.foo()
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -8,4 +8,6 @@ fun bar() {
|
||||
a().foo()
|
||||
}
|
||||
|
||||
private fun a() = A
|
||||
private fun a() = A
|
||||
|
||||
// IGNORE_K2
|
||||
+13
@@ -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
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// SUGGESTED_NAMES: s, getA
|
||||
// PARAM_TYPES: kotlin.String
|
||||
// PARAM_TYPES: kotlin.String, kotlin.Comparable<kotlin.String>, kotlin.CharSequence, java.io.Serializable, kotlin.Any
|
||||
// PARAM_TYPES: kotlin.String, kotlin.Comparable<kotlin.String>, kotlin.CharSequence, java.io.Serializable, kotlin.Any
|
||||
// PARAM_DESCRIPTOR: local final fun kotlin.String.`<anonymous>`(): kotlin.Unit defined in Foo.foo.`<anonymous>`.`<anonymous>`, local final fun kotlin.String.`<anonymous>`(): kotlin.Unit defined in Foo.foo.`<anonymous>`, local final fun kotlin.String.`<anonymous>`(): kotlin.Unit defined in Foo.foo
|
||||
class Foo {
|
||||
fun foo() {
|
||||
block("a") a@ {
|
||||
block("b") b@ {
|
||||
block("c") c@ {
|
||||
val a = string(this@c, this@b, this@a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun string(string: String, string0: String, string1: String): String =
|
||||
string + string0 + string1 + string + this@Foo.toString()
|
||||
}
|
||||
|
||||
private inline fun <T> block(t: T, block: T.() -> Unit) {
|
||||
t.block()
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SUGGESTED_NAMES: i, getKm
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: public val meters: kotlin.Int? defined in root package in file topLevelValUnderSmartCast.kt
|
||||
val meters: Int? = 1
|
||||
|
||||
fun test() {
|
||||
if (meters == null) return
|
||||
val km = i(meters)
|
||||
}
|
||||
|
||||
private fun i(meters: Int): Int = meters / 10
|
||||
+1
@@ -0,0 +1 @@
|
||||
Function A.unresolved() on B will no longer be accessible after extraction
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
for (n in 1..a) {
|
||||
if (bool(a, b)) break
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a + b > 0) return true
|
||||
println(a - b)
|
||||
if (a - b > 0) return true
|
||||
println(a + b)
|
||||
return false
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
for (n in 1..a) {
|
||||
val c: Int
|
||||
if (bool(a, b)) break
|
||||
c = 1
|
||||
println(c)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a + b > 0) return true
|
||||
val c: Int
|
||||
println(a - b)
|
||||
if (a - b > 0) return true
|
||||
println(a + b)
|
||||
return false
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
for (n in 1..a) {
|
||||
if (bool(a, b)) break
|
||||
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a + b > 0) return true
|
||||
else {
|
||||
println(a - b)
|
||||
if (a - b > 0) return true else println(a + b)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
for (n in 1..a) {
|
||||
if (bool(a, b)) break
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
when {
|
||||
a + b > 0 -> return true
|
||||
a - b > 0 -> return true
|
||||
else -> println(0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
if (bool(a, b)) return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a + b > 0) return true
|
||||
println(a - b)
|
||||
return false
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
if (bool(a, b)) return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a + b > 0) return true
|
||||
else if (a - b < 0) println(a - b)
|
||||
else println(0)
|
||||
return false
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
if (bool(a, b)) return 0
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
when (a + b) {
|
||||
0 -> return true
|
||||
1 -> println(1)
|
||||
else -> println(2)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+3
-1
@@ -11,4 +11,6 @@ fun foo(a: Int): Int {
|
||||
break</selection>
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -16,4 +16,6 @@ private fun __dummyTestFun__(a: Int, b: Int) {
|
||||
if (a + b > 0) return
|
||||
println(a - b)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// WITH_STDLIB
|
||||
// SIBLING:
|
||||
fun foo(a: Int) {
|
||||
val b: Int = 1
|
||||
|
||||
__dummyTestFun__(a, b)
|
||||
}
|
||||
|
||||
private fun __dummyTestFun__(a: Int, b: Int) {
|
||||
if (a > 0) {
|
||||
println(a)
|
||||
}
|
||||
println(b)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// WITH_STDLIB
|
||||
// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`<anonymous>`
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
fun foo(a: Int): Int {
|
||||
a.let {
|
||||
return i(it)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun i(i: Int): Int {
|
||||
if (i > 0) return i else return -i
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// WITH_STDLIB
|
||||
// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`<anonymous>`
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
fun foo(a: Int): Int {
|
||||
a.let {
|
||||
return@foo i(it)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun i(i: Int): Int {
|
||||
if (i > 0) return i else return -i
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`<anonymous>`
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
fun foo(): Int {
|
||||
1.let { return i(it) }
|
||||
}
|
||||
|
||||
private fun i(i: Int): Int {
|
||||
return i + 1
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`<anonymous>`
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
fun foo(): Int {
|
||||
1.let { return@foo i(it) }
|
||||
}
|
||||
|
||||
private fun i(i: Int): Int {
|
||||
return i + 1
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return if (bool(a, b)) 1 else if (a - b < 0) 2 else b
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean = a + b > 0
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return if (a + b > 0) 1 else i(a, b)
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = if (a - b < 0) 2 else b
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return if (a + b > 0) i() else if (a - b < 0) 2 else b
|
||||
}
|
||||
|
||||
private fun i(): Int = 1
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return when (a + b) {
|
||||
0 -> i(b)
|
||||
1 -> -b
|
||||
else -> a - b
|
||||
}
|
||||
}
|
||||
|
||||
private fun i(b: Int): Int = b
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return when (a + b) {
|
||||
i() -> b
|
||||
1 -> -b
|
||||
else -> a - b
|
||||
}
|
||||
}
|
||||
|
||||
private fun i(): Int = 0
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return when (i(a, b)) {
|
||||
0 -> b
|
||||
1 -> -b
|
||||
else -> a - b
|
||||
}
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = a + b
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int, b: Int): Int = i(a, b)
|
||||
|
||||
private fun i(a: Int, b: Int): Int = a + b
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return i(a, b)
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = if (a + b > 0) 1 else if (a - b < 0) 2 else b
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return i(a, b)
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = when (a + b) {
|
||||
0 -> b
|
||||
1 -> -b
|
||||
else -> a - b
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
return i(a, b)
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = a + b
|
||||
+3
-1
@@ -11,4 +11,6 @@ fun foo(a: Int) {
|
||||
println(a - b)
|
||||
return</selection>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -16,4 +16,6 @@ private fun __dummyTestFun__(a: Int, b: Int) {
|
||||
if (a + b > 0) return
|
||||
println(a - b)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -12,4 +12,6 @@ fun foo(a: Int) {
|
||||
if (a - b > 0) continue@loop1</selection>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -17,4 +17,6 @@ private fun b(a: Int, b: Int): Boolean {
|
||||
if (a + b > 0) return true
|
||||
if (a - b > 0) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int) {
|
||||
val b: Int = 1
|
||||
for (n in 1..b) {
|
||||
if (bool(a, b)) break
|
||||
}
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a > 0) throw Exception("")
|
||||
if (a + b > 0) return true
|
||||
println(a - b)
|
||||
return false
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int) {
|
||||
val b: Int = 1
|
||||
|
||||
__dummyTestFun__(a, b)
|
||||
}
|
||||
|
||||
private fun __dummyTestFun__(a: Int, b: Int) {
|
||||
if (a > 0) throw Exception("")
|
||||
if (b + a > 0) return
|
||||
println(a - b)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_STDLIB
|
||||
// SUGGESTED_NAMES: s, getS
|
||||
fun test() {
|
||||
val s = string()
|
||||
}
|
||||
|
||||
private fun string(): String = J.notNull()
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// SUGGESTED_NAMES: s, getS
|
||||
fun test() {
|
||||
val s = string()
|
||||
}
|
||||
|
||||
private fun string(): String? = J.nullable()
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// SUGGESTED_NAMES: s, getS
|
||||
// SUGGESTED_RETURN_TYPES: kotlin.String?, kotlin.String
|
||||
fun test() {
|
||||
val s = getS()
|
||||
}
|
||||
|
||||
private fun getS(): String? = J.unknown()
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
for (n in 1..b) {
|
||||
if (bool(a, b)) break
|
||||
}
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a > 0) throw Exception("")
|
||||
if (a + b > 0) return true
|
||||
println(a - b)
|
||||
return false
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
for (n in 1..b) {
|
||||
if (bool(a, b)) continue
|
||||
println(a + b)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bool(a: Int, b: Int): Boolean {
|
||||
if (a > 0) throw Exception("")
|
||||
if (a + b > 0) return true
|
||||
println(a - b)
|
||||
return false
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// SUGGESTED_NAMES: i, getT
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo
|
||||
// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
val b: Int = 1
|
||||
|
||||
val t = i(a, b)
|
||||
return t
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = if (a > 0) throw Exception("") else a + b
|
||||
+3
-1
@@ -10,4 +10,6 @@ fun foo(a: Int) {
|
||||
if (b + a > 0) return
|
||||
println(a - b)</selection>
|
||||
println(a + b)
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+3
-1
@@ -15,4 +15,6 @@ private fun b(a: Int, b: Int): Boolean {
|
||||
if (b + a > 0) return true
|
||||
println(a - b)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IGNORE_K2
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
class A {
|
||||
fun foo(a: Int, b: Int): Int {
|
||||
return object: Function0<Int> {
|
||||
override fun invoke(): Int {
|
||||
return i()
|
||||
}
|
||||
|
||||
private fun i(): Int = a + b - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.B.foo
|
||||
// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.B.foo
|
||||
class A {
|
||||
class B {
|
||||
fun foo(a: Int, b: Int): Int {
|
||||
return i(a, b)
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = a + b - 1
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo
|
||||
// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.foo
|
||||
class A {
|
||||
fun foo(a: Int, b: Int): Int {
|
||||
return {
|
||||
i(a, b)
|
||||
}.invoke()
|
||||
}
|
||||
|
||||
private fun i(a: Int, b: Int): Int = a + b - 1
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
class A {
|
||||
fun foo(a: Int, b: Int): Int {
|
||||
class L: Function0<Int> {
|
||||
override fun invoke(): Int {
|
||||
return i()
|
||||
}
|
||||
|
||||
private fun i(): Int = a + b - 1
|
||||
}
|
||||
|
||||
return L().invoke()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user