[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:
Anna Kozlova
2024-03-27 19:29:45 +00:00
committed by intellij-monorepo-bot
parent 49298bfdaf
commit 6e52013a66
278 changed files with 9397 additions and 2038 deletions
@@ -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
}
}
@@ -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");
@@ -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>
@@ -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
}
@@ -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)
@@ -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
@@ -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
@@ -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.*
@@ -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(
@@ -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()
@@ -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<>() {
@@ -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>
@@ -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 }
}
}
@@ -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()
}
}
@@ -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)
@@ -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)
}
@@ -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
@@ -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
}
}
@@ -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<>() {
@@ -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>,
@@ -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");
@@ -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)
}
}
@@ -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");
@@ -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
}
@@ -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
@@ -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,4 +3,6 @@
fun foo(x : String, y : String.() -> Unit) {
x.<selection>y</selection>()
}
}
// IGNORE_K2
@@ -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
@@ -8,4 +8,6 @@ class A {
fun bar() {
<selection>A</selection>.foo()
}
}
// IGNORE_K2
@@ -3,4 +3,6 @@
fun foo(x : String.(String) -> Unit) {
"A" <selection>x</selection> "B"
}
}
// IGNORE_K2
@@ -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
@@ -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
@@ -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
@@ -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()
}
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
@@ -0,0 +1,7 @@
interface T{
fun foo(): Int {
return i()
}
fun i(): Int = 1
}
@@ -0,0 +1 @@
Class A.Inner will become invisible after extraction Function Inner.foo() will become invisible after extraction
@@ -0,0 +1 @@
Following declarations are used outside of selected code fragment: class A(n: Int) constructor(n: Int) n: Int
@@ -0,0 +1,6 @@
fun foo(a: Int): Int {
// SIBLING:
fun i(): Int = a + 10
return i()
}
@@ -0,0 +1 @@
Following declarations are used outside of selected code fragment: fun bar(): Int
@@ -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
@@ -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
@@ -0,0 +1,10 @@
fun foo() {
val a = 1
// SIBLING:
fun bool(): Boolean = a > 0
if (bool()) {
fun b(): Int { return 0 }
println(b())
}
}
@@ -0,0 +1,8 @@
fun foo() {
val a = 1
// SIBLING:
if (<selection>a > 0</selection>) {
fun bool(): Int { return 0 }
println(bool())
}
}
@@ -0,0 +1,10 @@
fun foo() {
val a = 1
// SIBLING:
fun b() = a > 0
if (b()) {
fun bool(): Int { return 0 }
println(bool())
}
}
@@ -0,0 +1,10 @@
fun foo() {
val a = 1
// SIBLING:
fun b(): Boolean = a > 0
if (b()) {
fun bool(): Int { return 0 }
println(bool())
}
}
@@ -6,4 +6,6 @@ object A {
fun bar() {
<selection>A</selection>.foo()
}
}
// IGNORE_K2
@@ -8,4 +8,6 @@ fun bar() {
a().foo()
}
private fun a() = A
private fun a() = A
// IGNORE_K2
@@ -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
}
@@ -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()
}
@@ -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
@@ -0,0 +1 @@
Function A.unresolved() on B will no longer be accessible after extraction
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -11,4 +11,6 @@ fun foo(a: Int): Int {
break</selection>
}
return 1
}
}
// IGNORE_K2
@@ -16,4 +16,6 @@ private fun __dummyTestFun__(a: Int, b: Int) {
if (a + b > 0) return
println(a - b)
return
}
}
// IGNORE_K2
@@ -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)
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
}
@@ -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
@@ -11,4 +11,6 @@ fun foo(a: Int) {
println(a - b)
return</selection>
}
}
}
// IGNORE_K2
@@ -16,4 +16,6 @@ private fun __dummyTestFun__(a: Int, b: Int) {
if (a + b > 0) return
println(a - b)
return
}
}
// IGNORE_K2
@@ -12,4 +12,6 @@ fun foo(a: Int) {
if (a - b > 0) continue@loop1</selection>
}
}
}
}
// IGNORE_K2
@@ -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
@@ -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
}
@@ -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)
}
@@ -0,0 +1,7 @@
// WITH_STDLIB
// SUGGESTED_NAMES: s, getS
fun test() {
val s = string()
}
private fun string(): String = J.notNull()
@@ -0,0 +1,6 @@
// SUGGESTED_NAMES: s, getS
fun test() {
val s = string()
}
private fun string(): String? = J.nullable()
@@ -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()
@@ -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
}
@@ -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
}
@@ -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
@@ -10,4 +10,6 @@ fun foo(a: Int) {
if (b + a > 0) return
println(a - b)</selection>
println(a + b)
}
}
// IGNORE_K2
@@ -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
@@ -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
}
}
}
@@ -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
}
}
@@ -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
}
@@ -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