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