diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index 6dd32570aa5..145de6475b3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -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 diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt index e414dd3c30f..f196f30e740 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt @@ -21,13 +21,13 @@ import com.intellij.openapi.util.UserDataHolder import com.intellij.psi.PsiElement import kotlin.reflect.KProperty -public class UserDataProperty(val key: Key) { +public class UserDataProperty(val key: Key) { 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(val key: Key, val defaultValue: T) { +public class NotNullableUserDataProperty(val key: Key, 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(val key: } } -public class CopyableUserDataProperty(val key: Key) { +public class CopyableUserDataProperty(val key: Key) { 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(val key: Key, val defaultValue: T) { +public class NotNullableCopyableUserDataProperty(val key: Key, 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(val key: Key, + val computeDefaultValue: () -> T) { + private val delegate by lazy { NotNullableCopyableUserDataProperty(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) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt index 3b2836c4268..da2bf30f61e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt @@ -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() ?: return val receiverTypeRef = psiFactory.createType(newReceiverInfo.currentTypeText) functionWithReceiver.setReceiverTypeReference(receiverTypeRef) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java index d8d17f47841..b2c59193d85 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java @@ -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 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 outputValues = new ArrayList(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 replacementMap = MultiMap.create(); - for (Map.Entry> e : originalDescriptor.getReplacementMap().entrySet()) { - Integer offset = e.getKey(); - Collection 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); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index efd6ed2ba41..33b42b04a66 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -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().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) { abstract val returnType: KotlinType - protected abstract fun getBoxingExpressionText(arguments: List): String? + protected abstract fun getBoxingExpressionPattern(arguments: List): String? abstract val boxingRequired: Boolean - fun getReturnExpression(arguments: List, psiFactory: KtPsiFactory): KtReturnExpression? { - val expressionText = getBoxingExpressionText(arguments) ?: return null - return psiFactory.createExpression("return $expressionText") as KtReturnExpression + fun getReturnExpression(arguments: List, 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) { override val boxingRequired: Boolean = outputValues.size() > 1 - override fun getBoxingExpressionText(arguments: List): String? { - return when (arguments.size()) { + override fun getBoxingExpressionPattern(arguments: List): 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) { override val boxingRequired: Boolean = outputValues.size() > 0 - override fun getBoxingExpressionText(arguments: List): String? { + override fun getBoxingExpressionPattern(arguments: List): 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 } 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): 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, val receiverParameter: Parameter?, val typeParameters: List, - val replacementMap: MultiMap, + val replacementMap: MultiMap, val controlFlow: ControlFlow, val returnType: KotlinType ) { @@ -342,6 +350,39 @@ data class ExtractableCodeDescriptor( val duplicates: List by lazy { findDuplicates() } } +fun ExtractableCodeDescriptor.copy( + newName: String, + newVisibility: String, + oldToNewParameters: Map, + newReceiver: Parameter?, + returnType: KotlinType? +): ExtractableCodeDescriptor { + val newReplacementMap = MultiMap.create() + 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 Unit>, - val nameByOffset: MultiMap -) + val duplicateReplacers: Map Unit> +) : Disposable { + override fun dispose() = unmarkReferencesInside(declaration) +} class AnalysisResult ( val descriptor: ExtractableCodeDescriptor?, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt index d64072e5420..c31570b8bdd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt @@ -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 ) +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 = 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() - - 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() - } ?: 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() + } ?: 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() + expressions.forEach { it.accept(visitor) } } fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set { @@ -195,75 +193,72 @@ data class ExtractionData( fun getBrokenReferencesInfo(body: KtBlockExpression): List { val originalContext = bindingContext ?: return listOf() - val startOffset = body.getBlockContentOffset() + val newReferences = body.collectDescendantsOfType { it.resolveResult != null } val referencesInfo = ArrayList() 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 // 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 { it.resolveResult = null } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt index 4882f843b8c..01b2de5ea14 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt @@ -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() + } + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 0f0a51d30e2..979cd5739bb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -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( + 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()!! } 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 { if (!processTypeArguments) return Collections.singletonList(this) return DFS.dfsFromNode( this, - object: Neighbors { - override fun getNeighbors(current: KotlinType): Iterable = current.arguments.map { it.type } - }, + Neighbors { current -> current.arguments.map { it.type } }, VisitedWithSet(), object: CollectingNodeHandler>(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 = MultiMap.create() - val originalRefToParameter: MultiMap = MultiMap.create() - val parameters: MutableSet = HashSet() - val typeParameters: MutableSet = HashSet() - val nonDenotableTypes: MutableSet = 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) } } ) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index 60227e42690..df37f2e03c8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -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 { - val map = HashMap() - - 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() - assert(newExpression != null) { "Couldn't find expression at $offset in '${to.text}'" } - - map[expression] = newExpression!! - } - } - ) - - return map + return from.collectDescendantsOfType().zip(to.collectDescendantsOfType()).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() + + fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues + .flatMapTo(ArrayList()) { it.originalExpressions.filterIsInstance() } 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 { + fun getReturnArguments(resultExpression: KtExpression?): List { 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(originalExpressions: Collection, - body: KtExpression, - bodyOffset: Int, - file: PsiFile): List { - 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>() - val originalOffsetByExpr = LinkedHashMap() + val jumpValue = descriptor.controlFlow.jumpOutputValue + if (jumpValue != null) { + val replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return") + body.collectDescendantsOfType { it.isJumpElementToReplace }.forEach { + it.replace(replacingReturn) + it.isJumpElementToReplace = false + } + } - val bodyOffset = body.getBlockContentOffset() - val file = body.containingFile!! + body.collectDescendantsOfType { 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() - assert(expr != null) { "Couldn't find expression at $offsetInBody in '${body.text}'" } + val currentRefs = body + .collectDescendantsOfType { 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 - - val returnsForLabelRemoval = descriptor.controlFlow.outputValues - .flatMapTo(ArrayList()) { it.originalExpressions.filterIsInstance() } - - 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 { 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 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) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt index 44dfeb85db8..700a1b5c808 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt @@ -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() + val parameters = HashSet() + val typeParameters = HashSet() + val nonDenotableTypes = HashSet() + val replacementMap = MultiMap.create() +} + 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)) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index f38c3462a54..aad555663a5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -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 } diff --git a/idea/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.after b/idea/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.after index 3fd8b275d61..15e4e17c850 100644 --- a/idea/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.after +++ b/idea/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.after @@ -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