diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index e76c4fc37a9..748d5ea230a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -66,25 +66,25 @@ interface Parameter { val Parameter.nameForRef: String get() = mirrorVarName ?: name data class TypeParameter( - val originalDeclaration: KtTypeParameter, - val originalConstraints: List + val originalDeclaration: KtTypeParameter, + val originalConstraints: List ) -interface Replacement: Function2 +interface Replacement : Function2 interface ParameterReplacement : Replacement { val parameter: Parameter fun copy(parameter: Parameter): ParameterReplacement } -class RenameReplacement(override val 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 { - var expressionToReplace = (e.parent as? KtThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it } + 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 + if (e.text.startsWith('`') || !parameterName.isIdentifier()) "`$parameterName`" else parameterName val psiFactory = KtPsiFactory(e) val replacement = when { parameter == descriptor.receiverParameter -> psiFactory.createExpression("this") @@ -106,19 +106,19 @@ abstract class WrapInWithReplacement : Replacement { } } -class WrapParameterInWithReplacement(override val parameter: Parameter): WrapInWithReplacement(), ParameterReplacement { +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() { +class WrapObjectInWithReplacement(val descriptor: ClassDescriptor) : WrapInWithReplacement() { override val argumentText: String get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor) } -class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement { +class AddPrefixReplacement(override val parameter: Parameter) : ParameterReplacement { override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter) override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { @@ -131,7 +131,7 @@ class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacem } } -class FqNameReplacement(val fqName: FqName): Replacement { +class FqNameReplacement(val fqName: FqName) : Replacement { override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { val thisExpr = e.parent as? KtThisExpression if (thisExpr != null) { @@ -148,32 +148,32 @@ interface OutputValue { val valueType: KotlinType class ExpressionValue( - val callSiteReturn: Boolean, - override val originalExpressions: List, - override val valueType: KotlinType - ): OutputValue + val callSiteReturn: Boolean, + override val originalExpressions: List, + override val valueType: KotlinType + ) : OutputValue class Jump( - val elementsToReplace: List, - val elementToInsertAfterCall: KtElement?, - val conditional: Boolean, - builtIns: KotlinBuiltIns - ): OutputValue { + val elementsToReplace: List, + val elementToInsertAfterCall: KtElement?, + val conditional: Boolean, + builtIns: KotlinBuiltIns + ) : OutputValue { override val originalExpressions: List get() = elementsToReplace override val valueType: KotlinType = with(builtIns) { if (conditional) booleanType else unitType } } class ParameterUpdate( - val parameter: Parameter, - override val originalExpressions: List - ): OutputValue { + val parameter: Parameter, + override val originalExpressions: List + ) : OutputValue { override val valueType: KotlinType get() = parameter.getParameterType(false) } class Initializer( - val initializedDeclaration: KtProperty, - override val valueType: KotlinType - ): OutputValue { + val initializedDeclaration: KtProperty, + override val valueType: KotlinType + ) : OutputValue { override val originalExpressions: List get() = Collections.singletonList(initializedDeclaration) } } @@ -216,8 +216,8 @@ abstract class OutputValueBoxer(val outputValues: List) { abstract fun getUnboxingExpressions(boxedText: String): Map class AsTuple( - outputValues: List, - val module: ModuleDescriptor + outputValues: List, + val module: ModuleDescriptor ) : OutputValueBoxer(outputValues) { init { assert(outputValues.size <= 3) { "At most 3 output values are supported" } @@ -271,17 +271,17 @@ abstract class OutputValueBoxer(val outputValues: List) { } } - class AsList(outputValues: List): OutputValueBoxer(outputValues) { + class AsList(outputValues: List) : OutputValueBoxer(outputValues) { override val returnType: KotlinType by lazy { assert(outputValues.isNotEmpty()) val builtIns = outputValues.first().valueType.builtIns TypeUtils.substituteParameters( - builtIns.list, - Collections.singletonList(CommonSupertypes.commonSupertype(outputValues.map { it.valueType })) + builtIns.list, + Collections.singletonList(CommonSupertypes.commonSupertype(outputValues.map { it.valueType })) ) } - override val boxingRequired: Boolean = outputValues.size > 0 + override val boxingRequired: Boolean = outputValues.isNotEmpty() override fun getBoxingExpressionPattern(arguments: List): String? { if (arguments.isEmpty()) return null @@ -300,9 +300,9 @@ abstract class OutputValueBoxer(val outputValues: List) { } data class ControlFlow( - val outputValues: List, - val boxerFactory: (List) -> OutputValueBoxer, - val declarationsToCopy: List + val outputValues: List, + val boxerFactory: (List) -> OutputValueBoxer, + val declarationsToCopy: List ) { val outputValueBoxer = boxerFactory(outputValues) @@ -337,7 +337,7 @@ val ControlFlow.possibleReturnTypes: List } fun ControlFlow.toDefault(): ControlFlow = - copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue }) + copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue }) fun ControlFlow.copy(oldToNewParameters: Map): ControlFlow { val newOutputValues = outputValues.map { @@ -347,28 +347,28 @@ fun ControlFlow.copy(oldToNewParameters: Map): ControlFlow } data class ExtractableCodeDescriptor( - val extractionData: ExtractionData, - val originalContext: BindingContext, - val suggestedNames: List, - val visibility: KtModifierKeywordToken?, - val parameters: List, - val receiverParameter: Parameter?, - val typeParameters: List, - val replacementMap: MultiMap, - val controlFlow: ControlFlow, - val returnType: KotlinType, - val modifiers: List = emptyList() + val extractionData: ExtractionData, + val originalContext: BindingContext, + val suggestedNames: List, + val visibility: KtModifierKeywordToken?, + val parameters: List, + val receiverParameter: Parameter?, + val typeParameters: List, + val replacementMap: MultiMap, + val controlFlow: ControlFlow, + val returnType: KotlinType, + val modifiers: List = emptyList() ) { val name: String get() = suggestedNames.firstOrNull() ?: "" val duplicates: List by lazy { findDuplicates() } } fun ExtractableCodeDescriptor.copy( - newName: String, - newVisibility: KtModifierKeywordToken?, - oldToNewParameters: Map, - newReceiver: Parameter?, - returnType: KotlinType? + newName: String, + newVisibility: KtModifierKeywordToken?, + oldToNewParameters: Map, + newReceiver: Parameter?, + returnType: KotlinType? ): ExtractableCodeDescriptor { val newReplacementMap = MultiMap.create() for ((ref, replacements) in replacementMap.entrySet()) { @@ -377,24 +377,24 @@ fun ExtractableCodeDescriptor.copy( val parameter = it.parameter val newParameter = oldToNewParameters[parameter] ?: return@map it it.copy(newParameter) - } - else it + } else it } newReplacementMap.putValues(ref, newReplacements) } return ExtractableCodeDescriptor( - extractionData, - originalContext, - listOf(newName), - newVisibility, - oldToNewParameters.values.filter { it != newReceiver }, - newReceiver, - typeParameters, - newReplacementMap, - controlFlow.copy(oldToNewParameters), - returnType ?: this.returnType, - modifiers) + extractionData, + originalContext, + listOf(newName), + newVisibility, + oldToNewParameters.values.filter { it != newReceiver }, + newReceiver, + typeParameters, + newReplacementMap, + controlFlow.copy(oldToNewParameters), + returnType ?: this.returnType, + modifiers + ) } enum class ExtractionTarget(val targetName: String) { @@ -411,10 +411,10 @@ enum class ExtractionTarget(val targetName: String) { PROPERTY_WITH_INITIALIZER("property with initializer") { override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { return checkSignatureAndParent(descriptor) - && checkSimpleControlFlow(descriptor) - && checkSimpleBody(descriptor) - && checkNotTrait(descriptor) - && descriptor.receiverParameter == null + && checkSimpleControlFlow(descriptor) + && checkSimpleBody(descriptor) + && checkNotTrait(descriptor) + && descriptor.receiverParameter == null } }, @@ -427,9 +427,9 @@ enum class ExtractionTarget(val targetName: String) { LAZY_PROPERTY("lazy property") { override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { return checkSignatureAndParent(descriptor) - && checkSimpleControlFlow(descriptor) - && checkNotTrait(descriptor) - && descriptor.receiverParameter == null + && checkSimpleControlFlow(descriptor) + && checkNotTrait(descriptor) + && descriptor.receiverParameter == null } }; @@ -461,39 +461,42 @@ enum class ExtractionTarget(val targetName: String) { } } -val propertyTargets: List = listOf(ExtractionTarget.PROPERTY_WITH_INITIALIZER, - ExtractionTarget.PROPERTY_WITH_GETTER, - ExtractionTarget.LAZY_PROPERTY) +val propertyTargets: List = listOf( + ExtractionTarget.PROPERTY_WITH_INITIALIZER, + ExtractionTarget.PROPERTY_WITH_GETTER, + ExtractionTarget.LAZY_PROPERTY +) data class ExtractionGeneratorOptions( - val inTempFile: Boolean = false, - val target: ExtractionTarget = ExtractionTarget.FUNCTION, - val dummyName: String? = null, - val allowExpressionBody: Boolean = true, - val delayInitialOccurrenceReplacement: Boolean = false + val inTempFile: Boolean = false, + val target: ExtractionTarget = ExtractionTarget.FUNCTION, + val dummyName: String? = null, + val allowExpressionBody: Boolean = true, + val delayInitialOccurrenceReplacement: Boolean = false ) { companion object { - @JvmField val DEFAULT = ExtractionGeneratorOptions() + @JvmField + val DEFAULT = ExtractionGeneratorOptions() } } data class ExtractionGeneratorConfiguration( - val descriptor: ExtractableCodeDescriptor, - val generatorOptions: ExtractionGeneratorOptions + val descriptor: ExtractableCodeDescriptor, + val generatorOptions: ExtractionGeneratorOptions ) data class ExtractionResult( - val config: ExtractionGeneratorConfiguration, - val declaration: KtNamedDeclaration, - val duplicateReplacers: Map Unit> + val config: ExtractionGeneratorConfiguration, + val declaration: KtNamedDeclaration, + val duplicateReplacers: Map Unit> ) : Disposable { override fun dispose() = unmarkReferencesInside(declaration) } -class AnalysisResult ( - val descriptor: ExtractableCodeDescriptor?, - val status: Status, - val messages: List +class AnalysisResult( + val descriptor: ExtractableCodeDescriptor?, + val status: Status, + val messages: List ) { enum class Status { SUCCESS, @@ -523,27 +526,27 @@ class AnalysisResult ( fun renderMessage(): String { val message = KotlinRefactoringBundle.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" - } + 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") { StringUtil.htmlEmphasize(it) }}" } ?: message + return additionalInfo?.let { "$message\n\n${it.joinToString("\n") { msg -> StringUtil.htmlEmphasize(msg) }}" } ?: message } } } class ExtractableCodeDescriptorWithConflicts( - val descriptor: ExtractableCodeDescriptor, - val conflicts: MultiMap + val descriptor: ExtractableCodeDescriptor, + val conflicts: MultiMap ) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt index 227b7a29697..a1a7afe1807 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt @@ -55,12 +55,12 @@ import org.jetbrains.kotlin.types.KotlinType import java.util.* data class ExtractionOptions( - val inferUnitTypeForUnusedValues: Boolean = true, - val enableListBoxing: Boolean = false, - val extractAsProperty: Boolean = false, - val allowSpecialClassNames: Boolean = false, - val captureLocalFunctions: Boolean = false, - val canWrapInWith: Boolean = false + val inferUnitTypeForUnusedValues: Boolean = true, + val enableListBoxing: Boolean = false, + val extractAsProperty: Boolean = false, + val allowSpecialClassNames: Boolean = false, + val captureLocalFunctions: Boolean = false, + val canWrapInWith: Boolean = false ) { companion object { val DEFAULT = ExtractionOptions() @@ -68,28 +68,28 @@ data class ExtractionOptions( } data class ResolveResult( - val originalRefExpr: KtSimpleNameExpression, - val declaration: PsiElement, - val descriptor: DeclarationDescriptor, - val resolvedCall: ResolvedCall<*>? + val originalRefExpr: KtSimpleNameExpression, + val declaration: PsiElement, + val descriptor: DeclarationDescriptor, + val resolvedCall: ResolvedCall<*>? ) data class ResolvedReferenceInfo( - val refExpr: KtSimpleNameExpression, - val resolveResult: ResolveResult, - val smartCast: KotlinType?, - val possibleTypes: Set, - val shouldSkipPrimaryReceiver: Boolean + val refExpr: KtSimpleNameExpression, + val resolveResult: ResolveResult, + val smartCast: KotlinType?, + val possibleTypes: Set, + val shouldSkipPrimaryReceiver: Boolean ) internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyablePsiUserDataProperty(Key.create("RESOLVE_RESULT")) data class ExtractionData( - val originalFile: KtFile, - val originalRange: KotlinPsiRange, - val targetSibling: PsiElement, - val duplicateContainer: PsiElement? = null, - val options: ExtractionOptions = ExtractionOptions.DEFAULT + val originalFile: KtFile, + val originalRange: KotlinPsiRange, + val targetSibling: PsiElement, + val duplicateContainer: PsiElement? = null, + val options: ExtractionOptions = ExtractionOptions.DEFAULT ) : Disposable { val project: Project = originalFile.project val originalElements: List = originalRange.elements @@ -99,9 +99,9 @@ data class ExtractionData( get() = (originalElements.singleOrNull() as? KtExpression)?.extractableSubstringInfo val insertBefore: Boolean = options.extractAsProperty - || targetSibling.getStrictParentOfType()?.let { - it is KtDeclarationWithBody || it is KtAnonymousInitializer - } ?: false + || targetSibling.getStrictParentOfType()?.let { + it is KtDeclarationWithBody || it is KtAnonymousInitializer + } ?: false val expressions = originalElements.filterIsInstance() @@ -126,7 +126,7 @@ data class ExtractionData( } private fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean { - if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] ?: false))) return false + if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] == true))) return false val function = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.containingDeclaration) as? KtFunctionLiteral return function == null || !function.isInsideOf(physicalElements) } @@ -183,12 +183,15 @@ data class ExtractionData( expressions.forEach { it.accept(visitor) } } - fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set { + private fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set { val dataFlowValueFactory = expression.getResolutionFacade().frontendService() val dataFlowInfo = context.getDataFlowInfoAfter(expression) resolvedCall?.getImplicitReceiverValue()?.let { - return dataFlowInfo.getCollectedTypes(dataFlowValueFactory.createDataFlowValueForStableReceiver(it), expression.languageVersionSettings) + return dataFlowInfo.getCollectedTypes( + dataFlowValueFactory.createDataFlowValueForStableReceiver(it), + expression.languageVersionSettings + ) } val type = resolvedCall?.resultingDescriptor?.returnType ?: return emptySet() @@ -219,13 +222,12 @@ data class ExtractionData( smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]?.defaultType possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext) val receiverDescriptor = - (originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor + (originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor shouldSkipPrimaryReceiver = smartCast == null - && !DescriptorUtils.isCompanionObject(receiverDescriptor) - && qualifiedExpression.receiverExpression !is KtSuperExpression - if (shouldSkipPrimaryReceiver && !(originalResolveResult.resolvedCall?.hasBothReceivers() ?: false)) continue - } - else { + && !DescriptorUtils.isCompanionObject(receiverDescriptor) + && qualifiedExpression.receiverExpression !is KtSuperExpression + if (shouldSkipPrimaryReceiver && originalResolveResult.resolvedCall?.hasBothReceivers() != true) continue + } else { if (newRef.getParentOfTypeAndBranch { callableReference } != null) continue smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]?.defaultType possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext) @@ -239,8 +241,10 @@ data class ExtractionData( val descriptor = context[BindingContext.REFERENCE_TARGET, newRef] val isBadRef = !(compareDescriptors(project, originalResolveResult.descriptor, descriptor) - && originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement(newRef)) - || smartCast != null + && originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement( + newRef + )) + || smartCast != null if (isBadRef && !originalResolveResult.declaration.isInsideOf(physicalElements)) { val originalResolvedCall = originalResolveResult.resolvedCall as? VariableAsFunctionResolvedCall val originalFunctionCall = originalResolvedCall?.functionCall @@ -248,16 +252,43 @@ data class ExtractionData( val invokeDescriptor = originalFunctionCall?.resultingDescriptor if (invokeDescriptor != null) { val invokeDeclaration = getDeclaration(invokeDescriptor, context) ?: synthesizedInvokeDeclaration - val variableResolveResult = originalResolveResult.copy(resolvedCall = originalVariableCall!!, - descriptor = originalVariableCall.resultingDescriptor) - val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall, - descriptor = originalFunctionCall.resultingDescriptor, - declaration = invokeDeclaration) - referencesInfo.add(ResolvedReferenceInfo(newRef, variableResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver)) - referencesInfo.add(ResolvedReferenceInfo(newRef, functionResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver)) - } - else { - referencesInfo.add(ResolvedReferenceInfo(newRef, originalResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver)) + val variableResolveResult = originalResolveResult.copy( + resolvedCall = originalVariableCall!!, + descriptor = originalVariableCall.resultingDescriptor + ) + val functionResolveResult = originalResolveResult.copy( + resolvedCall = originalFunctionCall, + descriptor = originalFunctionCall.resultingDescriptor, + declaration = invokeDeclaration + ) + referencesInfo.add( + ResolvedReferenceInfo( + newRef, + variableResolveResult, + smartCast, + possibleTypes, + shouldSkipPrimaryReceiver + ) + ) + referencesInfo.add( + ResolvedReferenceInfo( + newRef, + functionResolveResult, + smartCast, + possibleTypes, + shouldSkipPrimaryReceiver + ) + ) + } else { + referencesInfo.add( + ResolvedReferenceInfo( + newRef, + originalResolveResult, + smartCast, + possibleTypes, + shouldSkipPrimaryReceiver + ) + ) } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt index bd2115c47ea..52e0511af97 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt @@ -40,19 +40,20 @@ abstract class ExtractionEngineHelper(val operationName: String) { open fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate() abstract fun configureAndRun( - project: Project, - editor: Editor, - descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, - onFinish: (ExtractionResult) -> Unit = {} + project: Project, + editor: Editor, + descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, + onFinish: (ExtractionResult) -> Unit = {} ) } class ExtractionEngine( - val helper: ExtractionEngineHelper + val helper: ExtractionEngineHelper ) { - fun run(editor: Editor, - extractionData: ExtractionData, - onFinish: (ExtractionResult) -> Unit = {} + fun run( + editor: Editor, + extractionData: ExtractionData, + onFinish: (ExtractionResult) -> Unit = {} ) { val project = extractionData.project @@ -68,8 +69,7 @@ class ExtractionEngine( helper.configureAndRun(project, editor, validationResult) { try { onFinish(it) - } - finally { + } finally { it.dispose() extractionData.dispose() } @@ -85,24 +85,23 @@ class ExtractionEngine( AnalysisResult.Status.NON_CRITICAL_ERROR -> { val anchorPoint = RelativePoint( - editor.contentComponent, - editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!) + editor.contentComponent, + editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!) ) JBPopupFactory.getInstance()!! - .createHtmlTextBalloonBuilder( - "$message

Proceed with extraction", - MessageType.WARNING, - { event -> - if (event?.eventType == HyperlinkEvent.EventType.ACTIVATED) { - validateAndRefactor() - } - } - ) - .setHideOnClickOutside(true) - .setHideOnFrameResize(false) - .setHideOnLinkClick(true) - .createBalloon() - .show(anchorPoint, Balloon.Position.below) + .createHtmlTextBalloonBuilder( + "$message

Proceed with extraction", + 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() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt index 552cf4c2d97..c4549d8ba83 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/duplicateUtil.kt @@ -42,33 +42,35 @@ fun KotlinPsiRange.highlight(project: Project, editor: Editor): RangeHighlighter val highlighters = ArrayList() val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!! HighlightManager.getInstance(project).addRangeHighlight( - editor, textRange.startOffset, textRange.endOffset, attributes, true, highlighters + editor, textRange.startOffset, textRange.endOffset, attributes, true, highlighters ) return highlighters.firstOrNull() } fun KotlinPsiRange.preview(project: Project, editor: Editor): RangeHighlighter? { - return highlight(project, editor)?.let { - val startOffset = getPhysicalTextRange().startOffset - val foldedRegions = - CodeFoldingManager.getInstance(project) - .getFoldRegionsAtOffset(editor, startOffset) - .filter { !it.isExpanded } - if (!foldedRegions.isEmpty()) { - editor.foldingModel.runBatchFoldingOperation { foldedRegions.forEach { it.isExpanded = true } } - } - editor.scrollingModel.scrollTo(editor.offsetToLogicalPosition(startOffset), ScrollType.MAKE_VISIBLE) + val highlight = highlight(project, editor) ?: return null - it + val startOffset = getPhysicalTextRange().startOffset + val foldedRegions = CodeFoldingManager.getInstance(project) + .getFoldRegionsAtOffset(editor, startOffset) + .filter { !it.isExpanded } + + if (!foldedRegions.isEmpty()) { + editor.foldingModel.runBatchFoldingOperation { + foldedRegions.forEach { it.isExpanded = true } + } } + + editor.scrollingModel.scrollTo(editor.offsetToLogicalPosition(startOffset), ScrollType.MAKE_VISIBLE) + return highlight } fun processDuplicates( - duplicateReplacers: Map Unit>, - project: Project, - editor: Editor, - scopeDescription: String = "this file", - usageDescription: String = "a usage of extracted declaration" + duplicateReplacers: Map Unit>, + project: Project, + editor: Editor, + scopeDescription: String = "this file", + usageDescription: String = "a usage of extracted declaration" ) { val size = duplicateReplacers.size if (size == 0) return @@ -77,22 +79,26 @@ fun processDuplicates( duplicateReplacers.keys.first().preview(project, editor) } - val answer = if (ApplicationManager.getApplication()!!.isUnitTestMode) + val answer = if (ApplicationManager.getApplication()!!.isUnitTestMode) { Messages.YES - else + } else { Messages.showYesNoDialog( - project, - KotlinRefactoringBundle.message( - "0.has.detected.1.code.fragments.in.2.that.can.be.replaced.with.3", - ApplicationNamesInfo.getInstance().productName, - duplicateReplacers.size, - scopeDescription, - usageDescription - ), - "Process Duplicates", - Messages.getQuestionIcon() + project, + KotlinRefactoringBundle.message( + "0.has.detected.1.code.fragments.in.2.that.can.be.replaced.with.3", + ApplicationNamesInfo.getInstance().productName, + duplicateReplacers.size, + scopeDescription, + usageDescription + ), + "Process Duplicates", + Messages.getQuestionIcon() ) - if (answer != Messages.YES) return + } + + if (answer != Messages.YES) { + return + } var showAll = false @@ -106,15 +112,15 @@ fun processDuplicates( if (size > 1 && !showAll) { val promptDialog = ReplacePromptDialog(false, RefactoringBundle.message("process.duplicates.title", i + 1, size), project) promptDialog.show() - when(promptDialog.exitCode) { + when (promptDialog.exitCode) { FindManager.PromptResult.ALL -> showAll = true FindManager.PromptResult.SKIP -> continue@duplicateReplacersLoop FindManager.PromptResult.CANCEL -> return } } } - highlighter?.let { HighlightManager.getInstance(project).removeSegmentHighlighter(editor, it) } + highlighter?.let { HighlightManager.getInstance(project).removeSegmentHighlighter(editor, it) } project.executeWriteCommand(MethodDuplicatesHandler.REFACTORING_NAME, replacer) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 100dc4f4c1c..8f2d2f02b04 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -75,8 +75,9 @@ import java.util.* internal val KotlinBuiltIns.defaultReturnType: KotlinType get() = unitType internal val KotlinBuiltIns.defaultParameterType: KotlinType get() = nullableAnyType -private fun DeclarationDescriptor.renderForMessage(): String = - IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.render(this) +private fun DeclarationDescriptor.renderForMessage(): String { + return IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.render(this) +} private val TYPE_RENDERER = DescriptorRenderer.FQ_NAMES_IN_TYPES.withOptions { typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES @@ -87,8 +88,6 @@ private fun KotlinType.renderForMessage(): String = TYPE_RENDERER.renderType(thi private fun KtDeclaration.renderForMessage(bindingContext: BindingContext): String? = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this]?.renderForMessage() -internal fun KotlinType.isDefault(): Boolean = KotlinBuiltIns.isUnit(this) - private fun List.getModifiedVarDescriptors(bindingContext: BindingContext): Map> { val result = HashMap>() for (instruction in filterIsInstance()) { @@ -125,13 +124,13 @@ private fun List.getVarDescriptorsAccessedAfterwards(bindingContext } private fun List.getExitPoints(): List = - filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } } + filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } } private fun ExtractionData.getResultTypeAndExpressions( - instructions: List, - bindingContext: BindingContext, - targetScope: LexicalScope?, - options: ExtractionOptions, module: ModuleDescriptor + instructions: List, + bindingContext: BindingContext, + targetScope: LexicalScope?, + options: ExtractionOptions, module: ModuleDescriptor ): Pair> { fun instructionToExpression(instruction: Instruction, unwrapReturn: Boolean): KtExpression? { return when (instruction) { @@ -153,14 +152,15 @@ private fun ExtractionData.getResultTypeAndExpressions( if (options.inferUnitTypeForUnusedValues && expression.isUsedAsStatement(bindingContext)) return null return bindingContext.getType(expression) - ?: (expression as? KtReferenceExpression)?.let { - (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType - } + ?: (expression as? KtReferenceExpression)?.let { + (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType + } } val resultTypes = instructions.mapNotNull(::instructionToType) val commonSupertype = if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else module.builtIns.defaultReturnType - val resultType = if (options.allowSpecialClassNames) commonSupertype else commonSupertype.approximateWithResolvableType(targetScope, false) + val resultType = + if (options.allowSpecialClassNames) commonSupertype else commonSupertype.approximateWithResolvableType(targetScope, false) val expressions = instructions.mapNotNull { instructionToExpression(it, false) } @@ -168,7 +168,7 @@ private fun ExtractionData.getResultTypeAndExpressions( } private fun getCommonNonTrivialSuccessorIfAny(instructions: List): Instruction? { - val singleSuccessorCheckingVisitor = object: InstructionVisitorWithResult() { + val singleSuccessorCheckingVisitor = object : InstructionVisitorWithResult() { var target: Instruction? = null override fun visitInstructionWithNext(instruction: InstructionWithNext): Boolean { @@ -205,9 +205,9 @@ private fun KotlinType.isMeaningful(): Boolean { } private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages( - pseudocode: Pseudocode, - localInstructions: List, - bindingContext: BindingContext + pseudocode: Pseudocode, + localInstructions: List, + bindingContext: BindingContext ): List { val declarations = HashSet() pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> @@ -224,14 +224,14 @@ private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages( } private fun ExtractionData.analyzeControlFlow( - localInstructions: List, - pseudocode: Pseudocode, - module: ModuleDescriptor, - bindingContext: BindingContext, - modifiedVarDescriptors: Map>, - options: ExtractionOptions, - targetScope: LexicalScope?, - parameters: Set + localInstructions: List, + pseudocode: Pseudocode, + module: ModuleDescriptor, + bindingContext: BindingContext, + modifiedVarDescriptors: Map>, + options: ExtractionOptions, + targetScope: LexicalScope?, + parameters: Set ): Pair { val exitPoints = localInstructions.getExitPoints() @@ -241,24 +241,23 @@ private fun ExtractionData.analyzeControlFlow( exitPoints.forEach { val e = (it as? UnconditionalJumpInstruction)?.element 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 - } + 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 + } when (inst) { is ReturnValueInstruction -> { if (inst.owner == pseudocode) { if (inst.returnExpressionIfAny == null) { defaultExits.add(inst) - } - else { + } else { valuedReturnExits.add(inst) } } @@ -267,11 +266,11 @@ private fun ExtractionData.analyzeControlFlow( is AbstractJumpInstruction -> { val element = inst.element if ((element is KtReturnExpression && inst.owner == pseudocode) - || element is KtBreakExpression - || element is KtContinueExpression) { + || element is KtBreakExpression + || element is KtContinueExpression + ) { jumpExits.add(inst) - } - else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) { + } else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) { defaultExits.add(inst) } } @@ -285,19 +284,31 @@ private fun ExtractionData.analyzeControlFlow( 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 (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) + 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 { + } else { emptyControlFlow } @@ -309,7 +320,7 @@ private fun ExtractionData.analyzeControlFlow( 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 } + declarationsToCopy.filter { modifiedVarDescriptors[bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]] != null } val modifiedValueCount = outParameters.size + outDeclarations.size val outputValues = ArrayList() @@ -321,8 +332,7 @@ private fun ExtractionData.analyzeControlFlow( if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow)) - } - else if (valuedReturnExits.isNotEmpty()) { + } else if (valuedReturnExits.isNotEmpty()) { if (jumpExits.isNotEmpty()) return multipleExitsError if (defaultExits.isNotEmpty()) { @@ -350,11 +360,11 @@ private fun ExtractionData.analyzeControlFlow( outputValues.size > 3 -> { if (!options.enableListBoxing) { val outValuesStr = - (outParameters.map { it.originalDescriptor.renderForMessage() } - + outDeclarations.map { it.renderForMessage(bindingContext)!! }).sorted() + (outParameters.map { it.originalDescriptor.renderForMessage() } + + outDeclarations.map { it.renderForMessage(bindingContext)!! }).sorted() return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr) } - { outputValues -> OutputValueBoxer.AsList(outputValues) } // KT-8596 + { values -> OutputValueBoxer.AsList(values) } } else -> controlFlow.boxerFactory @@ -370,7 +380,16 @@ private fun ExtractionData.analyzeControlFlow( 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.copy( + outputValues = Collections.singletonList( + Jump( + elements, + elementToInsertAfterCall, + conditional, + module.builtIns + ) + ) + ) to null } return controlFlow to null @@ -386,13 +405,12 @@ fun ExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclarati val newTargetParent = newTargetSibling.parent val declaration = KtPsiFactory(originalFile).createDeclarationByPattern( - pattern, - PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) + pattern, + PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) ) return if (insertBefore) { newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration - } - else { + } else { newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration } } @@ -407,20 +425,20 @@ internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression { private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List { if (!processTypeArguments) return Collections.singletonList(this) return DFS.dfsFromNode( - this, - Neighbors { current -> current.arguments.map { it.type } }, - VisitedWithSet(), - object: CollectingNodeHandler>(ArrayList()) { - override fun afterChildren(current: KotlinType) { - result.add(current) - } + this, + Neighbors { current -> current.arguments.map { it.type } }, + VisitedWithSet(), + object : CollectingNodeHandler>(ArrayList()) { + override fun afterChildren(current: KotlinType) { + result.add(current) } + } )!! } fun KtTypeParameter.collectRelevantConstraints(): List { val typeConstraints = getNonStrictParentOfType()?.typeConstraints ?: return Collections.emptyList() - return typeConstraints.filter { it.subjectTypeParameterName?.mainReference?.resolve() == this} + return typeConstraints.filter { it.subjectTypeParameterName?.mainReference?.resolve() == this } } fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List { @@ -443,11 +461,11 @@ private fun KotlinType.isExtractable(targetScope: LexicalScope?): Boolean { } internal fun KotlinType.processTypeIfExtractable( - typeParameters: MutableSet, - nonDenotableTypes: MutableSet, - options: ExtractionOptions, - targetScope: LexicalScope?, - processTypeArguments: Boolean = true + typeParameters: MutableSet, + nonDenotableTypes: MutableSet, + options: ExtractionOptions, + targetScope: LexicalScope?, + processTypeArguments: Boolean = true ): Boolean { return collectReferencedTypes(processTypeArguments).fold(true) { extractable, typeToCheck -> val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor @@ -479,13 +497,13 @@ internal fun KotlinType.processTypeIfExtractable( } internal class MutableParameter( - override val argumentText: String, - override val originalDescriptor: DeclarationDescriptor, - override val receiverCandidate: Boolean, - private val targetScope: LexicalScope?, - private val originalType: KotlinType, - private val possibleTypes: Set -): Parameter { + override val argumentText: String, + override val originalDescriptor: DeclarationDescriptor, + override val receiverCandidate: Boolean, + private val targetScope: LexicalScope?, + private val originalType: KotlinType, + private val possibleTypes: Set +) : Parameter { // All modifications happen in the same thread private var writable: Boolean = true private val defaultTypes = LinkedHashSet() @@ -515,8 +533,7 @@ internal class MutableParameter( writable = false if (defaultTypes.isNotEmpty()) { TypeIntersector.intersectTypes(defaultTypes)!! - } - else originalType + } else originalType } private val parameterTypeCandidates: List by lazy { @@ -530,8 +547,7 @@ internal class MutableParameter( if (typePredicate(bounds.upperBound)) add(bounds.upperBound) if (typePredicate(bounds.lowerBound)) add(bounds.lowerBound) } - } - else linkedSetOf(defaultType) + } else linkedSetOf(defaultType) val addNullableTypes = defaultType.isNullabilityFlexible() && typeSet.size > 1 val superTypes = TypeUtils.getAllSupertypes(defaultType).filter(typePredicate) @@ -547,11 +563,11 @@ internal class MutableParameter( } override fun getParameterTypeCandidates(allowSpecialClassNames: Boolean): List { - return if (!allowSpecialClassNames) { - parameterTypeCandidates.filter { it.isExtractable(targetScope) } - } else { - parameterTypeCandidates - } + return if (!allowSpecialClassNames) { + parameterTypeCandidates.filter { it.isExtractable(targetScope) } + } else { + parameterTypeCandidates + } } override fun getParameterType(allowSpecialClassNames: Boolean): KotlinType { @@ -562,31 +578,32 @@ internal class MutableParameter( } private class DelegatingParameter( - val original: Parameter, - override val name: String, - val parameterType: KotlinType -): Parameter by original { + val original: Parameter, + override val name: String, + val parameterType: KotlinType +) : Parameter by original { override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(original, name, parameterType) override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType } private fun ExtractionData.checkDeclarationsMovingOutOfScope( - enclosingDeclaration: KtDeclaration, - controlFlow: ControlFlow, - bindingContext: BindingContext + enclosingDeclaration: KtDeclaration, + controlFlow: ControlFlow, + bindingContext: BindingContext ): ErrorMessage? { val declarationsOutOfScope = HashSet() controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( - object : KtTreeVisitorVoid() { - override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { - val target = expression.mainReference.resolve() - if (target is KtNamedDeclaration - && target.isInsideOf(physicalElements) - && target.getStrictParentOfType() == enclosingDeclaration) { - declarationsOutOfScope.add(target) - } + object : KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + val target = expression.mainReference.resolve() + if (target is KtNamedDeclaration + && target.isInsideOf(physicalElements) + && target.getStrictParentOfType() == enclosingDeclaration + ) { + declarationsOutOfScope.add(target) } } + } ) if (declarationsOutOfScope.isNotEmpty()) { @@ -639,7 +656,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val declaration = commonParent.containingDeclarationForPseudocode ?: return noContainerError val pseudocode = declaration.getContainingPseudocode(bindingContext) - ?: return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.SYNTAX_ERRORS)) + ?: return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.SYNTAX_ERRORS)) val localInstructions = getLocalInstructions(pseudocode) val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext) @@ -648,12 +665,12 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val targetScope = targetSibling.getResolutionScope(bindingContext, commonParent.getResolutionFacade()) val paramsInfo = inferParametersInfo( - virtualBlock, - commonParent, - pseudocode, - bindingContext, - targetScope, - modifiedVarDescriptorsWithExpressions.keys + virtualBlock, + commonParent, + pseudocode, + bindingContext, + targetScope, + modifiedVarDescriptorsWithExpressions.keys ) if (paramsInfo.errorMessage != null) { return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) @@ -665,14 +682,14 @@ fun ExtractionData.performAnalysis(): AnalysisResult { modifiedVarDescriptorsForControlFlow.keys.retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext)) val (controlFlow, controlFlowMessage) = analyzeControlFlow( - localInstructions, - pseudocode, - originalFile.findModuleDescriptor(), - bindingContext, - modifiedVarDescriptorsForControlFlow, - options, - targetScope, - paramsInfo.parameters + localInstructions, + pseudocode, + originalFile.findModuleDescriptor(), + bindingContext, + modifiedVarDescriptorsForControlFlow, + options, + targetScope, + paramsInfo.parameters ) controlFlowMessage?.let { messages.add(it) } @@ -680,11 +697,11 @@ fun ExtractionData.performAnalysis(): AnalysisResult { returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options, targetScope) if (paramsInfo.nonDenotableTypes.isNotEmpty()) { - val typeStr = paramsInfo.nonDenotableTypes.map {it.renderForMessage()}.sorted() + val typeStr = paramsInfo.nonDenotableTypes.map { it.renderForMessage() }.sorted() return AnalysisResult( - null, - Status.CRITICAL_ERROR, - listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)) + null, + Status.CRITICAL_ERROR, + listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)) ) } @@ -692,35 +709,35 @@ fun ExtractionData.performAnalysis(): AnalysisResult { 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-- } - } + object : KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + paramsInfo.originalRefToParameter[expression].firstOrNull()?.let { it.refCount-- } } + } ) val adjustedParameters = paramsInfo.parameters.filterTo(LinkedHashSet()) { it.refCount > 0 } - val receiverCandidates = adjustedParameters.filterTo(HashSet()) { it.receiverCandidate } + val receiverCandidates = adjustedParameters.filterTo(hashSetOf()) { it.receiverCandidate } val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null receiverParameter?.let { adjustedParameters.remove(it) } var descriptor = ExtractableCodeDescriptor( - this, - bindingContext, - suggestFunctionNames(returnType), - getDefaultVisibility(), - adjustedParameters.toList(), - receiverParameter, - paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! }, - paramsInfo.replacementMap, - if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), - returnType, - emptyList() + this, + bindingContext, + suggestFunctionNames(returnType), + getDefaultVisibility(), + adjustedParameters.toList(), + receiverParameter, + paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! }, + paramsInfo.replacementMap, + if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), + returnType, + emptyList() ) val generatedDeclaration = ExtractionGeneratorConfiguration( - descriptor, - ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false) + 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 }) { @@ -729,9 +746,9 @@ fun ExtractionData.performAnalysis(): AnalysisResult { return AnalysisResult( - descriptor, - if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, - messages + descriptor, + if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, + messages ) } @@ -739,12 +756,12 @@ private fun ExtractionData.suggestFunctionNames(returnType: KotlinType): List() val validator = - NewDeclarationNameValidator( - targetSibling.parent, - if (targetSibling is KtAnonymousInitializer) targetSibling.parent else targetSibling, - if (options.extractAsProperty) NewDeclarationNameValidator.Target.VARIABLES else NewDeclarationNameValidator.Target.FUNCTIONS_AND_CLASSES - ) - if (!returnType.isDefault()) { + NewDeclarationNameValidator( + targetSibling.parent, + if (targetSibling is KtAnonymousInitializer) targetSibling.parent else targetSibling, + if (options.extractAsProperty) NewDeclarationNameValidator.Target.VARIABLES else NewDeclarationNameValidator.Target.FUNCTIONS_AND_CLASSES + ) + if (!KotlinBuiltIns.isUnit(returnType)) { functionNames.addAll(KotlinNameSuggester.suggestNamesByType(returnType, validator)) } @@ -759,20 +776,20 @@ private fun ExtractionData.suggestFunctionNames(returnType: KotlinType): List bodyExpression - else -> { - val property = this as KtProperty + 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 - } + 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()}") + } + } ?: throw AssertionError("Couldn't get block body for this declaration: ${getElementTextWithContext()}") @JvmOverloads fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarget.FUNCTION): ExtractableCodeDescriptorWithConflicts { @@ -785,8 +802,8 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg val conflicts = MultiMap() val result = ExtractionGeneratorConfiguration( - this, - ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false, target = target) + this, + ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false, target = target) ).generateDeclaration() val valueParameterList = (result.declaration as? KtNamedFunction)?.valueParameterList @@ -802,19 +819,20 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg val currentDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, currentRefExpr] val currentTarget = - currentDescriptor?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(extractionData.project, it) } as? PsiNamedElement + currentDescriptor?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(extractionData.project, it) } as? PsiNamedElement if (currentTarget is KtParameter && currentTarget.parent == valueParameterList) return if (currentTarget is KtTypeParameter && currentTarget.parent == typeParameterList) return if (currentDescriptor is LocalVariableDescriptor - && parameters.any { it.mirrorVarName == currentDescriptor.name.asString() }) return + && parameters.any { it.mirrorVarName == currentDescriptor.name.asString() } + ) return if (diagnostics.any { it.factory in Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS } || (currentDescriptor != null - && !ErrorUtils.isError(currentDescriptor) - && !compareDescriptors(extractionData.project, currentDescriptor, resolveResult.descriptor))) { + && !ErrorUtils.isError(currentDescriptor) + && !compareDescriptors(extractionData.project, currentDescriptor, resolveResult.descriptor))) { conflicts.putValue( - resolveResult.originalRefExpr, - getDeclarationMessage(resolveResult.declaration, "0.will.no.longer.be.accessible.after.extraction") + resolveResult.originalRefExpr, + getDeclarationMessage(resolveResult.declaration, "0.will.no.longer.be.accessible.after.extraction") ) return } @@ -831,23 +849,21 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg } result.declaration.accept( - object : KtTreeVisitorVoid() { - override fun visitUserType(userType: KtUserType) { - val refExpr = userType.referenceExpression ?: return - val diagnostics = bindingContext.diagnostics.forElement(refExpr) - diagnostics.firstOrNull { it.factory == Errors.INVISIBLE_REFERENCE }?.let { - val declaration = refExpr.mainReference.resolve() as? PsiNamedElement ?: return - conflicts.putValue(declaration, getDeclarationMessage(declaration, "0.will.become.invisible.after.extraction")) - } - } - - override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { - processReference(expression) + object : KtTreeVisitorVoid() { + override fun visitUserType(userType: KtUserType) { + val refExpr = userType.referenceExpression ?: return + val diagnostics = bindingContext.diagnostics.forElement(refExpr) + diagnostics.firstOrNull { it.factory == Errors.INVISIBLE_REFERENCE }?.let { + val declaration = refExpr.mainReference.resolve() as? PsiNamedElement ?: return + conflicts.putValue(declaration, getDeclarationMessage(declaration, "0.will.become.invisible.after.extraction")) } } + + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + processReference(expression) + } + } ) return ExtractableCodeDescriptorWithConflicts(this, conflicts) -} - -private val LOG = Logger.getInstance(ExtractionEngine::class.java) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index 871628a5bad..944e37d3b12 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -24,6 +24,7 @@ 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.Visibilities @@ -81,22 +82,22 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D modifier(modifiers.joinToString(separator = " ")) typeParams( - config.descriptor.typeParameters.map { - val typeParameter = it.originalDeclaration - val bound = typeParameter.extendsBound + 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) - } + buildString { + if (it.isReified()) { + append(KtTokens.REIFIED_KEYWORD.value) + append(' ') + } + append(typeParameter.name) + if (bound != null) { + append(" : ") + append(bound.text) } } + } ) fun KotlinType.typeAsString(): String { @@ -116,12 +117,14 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D name(config.generatorOptions.dummyName ?: config.descriptor.name) config.descriptor.parameters.forEach { parameter -> - param(parameter.name, - parameter.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames).typeAsString()) + param( + parameter.name, + parameter.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames).typeAsString() + ) } with(config.descriptor.returnType) { - if (isDefault() || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) { + if (KotlinBuiltIns.isUnit(this) || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) { noReturnType() } else { returnType(typeAsString()) @@ -135,7 +138,7 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString() fun ExtractionGeneratorConfiguration.getDeclarationPattern( - descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE + descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE ): String { val extractionTarget = generatorOptions.target if (!extractionTarget.isAvailable(descriptor)) { @@ -172,9 +175,9 @@ fun createNameCounterpartMap(from: KtElement, to: KtElement): Map + val range: KotlinPsiRange, + val controlFlow: ControlFlow, + val arguments: List ) fun ExtractableCodeDescriptor.findDuplicates(): List { @@ -225,25 +228,31 @@ fun ExtractableCodeDescriptor.findDuplicates(): List { return if (matched) newControlFlow else null } - val unifierParameters = parameters.map { UnifierParameter(it.originalDescriptor, it.getParameterType(extractionData.options.allowSpecialClassNames)) } + val unifierParameters = + parameters.map { UnifierParameter(it.originalDescriptor, it.getParameterType(extractionData.options.allowSpecialClassNames)) } val unifier = KotlinPsiUnifier(unifierParameters, true) val scopeElement = getOccurrenceContainer() ?: return Collections.emptyList() val originalTextRange = extractionData.originalRange.getPhysicalTextRange() return extractionData - .originalRange - .match(scopeElement, unifier) - .asSequence() - .filter { !(it.range.getPhysicalTextRange().intersects(originalTextRange)) } - .mapNotNull { match -> - val controlFlow = getControlFlowIfMatched(match) - val range = with(match.range) { - (elements.singleOrNull() as? KtStringTemplateEntryWithExpression)?.expression?.toRange() ?: this - } - controlFlow?.let { DuplicateInfo(range, it, unifierParameters.map { match.substitution[it]!!.text!! }) } + .originalRange + .match(scopeElement, unifier) + .asSequence() + .filter { !(it.range.getPhysicalTextRange().intersects(originalTextRange)) } + .mapNotNull { match -> + val controlFlow = getControlFlowIfMatched(match) + val range = with(match.range) { + (elements.singleOrNull() as? KtStringTemplateEntryWithExpression)?.expression?.toRange() ?: this } - .toList() + + controlFlow?.let { + DuplicateInfo(range, it, unifierParameters.map { param -> + match.substitution[param]!!.text!! + }) + } + } + .toList() } private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? { @@ -251,11 +260,12 @@ private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? { } private fun makeCall( - extractableDescriptor: ExtractableCodeDescriptor, - declaration: KtNamedDeclaration, - controlFlow: ControlFlow, - rangeToReplace: KotlinPsiRange, - arguments: List) { + extractableDescriptor: ExtractableCodeDescriptor, + declaration: KtNamedDeclaration, + controlFlow: ControlFlow, + rangeToReplace: KotlinPsiRange, + arguments: List +) { fun insertCall(anchor: PsiElement, wrappedCall: KtExpression): KtExpression? { val firstExpression = rangeToReplace.elements.firstOrNull { it is KtExpression } as? KtExpression if (firstExpression?.isLambdaOutsideParentheses() ?: false) { @@ -318,13 +328,13 @@ private fun makeCall( 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(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") + psiFactory.createDestructuringDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText") ) return @@ -333,16 +343,15 @@ private fun makeCall( val inlinableCall = controlFlow.outputValues.size <= 1 val unboxingExpressions = - if (inlinableCall) { - controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return) - } - else { - val varNameValidator = NewDeclarationNameValidator(block, anchorInBlock, NewDeclarationNameValidator.Target.VARIABLES) - val resultVal = KotlinNameSuggester.suggestNamesByType(extractableDescriptor.returnType, varNameValidator, null).first() - block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) - block.addBefore(newLine, anchorInBlock) - controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) - } + if (inlinableCall) { + controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return) + } else { + val varNameValidator = NewDeclarationNameValidator(block, anchorInBlock, NewDeclarationNameValidator.Target.VARIABLES) + val resultVal = KotlinNameSuggester.suggestNamesByType(extractableDescriptor.returnType, varNameValidator, null).first() + block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) + block.addBefore(newLine, anchorInBlock) + controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) + } val copiedDeclarations = HashMap() for (decl in controlFlow.declarationsToCopy) { @@ -363,8 +372,7 @@ private fun makeCall( val firstReturn = outputValue.originalExpressions.asSequence().filterIsInstance().firstOrNull() val label = firstReturn?.getTargetLabel()?.text ?: "" "return$label $callText" - } - else { + } else { callText } Collections.singletonList(psiFactory.createExpression(exprText)) @@ -372,19 +380,19 @@ private fun makeCall( is ParameterUpdate -> Collections.singletonList( - psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText") + 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}") + psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.text}") ) else -> listOf( - psiFactory.createExpression(callText), - newLine, - psiFactory.createExpression(outputValue.elementToInsertAfterCall.text!!) + psiFactory.createExpression(callText), + newLine, + psiFactory.createExpression(outputValue.elementToInsertAfterCall.text!!) ) } } @@ -402,17 +410,17 @@ private fun makeCall( val defaultValue = controlFlow.defaultOutputValue controlFlow.outputValues - .filter { it != defaultValue } - .flatMap { wrapCall(it, unboxingExpressions[it]!!) } - .withIndex() - .forEach { - val (i, e) = it + .filter { it != defaultValue } + .flatMap { wrapCall(it, unboxingExpressions[it]!!) } + .withIndex() + .forEach { + val (i, e) = it - if (i > 0) { - block.addBefore(newLine, anchorInBlock) - } - block.addBefore(e, anchorInBlock) + if (i > 0) { + block.addBefore(newLine, anchorInBlock) } + block.addBefore(e, anchorInBlock) + } defaultValue?.let { if (!inlinableCall) { @@ -433,12 +441,12 @@ private var KtReturnExpression.isReturnForLabelRemoval: Boolean by NotNullablePsiCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false) fun ExtractionGeneratorConfiguration.generateDeclaration( - declarationToReplace: KtNamedDeclaration? = null -): ExtractionResult{ + declarationToReplace: KtNamedDeclaration? = null +): ExtractionResult { val psiFactory = KtPsiFactory(descriptor.extractionData.originalFile) fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues - .flatMapTo(ArrayList()) { it.originalExpressions.filterIsInstance() } + .flatMapTo(arrayListOf()) { it.originalExpressions.filterIsInstance() } fun createDeclaration(): KtNamedDeclaration { descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true } @@ -447,11 +455,10 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( return with(descriptor.extractionData) { if (generatorOptions.inTempFile) { createTemporaryDeclaration("${getDeclarationPattern()}\n") - } - else { + } else { psiFactory.createDeclarationByPattern( - getDeclarationPattern(), - PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) + getDeclarationPattern(), + PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) ) } } @@ -459,15 +466,15 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( fun getReturnArguments(resultExpression: KtExpression?): List { return descriptor.controlFlow.outputValues - .mapNotNull { - when (it) { - is ExpressionValue -> resultExpression - is Jump -> if (it.conditional) psiFactory.createExpression("false") else null - is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef) - is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!) - else -> throw IllegalArgumentException("Unknown output value: $it") - } + .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) { @@ -528,20 +535,19 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( * before calls/types themselves */ val currentRefs = body - .collectDescendantsOfType { it.resolveResult != null } - .sortedByDescending { it.startOffset } + .collectDescendantsOfType { it.resolveResult != null } + .sortedByDescending { it.startOffset } currentRefs.forEach { val resolveResult = it.resolveResult!! val currentRef = if (it.isValid) { it - } - else { - body.findDescendantOfType { it.resolveResult == resolveResult } ?: return@forEach + } else { + body.findDescendantOfType { expr -> expr.resolveResult == resolveResult } ?: return@forEach } val originalRef = resolveResult.originalRefExpr val newRef = descriptor.replacementMap[originalRef] - .fold(currentRef as KtElement) { currentRef, replacement -> replacement(descriptor, currentRef) } + .fold(currentRef as KtElement) { currentRef, replacement -> replacement(descriptor, currentRef) } (newRef as? KtSimpleNameExpression)?.resolveResult = resolveResult } @@ -565,19 +571,19 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( if (lastExpression is KtReturnExpression) return val defaultExpression = - if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) { - val varNameValidator = NewDeclarationNameValidator(body, lastExpression, NewDeclarationNameValidator.Target.VARIABLES) - val resultVal = KotlinNameSuggester.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 + if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) { + val varNameValidator = NewDeclarationNameValidator(body, lastExpression, NewDeclarationNameValidator.Target.VARIABLES) + val resultVal = KotlinNameSuggester.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 + val returnExpression = + descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return @Suppress("NON_EXHAUSTIVE_WHEN") - when(generatorOptions.target) { + 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 @@ -614,8 +620,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( (targetContainer.addBefore(declaration, anchor) as KtNamedDeclaration).apply { targetContainer.addBefore(emptyLines, anchor) } - } - else { + } else { (targetContainer.addAfter(declaration, anchor) as KtNamedDeclaration).apply { if (!(targetContainer is KtClassBody && (targetContainer.parent as? KtClass)?.isEnum() ?: false)) { targetContainer.addAfter(emptyLines, anchor) @@ -630,7 +635,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( val anchor = with(descriptor.extractionData) { val targetParent = targetSibling.parent - val anchorCandidates = duplicates.mapTo(ArrayList()) { it.range.elements.first().substringContextOrThis } + 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 }) @@ -638,8 +643,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( val marginalCandidate = if (insertBefore) { anchorCandidates.minBy { it.startOffset }!! - } - else { + } else { anchorCandidates.maxBy { it.startOffset }!! } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt index e230b85ed1c..d0d5d56e70b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt @@ -70,12 +70,12 @@ internal class ParametersInfo { } internal fun ExtractionData.inferParametersInfo( - virtualBlock: KtBlockExpression, - commonParent: PsiElement, - pseudocode: Pseudocode, - bindingContext: BindingContext, - targetScope: LexicalScope, - modifiedVarDescriptors: Set + virtualBlock: KtBlockExpression, + commonParent: PsiElement, + pseudocode: Pseudocode, + bindingContext: BindingContext, + targetScope: LexicalScope, + modifiedVarDescriptors: Set ): ParametersInfo { val info = ParametersInfo() @@ -95,8 +95,7 @@ internal fun ExtractionData.inferParametersInfo( val extensionReceiver = resolvedCall?.extensionReceiver val receiverToExtract = (if (extensionReceiver == null || isSynthesizedInvoke(refInfo.resolveResult.descriptor)) { resolvedCall?.dispatchReceiver - } - else { + } else { extensionReceiver }) @@ -105,36 +104,60 @@ internal fun ExtractionData.inferParametersInfo( if (options.canWrapInWith && twoReceivers && resolvedCall!!.extensionReceiver is ExpressionReceiver - && DescriptorUtils.isObject(dispatchReceiverDescriptor)) { - info.replacementMap.putValue(refInfo.resolveResult.originalRefExpr, - WrapObjectInWithReplacement(dispatchReceiverDescriptor as ClassDescriptor)) + && DescriptorUtils.isObject(dispatchReceiverDescriptor) + ) { + info.replacementMap.putValue( + refInfo.resolveResult.originalRefExpr, + WrapObjectInWithReplacement(dispatchReceiverDescriptor as ClassDescriptor) + ) 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(resolvedCall!!.dispatchReceiver, info, targetScope, refInfo, extractedDescriptorToParameter, pseudocode, bindingContext, true) + extractReceiver( + resolvedCall!!.dispatchReceiver, + info, + targetScope, + refInfo, + extractedDescriptorToParameter, + pseudocode, + bindingContext, + true + ) } } val varNameValidator = NewDeclarationNameValidator( - commonParent.getNonStrictParentOfType()!!, - physicalElements.firstOrNull(), - NewDeclarationNameValidator.Target.VARIABLES + commonParent.getNonStrictParentOfType()!!, + physicalElements.firstOrNull(), + NewDeclarationNameValidator.Target.VARIABLES ) val existingParameterNames = hashSetOf() for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) { if (!parameter .getParameterType(options.allowSpecialClassNames) - .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope)) continue + .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope) + ) continue - with (parameter) { + with(parameter) { if (currentName == null) { - currentName = KotlinNameSuggester.suggestNamesByType(getParameterType(options.allowSpecialClassNames), varNameValidator, "p").first() + currentName = + KotlinNameSuggester.suggestNamesByType(getParameterType(options.allowSpecialClassNames), varNameValidator, "p") + .first() } require(currentName != null) @@ -147,7 +170,10 @@ internal fun ExtractionData.inferParametersInfo( currentName = "$currentName$index" } - mirrorVarName = if (descriptorToExtract in modifiedVarDescriptors) KotlinNameSuggester.suggestNameByName(name, varNameValidator) else null + mirrorVarName = if (descriptorToExtract in modifiedVarDescriptors) KotlinNameSuggester.suggestNameByName( + name, + varNameValidator + ) else null info.parameters.add(this) currentName?.let { existingParameterNames += it } } @@ -162,14 +188,14 @@ internal fun ExtractionData.inferParametersInfo( } private fun ExtractionData.extractReceiver( - receiverToExtract: ReceiverValue?, - info: ParametersInfo, - targetScope: LexicalScope, - refInfo: ResolvedReferenceInfo, - extractedDescriptorToParameter: HashMap, - pseudocode: Pseudocode, - bindingContext: BindingContext, - isMemberExtension: Boolean + receiverToExtract: ReceiverValue?, + info: ParametersInfo, + targetScope: LexicalScope, + refInfo: ResolvedReferenceInfo, + extractedDescriptorToParameter: HashMap, + pseudocode: Pseudocode, + bindingContext: BindingContext, + isMemberExtension: Boolean ) { val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult @@ -178,14 +204,15 @@ private fun ExtractionData.extractReceiver( val thisExpr = refInfo.refExpr.parent as? KtThisExpression if (hasThisReceiver - && DescriptorToSourceUtilsIde.getAllDeclarations(project, thisDescriptor!!).all { it.isInsideOf(physicalElements) }) { + && DescriptorToSourceUtilsIde.getAllDeclarations(project, thisDescriptor!!).all { it.isInsideOf(physicalElements) } + ) { return } val referencedClassifierDescriptor: ClassifierDescriptor? = (thisDescriptor ?: originalDescriptor).let { when (it) { is ClassDescriptor -> - when(it.kind) { + when (it.kind) { ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it ClassKind.ENUM_ENTRY -> it.containingDeclaration as? ClassDescriptor else -> if (refInfo.refExpr.getNonStrictParentOfType() != null) it else null @@ -202,29 +229,30 @@ private fun ExtractionData.extractReceiver( if (referencedClassifierDescriptor != null) { if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable( info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor - )) return + ) + ) return if (options.canWrapInWith && resolvedCall != null && resolvedCall.hasBothReceivers() - && DescriptorUtils.isObject(referencedClassifierDescriptor)) { + && DescriptorUtils.isObject(referencedClassifierDescriptor) + ) { info.replacementMap.putValue(originalRef, WrapObjectInWithReplacement(referencedClassifierDescriptor as ClassDescriptor)) } else if (referencedClassifierDescriptor is ClassDescriptor) { info.replacementMap.putValue(originalRef, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe)) } - } - else { + } else { val extractThis = (hasThisReceiver && refInfo.smartCast == null) || thisExpr != null val extractOrdinaryParameter = - originalDeclaration is KtDestructuringDeclarationEntry || - originalDeclaration is KtProperty || - originalDeclaration is KtParameter + originalDeclaration is KtDestructuringDeclarationEntry || + originalDeclaration is KtProperty || + originalDeclaration is KtParameter val extractFunctionRef = - options.captureLocalFunctions - && originalRef.getReferencedName() == originalDescriptor.name.asString() // to forbid calls by convention - && originalDeclaration is KtNamedFunction && originalDeclaration.isLocal - && targetScope.findFunction(originalDescriptor.name, NoLookupLocation.FROM_IDE) { it == originalDescriptor } == null + options.captureLocalFunctions + && originalRef.getReferencedName() == originalDescriptor.name.asString() // to forbid calls by convention + && originalDeclaration is KtNamedFunction && originalDeclaration.isLocal + && targetScope.findFunction(originalDescriptor.name, NoLookupLocation.FROM_IDE) { it == originalDescriptor } == null val descriptorToExtract = (if (extractThis) thisDescriptor else null) ?: originalDescriptor @@ -240,32 +268,46 @@ private fun ExtractionData.extractReceiver( else -> (originalRef.parent as? KtThisExpression) ?: originalRef } - val parameterType = suggestParameterType(extractFunctionRef, originalDescriptor, parameterExpression, receiverToExtract, resolvedCall, true, bindingContext) + val parameterType = suggestParameterType( + extractFunctionRef, + originalDescriptor, + parameterExpression, + receiverToExtract, + resolvedCall, + true, + bindingContext + ) val parameter = extractedDescriptorToParameter.getOrPut(descriptorToExtract) { var argumentText = - if (hasThisReceiver && extractThis) { - val label = if (descriptorToExtract is ClassDescriptor) "@${descriptorToExtract.name.asString()}" else "" - "this$label" - } - else { - val argumentExpr = (thisExpr ?: refInfo.refExpr).getQualifiedExpressionForSelectorOrThis() - if (argumentExpr is KtOperationReferenceExpression) { - val nameElement = argumentExpr.getReferencedNameElement() - val nameElementType = nameElement.node.elementType - (nameElementType as? KtToken)?.let { - OperatorConventions.getNameForOperationSymbol(it)?.asString() - } ?: nameElement.text - } - else argumentExpr.text - ?: throw AssertionError("reference shouldn't be empty: code fragment = $codeFragmentText") - } + if (hasThisReceiver && extractThis) { + val label = if (descriptorToExtract is ClassDescriptor) "@${descriptorToExtract.name.asString()}" else "" + "this$label" + } else { + val argumentExpr = (thisExpr ?: refInfo.refExpr).getQualifiedExpressionForSelectorOrThis() + if (argumentExpr is KtOperationReferenceExpression) { + val nameElement = argumentExpr.getReferencedNameElement() + val nameElementType = nameElement.node.elementType + (nameElementType as? KtToken)?.let { + OperatorConventions.getNameForOperationSymbol(it)?.asString() + } ?: nameElement.text + } else argumentExpr.text + ?: throw AssertionError("reference shouldn't be empty: code fragment = $codeFragmentText") + } if (extractFunctionRef) { val receiverTypeText = (originalDeclaration as KtCallableDeclaration).receiverTypeReference?.text ?: "" argumentText = "$receiverTypeText::$argumentText" } - val originalType = suggestParameterType(extractFunctionRef, originalDescriptor, parameterExpression, receiverToExtract, resolvedCall, false, bindingContext) + val originalType = suggestParameterType( + extractFunctionRef, + originalDescriptor, + parameterExpression, + receiverToExtract, + resolvedCall, + false, + bindingContext + ) MutableParameter(argumentText, descriptorToExtract, extractThis, targetScope, originalType, refInfo.possibleTypes) } @@ -287,13 +329,17 @@ private fun ExtractionData.extractReceiver( val instruction = pseudocode.getElementValue(callElement)?.createdAt as? InstructionWithReceivers val receiverValue = instruction?.receiverValues?.entries?.singleOrNull { it.value == receiverToExtract }?.key if (receiverValue != null) { - parameter.addTypePredicate(getExpectedTypePredicate(receiverValue, bindingContext, targetScope.ownerDescriptor.builtIns)) + parameter.addTypePredicate( + getExpectedTypePredicate( + receiverValue, + bindingContext, + targetScope.ownerDescriptor.builtIns + ) + ) } - } - else if (extractFunctionRef) { + } else if (extractFunctionRef) { parameter.addTypePredicate(SingleType(parameterType)) - } - else { + } else { pseudocode.getElementValuesRecursively(originalRef).forEach { parameter.addTypePredicate(getExpectedTypePredicate(it, bindingContext, targetScope.ownerDescriptor.builtIns)) } @@ -310,50 +356,50 @@ private fun ExtractionData.extractReceiver( } private fun suggestParameterType( - extractFunctionRef: Boolean, - originalDescriptor: DeclarationDescriptor, - parameterExpression: KtExpression?, - receiverToExtract: ReceiverValue?, - resolvedCall: ResolvedCall<*>?, - useSmartCastsIfPossible: Boolean, bindingContext: BindingContext + extractFunctionRef: Boolean, + originalDescriptor: DeclarationDescriptor, + parameterExpression: KtExpression?, + receiverToExtract: ReceiverValue?, + resolvedCall: ResolvedCall<*>?, + useSmartCastsIfPossible: Boolean, bindingContext: BindingContext ): KotlinType { val builtIns = originalDescriptor.builtIns return when { - extractFunctionRef -> { - originalDescriptor as FunctionDescriptor - createFunctionType( - builtIns, - Annotations.EMPTY, - originalDescriptor.extensionReceiverParameter?.type, - originalDescriptor.valueParameters.map { it.type }, - originalDescriptor.valueParameters.map { it.name }, - originalDescriptor.returnType ?: builtIns.defaultReturnType - ) - } + extractFunctionRef -> { + originalDescriptor as FunctionDescriptor + createFunctionType( + builtIns, + Annotations.EMPTY, + originalDescriptor.extensionReceiverParameter?.type, + originalDescriptor.valueParameters.map { it.type }, + originalDescriptor.valueParameters.map { it.name }, + originalDescriptor.returnType ?: builtIns.defaultReturnType + ) + } - parameterExpression != null -> - (if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression]?.defaultType else null) - ?: bindingContext.getType(parameterExpression) - ?: (parameterExpression as? KtReferenceExpression)?.let { - (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType - } - ?: receiverToExtract?.type + parameterExpression != null -> + (if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression]?.defaultType else null) + ?: bindingContext.getType(parameterExpression) + ?: (parameterExpression as? KtReferenceExpression)?.let { + (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType + } + ?: receiverToExtract?.type - receiverToExtract is ImplicitReceiver -> { - val typeByDataFlowInfo = if (useSmartCastsIfPossible) { - val callElement = resolvedCall!!.call.callElement - val dataFlowInfo = bindingContext.getDataFlowInfoAfter(callElement) + receiverToExtract is ImplicitReceiver -> { + val typeByDataFlowInfo = if (useSmartCastsIfPossible) { + val callElement = resolvedCall!!.call.callElement + val dataFlowInfo = bindingContext.getDataFlowInfoAfter(callElement) - val dataFlowValueFactory = callElement.getResolutionFacade().frontendService() - val possibleTypes = dataFlowInfo.getCollectedTypes( - dataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract), - callElement.languageVersionSettings - ) - if (possibleTypes.isNotEmpty()) CommonSupertypes.commonSupertype(possibleTypes) else null - } else null - typeByDataFlowInfo ?: receiverToExtract.type - } + val dataFlowValueFactory = callElement.getResolutionFacade().frontendService() + val possibleTypes = dataFlowInfo.getCollectedTypes( + dataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract), + callElement.languageVersionSettings + ) + if (possibleTypes.isNotEmpty()) CommonSupertypes.commonSupertype(possibleTypes) else null + } else null + typeByDataFlowInfo ?: receiverToExtract.type + } - else -> receiverToExtract?.type - } ?: builtIns.defaultParameterType + else -> receiverToExtract?.type + } ?: builtIns.defaultParameterType }