Minor: Reformat extraction engine files

This commit is contained in:
Yan Zhulanow
2019-01-14 20:06:45 +03:00
parent 488418d960
commit ed88020a84
7 changed files with 698 additions and 593 deletions
@@ -66,25 +66,25 @@ interface Parameter {
val Parameter.nameForRef: String get() = mirrorVarName ?: name val Parameter.nameForRef: String get() = mirrorVarName ?: name
data class TypeParameter( data class TypeParameter(
val originalDeclaration: KtTypeParameter, val originalDeclaration: KtTypeParameter,
val originalConstraints: List<KtTypeConstraint> val originalConstraints: List<KtTypeConstraint>
) )
interface Replacement: Function2<ExtractableCodeDescriptor, KtElement, KtElement> interface Replacement : Function2<ExtractableCodeDescriptor, KtElement, KtElement>
interface ParameterReplacement : Replacement { interface ParameterReplacement : Replacement {
val parameter: Parameter val parameter: Parameter
fun copy(parameter: Parameter): ParameterReplacement 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 copy(parameter: Parameter) = RenameReplacement(parameter)
override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { 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 parameterName = KtPsiUtil.unquoteIdentifier(parameter.nameForRef)
val replacingName = 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 psiFactory = KtPsiFactory(e)
val replacement = when { val replacement = when {
parameter == descriptor.receiverParameter -> psiFactory.createExpression("this") 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 override val argumentText: String
get() = parameter.name get() = parameter.name
override fun copy(parameter: Parameter) = WrapParameterInWithReplacement(parameter) override fun copy(parameter: Parameter) = WrapParameterInWithReplacement(parameter)
} }
class WrapObjectInWithReplacement(val descriptor: ClassDescriptor): WrapInWithReplacement() { class WrapObjectInWithReplacement(val descriptor: ClassDescriptor) : WrapInWithReplacement() {
override val argumentText: String override val argumentText: String
get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor) 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 copy(parameter: Parameter) = AddPrefixReplacement(parameter)
override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { 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 { override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
val thisExpr = e.parent as? KtThisExpression val thisExpr = e.parent as? KtThisExpression
if (thisExpr != null) { if (thisExpr != null) {
@@ -148,32 +148,32 @@ interface OutputValue {
val valueType: KotlinType val valueType: KotlinType
class ExpressionValue( class ExpressionValue(
val callSiteReturn: Boolean, val callSiteReturn: Boolean,
override val originalExpressions: List<KtExpression>, override val originalExpressions: List<KtExpression>,
override val valueType: KotlinType override val valueType: KotlinType
): OutputValue ) : OutputValue
class Jump( class Jump(
val elementsToReplace: List<KtExpression>, val elementsToReplace: List<KtExpression>,
val elementToInsertAfterCall: KtElement?, val elementToInsertAfterCall: KtElement?,
val conditional: Boolean, val conditional: Boolean,
builtIns: KotlinBuiltIns builtIns: KotlinBuiltIns
): OutputValue { ) : OutputValue {
override val originalExpressions: List<KtExpression> get() = elementsToReplace override val originalExpressions: List<KtExpression> get() = elementsToReplace
override val valueType: KotlinType = with(builtIns) { if (conditional) booleanType else unitType } override val valueType: KotlinType = with(builtIns) { if (conditional) booleanType else unitType }
} }
class ParameterUpdate( class ParameterUpdate(
val parameter: Parameter, val parameter: Parameter,
override val originalExpressions: List<KtExpression> override val originalExpressions: List<KtExpression>
): OutputValue { ) : OutputValue {
override val valueType: KotlinType get() = parameter.getParameterType(false) override val valueType: KotlinType get() = parameter.getParameterType(false)
} }
class Initializer( class Initializer(
val initializedDeclaration: KtProperty, val initializedDeclaration: KtProperty,
override val valueType: KotlinType override val valueType: KotlinType
): OutputValue { ) : OutputValue {
override val originalExpressions: List<KtExpression> get() = Collections.singletonList(initializedDeclaration) override val originalExpressions: List<KtExpression> get() = Collections.singletonList(initializedDeclaration)
} }
} }
@@ -216,8 +216,8 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
abstract fun getUnboxingExpressions(boxedText: String): Map<OutputValue, String> abstract fun getUnboxingExpressions(boxedText: String): Map<OutputValue, String>
class AsTuple( class AsTuple(
outputValues: List<OutputValue>, outputValues: List<OutputValue>,
val module: ModuleDescriptor val module: ModuleDescriptor
) : OutputValueBoxer(outputValues) { ) : OutputValueBoxer(outputValues) {
init { init {
assert(outputValues.size <= 3) { "At most 3 output values are supported" } assert(outputValues.size <= 3) { "At most 3 output values are supported" }
@@ -271,17 +271,17 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
} }
} }
class AsList(outputValues: List<OutputValue>): OutputValueBoxer(outputValues) { class AsList(outputValues: List<OutputValue>) : OutputValueBoxer(outputValues) {
override val returnType: KotlinType by lazy { override val returnType: KotlinType by lazy {
assert(outputValues.isNotEmpty()) assert(outputValues.isNotEmpty())
val builtIns = outputValues.first().valueType.builtIns val builtIns = outputValues.first().valueType.builtIns
TypeUtils.substituteParameters( TypeUtils.substituteParameters(
builtIns.list, builtIns.list,
Collections.singletonList(CommonSupertypes.commonSupertype(outputValues.map { it.valueType })) 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<KtExpression>): String? { override fun getBoxingExpressionPattern(arguments: List<KtExpression>): String? {
if (arguments.isEmpty()) return null if (arguments.isEmpty()) return null
@@ -300,9 +300,9 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
} }
data class ControlFlow( data class ControlFlow(
val outputValues: List<OutputValue>, val outputValues: List<OutputValue>,
val boxerFactory: (List<OutputValue>) -> OutputValueBoxer, val boxerFactory: (List<OutputValue>) -> OutputValueBoxer,
val declarationsToCopy: List<KtDeclaration> val declarationsToCopy: List<KtDeclaration>
) { ) {
val outputValueBoxer = boxerFactory(outputValues) val outputValueBoxer = boxerFactory(outputValues)
@@ -337,7 +337,7 @@ val ControlFlow.possibleReturnTypes: List<KotlinType>
} }
fun ControlFlow.toDefault(): ControlFlow = 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<Parameter, Parameter>): ControlFlow { fun ControlFlow.copy(oldToNewParameters: Map<Parameter, Parameter>): ControlFlow {
val newOutputValues = outputValues.map { val newOutputValues = outputValues.map {
@@ -347,28 +347,28 @@ fun ControlFlow.copy(oldToNewParameters: Map<Parameter, Parameter>): ControlFlow
} }
data class ExtractableCodeDescriptor( data class ExtractableCodeDescriptor(
val extractionData: ExtractionData, val extractionData: ExtractionData,
val originalContext: BindingContext, val originalContext: BindingContext,
val suggestedNames: List<String>, val suggestedNames: List<String>,
val visibility: KtModifierKeywordToken?, val visibility: KtModifierKeywordToken?,
val parameters: List<Parameter>, val parameters: List<Parameter>,
val receiverParameter: Parameter?, val receiverParameter: Parameter?,
val typeParameters: List<TypeParameter>, val typeParameters: List<TypeParameter>,
val replacementMap: MultiMap<KtSimpleNameExpression, Replacement>, val replacementMap: MultiMap<KtSimpleNameExpression, Replacement>,
val controlFlow: ControlFlow, val controlFlow: ControlFlow,
val returnType: KotlinType, val returnType: KotlinType,
val modifiers: List<KtKeywordToken> = emptyList() val modifiers: List<KtKeywordToken> = emptyList()
) { ) {
val name: String get() = suggestedNames.firstOrNull() ?: "" val name: String get() = suggestedNames.firstOrNull() ?: ""
val duplicates: List<DuplicateInfo> by lazy { findDuplicates() } val duplicates: List<DuplicateInfo> by lazy { findDuplicates() }
} }
fun ExtractableCodeDescriptor.copy( fun ExtractableCodeDescriptor.copy(
newName: String, newName: String,
newVisibility: KtModifierKeywordToken?, newVisibility: KtModifierKeywordToken?,
oldToNewParameters: Map<Parameter, Parameter>, oldToNewParameters: Map<Parameter, Parameter>,
newReceiver: Parameter?, newReceiver: Parameter?,
returnType: KotlinType? returnType: KotlinType?
): ExtractableCodeDescriptor { ): ExtractableCodeDescriptor {
val newReplacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>() val newReplacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
for ((ref, replacements) in replacementMap.entrySet()) { for ((ref, replacements) in replacementMap.entrySet()) {
@@ -377,24 +377,24 @@ fun ExtractableCodeDescriptor.copy(
val parameter = it.parameter val parameter = it.parameter
val newParameter = oldToNewParameters[parameter] ?: return@map it val newParameter = oldToNewParameters[parameter] ?: return@map it
it.copy(newParameter) it.copy(newParameter)
} } else it
else it
} }
newReplacementMap.putValues(ref, newReplacements) newReplacementMap.putValues(ref, newReplacements)
} }
return ExtractableCodeDescriptor( return ExtractableCodeDescriptor(
extractionData, extractionData,
originalContext, originalContext,
listOf(newName), listOf(newName),
newVisibility, newVisibility,
oldToNewParameters.values.filter { it != newReceiver }, oldToNewParameters.values.filter { it != newReceiver },
newReceiver, newReceiver,
typeParameters, typeParameters,
newReplacementMap, newReplacementMap,
controlFlow.copy(oldToNewParameters), controlFlow.copy(oldToNewParameters),
returnType ?: this.returnType, returnType ?: this.returnType,
modifiers) modifiers
)
} }
enum class ExtractionTarget(val targetName: String) { enum class ExtractionTarget(val targetName: String) {
@@ -411,10 +411,10 @@ enum class ExtractionTarget(val targetName: String) {
PROPERTY_WITH_INITIALIZER("property with initializer") { PROPERTY_WITH_INITIALIZER("property with initializer") {
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
return checkSignatureAndParent(descriptor) return checkSignatureAndParent(descriptor)
&& checkSimpleControlFlow(descriptor) && checkSimpleControlFlow(descriptor)
&& checkSimpleBody(descriptor) && checkSimpleBody(descriptor)
&& checkNotTrait(descriptor) && checkNotTrait(descriptor)
&& descriptor.receiverParameter == null && descriptor.receiverParameter == null
} }
}, },
@@ -427,9 +427,9 @@ enum class ExtractionTarget(val targetName: String) {
LAZY_PROPERTY("lazy property") { LAZY_PROPERTY("lazy property") {
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
return checkSignatureAndParent(descriptor) return checkSignatureAndParent(descriptor)
&& checkSimpleControlFlow(descriptor) && checkSimpleControlFlow(descriptor)
&& checkNotTrait(descriptor) && checkNotTrait(descriptor)
&& descriptor.receiverParameter == null && descriptor.receiverParameter == null
} }
}; };
@@ -461,39 +461,42 @@ enum class ExtractionTarget(val targetName: String) {
} }
} }
val propertyTargets: List<ExtractionTarget> = listOf(ExtractionTarget.PROPERTY_WITH_INITIALIZER, val propertyTargets: List<ExtractionTarget> = listOf(
ExtractionTarget.PROPERTY_WITH_GETTER, ExtractionTarget.PROPERTY_WITH_INITIALIZER,
ExtractionTarget.LAZY_PROPERTY) ExtractionTarget.PROPERTY_WITH_GETTER,
ExtractionTarget.LAZY_PROPERTY
)
data class ExtractionGeneratorOptions( data class ExtractionGeneratorOptions(
val inTempFile: Boolean = false, val inTempFile: Boolean = false,
val target: ExtractionTarget = ExtractionTarget.FUNCTION, val target: ExtractionTarget = ExtractionTarget.FUNCTION,
val dummyName: String? = null, val dummyName: String? = null,
val allowExpressionBody: Boolean = true, val allowExpressionBody: Boolean = true,
val delayInitialOccurrenceReplacement: Boolean = false val delayInitialOccurrenceReplacement: Boolean = false
) { ) {
companion object { companion object {
@JvmField val DEFAULT = ExtractionGeneratorOptions() @JvmField
val DEFAULT = ExtractionGeneratorOptions()
} }
} }
data class ExtractionGeneratorConfiguration( data class ExtractionGeneratorConfiguration(
val descriptor: ExtractableCodeDescriptor, val descriptor: ExtractableCodeDescriptor,
val generatorOptions: ExtractionGeneratorOptions val generatorOptions: ExtractionGeneratorOptions
) )
data class ExtractionResult( data class ExtractionResult(
val config: ExtractionGeneratorConfiguration, val config: ExtractionGeneratorConfiguration,
val declaration: KtNamedDeclaration, val declaration: KtNamedDeclaration,
val duplicateReplacers: Map<KotlinPsiRange, () -> Unit> val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>
) : Disposable { ) : Disposable {
override fun dispose() = unmarkReferencesInside(declaration) override fun dispose() = unmarkReferencesInside(declaration)
} }
class AnalysisResult ( class AnalysisResult(
val descriptor: ExtractableCodeDescriptor?, val descriptor: ExtractableCodeDescriptor?,
val status: Status, val status: Status,
val messages: List<ErrorMessage> val messages: List<ErrorMessage>
) { ) {
enum class Status { enum class Status {
SUCCESS, SUCCESS,
@@ -523,27 +526,27 @@ class AnalysisResult (
fun renderMessage(): String { fun renderMessage(): String {
val message = KotlinRefactoringBundle.message( val message = KotlinRefactoringBundle.message(
when (this) { when (this) {
NO_EXPRESSION -> "cannot.refactor.no.expression" NO_EXPRESSION -> "cannot.refactor.no.expression"
NO_CONTAINER -> "cannot.refactor.no.container" NO_CONTAINER -> "cannot.refactor.no.container"
SYNTAX_ERRORS -> "cannot.refactor.syntax.errors" SYNTAX_ERRORS -> "cannot.refactor.syntax.errors"
SUPER_CALL -> "cannot.extract.super.call" SUPER_CALL -> "cannot.extract.super.call"
DENOTABLE_TYPES -> "parameter.types.are.not.denotable" DENOTABLE_TYPES -> "parameter.types.are.not.denotable"
ERROR_TYPES -> "error.types.in.generated.function" ERROR_TYPES -> "error.types.in.generated.function"
MULTIPLE_OUTPUT -> "selected.code.fragment.has.multiple.output.values" MULTIPLE_OUTPUT -> "selected.code.fragment.has.multiple.output.values"
OUTPUT_AND_EXIT_POINT -> "selected.code.fragment.has.output.values.and.exit.points" OUTPUT_AND_EXIT_POINT -> "selected.code.fragment.has.output.values.and.exit.points"
MULTIPLE_EXIT_POINTS -> "selected.code.fragment.has.multiple.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_ARE_USED_OUTSIDE -> "declarations.are.used.outside.of.selected.code.fragment"
DECLARATIONS_OUT_OF_SCOPE -> "declarations.will.move.out.of.scope" 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( class ExtractableCodeDescriptorWithConflicts(
val descriptor: ExtractableCodeDescriptor, val descriptor: ExtractableCodeDescriptor,
val conflicts: MultiMap<PsiElement, String> val conflicts: MultiMap<PsiElement, String>
) )
@@ -55,12 +55,12 @@ import org.jetbrains.kotlin.types.KotlinType
import java.util.* import java.util.*
data class ExtractionOptions( data class ExtractionOptions(
val inferUnitTypeForUnusedValues: Boolean = true, val inferUnitTypeForUnusedValues: Boolean = true,
val enableListBoxing: Boolean = false, val enableListBoxing: Boolean = false,
val extractAsProperty: Boolean = false, val extractAsProperty: Boolean = false,
val allowSpecialClassNames: Boolean = false, val allowSpecialClassNames: Boolean = false,
val captureLocalFunctions: Boolean = false, val captureLocalFunctions: Boolean = false,
val canWrapInWith: Boolean = false val canWrapInWith: Boolean = false
) { ) {
companion object { companion object {
val DEFAULT = ExtractionOptions() val DEFAULT = ExtractionOptions()
@@ -68,28 +68,28 @@ data class ExtractionOptions(
} }
data class ResolveResult( data class ResolveResult(
val originalRefExpr: KtSimpleNameExpression, val originalRefExpr: KtSimpleNameExpression,
val declaration: PsiElement, val declaration: PsiElement,
val descriptor: DeclarationDescriptor, val descriptor: DeclarationDescriptor,
val resolvedCall: ResolvedCall<*>? val resolvedCall: ResolvedCall<*>?
) )
data class ResolvedReferenceInfo( data class ResolvedReferenceInfo(
val refExpr: KtSimpleNameExpression, val refExpr: KtSimpleNameExpression,
val resolveResult: ResolveResult, val resolveResult: ResolveResult,
val smartCast: KotlinType?, val smartCast: KotlinType?,
val possibleTypes: Set<KotlinType>, val possibleTypes: Set<KotlinType>,
val shouldSkipPrimaryReceiver: Boolean val shouldSkipPrimaryReceiver: Boolean
) )
internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyablePsiUserDataProperty(Key.create("RESOLVE_RESULT")) internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyablePsiUserDataProperty(Key.create("RESOLVE_RESULT"))
data class ExtractionData( data class ExtractionData(
val originalFile: KtFile, val originalFile: KtFile,
val originalRange: KotlinPsiRange, val originalRange: KotlinPsiRange,
val targetSibling: PsiElement, val targetSibling: PsiElement,
val duplicateContainer: PsiElement? = null, val duplicateContainer: PsiElement? = null,
val options: ExtractionOptions = ExtractionOptions.DEFAULT val options: ExtractionOptions = ExtractionOptions.DEFAULT
) : Disposable { ) : Disposable {
val project: Project = originalFile.project val project: Project = originalFile.project
val originalElements: List<PsiElement> = originalRange.elements val originalElements: List<PsiElement> = originalRange.elements
@@ -99,9 +99,9 @@ data class ExtractionData(
get() = (originalElements.singleOrNull() as? KtExpression)?.extractableSubstringInfo get() = (originalElements.singleOrNull() as? KtExpression)?.extractableSubstringInfo
val insertBefore: Boolean = options.extractAsProperty val insertBefore: Boolean = options.extractAsProperty
|| targetSibling.getStrictParentOfType<KtDeclaration>()?.let { || targetSibling.getStrictParentOfType<KtDeclaration>()?.let {
it is KtDeclarationWithBody || it is KtAnonymousInitializer it is KtDeclarationWithBody || it is KtAnonymousInitializer
} ?: false } ?: false
val expressions = originalElements.filterIsInstance<KtExpression>() val expressions = originalElements.filterIsInstance<KtExpression>()
@@ -126,7 +126,7 @@ data class ExtractionData(
} }
private fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean { 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 val function = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.containingDeclaration) as? KtFunctionLiteral
return function == null || !function.isInsideOf(physicalElements) return function == null || !function.isInsideOf(physicalElements)
} }
@@ -183,12 +183,15 @@ data class ExtractionData(
expressions.forEach { it.accept(visitor) } expressions.forEach { it.accept(visitor) }
} }
fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set<KotlinType> { private fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set<KotlinType> {
val dataFlowValueFactory = expression.getResolutionFacade().frontendService<DataFlowValueFactory>() val dataFlowValueFactory = expression.getResolutionFacade().frontendService<DataFlowValueFactory>()
val dataFlowInfo = context.getDataFlowInfoAfter(expression) val dataFlowInfo = context.getDataFlowInfoAfter(expression)
resolvedCall?.getImplicitReceiverValue()?.let { 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() val type = resolvedCall?.resultingDescriptor?.returnType ?: return emptySet()
@@ -219,13 +222,12 @@ data class ExtractionData(
smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]?.defaultType smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]?.defaultType
possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext) possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext)
val receiverDescriptor = val receiverDescriptor =
(originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor (originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor
shouldSkipPrimaryReceiver = smartCast == null shouldSkipPrimaryReceiver = smartCast == null
&& !DescriptorUtils.isCompanionObject(receiverDescriptor) && !DescriptorUtils.isCompanionObject(receiverDescriptor)
&& qualifiedExpression.receiverExpression !is KtSuperExpression && qualifiedExpression.receiverExpression !is KtSuperExpression
if (shouldSkipPrimaryReceiver && !(originalResolveResult.resolvedCall?.hasBothReceivers() ?: false)) continue if (shouldSkipPrimaryReceiver && originalResolveResult.resolvedCall?.hasBothReceivers() != true) continue
} } else {
else {
if (newRef.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference } != null) continue if (newRef.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference } != null) continue
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]?.defaultType smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]?.defaultType
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext) possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
@@ -239,8 +241,10 @@ data class ExtractionData(
val descriptor = context[BindingContext.REFERENCE_TARGET, newRef] val descriptor = context[BindingContext.REFERENCE_TARGET, newRef]
val isBadRef = !(compareDescriptors(project, originalResolveResult.descriptor, descriptor) val isBadRef = !(compareDescriptors(project, originalResolveResult.descriptor, descriptor)
&& originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement(newRef)) && originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement(
|| smartCast != null newRef
))
|| smartCast != null
if (isBadRef && !originalResolveResult.declaration.isInsideOf(physicalElements)) { if (isBadRef && !originalResolveResult.declaration.isInsideOf(physicalElements)) {
val originalResolvedCall = originalResolveResult.resolvedCall as? VariableAsFunctionResolvedCall val originalResolvedCall = originalResolveResult.resolvedCall as? VariableAsFunctionResolvedCall
val originalFunctionCall = originalResolvedCall?.functionCall val originalFunctionCall = originalResolvedCall?.functionCall
@@ -248,16 +252,43 @@ data class ExtractionData(
val invokeDescriptor = originalFunctionCall?.resultingDescriptor val invokeDescriptor = originalFunctionCall?.resultingDescriptor
if (invokeDescriptor != null) { if (invokeDescriptor != null) {
val invokeDeclaration = getDeclaration(invokeDescriptor, context) ?: synthesizedInvokeDeclaration val invokeDeclaration = getDeclaration(invokeDescriptor, context) ?: synthesizedInvokeDeclaration
val variableResolveResult = originalResolveResult.copy(resolvedCall = originalVariableCall!!, val variableResolveResult = originalResolveResult.copy(
descriptor = originalVariableCall.resultingDescriptor) resolvedCall = originalVariableCall!!,
val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall, descriptor = originalVariableCall.resultingDescriptor
descriptor = originalFunctionCall.resultingDescriptor, )
declaration = invokeDeclaration) val functionResolveResult = originalResolveResult.copy(
referencesInfo.add(ResolvedReferenceInfo(newRef, variableResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver)) resolvedCall = originalFunctionCall,
referencesInfo.add(ResolvedReferenceInfo(newRef, functionResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver)) descriptor = originalFunctionCall.resultingDescriptor,
} declaration = invokeDeclaration
else { )
referencesInfo.add(ResolvedReferenceInfo(newRef, originalResolveResult, smartCast, possibleTypes, shouldSkipPrimaryReceiver)) 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
)
)
} }
} }
} }
@@ -40,19 +40,20 @@ abstract class ExtractionEngineHelper(val operationName: String) {
open fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate() open fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate()
abstract fun configureAndRun( abstract fun configureAndRun(
project: Project, project: Project,
editor: Editor, editor: Editor,
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
onFinish: (ExtractionResult) -> Unit = {} onFinish: (ExtractionResult) -> Unit = {}
) )
} }
class ExtractionEngine( class ExtractionEngine(
val helper: ExtractionEngineHelper val helper: ExtractionEngineHelper
) { ) {
fun run(editor: Editor, fun run(
extractionData: ExtractionData, editor: Editor,
onFinish: (ExtractionResult) -> Unit = {} extractionData: ExtractionData,
onFinish: (ExtractionResult) -> Unit = {}
) { ) {
val project = extractionData.project val project = extractionData.project
@@ -68,8 +69,7 @@ class ExtractionEngine(
helper.configureAndRun(project, editor, validationResult) { helper.configureAndRun(project, editor, validationResult) {
try { try {
onFinish(it) onFinish(it)
} } finally {
finally {
it.dispose() it.dispose()
extractionData.dispose() extractionData.dispose()
} }
@@ -85,24 +85,23 @@ class ExtractionEngine(
AnalysisResult.Status.NON_CRITICAL_ERROR -> { AnalysisResult.Status.NON_CRITICAL_ERROR -> {
val anchorPoint = RelativePoint( val anchorPoint = RelativePoint(
editor.contentComponent, editor.contentComponent,
editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!) editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!)
) )
JBPopupFactory.getInstance()!! JBPopupFactory.getInstance()!!
.createHtmlTextBalloonBuilder( .createHtmlTextBalloonBuilder(
"$message<br/><br/><a href=\"EXTRACT\">Proceed with extraction</a>", "$message<br/><br/><a href=\"EXTRACT\">Proceed with extraction</a>",
MessageType.WARNING, MessageType.WARNING
{ event -> ) { event ->
if (event?.eventType == HyperlinkEvent.EventType.ACTIVATED) { if (event?.eventType == HyperlinkEvent.EventType.ACTIVATED) {
validateAndRefactor() validateAndRefactor()
} }
} }
) .setHideOnClickOutside(true)
.setHideOnClickOutside(true) .setHideOnFrameResize(false)
.setHideOnFrameResize(false) .setHideOnLinkClick(true)
.setHideOnLinkClick(true) .createBalloon()
.createBalloon() .show(anchorPoint, Balloon.Position.below)
.show(anchorPoint, Balloon.Position.below)
} }
AnalysisResult.Status.SUCCESS -> validateAndRefactor() AnalysisResult.Status.SUCCESS -> validateAndRefactor()
@@ -42,33 +42,35 @@ fun KotlinPsiRange.highlight(project: Project, editor: Editor): RangeHighlighter
val highlighters = ArrayList<RangeHighlighter>() val highlighters = ArrayList<RangeHighlighter>()
val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!! val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!!
HighlightManager.getInstance(project).addRangeHighlight( HighlightManager.getInstance(project).addRangeHighlight(
editor, textRange.startOffset, textRange.endOffset, attributes, true, highlighters editor, textRange.startOffset, textRange.endOffset, attributes, true, highlighters
) )
return highlighters.firstOrNull() return highlighters.firstOrNull()
} }
fun KotlinPsiRange.preview(project: Project, editor: Editor): RangeHighlighter? { fun KotlinPsiRange.preview(project: Project, editor: Editor): RangeHighlighter? {
return highlight(project, editor)?.let { val highlight = highlight(project, editor) ?: return null
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)
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( fun processDuplicates(
duplicateReplacers: Map<KotlinPsiRange, () -> Unit>, duplicateReplacers: Map<KotlinPsiRange, () -> Unit>,
project: Project, project: Project,
editor: Editor, editor: Editor,
scopeDescription: String = "this file", scopeDescription: String = "this file",
usageDescription: String = "a usage of extracted declaration" usageDescription: String = "a usage of extracted declaration"
) { ) {
val size = duplicateReplacers.size val size = duplicateReplacers.size
if (size == 0) return if (size == 0) return
@@ -77,22 +79,26 @@ fun processDuplicates(
duplicateReplacers.keys.first().preview(project, editor) duplicateReplacers.keys.first().preview(project, editor)
} }
val answer = if (ApplicationManager.getApplication()!!.isUnitTestMode) val answer = if (ApplicationManager.getApplication()!!.isUnitTestMode) {
Messages.YES Messages.YES
else } else {
Messages.showYesNoDialog( Messages.showYesNoDialog(
project, project,
KotlinRefactoringBundle.message( KotlinRefactoringBundle.message(
"0.has.detected.1.code.fragments.in.2.that.can.be.replaced.with.3", "0.has.detected.1.code.fragments.in.2.that.can.be.replaced.with.3",
ApplicationNamesInfo.getInstance().productName, ApplicationNamesInfo.getInstance().productName,
duplicateReplacers.size, duplicateReplacers.size,
scopeDescription, scopeDescription,
usageDescription usageDescription
), ),
"Process Duplicates", "Process Duplicates",
Messages.getQuestionIcon() Messages.getQuestionIcon()
) )
if (answer != Messages.YES) return }
if (answer != Messages.YES) {
return
}
var showAll = false var showAll = false
@@ -106,15 +112,15 @@ fun processDuplicates(
if (size > 1 && !showAll) { if (size > 1 && !showAll) {
val promptDialog = ReplacePromptDialog(false, RefactoringBundle.message("process.duplicates.title", i + 1, size), project) val promptDialog = ReplacePromptDialog(false, RefactoringBundle.message("process.duplicates.title", i + 1, size), project)
promptDialog.show() promptDialog.show()
when(promptDialog.exitCode) { when (promptDialog.exitCode) {
FindManager.PromptResult.ALL -> showAll = true FindManager.PromptResult.ALL -> showAll = true
FindManager.PromptResult.SKIP -> continue@duplicateReplacersLoop FindManager.PromptResult.SKIP -> continue@duplicateReplacersLoop
FindManager.PromptResult.CANCEL -> return 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) project.executeWriteCommand(MethodDuplicatesHandler.REFACTORING_NAME, replacer)
} }
} }
@@ -75,8 +75,9 @@ import java.util.*
internal val KotlinBuiltIns.defaultReturnType: KotlinType get() = unitType internal val KotlinBuiltIns.defaultReturnType: KotlinType get() = unitType
internal val KotlinBuiltIns.defaultParameterType: KotlinType get() = nullableAnyType internal val KotlinBuiltIns.defaultParameterType: KotlinType get() = nullableAnyType
private fun DeclarationDescriptor.renderForMessage(): String = private fun DeclarationDescriptor.renderForMessage(): String {
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.render(this) return IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.render(this)
}
private val TYPE_RENDERER = DescriptorRenderer.FQ_NAMES_IN_TYPES.withOptions { private val TYPE_RENDERER = DescriptorRenderer.FQ_NAMES_IN_TYPES.withOptions {
typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES 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? = private fun KtDeclaration.renderForMessage(bindingContext: BindingContext): String? =
bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this]?.renderForMessage() bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this]?.renderForMessage()
internal fun KotlinType.isDefault(): Boolean = KotlinBuiltIns.isUnit(this)
private fun List<Instruction>.getModifiedVarDescriptors(bindingContext: BindingContext): Map<VariableDescriptor, List<KtExpression>> { private fun List<Instruction>.getModifiedVarDescriptors(bindingContext: BindingContext): Map<VariableDescriptor, List<KtExpression>> {
val result = HashMap<VariableDescriptor, MutableList<KtExpression>>() val result = HashMap<VariableDescriptor, MutableList<KtExpression>>()
for (instruction in filterIsInstance<WriteValueInstruction>()) { for (instruction in filterIsInstance<WriteValueInstruction>()) {
@@ -125,13 +124,13 @@ private fun List<Instruction>.getVarDescriptorsAccessedAfterwards(bindingContext
} }
private fun List<Instruction>.getExitPoints(): List<Instruction> = private fun List<Instruction>.getExitPoints(): List<Instruction> =
filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } } filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } }
private fun ExtractionData.getResultTypeAndExpressions( private fun ExtractionData.getResultTypeAndExpressions(
instructions: List<Instruction>, instructions: List<Instruction>,
bindingContext: BindingContext, bindingContext: BindingContext,
targetScope: LexicalScope?, targetScope: LexicalScope?,
options: ExtractionOptions, module: ModuleDescriptor options: ExtractionOptions, module: ModuleDescriptor
): Pair<KotlinType, List<KtExpression>> { ): Pair<KotlinType, List<KtExpression>> {
fun instructionToExpression(instruction: Instruction, unwrapReturn: Boolean): KtExpression? { fun instructionToExpression(instruction: Instruction, unwrapReturn: Boolean): KtExpression? {
return when (instruction) { return when (instruction) {
@@ -153,14 +152,15 @@ private fun ExtractionData.getResultTypeAndExpressions(
if (options.inferUnitTypeForUnusedValues && expression.isUsedAsStatement(bindingContext)) return null if (options.inferUnitTypeForUnusedValues && expression.isUsedAsStatement(bindingContext)) return null
return bindingContext.getType(expression) return bindingContext.getType(expression)
?: (expression as? KtReferenceExpression)?.let { ?: (expression as? KtReferenceExpression)?.let {
(bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType
} }
} }
val resultTypes = instructions.mapNotNull(::instructionToType) val resultTypes = instructions.mapNotNull(::instructionToType)
val commonSupertype = if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else module.builtIns.defaultReturnType 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) } val expressions = instructions.mapNotNull { instructionToExpression(it, false) }
@@ -168,7 +168,7 @@ private fun ExtractionData.getResultTypeAndExpressions(
} }
private fun getCommonNonTrivialSuccessorIfAny(instructions: List<Instruction>): Instruction? { private fun getCommonNonTrivialSuccessorIfAny(instructions: List<Instruction>): Instruction? {
val singleSuccessorCheckingVisitor = object: InstructionVisitorWithResult<Boolean>() { val singleSuccessorCheckingVisitor = object : InstructionVisitorWithResult<Boolean>() {
var target: Instruction? = null var target: Instruction? = null
override fun visitInstructionWithNext(instruction: InstructionWithNext): Boolean { override fun visitInstructionWithNext(instruction: InstructionWithNext): Boolean {
@@ -205,9 +205,9 @@ private fun KotlinType.isMeaningful(): Boolean {
} }
private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages( private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages(
pseudocode: Pseudocode, pseudocode: Pseudocode,
localInstructions: List<Instruction>, localInstructions: List<Instruction>,
bindingContext: BindingContext bindingContext: BindingContext
): List<KtNamedDeclaration> { ): List<KtNamedDeclaration> {
val declarations = HashSet<KtNamedDeclaration>() val declarations = HashSet<KtNamedDeclaration>()
pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> pseudocode.traverse(TraversalOrder.FORWARD) { instruction ->
@@ -224,14 +224,14 @@ private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages(
} }
private fun ExtractionData.analyzeControlFlow( private fun ExtractionData.analyzeControlFlow(
localInstructions: List<Instruction>, localInstructions: List<Instruction>,
pseudocode: Pseudocode, pseudocode: Pseudocode,
module: ModuleDescriptor, module: ModuleDescriptor,
bindingContext: BindingContext, bindingContext: BindingContext,
modifiedVarDescriptors: Map<VariableDescriptor, List<KtExpression>>, modifiedVarDescriptors: Map<VariableDescriptor, List<KtExpression>>,
options: ExtractionOptions, options: ExtractionOptions,
targetScope: LexicalScope?, targetScope: LexicalScope?,
parameters: Set<Parameter> parameters: Set<Parameter>
): Pair<ControlFlow, ErrorMessage?> { ): Pair<ControlFlow, ErrorMessage?> {
val exitPoints = localInstructions.getExitPoints() val exitPoints = localInstructions.getExitPoints()
@@ -241,24 +241,23 @@ private fun ExtractionData.analyzeControlFlow(
exitPoints.forEach { exitPoints.forEach {
val e = (it as? UnconditionalJumpInstruction)?.element val e = (it as? UnconditionalJumpInstruction)?.element
val inst = val inst =
when { when {
it !is ReturnValueInstruction && it !is ReturnNoValueInstruction && it.owner != pseudocode -> it !is ReturnValueInstruction && it !is ReturnNoValueInstruction && it.owner != pseudocode ->
null null
it is UnconditionalJumpInstruction && it.targetLabel.isJumpToError -> it is UnconditionalJumpInstruction && it.targetLabel.isJumpToError ->
it it
e != null && e !is KtBreakExpression && e !is KtContinueExpression -> e != null && e !is KtBreakExpression && e !is KtContinueExpression ->
it.previousInstructions.firstOrNull() it.previousInstructions.firstOrNull()
else -> else ->
it it
} }
when (inst) { when (inst) {
is ReturnValueInstruction -> { is ReturnValueInstruction -> {
if (inst.owner == pseudocode) { if (inst.owner == pseudocode) {
if (inst.returnExpressionIfAny == null) { if (inst.returnExpressionIfAny == null) {
defaultExits.add(inst) defaultExits.add(inst)
} } else {
else {
valuedReturnExits.add(inst) valuedReturnExits.add(inst)
} }
} }
@@ -267,11 +266,11 @@ private fun ExtractionData.analyzeControlFlow(
is AbstractJumpInstruction -> { is AbstractJumpInstruction -> {
val element = inst.element val element = inst.element
if ((element is KtReturnExpression && inst.owner == pseudocode) if ((element is KtReturnExpression && inst.owner == pseudocode)
|| element is KtBreakExpression || element is KtBreakExpression
|| element is KtContinueExpression) { || element is KtContinueExpression
) {
jumpExits.add(inst) jumpExits.add(inst)
} } else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) {
else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) {
defaultExits.add(inst) defaultExits.add(inst)
} }
} }
@@ -285,19 +284,31 @@ private fun ExtractionData.analyzeControlFlow(
val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext) val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext)
val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is KtProperty && it.isLocal } val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is KtProperty && it.isLocal }
val (typeOfDefaultFlow, defaultResultExpressions) = getResultTypeAndExpressions(defaultExits, bindingContext, targetScope, options, module) val (typeOfDefaultFlow, defaultResultExpressions) = getResultTypeAndExpressions(
val (returnValueType, valuedReturnExpressions) = getResultTypeAndExpressions(valuedReturnExits, bindingContext, targetScope, options, module) defaultExits,
bindingContext,
targetScope,
options,
module
)
val (returnValueType, valuedReturnExpressions) = getResultTypeAndExpressions(
valuedReturnExits,
bindingContext,
targetScope,
options,
module
)
val emptyControlFlow = 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 val defaultReturnType = if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow
if (defaultReturnType.isError) return emptyControlFlow to ErrorMessage.ERROR_TYPES if (defaultReturnType.isError) return emptyControlFlow to ErrorMessage.ERROR_TYPES
val controlFlow = if (defaultReturnType.isMeaningful()) { val controlFlow = if (defaultReturnType.isMeaningful()) {
emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultResultExpressions, defaultReturnType))) emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultResultExpressions, defaultReturnType)))
} } else {
else {
emptyControlFlow emptyControlFlow
} }
@@ -309,7 +320,7 @@ private fun ExtractionData.analyzeControlFlow(
val outParameters = val outParameters =
parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.originalDescriptor] != null }.sortedBy { it.nameForRef } parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.originalDescriptor] != null }.sortedBy { it.nameForRef }
val outDeclarations = 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 modifiedValueCount = outParameters.size + outDeclarations.size
val outputValues = ArrayList<OutputValue>() val outputValues = ArrayList<OutputValue>()
@@ -321,8 +332,7 @@ private fun ExtractionData.analyzeControlFlow(
if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError
outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow)) outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow))
} } else if (valuedReturnExits.isNotEmpty()) {
else if (valuedReturnExits.isNotEmpty()) {
if (jumpExits.isNotEmpty()) return multipleExitsError if (jumpExits.isNotEmpty()) return multipleExitsError
if (defaultExits.isNotEmpty()) { if (defaultExits.isNotEmpty()) {
@@ -350,11 +360,11 @@ private fun ExtractionData.analyzeControlFlow(
outputValues.size > 3 -> { outputValues.size > 3 -> {
if (!options.enableListBoxing) { if (!options.enableListBoxing) {
val outValuesStr = val outValuesStr =
(outParameters.map { it.originalDescriptor.renderForMessage() } (outParameters.map { it.originalDescriptor.renderForMessage() }
+ outDeclarations.map { it.renderForMessage(bindingContext)!! }).sorted() + outDeclarations.map { it.renderForMessage(bindingContext)!! }).sorted()
return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr) return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr)
} }
{ outputValues -> OutputValueBoxer.AsList(outputValues) } // KT-8596 { values -> OutputValueBoxer.AsList(values) }
} }
else -> controlFlow.boxerFactory else -> controlFlow.boxerFactory
@@ -370,7 +380,16 @@ private fun ExtractionData.analyzeControlFlow(
val conditional = !singleExit && defaultExits.isNotEmpty() val conditional = !singleExit && defaultExits.isNotEmpty()
val elements = jumpExits.map { it.element as KtExpression } val elements = jumpExits.map { it.element as KtExpression }
val elementToInsertAfterCall = if (singleExit) null else elements.first() 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 return controlFlow to null
@@ -386,13 +405,12 @@ fun ExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclarati
val newTargetParent = newTargetSibling.parent val newTargetParent = newTargetSibling.parent
val declaration = KtPsiFactory(originalFile).createDeclarationByPattern<KtNamedDeclaration>( val declaration = KtPsiFactory(originalFile).createDeclarationByPattern<KtNamedDeclaration>(
pattern, pattern,
PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
) )
return if (insertBefore) { return if (insertBefore) {
newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration
} } else {
else {
newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration
} }
} }
@@ -407,20 +425,20 @@ internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression {
private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List<KotlinType> { private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List<KotlinType> {
if (!processTypeArguments) return Collections.singletonList(this) if (!processTypeArguments) return Collections.singletonList(this)
return DFS.dfsFromNode( return DFS.dfsFromNode(
this, this,
Neighbors<KotlinType> { current -> current.arguments.map { it.type } }, Neighbors<KotlinType> { current -> current.arguments.map { it.type } },
VisitedWithSet(), VisitedWithSet(),
object: CollectingNodeHandler<KotlinType, KotlinType, ArrayList<KotlinType>>(ArrayList()) { object : CollectingNodeHandler<KotlinType, KotlinType, ArrayList<KotlinType>>(ArrayList()) {
override fun afterChildren(current: KotlinType) { override fun afterChildren(current: KotlinType) {
result.add(current) result.add(current)
}
} }
}
)!! )!!
} }
fun KtTypeParameter.collectRelevantConstraints(): List<KtTypeConstraint> { fun KtTypeParameter.collectRelevantConstraints(): List<KtTypeConstraint> {
val typeConstraints = getNonStrictParentOfType<KtTypeParameterListOwner>()?.typeConstraints ?: return Collections.emptyList() val typeConstraints = getNonStrictParentOfType<KtTypeParameterListOwner>()?.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<KotlinType> { fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List<KotlinType> {
@@ -443,11 +461,11 @@ private fun KotlinType.isExtractable(targetScope: LexicalScope?): Boolean {
} }
internal fun KotlinType.processTypeIfExtractable( internal fun KotlinType.processTypeIfExtractable(
typeParameters: MutableSet<TypeParameter>, typeParameters: MutableSet<TypeParameter>,
nonDenotableTypes: MutableSet<KotlinType>, nonDenotableTypes: MutableSet<KotlinType>,
options: ExtractionOptions, options: ExtractionOptions,
targetScope: LexicalScope?, targetScope: LexicalScope?,
processTypeArguments: Boolean = true processTypeArguments: Boolean = true
): Boolean { ): Boolean {
return collectReferencedTypes(processTypeArguments).fold(true) { extractable, typeToCheck -> return collectReferencedTypes(processTypeArguments).fold(true) { extractable, typeToCheck ->
val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor
@@ -479,13 +497,13 @@ internal fun KotlinType.processTypeIfExtractable(
} }
internal class MutableParameter( internal class MutableParameter(
override val argumentText: String, override val argumentText: String,
override val originalDescriptor: DeclarationDescriptor, override val originalDescriptor: DeclarationDescriptor,
override val receiverCandidate: Boolean, override val receiverCandidate: Boolean,
private val targetScope: LexicalScope?, private val targetScope: LexicalScope?,
private val originalType: KotlinType, private val originalType: KotlinType,
private val possibleTypes: Set<KotlinType> private val possibleTypes: Set<KotlinType>
): Parameter { ) : Parameter {
// All modifications happen in the same thread // All modifications happen in the same thread
private var writable: Boolean = true private var writable: Boolean = true
private val defaultTypes = LinkedHashSet<KotlinType>() private val defaultTypes = LinkedHashSet<KotlinType>()
@@ -515,8 +533,7 @@ internal class MutableParameter(
writable = false writable = false
if (defaultTypes.isNotEmpty()) { if (defaultTypes.isNotEmpty()) {
TypeIntersector.intersectTypes(defaultTypes)!! TypeIntersector.intersectTypes(defaultTypes)!!
} } else originalType
else originalType
} }
private val parameterTypeCandidates: List<KotlinType> by lazy { private val parameterTypeCandidates: List<KotlinType> by lazy {
@@ -530,8 +547,7 @@ internal class MutableParameter(
if (typePredicate(bounds.upperBound)) add(bounds.upperBound) if (typePredicate(bounds.upperBound)) add(bounds.upperBound)
if (typePredicate(bounds.lowerBound)) add(bounds.lowerBound) if (typePredicate(bounds.lowerBound)) add(bounds.lowerBound)
} }
} } else linkedSetOf(defaultType)
else linkedSetOf(defaultType)
val addNullableTypes = defaultType.isNullabilityFlexible() && typeSet.size > 1 val addNullableTypes = defaultType.isNullabilityFlexible() && typeSet.size > 1
val superTypes = TypeUtils.getAllSupertypes(defaultType).filter(typePredicate) val superTypes = TypeUtils.getAllSupertypes(defaultType).filter(typePredicate)
@@ -547,11 +563,11 @@ internal class MutableParameter(
} }
override fun getParameterTypeCandidates(allowSpecialClassNames: Boolean): List<KotlinType> { override fun getParameterTypeCandidates(allowSpecialClassNames: Boolean): List<KotlinType> {
return if (!allowSpecialClassNames) { return if (!allowSpecialClassNames) {
parameterTypeCandidates.filter { it.isExtractable(targetScope) } parameterTypeCandidates.filter { it.isExtractable(targetScope) }
} else { } else {
parameterTypeCandidates parameterTypeCandidates
} }
} }
override fun getParameterType(allowSpecialClassNames: Boolean): KotlinType { override fun getParameterType(allowSpecialClassNames: Boolean): KotlinType {
@@ -562,31 +578,32 @@ internal class MutableParameter(
} }
private class DelegatingParameter( private class DelegatingParameter(
val original: Parameter, val original: Parameter,
override val name: String, override val name: String,
val parameterType: KotlinType val parameterType: KotlinType
): Parameter by original { ) : Parameter by original {
override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(original, name, parameterType) override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(original, name, parameterType)
override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType
} }
private fun ExtractionData.checkDeclarationsMovingOutOfScope( private fun ExtractionData.checkDeclarationsMovingOutOfScope(
enclosingDeclaration: KtDeclaration, enclosingDeclaration: KtDeclaration,
controlFlow: ControlFlow, controlFlow: ControlFlow,
bindingContext: BindingContext bindingContext: BindingContext
): ErrorMessage? { ): ErrorMessage? {
val declarationsOutOfScope = HashSet<KtNamedDeclaration>() val declarationsOutOfScope = HashSet<KtNamedDeclaration>()
controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(
object : KtTreeVisitorVoid() { object : KtTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val target = expression.mainReference.resolve() val target = expression.mainReference.resolve()
if (target is KtNamedDeclaration if (target is KtNamedDeclaration
&& target.isInsideOf(physicalElements) && target.isInsideOf(physicalElements)
&& target.getStrictParentOfType<KtDeclaration>() == enclosingDeclaration) { && target.getStrictParentOfType<KtDeclaration>() == enclosingDeclaration
declarationsOutOfScope.add(target) ) {
} declarationsOutOfScope.add(target)
} }
} }
}
) )
if (declarationsOutOfScope.isNotEmpty()) { if (declarationsOutOfScope.isNotEmpty()) {
@@ -639,7 +656,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val declaration = commonParent.containingDeclarationForPseudocode ?: return noContainerError val declaration = commonParent.containingDeclarationForPseudocode ?: return noContainerError
val pseudocode = declaration.getContainingPseudocode(bindingContext) 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 localInstructions = getLocalInstructions(pseudocode)
val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext) val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext)
@@ -648,12 +665,12 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val targetScope = targetSibling.getResolutionScope(bindingContext, commonParent.getResolutionFacade()) val targetScope = targetSibling.getResolutionScope(bindingContext, commonParent.getResolutionFacade())
val paramsInfo = inferParametersInfo( val paramsInfo = inferParametersInfo(
virtualBlock, virtualBlock,
commonParent, commonParent,
pseudocode, pseudocode,
bindingContext, bindingContext,
targetScope, targetScope,
modifiedVarDescriptorsWithExpressions.keys modifiedVarDescriptorsWithExpressions.keys
) )
if (paramsInfo.errorMessage != null) { if (paramsInfo.errorMessage != null) {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!))
@@ -665,14 +682,14 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
modifiedVarDescriptorsForControlFlow.keys.retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext)) modifiedVarDescriptorsForControlFlow.keys.retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext))
val (controlFlow, controlFlowMessage) = val (controlFlow, controlFlowMessage) =
analyzeControlFlow( analyzeControlFlow(
localInstructions, localInstructions,
pseudocode, pseudocode,
originalFile.findModuleDescriptor(), originalFile.findModuleDescriptor(),
bindingContext, bindingContext,
modifiedVarDescriptorsForControlFlow, modifiedVarDescriptorsForControlFlow,
options, options,
targetScope, targetScope,
paramsInfo.parameters paramsInfo.parameters
) )
controlFlowMessage?.let { messages.add(it) } controlFlowMessage?.let { messages.add(it) }
@@ -680,11 +697,11 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options, targetScope) returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options, targetScope)
if (paramsInfo.nonDenotableTypes.isNotEmpty()) { if (paramsInfo.nonDenotableTypes.isNotEmpty()) {
val typeStr = paramsInfo.nonDenotableTypes.map {it.renderForMessage()}.sorted() val typeStr = paramsInfo.nonDenotableTypes.map { it.renderForMessage() }.sorted()
return AnalysisResult( return AnalysisResult(
null, null,
Status.CRITICAL_ERROR, Status.CRITICAL_ERROR,
listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)) listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr))
) )
} }
@@ -692,35 +709,35 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
checkDeclarationsMovingOutOfScope(enclosingDeclaration, controlFlow, bindingContext)?.let { messages.add(it) } checkDeclarationsMovingOutOfScope(enclosingDeclaration, controlFlow, bindingContext)?.let { messages.add(it) }
controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(
object : KtTreeVisitorVoid() { object : KtTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
paramsInfo.originalRefToParameter[expression].firstOrNull()?.let { it.refCount-- } paramsInfo.originalRefToParameter[expression].firstOrNull()?.let { it.refCount-- }
}
} }
}
) )
val adjustedParameters = paramsInfo.parameters.filterTo(LinkedHashSet<Parameter>()) { it.refCount > 0 } val adjustedParameters = paramsInfo.parameters.filterTo(LinkedHashSet<Parameter>()) { it.refCount > 0 }
val receiverCandidates = adjustedParameters.filterTo(HashSet<Parameter>()) { it.receiverCandidate } val receiverCandidates = adjustedParameters.filterTo(hashSetOf()) { it.receiverCandidate }
val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null
receiverParameter?.let { adjustedParameters.remove(it) } receiverParameter?.let { adjustedParameters.remove(it) }
var descriptor = ExtractableCodeDescriptor( var descriptor = ExtractableCodeDescriptor(
this, this,
bindingContext, bindingContext,
suggestFunctionNames(returnType), suggestFunctionNames(returnType),
getDefaultVisibility(), getDefaultVisibility(),
adjustedParameters.toList(), adjustedParameters.toList(),
receiverParameter, receiverParameter,
paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! }, paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! },
paramsInfo.replacementMap, paramsInfo.replacementMap,
if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), if (messages.isEmpty()) controlFlow else controlFlow.toDefault(),
returnType, returnType,
emptyList() emptyList()
) )
val generatedDeclaration = ExtractionGeneratorConfiguration( val generatedDeclaration = ExtractionGeneratorConfiguration(
descriptor, descriptor,
ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false) ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false)
).generateDeclaration().declaration ).generateDeclaration().declaration
val virtualContext = generatedDeclaration.analyzeWithContent() val virtualContext = generatedDeclaration.analyzeWithContent()
if (virtualContext.diagnostics.all().any { it.factory == Errors.ILLEGAL_SUSPEND_FUNCTION_CALL || it.factory == Errors.ILLEGAL_SUSPEND_PROPERTY_ACCESS }) { 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( return AnalysisResult(
descriptor, descriptor,
if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR,
messages messages
) )
} }
@@ -739,12 +756,12 @@ private fun ExtractionData.suggestFunctionNames(returnType: KotlinType): List<St
val functionNames = LinkedHashSet<String>() val functionNames = LinkedHashSet<String>()
val validator = val validator =
NewDeclarationNameValidator( NewDeclarationNameValidator(
targetSibling.parent, targetSibling.parent,
if (targetSibling is KtAnonymousInitializer) targetSibling.parent else targetSibling, if (targetSibling is KtAnonymousInitializer) targetSibling.parent else targetSibling,
if (options.extractAsProperty) NewDeclarationNameValidator.Target.VARIABLES else NewDeclarationNameValidator.Target.FUNCTIONS_AND_CLASSES if (options.extractAsProperty) NewDeclarationNameValidator.Target.VARIABLES else NewDeclarationNameValidator.Target.FUNCTIONS_AND_CLASSES
) )
if (!returnType.isDefault()) { if (!KotlinBuiltIns.isUnit(returnType)) {
functionNames.addAll(KotlinNameSuggester.suggestNamesByType(returnType, validator)) functionNames.addAll(KotlinNameSuggester.suggestNamesByType(returnType, validator))
} }
@@ -759,20 +776,20 @@ private fun ExtractionData.suggestFunctionNames(returnType: KotlinType): List<St
} }
internal fun KtNamedDeclaration.getGeneratedBody() = internal fun KtNamedDeclaration.getGeneratedBody() =
when (this) { when (this) {
is KtNamedFunction -> bodyExpression is KtNamedFunction -> bodyExpression
else -> { else -> {
val property = this as KtProperty val property = this as KtProperty
property.getter?.bodyExpression?.let { return it } property.getter?.bodyExpression?.let { return it }
property.initializer?.let { return it } property.initializer?.let { return it }
// We assume lazy property here with delegate expression 'by Delegates.lazy { body }' // We assume lazy property here with delegate expression 'by Delegates.lazy { body }'
property.delegateExpression?.let { property.delegateExpression?.let {
val call = it.getCalleeExpressionIfAny()?.parent as? KtCallExpression val call = it.getCalleeExpressionIfAny()?.parent as? KtCallExpression
call?.lambdaArguments?.singleOrNull()?.getLambdaExpression()?.bodyExpression 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 @JvmOverloads
fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarget.FUNCTION): ExtractableCodeDescriptorWithConflicts { fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarget.FUNCTION): ExtractableCodeDescriptorWithConflicts {
@@ -785,8 +802,8 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg
val conflicts = MultiMap<PsiElement, String>() val conflicts = MultiMap<PsiElement, String>()
val result = ExtractionGeneratorConfiguration( val result = ExtractionGeneratorConfiguration(
this, this,
ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false, target = target) ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false, target = target)
).generateDeclaration() ).generateDeclaration()
val valueParameterList = (result.declaration as? KtNamedFunction)?.valueParameterList 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 currentDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, currentRefExpr]
val currentTarget = 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 KtParameter && currentTarget.parent == valueParameterList) return
if (currentTarget is KtTypeParameter && currentTarget.parent == typeParameterList) return if (currentTarget is KtTypeParameter && currentTarget.parent == typeParameterList) return
if (currentDescriptor is LocalVariableDescriptor 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 } if (diagnostics.any { it.factory in Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS }
|| (currentDescriptor != null || (currentDescriptor != null
&& !ErrorUtils.isError(currentDescriptor) && !ErrorUtils.isError(currentDescriptor)
&& !compareDescriptors(extractionData.project, currentDescriptor, resolveResult.descriptor))) { && !compareDescriptors(extractionData.project, currentDescriptor, resolveResult.descriptor))) {
conflicts.putValue( conflicts.putValue(
resolveResult.originalRefExpr, resolveResult.originalRefExpr,
getDeclarationMessage(resolveResult.declaration, "0.will.no.longer.be.accessible.after.extraction") getDeclarationMessage(resolveResult.declaration, "0.will.no.longer.be.accessible.after.extraction")
) )
return return
} }
@@ -831,23 +849,21 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg
} }
result.declaration.accept( result.declaration.accept(
object : KtTreeVisitorVoid() { object : KtTreeVisitorVoid() {
override fun visitUserType(userType: KtUserType) { override fun visitUserType(userType: KtUserType) {
val refExpr = userType.referenceExpression ?: return val refExpr = userType.referenceExpression ?: return
val diagnostics = bindingContext.diagnostics.forElement(refExpr) val diagnostics = bindingContext.diagnostics.forElement(refExpr)
diagnostics.firstOrNull { it.factory == Errors.INVISIBLE_REFERENCE }?.let { diagnostics.firstOrNull { it.factory == Errors.INVISIBLE_REFERENCE }?.let {
val declaration = refExpr.mainReference.resolve() as? PsiNamedElement ?: return val declaration = refExpr.mainReference.resolve() as? PsiNamedElement ?: return
conflicts.putValue(declaration, getDeclarationMessage(declaration, "0.will.become.invisible.after.extraction")) conflicts.putValue(declaration, getDeclarationMessage(declaration, "0.will.become.invisible.after.extraction"))
}
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
processReference(expression)
} }
} }
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
processReference(expression)
}
}
) )
return ExtractableCodeDescriptorWithConflicts(this, conflicts) return ExtractableCodeDescriptorWithConflicts(this, conflicts)
} }
private val LOG = Logger.getInstance(ExtractionEngine::class.java)
@@ -24,6 +24,7 @@ import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.BaseRefactoringProcessor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
@@ -81,22 +82,22 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D
modifier(modifiers.joinToString(separator = " ")) modifier(modifiers.joinToString(separator = " "))
typeParams( typeParams(
config.descriptor.typeParameters.map { config.descriptor.typeParameters.map {
val typeParameter = it.originalDeclaration val typeParameter = it.originalDeclaration
val bound = typeParameter.extendsBound val bound = typeParameter.extendsBound
buildString { buildString {
if (it.isReified()) { if (it.isReified()) {
append(KtTokens.REIFIED_KEYWORD.value) append(KtTokens.REIFIED_KEYWORD.value)
append(' ') append(' ')
} }
append(typeParameter.name) append(typeParameter.name)
if (bound != null) { if (bound != null) {
append(" : ") append(" : ")
append(bound.text) append(bound.text)
}
} }
} }
}
) )
fun KotlinType.typeAsString(): String { fun KotlinType.typeAsString(): String {
@@ -116,12 +117,14 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D
name(config.generatorOptions.dummyName ?: config.descriptor.name) name(config.generatorOptions.dummyName ?: config.descriptor.name)
config.descriptor.parameters.forEach { parameter -> config.descriptor.parameters.forEach { parameter ->
param(parameter.name, param(
parameter.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames).typeAsString()) parameter.name,
parameter.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames).typeAsString()
)
} }
with(config.descriptor.returnType) { with(config.descriptor.returnType) {
if (isDefault() || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) { if (KotlinBuiltIns.isUnit(this) || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
noReturnType() noReturnType()
} else { } else {
returnType(typeAsString()) returnType(typeAsString())
@@ -135,7 +138,7 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D
fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString() fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString()
fun ExtractionGeneratorConfiguration.getDeclarationPattern( fun ExtractionGeneratorConfiguration.getDeclarationPattern(
descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE
): String { ): String {
val extractionTarget = generatorOptions.target val extractionTarget = generatorOptions.target
if (!extractionTarget.isAvailable(descriptor)) { if (!extractionTarget.isAvailable(descriptor)) {
@@ -172,9 +175,9 @@ fun createNameCounterpartMap(from: KtElement, to: KtElement): Map<KtSimpleNameEx
} }
class DuplicateInfo( class DuplicateInfo(
val range: KotlinPsiRange, val range: KotlinPsiRange,
val controlFlow: ControlFlow, val controlFlow: ControlFlow,
val arguments: List<String> val arguments: List<String>
) )
fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> { fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> {
@@ -225,25 +228,31 @@ fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> {
return if (matched) newControlFlow else null 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 unifier = KotlinPsiUnifier(unifierParameters, true)
val scopeElement = getOccurrenceContainer() ?: return Collections.emptyList() val scopeElement = getOccurrenceContainer() ?: return Collections.emptyList()
val originalTextRange = extractionData.originalRange.getPhysicalTextRange() val originalTextRange = extractionData.originalRange.getPhysicalTextRange()
return extractionData return extractionData
.originalRange .originalRange
.match(scopeElement, unifier) .match(scopeElement, unifier)
.asSequence() .asSequence()
.filter { !(it.range.getPhysicalTextRange().intersects(originalTextRange)) } .filter { !(it.range.getPhysicalTextRange().intersects(originalTextRange)) }
.mapNotNull { match -> .mapNotNull { match ->
val controlFlow = getControlFlowIfMatched(match) val controlFlow = getControlFlowIfMatched(match)
val range = with(match.range) { val range = with(match.range) {
(elements.singleOrNull() as? KtStringTemplateEntryWithExpression)?.expression?.toRange() ?: this (elements.singleOrNull() as? KtStringTemplateEntryWithExpression)?.expression?.toRange() ?: this
}
controlFlow?.let { DuplicateInfo(range, it, unifierParameters.map { match.substitution[it]!!.text!! }) }
} }
.toList()
controlFlow?.let {
DuplicateInfo(range, it, unifierParameters.map { param ->
match.substitution[param]!!.text!!
})
}
}
.toList()
} }
private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? { private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? {
@@ -251,11 +260,12 @@ private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? {
} }
private fun makeCall( private fun makeCall(
extractableDescriptor: ExtractableCodeDescriptor, extractableDescriptor: ExtractableCodeDescriptor,
declaration: KtNamedDeclaration, declaration: KtNamedDeclaration,
controlFlow: ControlFlow, controlFlow: ControlFlow,
rangeToReplace: KotlinPsiRange, rangeToReplace: KotlinPsiRange,
arguments: List<String>) { arguments: List<String>
) {
fun insertCall(anchor: PsiElement, wrappedCall: KtExpression): KtExpression? { fun insertCall(anchor: PsiElement, wrappedCall: KtExpression): KtExpression? {
val firstExpression = rangeToReplace.elements.firstOrNull { it is KtExpression } as? KtExpression val firstExpression = rangeToReplace.elements.firstOrNull { it is KtExpression } as? KtExpression
if (firstExpression?.isLambdaOutsideParentheses() ?: false) { if (firstExpression?.isLambdaOutsideParentheses() ?: false) {
@@ -318,13 +328,13 @@ private fun makeCall(
val isVar = declarationsToMerge.first().isVar val isVar = declarationsToMerge.first().isVar
if (declarationsToMerge.all { it.isVar == isVar }) { if (declarationsToMerge.all { it.isVar == isVar }) {
controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach { controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach {
block.addBefore(psiFactory.createDeclaration<KtDeclaration>(it.text!!), anchorInBlock) as KtDeclaration block.addBefore(psiFactory.createDeclaration(it.text!!), anchorInBlock) as KtDeclaration
block.addBefore(newLine, anchorInBlock) block.addBefore(newLine, anchorInBlock)
} }
val entries = declarationsToMerge.map { p -> p.name + (p.typeReference?.let { ": ${it.text}" } ?: "") } val entries = declarationsToMerge.map { p -> p.name + (p.typeReference?.let { ": ${it.text}" } ?: "") }
anchorInBlock?.replace( anchorInBlock?.replace(
psiFactory.createDestructuringDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText") psiFactory.createDestructuringDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText")
) )
return return
@@ -333,16 +343,15 @@ private fun makeCall(
val inlinableCall = controlFlow.outputValues.size <= 1 val inlinableCall = controlFlow.outputValues.size <= 1
val unboxingExpressions = val unboxingExpressions =
if (inlinableCall) { if (inlinableCall) {
controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return) controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return)
} } else {
else { val varNameValidator = NewDeclarationNameValidator(block, anchorInBlock, NewDeclarationNameValidator.Target.VARIABLES)
val varNameValidator = NewDeclarationNameValidator(block, anchorInBlock, NewDeclarationNameValidator.Target.VARIABLES) val resultVal = KotlinNameSuggester.suggestNamesByType(extractableDescriptor.returnType, varNameValidator, null).first()
val resultVal = KotlinNameSuggester.suggestNamesByType(extractableDescriptor.returnType, varNameValidator, null).first() block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock)
block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) block.addBefore(newLine, anchorInBlock)
block.addBefore(newLine, anchorInBlock) controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal)
controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) }
}
val copiedDeclarations = HashMap<KtDeclaration, KtDeclaration>() val copiedDeclarations = HashMap<KtDeclaration, KtDeclaration>()
for (decl in controlFlow.declarationsToCopy) { for (decl in controlFlow.declarationsToCopy) {
@@ -363,8 +372,7 @@ private fun makeCall(
val firstReturn = outputValue.originalExpressions.asSequence().filterIsInstance<KtReturnExpression>().firstOrNull() val firstReturn = outputValue.originalExpressions.asSequence().filterIsInstance<KtReturnExpression>().firstOrNull()
val label = firstReturn?.getTargetLabel()?.text ?: "" val label = firstReturn?.getTargetLabel()?.text ?: ""
"return$label $callText" "return$label $callText"
} } else {
else {
callText callText
} }
Collections.singletonList(psiFactory.createExpression(exprText)) Collections.singletonList(psiFactory.createExpression(exprText))
@@ -372,19 +380,19 @@ private fun makeCall(
is ParameterUpdate -> is ParameterUpdate ->
Collections.singletonList( Collections.singletonList(
psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText") psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText")
) )
is Jump -> { is Jump -> {
when { when {
outputValue.elementToInsertAfterCall == null -> Collections.singletonList(psiFactory.createExpression(callText)) outputValue.elementToInsertAfterCall == null -> Collections.singletonList(psiFactory.createExpression(callText))
outputValue.conditional -> Collections.singletonList( outputValue.conditional -> Collections.singletonList(
psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.text}") psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.text}")
) )
else -> listOf( else -> listOf(
psiFactory.createExpression(callText), psiFactory.createExpression(callText),
newLine, newLine,
psiFactory.createExpression(outputValue.elementToInsertAfterCall.text!!) psiFactory.createExpression(outputValue.elementToInsertAfterCall.text!!)
) )
} }
} }
@@ -402,17 +410,17 @@ private fun makeCall(
val defaultValue = controlFlow.defaultOutputValue val defaultValue = controlFlow.defaultOutputValue
controlFlow.outputValues controlFlow.outputValues
.filter { it != defaultValue } .filter { it != defaultValue }
.flatMap { wrapCall(it, unboxingExpressions[it]!!) } .flatMap { wrapCall(it, unboxingExpressions[it]!!) }
.withIndex() .withIndex()
.forEach { .forEach {
val (i, e) = it val (i, e) = it
if (i > 0) { if (i > 0) {
block.addBefore(newLine, anchorInBlock) block.addBefore(newLine, anchorInBlock)
}
block.addBefore(e, anchorInBlock)
} }
block.addBefore(e, anchorInBlock)
}
defaultValue?.let { defaultValue?.let {
if (!inlinableCall) { if (!inlinableCall) {
@@ -433,12 +441,12 @@ private var KtReturnExpression.isReturnForLabelRemoval: Boolean
by NotNullablePsiCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false) by NotNullablePsiCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false)
fun ExtractionGeneratorConfiguration.generateDeclaration( fun ExtractionGeneratorConfiguration.generateDeclaration(
declarationToReplace: KtNamedDeclaration? = null declarationToReplace: KtNamedDeclaration? = null
): ExtractionResult{ ): ExtractionResult {
val psiFactory = KtPsiFactory(descriptor.extractionData.originalFile) val psiFactory = KtPsiFactory(descriptor.extractionData.originalFile)
fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues
.flatMapTo(ArrayList<KtReturnExpression>()) { it.originalExpressions.filterIsInstance<KtReturnExpression>() } .flatMapTo(arrayListOf()) { it.originalExpressions.filterIsInstance<KtReturnExpression>() }
fun createDeclaration(): KtNamedDeclaration { fun createDeclaration(): KtNamedDeclaration {
descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true } descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true }
@@ -447,11 +455,10 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
return with(descriptor.extractionData) { return with(descriptor.extractionData) {
if (generatorOptions.inTempFile) { if (generatorOptions.inTempFile) {
createTemporaryDeclaration("${getDeclarationPattern()}\n") createTemporaryDeclaration("${getDeclarationPattern()}\n")
} } else {
else {
psiFactory.createDeclarationByPattern( psiFactory.createDeclarationByPattern(
getDeclarationPattern(), getDeclarationPattern(),
PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
) )
} }
} }
@@ -459,15 +466,15 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
fun getReturnArguments(resultExpression: KtExpression?): List<KtExpression> { fun getReturnArguments(resultExpression: KtExpression?): List<KtExpression> {
return descriptor.controlFlow.outputValues return descriptor.controlFlow.outputValues
.mapNotNull { .mapNotNull {
when (it) { when (it) {
is ExpressionValue -> resultExpression is ExpressionValue -> resultExpression
is Jump -> if (it.conditional) psiFactory.createExpression("false") else null is Jump -> if (it.conditional) psiFactory.createExpression("false") else null
is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef) is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef)
is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!) is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!)
else -> throw IllegalArgumentException("Unknown output value: $it") else -> throw IllegalArgumentException("Unknown output value: $it")
}
} }
}
} }
fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) { fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) {
@@ -528,20 +535,19 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
* before calls/types themselves * before calls/types themselves
*/ */
val currentRefs = body val currentRefs = body
.collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null } .collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null }
.sortedByDescending { it.startOffset } .sortedByDescending { it.startOffset }
currentRefs.forEach { currentRefs.forEach {
val resolveResult = it.resolveResult!! val resolveResult = it.resolveResult!!
val currentRef = if (it.isValid) { val currentRef = if (it.isValid) {
it it
} } else {
else { body.findDescendantOfType { expr -> expr.resolveResult == resolveResult } ?: return@forEach
body.findDescendantOfType<KtSimpleNameExpression> { it.resolveResult == resolveResult } ?: return@forEach
} }
val originalRef = resolveResult.originalRefExpr val originalRef = resolveResult.originalRefExpr
val newRef = descriptor.replacementMap[originalRef] 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 (newRef as? KtSimpleNameExpression)?.resolveResult = resolveResult
} }
@@ -565,19 +571,19 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
if (lastExpression is KtReturnExpression) return if (lastExpression is KtReturnExpression) return
val defaultExpression = val defaultExpression =
if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) { if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) {
val varNameValidator = NewDeclarationNameValidator(body, lastExpression, NewDeclarationNameValidator.Target.VARIABLES) val varNameValidator = NewDeclarationNameValidator(body, lastExpression, NewDeclarationNameValidator.Target.VARIABLES)
val resultVal = KotlinNameSuggester.suggestNamesByType(defaultValue.valueType, varNameValidator, null).first() val resultVal = KotlinNameSuggester.suggestNamesByType(defaultValue.valueType, varNameValidator, null).first()
body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression) body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression)
body.addBefore(psiFactory.createNewLine(), lastExpression) body.addBefore(psiFactory.createNewLine(), lastExpression)
psiFactory.createExpression(resultVal) psiFactory.createExpression(resultVal)
} } else lastExpression
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") @Suppress("NON_EXHAUSTIVE_WHEN")
when(generatorOptions.target) { when (generatorOptions.target) {
ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> { 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 // 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 // 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(declaration, anchor) as KtNamedDeclaration).apply {
targetContainer.addBefore(emptyLines, anchor) targetContainer.addBefore(emptyLines, anchor)
} }
} } else {
else {
(targetContainer.addAfter(declaration, anchor) as KtNamedDeclaration).apply { (targetContainer.addAfter(declaration, anchor) as KtNamedDeclaration).apply {
if (!(targetContainer is KtClassBody && (targetContainer.parent as? KtClass)?.isEnum() ?: false)) { if (!(targetContainer is KtClassBody && (targetContainer.parent as? KtClass)?.isEnum() ?: false)) {
targetContainer.addAfter(emptyLines, anchor) targetContainer.addAfter(emptyLines, anchor)
@@ -630,7 +635,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
val anchor = with(descriptor.extractionData) { val anchor = with(descriptor.extractionData) {
val targetParent = targetSibling.parent val targetParent = targetSibling.parent
val anchorCandidates = duplicates.mapTo(ArrayList<PsiElement>()) { it.range.elements.first().substringContextOrThis } val anchorCandidates = duplicates.mapTo(arrayListOf()) { it.range.elements.first().substringContextOrThis }
anchorCandidates.add(targetSibling) anchorCandidates.add(targetSibling)
if (targetSibling is KtEnumEntry) { if (targetSibling is KtEnumEntry) {
anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry }) anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry })
@@ -638,8 +643,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
val marginalCandidate = if (insertBefore) { val marginalCandidate = if (insertBefore) {
anchorCandidates.minBy { it.startOffset }!! anchorCandidates.minBy { it.startOffset }!!
} } else {
else {
anchorCandidates.maxBy { it.startOffset }!! anchorCandidates.maxBy { it.startOffset }!!
} }
@@ -70,12 +70,12 @@ internal class ParametersInfo {
} }
internal fun ExtractionData.inferParametersInfo( internal fun ExtractionData.inferParametersInfo(
virtualBlock: KtBlockExpression, virtualBlock: KtBlockExpression,
commonParent: PsiElement, commonParent: PsiElement,
pseudocode: Pseudocode, pseudocode: Pseudocode,
bindingContext: BindingContext, bindingContext: BindingContext,
targetScope: LexicalScope, targetScope: LexicalScope,
modifiedVarDescriptors: Set<VariableDescriptor> modifiedVarDescriptors: Set<VariableDescriptor>
): ParametersInfo { ): ParametersInfo {
val info = ParametersInfo() val info = ParametersInfo()
@@ -95,8 +95,7 @@ internal fun ExtractionData.inferParametersInfo(
val extensionReceiver = resolvedCall?.extensionReceiver val extensionReceiver = resolvedCall?.extensionReceiver
val receiverToExtract = (if (extensionReceiver == null || isSynthesizedInvoke(refInfo.resolveResult.descriptor)) { val receiverToExtract = (if (extensionReceiver == null || isSynthesizedInvoke(refInfo.resolveResult.descriptor)) {
resolvedCall?.dispatchReceiver resolvedCall?.dispatchReceiver
} } else {
else {
extensionReceiver extensionReceiver
}) })
@@ -105,36 +104,60 @@ internal fun ExtractionData.inferParametersInfo(
if (options.canWrapInWith if (options.canWrapInWith
&& twoReceivers && twoReceivers
&& resolvedCall!!.extensionReceiver is ExpressionReceiver && resolvedCall!!.extensionReceiver is ExpressionReceiver
&& DescriptorUtils.isObject(dispatchReceiverDescriptor)) { && DescriptorUtils.isObject(dispatchReceiverDescriptor)
info.replacementMap.putValue(refInfo.resolveResult.originalRefExpr, ) {
WrapObjectInWithReplacement(dispatchReceiverDescriptor as ClassDescriptor)) info.replacementMap.putValue(
refInfo.resolveResult.originalRefExpr,
WrapObjectInWithReplacement(dispatchReceiverDescriptor as ClassDescriptor)
)
continue continue
} }
if (!refInfo.shouldSkipPrimaryReceiver) { 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) { 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( val varNameValidator = NewDeclarationNameValidator(
commonParent.getNonStrictParentOfType<KtExpression>()!!, commonParent.getNonStrictParentOfType<KtExpression>()!!,
physicalElements.firstOrNull(), physicalElements.firstOrNull(),
NewDeclarationNameValidator.Target.VARIABLES NewDeclarationNameValidator.Target.VARIABLES
) )
val existingParameterNames = hashSetOf<String>() val existingParameterNames = hashSetOf<String>()
for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) { for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) {
if (!parameter if (!parameter
.getParameterType(options.allowSpecialClassNames) .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) { if (currentName == null) {
currentName = KotlinNameSuggester.suggestNamesByType(getParameterType(options.allowSpecialClassNames), varNameValidator, "p").first() currentName =
KotlinNameSuggester.suggestNamesByType(getParameterType(options.allowSpecialClassNames), varNameValidator, "p")
.first()
} }
require(currentName != null) require(currentName != null)
@@ -147,7 +170,10 @@ internal fun ExtractionData.inferParametersInfo(
currentName = "$currentName$index" 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) info.parameters.add(this)
currentName?.let { existingParameterNames += it } currentName?.let { existingParameterNames += it }
} }
@@ -162,14 +188,14 @@ internal fun ExtractionData.inferParametersInfo(
} }
private fun ExtractionData.extractReceiver( private fun ExtractionData.extractReceiver(
receiverToExtract: ReceiverValue?, receiverToExtract: ReceiverValue?,
info: ParametersInfo, info: ParametersInfo,
targetScope: LexicalScope, targetScope: LexicalScope,
refInfo: ResolvedReferenceInfo, refInfo: ResolvedReferenceInfo,
extractedDescriptorToParameter: HashMap<DeclarationDescriptor, MutableParameter>, extractedDescriptorToParameter: HashMap<DeclarationDescriptor, MutableParameter>,
pseudocode: Pseudocode, pseudocode: Pseudocode,
bindingContext: BindingContext, bindingContext: BindingContext,
isMemberExtension: Boolean isMemberExtension: Boolean
) { ) {
val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult
@@ -178,14 +204,15 @@ private fun ExtractionData.extractReceiver(
val thisExpr = refInfo.refExpr.parent as? KtThisExpression val thisExpr = refInfo.refExpr.parent as? KtThisExpression
if (hasThisReceiver if (hasThisReceiver
&& DescriptorToSourceUtilsIde.getAllDeclarations(project, thisDescriptor!!).all { it.isInsideOf(physicalElements) }) { && DescriptorToSourceUtilsIde.getAllDeclarations(project, thisDescriptor!!).all { it.isInsideOf(physicalElements) }
) {
return return
} }
val referencedClassifierDescriptor: ClassifierDescriptor? = (thisDescriptor ?: originalDescriptor).let { val referencedClassifierDescriptor: ClassifierDescriptor? = (thisDescriptor ?: originalDescriptor).let {
when (it) { when (it) {
is ClassDescriptor -> is ClassDescriptor ->
when(it.kind) { when (it.kind) {
ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it
ClassKind.ENUM_ENTRY -> it.containingDeclaration as? ClassDescriptor ClassKind.ENUM_ENTRY -> it.containingDeclaration as? ClassDescriptor
else -> if (refInfo.refExpr.getNonStrictParentOfType<KtTypeReference>() != null) it else null else -> if (refInfo.refExpr.getNonStrictParentOfType<KtTypeReference>() != null) it else null
@@ -202,29 +229,30 @@ private fun ExtractionData.extractReceiver(
if (referencedClassifierDescriptor != null) { if (referencedClassifierDescriptor != null) {
if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable( if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable(
info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor
)) return )
) return
if (options.canWrapInWith if (options.canWrapInWith
&& resolvedCall != null && resolvedCall != null
&& resolvedCall.hasBothReceivers() && resolvedCall.hasBothReceivers()
&& DescriptorUtils.isObject(referencedClassifierDescriptor)) { && DescriptorUtils.isObject(referencedClassifierDescriptor)
) {
info.replacementMap.putValue(originalRef, WrapObjectInWithReplacement(referencedClassifierDescriptor as ClassDescriptor)) info.replacementMap.putValue(originalRef, WrapObjectInWithReplacement(referencedClassifierDescriptor as ClassDescriptor))
} else if (referencedClassifierDescriptor is ClassDescriptor) { } else if (referencedClassifierDescriptor is ClassDescriptor) {
info.replacementMap.putValue(originalRef, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe)) info.replacementMap.putValue(originalRef, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe))
} }
} } else {
else {
val extractThis = (hasThisReceiver && refInfo.smartCast == null) || thisExpr != null val extractThis = (hasThisReceiver && refInfo.smartCast == null) || thisExpr != null
val extractOrdinaryParameter = val extractOrdinaryParameter =
originalDeclaration is KtDestructuringDeclarationEntry || originalDeclaration is KtDestructuringDeclarationEntry ||
originalDeclaration is KtProperty || originalDeclaration is KtProperty ||
originalDeclaration is KtParameter originalDeclaration is KtParameter
val extractFunctionRef = val extractFunctionRef =
options.captureLocalFunctions options.captureLocalFunctions
&& originalRef.getReferencedName() == originalDescriptor.name.asString() // to forbid calls by convention && originalRef.getReferencedName() == originalDescriptor.name.asString() // to forbid calls by convention
&& originalDeclaration is KtNamedFunction && originalDeclaration.isLocal && originalDeclaration is KtNamedFunction && originalDeclaration.isLocal
&& targetScope.findFunction(originalDescriptor.name, NoLookupLocation.FROM_IDE) { it == originalDescriptor } == null && targetScope.findFunction(originalDescriptor.name, NoLookupLocation.FROM_IDE) { it == originalDescriptor } == null
val descriptorToExtract = (if (extractThis) thisDescriptor else null) ?: originalDescriptor val descriptorToExtract = (if (extractThis) thisDescriptor else null) ?: originalDescriptor
@@ -240,32 +268,46 @@ private fun ExtractionData.extractReceiver(
else -> (originalRef.parent as? KtThisExpression) ?: originalRef 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) { val parameter = extractedDescriptorToParameter.getOrPut(descriptorToExtract) {
var argumentText = var argumentText =
if (hasThisReceiver && extractThis) { if (hasThisReceiver && extractThis) {
val label = if (descriptorToExtract is ClassDescriptor) "@${descriptorToExtract.name.asString()}" else "" val label = if (descriptorToExtract is ClassDescriptor) "@${descriptorToExtract.name.asString()}" else ""
"this$label" "this$label"
} } else {
else { val argumentExpr = (thisExpr ?: refInfo.refExpr).getQualifiedExpressionForSelectorOrThis()
val argumentExpr = (thisExpr ?: refInfo.refExpr).getQualifiedExpressionForSelectorOrThis() if (argumentExpr is KtOperationReferenceExpression) {
if (argumentExpr is KtOperationReferenceExpression) { val nameElement = argumentExpr.getReferencedNameElement()
val nameElement = argumentExpr.getReferencedNameElement() val nameElementType = nameElement.node.elementType
val nameElementType = nameElement.node.elementType (nameElementType as? KtToken)?.let {
(nameElementType as? KtToken)?.let { OperatorConventions.getNameForOperationSymbol(it)?.asString()
OperatorConventions.getNameForOperationSymbol(it)?.asString() } ?: nameElement.text
} ?: nameElement.text } else argumentExpr.text
} ?: throw AssertionError("reference shouldn't be empty: code fragment = $codeFragmentText")
else argumentExpr.text }
?: throw AssertionError("reference shouldn't be empty: code fragment = $codeFragmentText")
}
if (extractFunctionRef) { if (extractFunctionRef) {
val receiverTypeText = (originalDeclaration as KtCallableDeclaration).receiverTypeReference?.text ?: "" val receiverTypeText = (originalDeclaration as KtCallableDeclaration).receiverTypeReference?.text ?: ""
argumentText = "$receiverTypeText::$argumentText" 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) 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 instruction = pseudocode.getElementValue(callElement)?.createdAt as? InstructionWithReceivers
val receiverValue = instruction?.receiverValues?.entries?.singleOrNull { it.value == receiverToExtract }?.key val receiverValue = instruction?.receiverValues?.entries?.singleOrNull { it.value == receiverToExtract }?.key
if (receiverValue != null) { 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)) parameter.addTypePredicate(SingleType(parameterType))
} } else {
else {
pseudocode.getElementValuesRecursively(originalRef).forEach { pseudocode.getElementValuesRecursively(originalRef).forEach {
parameter.addTypePredicate(getExpectedTypePredicate(it, bindingContext, targetScope.ownerDescriptor.builtIns)) parameter.addTypePredicate(getExpectedTypePredicate(it, bindingContext, targetScope.ownerDescriptor.builtIns))
} }
@@ -310,50 +356,50 @@ private fun ExtractionData.extractReceiver(
} }
private fun suggestParameterType( private fun suggestParameterType(
extractFunctionRef: Boolean, extractFunctionRef: Boolean,
originalDescriptor: DeclarationDescriptor, originalDescriptor: DeclarationDescriptor,
parameterExpression: KtExpression?, parameterExpression: KtExpression?,
receiverToExtract: ReceiverValue?, receiverToExtract: ReceiverValue?,
resolvedCall: ResolvedCall<*>?, resolvedCall: ResolvedCall<*>?,
useSmartCastsIfPossible: Boolean, bindingContext: BindingContext useSmartCastsIfPossible: Boolean, bindingContext: BindingContext
): KotlinType { ): KotlinType {
val builtIns = originalDescriptor.builtIns val builtIns = originalDescriptor.builtIns
return when { return when {
extractFunctionRef -> { extractFunctionRef -> {
originalDescriptor as FunctionDescriptor originalDescriptor as FunctionDescriptor
createFunctionType( createFunctionType(
builtIns, builtIns,
Annotations.EMPTY, Annotations.EMPTY,
originalDescriptor.extensionReceiverParameter?.type, originalDescriptor.extensionReceiverParameter?.type,
originalDescriptor.valueParameters.map { it.type }, originalDescriptor.valueParameters.map { it.type },
originalDescriptor.valueParameters.map { it.name }, originalDescriptor.valueParameters.map { it.name },
originalDescriptor.returnType ?: builtIns.defaultReturnType originalDescriptor.returnType ?: builtIns.defaultReturnType
) )
} }
parameterExpression != null -> parameterExpression != null ->
(if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression]?.defaultType else null) (if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression]?.defaultType else null)
?: bindingContext.getType(parameterExpression) ?: bindingContext.getType(parameterExpression)
?: (parameterExpression as? KtReferenceExpression)?.let { ?: (parameterExpression as? KtReferenceExpression)?.let {
(bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType
} }
?: receiverToExtract?.type ?: receiverToExtract?.type
receiverToExtract is ImplicitReceiver -> { receiverToExtract is ImplicitReceiver -> {
val typeByDataFlowInfo = if (useSmartCastsIfPossible) { val typeByDataFlowInfo = if (useSmartCastsIfPossible) {
val callElement = resolvedCall!!.call.callElement val callElement = resolvedCall!!.call.callElement
val dataFlowInfo = bindingContext.getDataFlowInfoAfter(callElement) val dataFlowInfo = bindingContext.getDataFlowInfoAfter(callElement)
val dataFlowValueFactory = callElement.getResolutionFacade().frontendService<DataFlowValueFactory>() val dataFlowValueFactory = callElement.getResolutionFacade().frontendService<DataFlowValueFactory>()
val possibleTypes = dataFlowInfo.getCollectedTypes( val possibleTypes = dataFlowInfo.getCollectedTypes(
dataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract), dataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract),
callElement.languageVersionSettings callElement.languageVersionSettings
) )
if (possibleTypes.isNotEmpty()) CommonSupertypes.commonSupertype(possibleTypes) else null if (possibleTypes.isNotEmpty()) CommonSupertypes.commonSupertype(possibleTypes) else null
} else null } else null
typeByDataFlowInfo ?: receiverToExtract.type typeByDataFlowInfo ?: receiverToExtract.type
} }
else -> receiverToExtract?.type else -> receiverToExtract?.type
} ?: builtIns.defaultParameterType } ?: builtIns.defaultParameterType
} }