Extraction Engine: Get rid of explicit element offset manipulation and use copyable user data instead

This commit is contained in:
Alexey Sedunov
2015-11-25 18:39:05 +03:00
parent 44bc937499
commit 03e1480476
12 changed files with 357 additions and 411 deletions
@@ -560,6 +560,8 @@ public class KtPsiFactory(private val project: Project) {
return this
}
public fun transform(f: StringBuilder.() -> Unit) = sb.f()
public fun asString(): String {
if (state != State.DONE) {
state = State.DONE
@@ -21,13 +21,13 @@ import com.intellij.openapi.util.UserDataHolder
import com.intellij.psi.PsiElement
import kotlin.reflect.KProperty
public class UserDataProperty<in R: UserDataHolder, T : Any>(val key: Key<T>) {
public class UserDataProperty<in R : UserDataHolder, T : Any>(val key: Key<T>) {
operator fun getValue(thisRef: R, desc: KProperty<*>) = thisRef.getUserData(key)
operator fun setValue(thisRef: R, desc: KProperty<*>, value: T?) = thisRef.putUserData(key, value)
}
public class NotNullableUserDataProperty<in R: UserDataHolder, T : Any>(val key: Key<T>, val defaultValue: T) {
public class NotNullableUserDataProperty<in R : UserDataHolder, T : Any>(val key: Key<T>, val defaultValue: T) {
operator fun getValue(thisRef: R, desc: KProperty<*>) = thisRef.getUserData(key) ?: defaultValue
operator fun setValue(thisRef: R, desc: KProperty<*>, value: T) {
@@ -35,16 +35,25 @@ public class NotNullableUserDataProperty<in R: UserDataHolder, T : Any>(val key:
}
}
public class CopyableUserDataProperty<in R: PsiElement, T : Any>(val key: Key<T>) {
public class CopyableUserDataProperty<in R : PsiElement, T : Any>(val key: Key<T>) {
operator fun getValue(thisRef: R, property: KProperty<*>) = thisRef.getCopyableUserData(key)
operator fun setValue(thisRef: R, property: KProperty<*>, value: T?) = thisRef.putCopyableUserData(key, value)
}
public class NotNullableCopyableUserDataProperty<in R: PsiElement, T : Any>(val key: Key<T>, val defaultValue: T) {
public class NotNullableCopyableUserDataProperty<in R : PsiElement, T : Any>(val key: Key<T>, val defaultValue: T) {
operator fun getValue(thisRef: R, property: KProperty<*>) = thisRef.getCopyableUserData(key) ?: defaultValue
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
thisRef.putCopyableUserData(key, if (value != defaultValue) value else null)
}
}
public class NotNullableCopyableUserDataPropertyWithLazyDefault<in R : PsiElement, T : Any>(val key: Key<T>,
val computeDefaultValue: () -> T) {
private val delegate by lazy { NotNullableCopyableUserDataProperty<R, T>(key, computeDefaultValue()) }
operator fun getValue(thisRef: R, property: KProperty<*>) = delegate.getValue(thisRef, property)
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) = delegate.setValue(thisRef, property, value)
}
@@ -671,7 +671,7 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
}
val psiFactory = KtPsiFactory(callable.project)
val tempFile = (callable.containingFile as KtFile).createTempCopy { it }
val tempFile = (callable.containingFile as KtFile).createTempCopy()
val functionWithReceiver = tempFile.findElementAt(callable.textOffset)?.getNonStrictParentOfType<KtNamedFunction>() ?: return
val receiverTypeRef = psiFactory.createType(newReceiverInfo.currentTypeText)
functionWithReceiver.setReceiverTypeReference(receiverTypeRef)
@@ -113,9 +113,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
setOKActionEnabled(checkNames());
signaturePreviewField.setText(
ExtractorUtilKt.getDeclarationText(getCurrentConfiguration(),
false,
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
ExtractorUtilKt.getSignaturePreview(getCurrentConfiguration(), IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
);
}
@@ -285,59 +283,12 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : newParameterInfos) {
oldToNewParameters.put(parameterInfo.getOriginalParameter(), parameterInfo.toParameter());
}
ArrayList<Parameter> newParameters = ContainerUtil.newArrayList(oldToNewParameters.values());
Parameter originalReceiver = originalDescriptor.getReceiverParameter();
Parameter newReceiver = newReceiverInfo != null ? newReceiverInfo.toParameter() : null;
if (originalReceiver != null && newReceiver != null) {
oldToNewParameters.put(originalReceiver, newReceiver);
}
ControlFlow controlFlow = originalDescriptor.getControlFlow();
List<OutputValue> outputValues = new ArrayList<OutputValue>(controlFlow.getOutputValues());
for (int i = 0; i < outputValues.size(); i++) {
OutputValue outputValue = outputValues.get(i);
if (outputValue instanceof OutputValue.ParameterUpdate) {
OutputValue.ParameterUpdate parameterUpdate = (OutputValue.ParameterUpdate) outputValue;
outputValues.set(i, new OutputValue.ParameterUpdate(oldToNewParameters.get(parameterUpdate.getParameter()), parameterUpdate.getOriginalExpressions()));
}
}
controlFlow = new ControlFlow(outputValues, controlFlow.getBoxerFactory(), controlFlow.getDeclarationsToCopy());
MultiMap<Integer, Replacement> replacementMap = MultiMap.<Integer, Replacement>create();
for (Map.Entry<Integer, Collection<Replacement>> e : originalDescriptor.getReplacementMap().entrySet()) {
Integer offset = e.getKey();
Collection<Replacement> replacements = e.getValue();
for (Replacement replacement : replacements) {
if (replacement instanceof ParameterReplacement) {
ParameterReplacement parameterReplacement = (ParameterReplacement) replacement;
Parameter parameter = parameterReplacement.getParameter();
Parameter newParameter = oldToNewParameters.get(parameter);
if (newParameter != null) {
replacementMap.remove(offset, replacement);
replacementMap.putValue(offset, parameterReplacement.copy(newParameter));
}
}
else {
replacementMap.put(offset, replacements);
}
}
}
return new ExtractableCodeDescriptor(
originalDescriptor.getExtractionData(),
originalDescriptor.getOriginalContext(),
Collections.singletonList(newName),
newVisibility,
newParameters,
newReceiver,
originalDescriptor.getTypeParameters(),
replacementMap,
controlFlow,
returnType != null ? returnType : originalDescriptor.getReturnType()
);
return ExtractableCodeDescriptorKt.copy(originalDescriptor, newName, newVisibility, oldToNewParameters, newReceiver, returnType);
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.util.containers.ContainerUtil
@@ -97,7 +98,7 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
class WrapInWithReplacement(override val parameter: Parameter): ParameterReplacement {
override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
val call = e.parents.firstIsInstance<KtCallExpression>().getQualifiedExpressionForSelectorOrThis()
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", parameter.name, call.text)
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", parameter.name, call)
val replace = call.replace(replacingExpression)
return (replace as KtCallExpression).functionLiteralArguments.first().getFunctionLiteral().bodyExpression!!.statements.first()
}
@@ -112,7 +113,7 @@ class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacem
if (descriptor.receiverParameter == parameter) return e
val selector = (e.parent as? KtCallExpression) ?: e
val replacingExpression = KtPsiFactory(e).createExpression("${parameter.nameForRef}.${selector.text}")
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("${parameter.nameForRef}.$0", selector)
val newExpr = (selector.replace(replacingExpression) as KtQualifiedExpression).selectorExpression!!
return (newExpr as? KtCallExpression)?.calleeExpression ?: newExpr
}
@@ -170,13 +171,13 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
abstract val returnType: KotlinType
protected abstract fun getBoxingExpressionText(arguments: List<String>): String?
protected abstract fun getBoxingExpressionPattern(arguments: List<KtExpression>): String?
abstract val boxingRequired: Boolean
fun getReturnExpression(arguments: List<String>, psiFactory: KtPsiFactory): KtReturnExpression? {
val expressionText = getBoxingExpressionText(arguments) ?: return null
return psiFactory.createExpression("return $expressionText") as KtReturnExpression
fun getReturnExpression(arguments: List<KtExpression>, psiFactory: KtPsiFactory): KtReturnExpression? {
val expressionPattern = getBoxingExpressionPattern(arguments) ?: return null
return psiFactory.createExpressionByPattern("return $expressionPattern", *arguments.toTypedArray()) as KtReturnExpression
}
protected abstract fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression?
@@ -230,13 +231,13 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
override val boxingRequired: Boolean = outputValues.size() > 1
override fun getBoxingExpressionText(arguments: List<String>): String? {
return when (arguments.size()) {
override fun getBoxingExpressionPattern(arguments: List<KtExpression>): String? {
return when (arguments.size) {
0 -> null
1 -> arguments.first()
1 -> "$0"
else -> {
val constructorName = DescriptorUtils.getFqName(returnType.getConstructor().getDeclarationDescriptor()!!).asString()
return arguments.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")")
val constructorName = DescriptorUtils.getFqName(returnType.constructor.declarationDescriptor!!).asString()
return arguments.indices.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")") { "\$$it" }
}
}
}
@@ -270,9 +271,9 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
override val boxingRequired: Boolean = outputValues.size() > 0
override fun getBoxingExpressionText(arguments: List<String>): String? {
override fun getBoxingExpressionPattern(arguments: List<KtExpression>): String? {
if (arguments.isEmpty()) return null
return arguments.joinToString(prefix = "kotlin.listOf(", separator = ", ", postfix = ")")
return arguments.indices.joinToString(prefix = "kotlin.listOf(", separator = ", ", postfix = ")") { "\$$it" }
}
override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? {
@@ -324,7 +325,14 @@ val ControlFlow.possibleReturnTypes: List<KotlinType>
}
fun ControlFlow.toDefault(): ControlFlow =
copy(outputValues = outputValues.filterNot { it is OutputValue.Jump || it is OutputValue.ExpressionValue })
copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue })
fun ControlFlow.copy(oldToNewParameters: Map<Parameter, Parameter>): ControlFlow {
val newOutputValues = outputValues.map {
if (it is ParameterUpdate) ParameterUpdate(oldToNewParameters[it.parameter]!!, it.originalExpressions) else it
}
return copy(outputValues = newOutputValues)
}
data class ExtractableCodeDescriptor(
val extractionData: ExtractionData,
@@ -334,7 +342,7 @@ data class ExtractableCodeDescriptor(
val parameters: List<Parameter>,
val receiverParameter: Parameter?,
val typeParameters: List<TypeParameter>,
val replacementMap: MultiMap<Int, Replacement>,
val replacementMap: MultiMap<KtSimpleNameExpression, Replacement>,
val controlFlow: ControlFlow,
val returnType: KotlinType
) {
@@ -342,6 +350,39 @@ data class ExtractableCodeDescriptor(
val duplicates: List<DuplicateInfo> by lazy { findDuplicates() }
}
fun ExtractableCodeDescriptor.copy(
newName: String,
newVisibility: String,
oldToNewParameters: Map<Parameter, Parameter>,
newReceiver: Parameter?,
returnType: KotlinType?
): ExtractableCodeDescriptor {
val newReplacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
for ((ref, replacements) in replacementMap.entrySet()) {
val newReplacements = replacements.map {
if (it is ParameterReplacement) {
val parameter = it.parameter
val newParameter = oldToNewParameters[parameter] ?: return@map it
it.copy(newParameter)
}
else it
}
newReplacementMap.putValues(ref, newReplacements)
}
return ExtractableCodeDescriptor(
extractionData,
originalContext,
listOf(newName),
newVisibility,
parameters.map { oldToNewParameters[it]!! },
newReceiver,
typeParameters,
newReplacementMap,
controlFlow.copy(oldToNewParameters),
returnType ?: this.returnType)
}
enum class ExtractionTarget(val targetName: String) {
FUNCTION("function") {
override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true
@@ -431,9 +472,10 @@ data class ExtractionGeneratorConfiguration(
data class ExtractionResult(
val config: ExtractionGeneratorConfiguration,
val declaration: KtNamedDeclaration,
val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>,
val nameByOffset: MultiMap<Int, KtElement>
)
val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>
) : Disposable {
override fun dispose() = unmarkReferencesInside(declaration)
}
class AnalysisResult (
val descriptor: ExtractableCodeDescriptor?,
@@ -16,7 +16,9 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.util.PsiTreeUtil
@@ -72,20 +74,21 @@ data class ResolveResult(
data class ResolvedReferenceInfo(
val refExpr: KtSimpleNameExpression,
val offsetInBody: Int,
val resolveResult: ResolveResult,
val smartCast: KotlinType?,
val possibleTypes: Set<KotlinType>
)
internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyableUserDataProperty(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 project: Project = originalFile.getProject()
) : Disposable {
val project: Project = originalFile.project
val originalElements: List<PsiElement> = originalRange.elements
val physicalElements = originalElements.map { it.substringContextOrThis }
@@ -108,9 +111,6 @@ data class ExtractionData(
}
}
val originalStartOffset: Int?
get() = originalElements.firstOrNull()?.let { e -> e.getTextRange()!!.getStartOffset() }
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
val bindingContext: BindingContext? by lazy { commonParent.getContextForContainingDeclarationBody() }
@@ -118,65 +118,63 @@ data class ExtractionData(
private val itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
val refOffsetToDeclaration by lazy {
fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean {
if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] ?: false))) return false
val function = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.getContainingDeclaration()) as? KtFunctionLiteral
return function == null || !function.isInsideOf(physicalElements)
init {
markReferences()
}
private fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean {
if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] ?: false))) return false
val function = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.containingDeclaration) as? KtFunctionLiteral
return function == null || !function.isInsideOf(physicalElements)
}
private tailrec fun getDeclaration(descriptor: DeclarationDescriptor, context: BindingContext): PsiNameIdentifierOwner? {
(DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? PsiNameIdentifierOwner)?.let { return it }
return when {
isExtractableIt(descriptor, context) -> itFakeDeclaration
isSynthesizedInvoke(descriptor) -> synthesizedInvokeDeclaration
descriptor is SyntheticJavaPropertyDescriptor -> getDeclaration(descriptor.getMethod, context)
else -> null
}
}
tailrec fun getDeclaration(descriptor: DeclarationDescriptor, context: BindingContext): PsiNameIdentifierOwner? {
(DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? PsiNameIdentifierOwner)?.let { return it }
return when {
isExtractableIt(descriptor, context) -> itFakeDeclaration
isSynthesizedInvoke(descriptor) -> synthesizedInvokeDeclaration
descriptor is SyntheticJavaPropertyDescriptor -> getDeclaration(descriptor.getMethod, context)
else -> null
}
}
val originalStartOffset = originalStartOffset
val context = bindingContext
if (originalStartOffset != null && context != null) {
val resultMap = HashMap<Int, ResolveResult>()
val visitor = object: KtTreeVisitorVoid() {
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
if (context[BindingContext.SMARTCAST, expression] != null) {
expression.getSelectorExpression()?.accept(this)
return
}
super.visitQualifiedExpression(expression)
private fun markReferences() {
val context = bindingContext ?: return
val visitor = object : KtTreeVisitorVoid() {
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
if (context[BindingContext.SMARTCAST, expression] != null) {
expression.selectorExpression?.accept(this)
return
}
override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) {
if (ref.getParent() is KtValueArgumentName) return
super.visitQualifiedExpression(expression)
}
val physicalRef = substringInfo?.let {
// If substring contains some references it must be extracted as a string template
val physicalExpression = expressions.single() as KtStringTemplateExpression
val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset
val offsetInExtracted = ref.startOffset - extractedContentOffset
val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted
it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType<KtSimpleNameExpression>()
} ?: ref
override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) {
if (ref.parent is KtValueArgumentName) return
val resolvedCall = physicalRef.getResolvedCall(context)
val descriptor = context[BindingContext.REFERENCE_TARGET, physicalRef] ?: return
val declaration = getDeclaration(descriptor, context) ?: return
val physicalRef = substringInfo?.let {
// If substring contains some references it must be extracted as a string template
val physicalExpression = expressions.single() as KtStringTemplateExpression
val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset
val offsetInExtracted = ref.startOffset - extractedContentOffset
val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted
it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType<KtSimpleNameExpression>()
} ?: ref
val offset = ref.getTextRange()!!.getStartOffset() - originalStartOffset
resultMap[offset] = ResolveResult(physicalRef, declaration, descriptor, resolvedCall)
val resolvedCall = physicalRef.getResolvedCall(context)
val descriptor = context[BindingContext.REFERENCE_TARGET, physicalRef] ?: return
val declaration = getDeclaration(descriptor, context) ?: return
val resolveResult = ResolveResult(physicalRef, declaration, descriptor, resolvedCall)
physicalRef.resolveResult = resolveResult
if (ref != physicalRef) {
ref.resolveResult = resolveResult
}
}
expressions.forEach { it.accept(visitor) }
resultMap
}
else Collections.emptyMap<Int, ResolveResult>()
expressions.forEach { it.accept(visitor) }
}
fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set<KotlinType> {
@@ -195,75 +193,72 @@ data class ExtractionData(
fun getBrokenReferencesInfo(body: KtBlockExpression): List<ResolvedReferenceInfo> {
val originalContext = bindingContext ?: return listOf()
val startOffset = body.getBlockContentOffset()
val newReferences = body.collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null }
val referencesInfo = ArrayList<ResolvedReferenceInfo>()
val refToContextMap = KotlinFileReferencesResolver.resolve(body)
for ((ref, context) in refToContextMap) {
if (ref !is KtSimpleNameExpression) continue
val offset = ref.getTextRange()!!.getStartOffset() - startOffset
val originalResolveResult = refOffsetToDeclaration[offset] ?: continue
for (newRef in newReferences) {
val context = refToContextMap[newRef] ?: continue
val originalResolveResult = newRef.resolveResult ?: continue
val smartCast: KotlinType?
val possibleTypes: Set<KotlinType>
// Qualified property reference: a.b
val qualifiedExpression = ref.getQualifiedExpressionForSelector()
val qualifiedExpression = newRef.getQualifiedExpressionForSelector()
if (qualifiedExpression != null) {
val smartCastTarget = originalResolveResult.originalRefExpr.getParent() as KtExpression
val smartCastTarget = originalResolveResult.originalRefExpr.parent as KtExpression
smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]
possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext)
val receiverDescriptor =
(originalResolveResult.resolvedCall?.getDispatchReceiver() as? ImplicitReceiver)?.declarationDescriptor
(originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor
if (smartCast == null
&& !DescriptorUtils.isCompanionObject(receiverDescriptor)
&& qualifiedExpression.getReceiverExpression() !is KtSuperExpression) continue
&& qualifiedExpression.receiverExpression !is KtSuperExpression) continue
}
else {
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
}
val parent = ref.getParent()
val parent = newRef.parent
// Skip P in type references like 'P.Q'
if (parent is KtUserType && (parent.getParent() as? KtUserType)?.getQualifier() == parent) continue
if (parent is KtUserType && (parent.parent as? KtUserType)?.qualifier == parent) continue
val descriptor = context[BindingContext.REFERENCE_TARGET, ref]
val descriptor = context[BindingContext.REFERENCE_TARGET, newRef]
val isBadRef = !(compareDescriptors(project, originalResolveResult.descriptor, descriptor)
&& originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement(ref))
&& 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
val originalVariableCall = originalResolvedCall?.variableCall
val invokeDescriptor = originalFunctionCall?.getResultingDescriptor()
val invokeDescriptor = originalFunctionCall?.resultingDescriptor
if (invokeDescriptor != null && isSynthesizedInvoke(invokeDescriptor) && invokeDescriptor.isExtension) {
val variableResolveResult = originalResolveResult.copy(resolvedCall = originalVariableCall!!,
descriptor = originalVariableCall.getResultingDescriptor())
descriptor = originalVariableCall.resultingDescriptor)
val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!,
descriptor = originalFunctionCall.getResultingDescriptor(),
descriptor = originalFunctionCall.resultingDescriptor,
declaration = synthesizedInvokeDeclaration)
referencesInfo.add(ResolvedReferenceInfo(ref, offset, variableResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(ref, offset, functionResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(newRef, variableResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(newRef, functionResolveResult, smartCast, possibleTypes))
}
else {
referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast, possibleTypes))
referencesInfo.add(ResolvedReferenceInfo(newRef, originalResolveResult, smartCast, possibleTypes))
}
}
}
return referencesInfo
}
override fun dispose() {
expressions.forEach { unmarkReferencesInside(it) }
}
}
// Hack:
// we can't get first element offset through getStatement()/getChildren() since they skip comments and whitespaces
// So we take offset of the left brace instead and increase it by 2 (which is length of "{\n" separating block start and its first element)
internal fun KtExpression.getBlockContentOffset(): Int {
(this as? KtBlockExpression)?.getLBrace()?.let {
return it.getTextRange()!!.getStartOffset() + 2
}
return getTextRange()!!.getStartOffset()
}
fun unmarkReferencesInside(root: PsiElement) {
if (!root.isValid) return
root.forEachDescendantOfType<KtSimpleNameExpression> { it.resolveResult = null }
}
@@ -16,21 +16,18 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.psi.*
import org.jetbrains.kotlin.psi.*
import com.intellij.openapi.editor.*
import org.jetbrains.kotlin.idea.util.psi.patternMatching.*
import com.intellij.openapi.application.*
import com.intellij.refactoring.*
import org.jetbrains.kotlin.idea.util.application.*
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.refactoring.introduce.*
import com.intellij.ui.awt.*
import com.intellij.openapi.ui.popup.*
import com.intellij.openapi.ui.*
import javax.swing.event.*
import com.intellij.openapi.project.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageType
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.ui.awt.RelativePoint
import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import javax.swing.event.HyperlinkEvent
public abstract class ExtractionEngineHelper(val operationName: String) {
open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
@@ -66,7 +63,15 @@ public class ExtractionEngine(
fun validateAndRefactor() {
val validationResult = analysisResult.descriptor!!.validate()
project.checkConflictsInteractively(validationResult.conflicts) {
helper.configureAndRun(project, editor, validationResult, onFinish)
helper.configureAndRun(project, editor, validationResult) {
try {
onFinish(it)
}
finally {
it.dispose()
extractionData.dispose()
}
}
}
}
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
@@ -55,10 +56,7 @@ import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.isResolvableInScope
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -374,39 +372,35 @@ private fun ExtractionData.analyzeControlFlow(
return controlFlow to null
}
fun ExtractionData.createTemporaryDeclaration(functionText: String): KtNamedDeclaration {
val textRange = targetSibling.textRange!!
fun ExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclaration {
val targetSiblingMarker = Any()
PsiTreeUtil.mark(targetSibling, targetSiblingMarker)
val tmpFile = originalFile.createTempCopy("")
tmpFile.deleteChildRange(tmpFile.firstChild, tmpFile.lastChild)
tmpFile.addRange(originalFile.firstChild, originalFile.lastChild)
val newTargetSibling = PsiTreeUtil.releaseMark(tmpFile, targetSiblingMarker)!!
val newTargetParent = newTargetSibling.parent
val insertText: String
val insertPosition: Int
val lookupPosition: Int
if (insertBefore) {
insertPosition = textRange.startOffset
lookupPosition = insertPosition
insertText = functionText
val declaration = KtPsiFactory(originalFile).createDeclarationByPattern<KtNamedDeclaration>(
pattern,
PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
)
return if (insertBefore) {
newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration
}
else {
insertPosition = textRange.endOffset
lookupPosition = insertPosition + 1
insertText = "\n$functionText"
newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration
}
val tmpFile = originalFile.createTempCopy { text ->
StringBuilder(text).insert(insertPosition, insertText).toString()
}
return tmpFile.findElementAt(lookupPosition)?.getNonStrictParentOfType<KtNamedDeclaration>()!!
}
internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression =
(createTemporaryDeclaration("fun() {\n$codeFragmentText\n}\n") as KtNamedFunction).bodyExpression as KtBlockExpression
(createTemporaryDeclaration("fun() {\n$0\n}\n") as KtNamedFunction).bodyExpression as KtBlockExpression
private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List<KotlinType> {
if (!processTypeArguments) return Collections.singletonList(this)
return DFS.dfsFromNode(
this,
object: Neighbors<KotlinType> {
override fun getNeighbors(current: KotlinType): Iterable<KotlinType> = current.arguments.map { it.type }
},
Neighbors<KotlinType> { current -> current.arguments.map { it.type } },
VisitedWithSet(),
object: CollectingNodeHandler<KotlinType, KotlinType, ArrayList<KotlinType>>(ArrayList()) {
override fun afterChildren(current: KotlinType) {
@@ -568,17 +562,6 @@ private class DelegatingParameter(
override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType
}
internal class ParametersInfo {
var errorMessage: ErrorMessage? = null
val replacementMap: MultiMap<Int, Replacement> = MultiMap.create()
val originalRefToParameter: MultiMap<KtSimpleNameExpression, MutableParameter> = MultiMap.create()
val parameters: MutableSet<MutableParameter> = HashSet()
val typeParameters: MutableSet<TypeParameter> = HashSet()
val nonDenotableTypes: MutableSet<KotlinType> = HashSet()
}
private fun ExtractionData.checkDeclarationsMovingOutOfScope(
enclosingDeclaration: KtDeclaration,
controlFlow: ControlFlow,
@@ -773,7 +756,8 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
val body = result.declaration.getGeneratedBody()
val bindingContext = body.analyzeFully()
fun processReference(resolveResult: ResolveResult, currentRefExpr: KtReferenceExpression) {
fun processReference(currentRefExpr: KtSimpleNameExpression) {
val resolveResult = currentRefExpr.resolveResult ?: return
if (currentRefExpr.parent is KtThisExpression) return
val diagnostics = bindingContext.diagnostics.forElement(currentRefExpr)
@@ -808,18 +792,6 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
}
}
fun validateBody() {
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
if (resolveResult.declaration.isInsideOf(extractionData.physicalElements)) continue
val currentRefExprs = result.nameByOffset[originalOffset].mapNotNull {
(it as? KtThisExpression)?.instanceReference ?: it as? KtSimpleNameExpression
}
currentRefExprs.forEach { processReference(resolveResult, it) }
}
}
result.declaration.accept(
object : KtTreeVisitorVoid() {
override fun visitUserType(userType: KtUserType) {
@@ -831,12 +803,8 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
}
}
override fun visitKtElement(element: KtElement) {
if (element == body) {
validateBody()
return
}
super.visitKtElement(element)
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
processReference(expression)
}
}
)
@@ -16,12 +16,12 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
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 com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.core.*
@@ -56,8 +56,65 @@ import org.jetbrains.kotlin.types.isFlexible
import org.jetbrains.kotlin.types.typeUtil.builtIns
import java.util.*
fun ExtractionGeneratorConfiguration.getDeclarationText(
withBody: Boolean = true,
private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: DescriptorRenderer): CallableBuilder {
val extractionTarget = config.generatorOptions.target
if (!extractionTarget.isAvailable(config.descriptor)) {
val message = "Can't generate ${extractionTarget.targetName}: ${config.descriptor.extractionData.codeFragmentText}"
throw BaseRefactoringProcessor.ConflictsInTestsException(listOf(message))
}
val builderTarget = when (extractionTarget) {
ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION
else -> CallableBuilder.Target.READ_ONLY_PROPERTY
}
return CallableBuilder(builderTarget).apply {
modifier(config.descriptor.visibility)
typeParams(
config.descriptor.typeParameters.map {
val typeParameter = it.originalDeclaration
val bound = typeParameter.extendsBound
typeParameter.name + (bound?.let { " : " + it.text } ?: "")
}
)
fun KotlinType.typeAsString(): String {
return if (config.descriptor.extractionData.options.allowSpecialClassNames && isSpecial()) {
DEBUG_TYPE_REFERENCE_STRING
} else {
renderer.renderType(this)
}
}
config.descriptor.receiverParameter?.let {
val receiverType = it.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames)
val receiverTypeAsString = receiverType.typeAsString()
val isFunctionType = KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(receiverType)
receiver(if (isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString)
}
name(config.generatorOptions.dummyName ?: config.descriptor.name)
config.descriptor.parameters.forEach { parameter ->
param(parameter.name,
parameter.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
}
with(config.descriptor.returnType) {
if (isDefault() || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
noReturnType()
} else {
returnType(typeAsString())
}
}
typeConstraints(config.descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! })
}
}
fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString()
fun ExtractionGeneratorConfiguration.getDeclarationPattern(
descriptorRenderer: DescriptorRenderer = if (generatorOptions.flexibleTypesAllowed)
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
else IdeDescriptorRenderers.SOURCE_CODE
@@ -67,58 +124,20 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
throw BaseRefactoringProcessor.ConflictsInTestsException(listOf("Can't generate ${extractionTarget.targetName}: ${descriptor.extractionData.codeFragmentText}"))
}
val builderTarget = when (extractionTarget) {
ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION
else -> CallableBuilder.Target.READ_ONLY_PROPERTY
}
return CallableBuilder(builderTarget).let { builder ->
builder.modifier(descriptor.visibility)
builder.typeParams(
descriptor.typeParameters.map {
val typeParameter = it.originalDeclaration
val bound = typeParameter.extendsBound
typeParameter.name + (bound?.let { " : " + it.text } ?: "")
}
)
fun KotlinType.typeAsString(): String {
return if (descriptor.extractionData.options.allowSpecialClassNames && isSpecial()) DEBUG_TYPE_REFERENCE_STRING else descriptorRenderer.renderType(this)
}
descriptor.receiverParameter?.let {
val receiverType = it.getParameterType(descriptor.extractionData.options.allowSpecialClassNames)
val receiverTypeAsString = receiverType.typeAsString()
val isFunctionType = KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(receiverType)
builder.receiver(if (isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString)
}
builder.name(generatorOptions.dummyName ?: descriptor.name)
descriptor.parameters.forEach { parameter ->
builder.param(parameter.name,
parameter.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
}
with(descriptor.returnType) {
if (isDefault() || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
builder.noReturnType()
} else {
builder.returnType(typeAsString())
return buildSignature(this, descriptorRenderer).let { builder ->
builder.transform {
for (i in sequence(indexOf('$')) { indexOf('$', it + 2) }) {
if (i < 0) break
insert(i + 1, '$')
}
}
builder.typeConstraints(descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! })
if (withBody) {
val bodyText = descriptor.extractionData.codeFragmentText
when (extractionTarget) {
ExtractionTarget.FUNCTION,
ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody(bodyText)
ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer(bodyText)
ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody(bodyText)
}
when (extractionTarget) {
ExtractionTarget.FUNCTION,
ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody("$0")
ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer("$0")
ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody("$0")
}
builder.asString()
@@ -131,22 +150,7 @@ fun KotlinType.isSpecial(): Boolean {
}
fun createNameCounterpartMap(from: KtElement, to: KtElement): Map<KtSimpleNameExpression, KtSimpleNameExpression> {
val map = HashMap<KtSimpleNameExpression, KtSimpleNameExpression>()
val fromOffset = from.textRange!!.startOffset
from.accept(
object : KtTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val offset = expression.textRange!!.startOffset - fromOffset
val newExpression = to.findElementAt(offset)?.getNonStrictParentOfType<KtSimpleNameExpression>()
assert(newExpression != null) { "Couldn't find expression at $offset in '${to.text}'" }
map[expression] = newExpression!!
}
}
)
return map
return from.collectDescendantsOfType<KtSimpleNameExpression>().zip(to.collectDescendantsOfType<KtSimpleNameExpression>()).toMap()
}
class DuplicateInfo(
@@ -408,131 +412,94 @@ private fun makeCall(
}
}
private var KtExpression.isJumpElementToReplace: Boolean
by NotNullableCopyableUserDataProperty(Key.create("IS_JUMP_ELEMENT_TO_REPLACE"), false)
private var KtReturnExpression.isReturnForLabelRemoval: Boolean
by NotNullableCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false)
fun ExtractionGeneratorConfiguration.generateDeclaration(
declarationToReplace: KtNamedDeclaration? = null
): ExtractionResult{
val psiFactory = KtPsiFactory(descriptor.extractionData.originalFile)
val nameByOffset = MultiMap<Int, KtElement>()
fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues
.flatMapTo(ArrayList<KtReturnExpression>()) { it.originalExpressions.filterIsInstance<KtReturnExpression>() }
fun createDeclaration(): KtNamedDeclaration {
descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true }
getReturnsForLabelRemoval().forEach { it.isReturnForLabelRemoval = true }
return with(descriptor.extractionData) {
if (generatorOptions.inTempFile) {
createTemporaryDeclaration("${getDeclarationText()}\n")
createTemporaryDeclaration("${getDeclarationPattern()}\n")
}
else {
psiFactory.createDeclaration(getDeclarationText())
psiFactory.createDeclarationByPattern(
getDeclarationPattern(),
PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
)
}
}
}
fun getReturnArguments(resultExpression: KtExpression?): List<String> {
fun getReturnArguments(resultExpression: KtExpression?): List<KtExpression> {
return descriptor.controlFlow.outputValues
.mapNotNull {
when (it) {
is ExpressionValue -> resultExpression?.text
is Jump -> if (it.conditional) "false" else null
is ParameterUpdate -> it.parameter.nameForRef
is Initializer -> it.initializedDeclaration.name
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 replaceWithReturn(
originalExpression: KtExpression,
replacingExpression: KtReturnExpression,
expressionToUnifyWith: KtExpression?
) {
val currentResultExpression =
(if (originalExpression is KtReturnExpression) originalExpression.returnedExpression else originalExpression) ?: return
val newResultExpression = descriptor.controlFlow.defaultOutputValue?.let {
val boxedExpression = originalExpression.replaced(replacingExpression).returnedExpression!!
fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) {
descriptor.controlFlow.defaultOutputValue?.let {
val boxedExpression = replaced(replacingExpression).returnedExpression!!
descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
}
@Suppress
if (newResultExpression == null) {
throw AssertionError("Can' replace '${originalExpression.text}' with '${replacingExpression.text}'")
}
val counterpartMap = createNameCounterpartMap(currentResultExpression, expressionToUnifyWith ?: newResultExpression)
counterpartMap.entries.forEach {
val (cmOriginalExpr, cmNewExpr) = it
nameByOffset.entrySet().find { cmOriginalExpr in it.value }?.let {
nameByOffset.remove(it.key, cmOriginalExpr)
nameByOffset.putValue(it.key, cmNewExpr)
}
}
}
fun getCounterparts<T : KtExpression>(originalExpressions: Collection<T>,
body: KtExpression,
bodyOffset: Int,
file: PsiFile): List<T> {
return originalExpressions.map { originalExpression ->
val offsetInBody = originalExpression.textRange!!.startOffset - descriptor.extractionData.originalStartOffset!!
file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType(originalExpression.javaClass)
?: throw AssertionError("Couldn't find expression at $offsetInBody in '${body.text}'")
}
}
fun adjustDeclarationBody(declaration: KtNamedDeclaration) {
val body = declaration.getGeneratedBody()
val exprReplacementMap = MultiMap<KtElement, (ExtractableCodeDescriptor, KtElement) -> KtElement>()
val originalOffsetByExpr = LinkedHashMap<KtElement, Int>()
val jumpValue = descriptor.controlFlow.jumpOutputValue
if (jumpValue != null) {
val replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
body.collectDescendantsOfType<KtExpression> { it.isJumpElementToReplace }.forEach {
it.replace(replacingReturn)
it.isJumpElementToReplace = false
}
}
val bodyOffset = body.getBlockContentOffset()
val file = body.containingFile!!
body.collectDescendantsOfType<KtReturnExpression> { it.isReturnForLabelRemoval }.forEach {
it.getTargetLabel()?.delete()
it.isReturnForLabelRemoval = false
}
/*
* Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
* before calls/types themselves
*/
for ((offsetInBody, resolveResult) in descriptor.extractionData.refOffsetToDeclaration.entries.sortedByDescending { it.key }) {
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType<KtSimpleNameExpression>()
assert(expr != null) { "Couldn't find expression at $offsetInBody in '${body.text}'" }
val currentRefs = body
.collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null }
.sortedByDescending { it.startOffset }
originalOffsetByExpr[expr!!] = offsetInBody
descriptor.replacementMap[offsetInBody].let { replacements ->
replacements.forEach {
exprReplacementMap.putValue(expr, it)
}
currentRefs.forEach {
val resolveResult = it.resolveResult!!
val currentRef = if (it.isValid) {
it
}
}
val replacingReturn: KtExpression?
val expressionsToReplaceWithReturn: List<KtElement>
val returnsForLabelRemoval = descriptor.controlFlow.outputValues
.flatMapTo(ArrayList<KtReturnExpression>()) { it.originalExpressions.filterIsInstance<KtReturnExpression>() }
val jumpValue = descriptor.controlFlow.jumpOutputValue
if (jumpValue != null) {
replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
returnsForLabelRemoval.removeAll(jumpValue.elementsToReplace)
expressionsToReplaceWithReturn = getCounterparts(jumpValue.elementsToReplace, body, bodyOffset, file)
}
else {
replacingReturn = null
expressionsToReplaceWithReturn = Collections.emptyList()
}
if (replacingReturn != null) {
for (expr in expressionsToReplaceWithReturn) {
expr.replace(replacingReturn)
}
}
getCounterparts(returnsForLabelRemoval, body, bodyOffset, file).forEach { it.getTargetLabel()?.delete() }
for ((expr, originalOffset) in originalOffsetByExpr) {
if (expr.isValid) {
val replacements = exprReplacementMap[expr].mapNotNull { it?.invoke(descriptor, expr) }
nameByOffset.put(originalOffset, if (replacements.isEmpty()) arrayListOf(expr) else replacements)
else {
body.findDescendantOfType<KtSimpleNameExpression> { it.resolveResult == resolveResult } ?: return@forEach
}
val originalRef = resolveResult.originalRefExpr
val newRef = descriptor.replacementMap[originalRef]
.fold(currentRef as KtElement) { currentRef, replacement -> replacement(descriptor, currentRef) }
(newRef as? KtSimpleNameExpression)?.resolveResult = resolveResult
}
if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return
@@ -554,20 +521,19 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
val lastExpression = body.statements.lastOrNull()
if (lastExpression is KtReturnExpression) return
val (defaultExpression, expressionToUnifyWith) =
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()
val newDecl = body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression) as KtProperty
body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression)
body.addBefore(psiFactory.createNewLine(), lastExpression)
psiFactory.createExpression(resultVal) to newDecl.initializer!!
}
else {
lastExpression to null
psiFactory.createExpression(resultVal)
}
else lastExpression
val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return
@Suppress("NON_EXHAUSTIVE_WHEN")
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
@@ -580,11 +546,8 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
}
when {
defaultValue == null ->
body.appendElement(returnExpression)
!defaultValue.callSiteReturn ->
replaceWithReturn(lastExpression!!, returnExpression, expressionToUnifyWith)
defaultValue == null -> body.appendElement(returnExpression)
!defaultValue.callSiteReturn -> lastExpression!!.replaceWithReturn(returnExpression)
}
if (generatorOptions.allowExpressionBody) {
@@ -657,7 +620,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
}
}
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap(), nameByOffset)
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap())
val replaceInitialOccurrence = {
val arguments = descriptor.parameters.map { it.argumentText }
@@ -670,7 +633,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
ShortenReferences.DEFAULT.process(declaration)
}
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, emptyMap(), nameByOffset)
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, emptyMap())
val duplicateReplacers = HashMap<KotlinPsiRange, () -> Unit>().apply {
if (generatorOptions.delayInitialOccurrenceReplacement) {
@@ -688,5 +651,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
}
}
return ExtractionResult(this, declaration, duplicateReplacers, nameByOffset)
CodeStyleManager.getInstance(descriptor.extractionData.project).reformat(declaration)
return ExtractionResult(this, declaration, duplicateReplacers)
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.psi.PsiElement
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.SingleType
import org.jetbrains.kotlin.cfg.pseudocode.getElementValuesRecursively
@@ -51,6 +52,15 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import java.util.*
internal class ParametersInfo {
var errorMessage: AnalysisResult.ErrorMessage? = null
val originalRefToParameter = MultiMap.create<KtSimpleNameExpression, MutableParameter>()
val parameters = HashSet<MutableParameter>()
val typeParameters = HashSet<TypeParameter>()
val nonDenotableTypes = HashSet<KotlinType>()
val replacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
}
internal fun ExtractionData.inferParametersInfo(
commonParent: PsiElement,
pseudocode: Pseudocode,
@@ -158,7 +168,7 @@ private fun ExtractionData.extractReceiver(
)) return
if (referencedClassifierDescriptor is ClassDescriptor) {
info.replacementMap.putValue(refInfo.offsetInBody, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe))
info.replacementMap.putValue(originalRef, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe))
}
}
else {
@@ -244,12 +254,12 @@ private fun ExtractionData.extractReceiver(
}
}
info.replacementMap.putValue(refInfo.offsetInBody,
when {
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
else -> RenameReplacement(parameter)
})
val replacement = when {
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
else -> RenameReplacement(parameter)
}
info.replacementMap.putValue(originalRef, replacement)
}
}
}
@@ -281,7 +291,6 @@ private fun suggestParameterType(
?: if (receiverToExtract.exists()) receiverToExtract.type else null
receiverToExtract is ImplicitReceiver -> {
val calleeExpression = resolvedCall!!.call.calleeExpression
val typeByDataFlowInfo = if (useSmartCastsIfPossible) {
val dataFlowInfo = bindingContext.getDataFlowInfo(resolvedCall!!.call.callElement)
val possibleTypes = dataFlowInfo.getPossibleTypes(DataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract))
@@ -130,9 +130,9 @@ public fun PsiElement.getUsageContext(): PsiElement {
public fun PsiElement.isInJavaSourceRoot(): Boolean =
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile())
public inline fun KtFile.createTempCopy(textTransform: (String) -> String): KtFile {
val tmpFile = KtPsiFactory(this).createAnalyzableFile(getName(), textTransform(getText() ?: ""), this)
tmpFile.setOriginalFile(this)
public fun KtFile.createTempCopy(text: String? = null): KtFile {
val tmpFile = KtPsiFactory(this).createAnalyzableFile(name, text ?: this.text ?: "", this)
tmpFile.originalFile = this
tmpFile.suppressDiagnosticsInDebugMode = suppressDiagnosticsInDebugMode
return tmpFile
}
@@ -8,8 +8,8 @@ fun foo(a: Int): Int {
private fun b(a: Int): Boolean {
/*
test
*/
test
*/
println(a)
if (a > 0) return true
return false