Refactoring

This commit is contained in:
Valentin Kipyatkov
2016-10-21 17:20:31 +03:00
parent 3b97a3feb1
commit 66c815968b
9 changed files with 228 additions and 129 deletions
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class CallableUsageReplacementStrategy( class CallableUsageReplacementStrategy(
private val replacement: ReplacementExpression private val replacement: ReplacementCode
) : UsageReplacementStrategy { ) : UsageReplacementStrategy {
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement)? { override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement)? {
@@ -44,9 +44,8 @@ class CallableUsageReplacementStrategy(
createReplacer(nameExpression)!!.invoke() createReplacer(nameExpression)!!.invoke()
} }
else { else {
// copy replacement expression because it is modified by performCallReplacement
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
ReplacementEngine.performCallReplacement(usage, bindingContext, resolvedCall, callElement, replacement.copy()) ReplacementEngine.performCallReplacement(usage, bindingContext, resolvedCall, callElement, replacement)
} }
} }
} }
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.check
class ClassUsageReplacementStrategy( class ClassUsageReplacementStrategy(
typeReplacement: KtUserType?, typeReplacement: KtUserType?,
constructorReplacement: ReplacementExpression?, constructorReplacement: ReplacementCode?,
project: Project project: Project
) : UsageReplacementStrategy { ) : UsageReplacementStrategy {
@@ -36,35 +36,27 @@ import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import java.util.* import java.util.*
internal abstract class ConstructedCodeHolder<TElement>( internal abstract class ConstructedCodeHolder<TElement : KtElement>(
var expression: KtExpression, val replacement: MutableReplacementCode,
val elementToBeReplaced: TElement, val elementToBeReplaced: TElement,
val bindingContext: BindingContext val bindingContext: BindingContext
) { ) {
val psiFactory = KtPsiFactory(expression) val psiFactory = KtPsiFactory(elementToBeReplaced)
fun replaceExpression(oldExpression: KtExpression, newExpression: KtExpression): KtExpression {
assert(expression.isAncestor(oldExpression))
if (oldExpression == expression) {
expression = newExpression
return expression
}
else {
return oldExpression.replace(newExpression) as KtExpression
}
}
abstract fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): TElement abstract fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): TElement
} }
internal class ConstructedAnnotationEntryHolder( internal class ConstructedAnnotationEntryHolder(
expression: KtExpression, replacement: MutableReplacementCode,
elementToBeReplaced: KtAnnotationEntry, elementToBeReplaced: KtAnnotationEntry,
bindingContext: BindingContext bindingContext: BindingContext
) : ConstructedCodeHolder<KtAnnotationEntry>(expression, elementToBeReplaced, bindingContext) { ) : ConstructedCodeHolder<KtAnnotationEntry>(replacement, elementToBeReplaced, bindingContext) {
override fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): KtAnnotationEntry { override fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): KtAnnotationEntry {
val dummyAnnotationEntry = createByPattern("@Dummy($0)", expression) { psiFactory.createAnnotationEntry(it) } assert(replacement.mainExpression != null)
assert(replacement.statementsBefore.isEmpty())
val dummyAnnotationEntry = createByPattern("@Dummy($0)", replacement.mainExpression!!) { psiFactory.createAnnotationEntry(it) }
val replaced = elementToBeReplaced.replace(dummyAnnotationEntry) val replaced = elementToBeReplaced.replace(dummyAnnotationEntry)
var range = PsiChildRange.singleElement(replaced) var range = PsiChildRange.singleElement(replaced)
range = postProcessing(range) range = postProcessing(range)
@@ -78,10 +70,10 @@ internal class ConstructedAnnotationEntryHolder(
} }
internal class ConstructedExpressionHolder( internal class ConstructedExpressionHolder(
expression: KtExpression, replacement: MutableReplacementCode,
expressionToBeReplaced: KtExpression, expressionToBeReplaced: KtExpression,
bindingContext: BindingContext bindingContext: BindingContext
) : ConstructedCodeHolder<KtExpression>(expression, expressionToBeReplaced, bindingContext) { ) : ConstructedCodeHolder<KtExpression>(replacement, expressionToBeReplaced, bindingContext) {
private data class StatementToInsert<TStatement : KtExpression>(val statement: TStatement, val postProcessing: (TStatement) -> Unit) private data class StatementToInsert<TStatement : KtExpression>(val statement: TStatement, val postProcessing: (TStatement) -> Unit)
@@ -104,7 +96,9 @@ internal class ConstructedExpressionHolder(
(toInsert.postProcessing as (KtExpression) -> Unit).invoke(inserted) (toInsert.postProcessing as (KtExpression) -> Unit).invoke(inserted)
} }
val replaced = elementToBeReplaced.replace(expression) val replaced = elementToBeReplaced.replace(replacement.mainExpression!!) //TODO: support null here
//TODO: support code.statementsBefore
var range = if (insertedStatements.isEmpty()) var range = if (insertedStatements.isEmpty())
PsiChildRange.singleElement(replaced) PsiChildRange.singleElement(replaced)
@@ -123,7 +117,7 @@ internal class ConstructedExpressionHolder(
nameSuggestion: String? = null, nameSuggestion: String? = null,
safeCall: Boolean = false safeCall: Boolean = false
) { ) {
assert(usages.all { expression.isAncestor(it, strict = true) }) assert(usages.all { replacement.containsStrictlyInside(it) })
fun replaceUsages(name: Name) { fun replaceUsages(name: Name) {
val nameInCode = psiFactory.createExpression(name.render()) val nameInCode = psiFactory.createExpression(name.render())
@@ -140,8 +134,8 @@ internal class ConstructedExpressionHolder(
return Name.identifier(name) return Name.identifier(name)
} }
// checks that name is used (without receiver) inside expression being constructed but not inside usages that will be replaced // checks that name is used (without receiver) inside code being constructed but not inside usages that will be replaced
fun isNameUsed(name: String) = collectNameUsages(expression, name).any { nameUsage -> usages.none { it.isAncestor(nameUsage) } } fun isNameUsed(name: String) = collectNameUsages(replacement, name).any { nameUsage -> usages.none { it.isAncestor(nameUsage) } }
if (!safeCall) { if (!safeCall) {
val block = elementToBeReplaced.parent as? KtBlockExpression val block = elementToBeReplaced.parent as? KtBlockExpression
@@ -182,19 +176,24 @@ internal class ConstructedExpressionHolder(
} }
} }
//TODO: handle mainExpression == null and statementsBefore!
val dot = if (safeCall) "?." else "." val dot = if (safeCall) "?." else "."
expression = if (!isNameUsed("it")) { replacement.mainExpression = if (!isNameUsed("it")) {
replaceUsages(Name.identifier("it")) replaceUsages(Name.identifier("it"))
psiFactory.createExpressionByPattern("$0${dot}let { $1 }", value, expression) psiFactory.createExpressionByPattern("$0${dot}let { $1 }", value, replacement.mainExpression!!)
} }
else { else {
val name = suggestName { !isNameUsed(it) } val name = suggestName { !isNameUsed(it) }
replaceUsages(name) replaceUsages(name)
psiFactory.createExpressionByPattern("$0${dot}let { $1 -> $2 }", value, name, expression) psiFactory.createExpressionByPattern("$0${dot}let { $1 -> $2 }", value, name, replacement.mainExpression!!)
} }
} }
private fun collectNameUsages(scope: KtExpression, name: String) private fun collectNameUsages(scope: MutableReplacementCode, name: String): List<KtSimpleNameExpression> {
= scope.collectDescendantsOfType<KtSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name } return scope.expressions.flatMap { expression ->
expression.collectDescendantsOfType<KtSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name }
}
}
} }
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.replacement
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
internal class MutableReplacementCode(
var mainExpression: KtExpression?,
val statementsBefore: MutableList<KtExpression>,
val fqNamesToImport: MutableCollection<FqName>
) {
fun replaceExpression(oldExpression: KtExpression, newExpression: KtExpression): KtExpression {
assert(oldExpression in this)
if (oldExpression == mainExpression) {
mainExpression = newExpression
return newExpression
}
val index = statementsBefore.indexOf(oldExpression)
if (index >= 0) {
statementsBefore[index] = newExpression
return newExpression
}
return oldExpression.replace(newExpression) as KtExpression
}
val expressions: Collection<KtExpression>
get() = statementsBefore + mainExpression.singletonOrEmptyList()
operator fun contains(element: PsiElement): Boolean {
return expressions.any { it.isAncestor(element) }
}
fun containsStrictlyInside(element: PsiElement): Boolean {
return expressions.any { it.isAncestor(element, strict = true) }
}
}
internal fun ReplacementCode.toMutable(): MutableReplacementCode {
return MutableReplacementCode(
mainExpression?.copied(),
statementsBefore.map { it.copied() }.toMutableList(),
fqNamesToImport.toMutableSet())
}
internal fun MutableReplacementCode.toNonMutable(): ReplacementCode {
return ReplacementCode(mainExpression, statementsBefore, fqNamesToImport)
}
internal inline fun <reified T : PsiElement> MutableReplacementCode.collectDescendantsOfType(noinline predicate: (T) -> Boolean = { true }): List<T> {
return expressions.flatMap { it.collectDescendantsOfType<T>({ true }, predicate) }
}
internal inline fun <reified T : PsiElement> MutableReplacementCode.forEachDescendantOfType(noinline action: (T) -> Unit) {
expressions.forEach { it.forEachDescendantOfType<T>(action) }
}
@@ -19,49 +19,41 @@ package org.jetbrains.kotlin.idea.replacement
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.core.asExpression import org.jetbrains.kotlin.idea.core.asExpression
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.references.canBeResolvedViaImport import org.jetbrains.kotlin.idea.references.canBeResolvedViaImport
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import java.util.* import java.util.*
class ReplacementBuilder( class ReplacementBuilder(
private val targetCallable: CallableDescriptor, private val targetCallable: CallableDescriptor,
private val resolutionFacade: ResolutionFacade private val resolutionFacade: ResolutionFacade
) { ) {
fun buildReplacementExpression( private val psiFactory = KtPsiFactory(resolutionFacade.project)
expression: KtExpression,
resolutionScope: LexicalScope,
expectedType: KotlinType = TypeUtils.NO_EXPECTED_TYPE,
importFqNames: Collection<FqName> = emptyList(),
copyExpression: Boolean = true
): ReplacementExpression {
@Suppress("NAME_SHADOWING")
var expression = if (copyExpression) expression.copied() else expression
var bindingContext = analyzeInContext(expression, resolutionScope, expectedType) fun buildReplacementCode(
mainExpression: KtExpression?,
statementsBefore: List<KtExpression>,
analyze: () -> BindingContext,
importFqNames: Collection<FqName> = emptyList()
): ReplacementCode {
var bindingContext = analyze()
val result = MutableReplacementCode(mainExpression, statementsBefore.toMutableList(), importFqNames.toMutableSet())
val typeArgsToAdd = ArrayList<Pair<KtCallExpression, KtTypeArgumentList>>() val typeArgsToAdd = ArrayList<Pair<KtCallExpression, KtTypeArgumentList>>()
expression.forEachDescendantOfType<KtCallExpression> { result.forEachDescendantOfType<KtCallExpression> {
if (InsertExplicitTypeArgumentsIntention.isApplicableTo(it, bindingContext)) { if (InsertExplicitTypeArgumentsIntention.isApplicableTo(it, bindingContext)) {
typeArgsToAdd.add(it to InsertExplicitTypeArgumentsIntention.createTypeArguments(it, bindingContext)!!) typeArgsToAdd.add(it to InsertExplicitTypeArgumentsIntention.createTypeArguments(it, bindingContext)!!)
} }
@@ -73,28 +65,25 @@ class ReplacementBuilder(
} }
// reanalyze expression - new usages of type parameters may be added // reanalyze expression - new usages of type parameters may be added
bindingContext = analyzeInContext(expression, resolutionScope, expectedType) bindingContext = analyze()
} }
val receiversToAdd = ArrayList<Pair<KtExpression, KtExpression>>() val receiversToAdd = ArrayList<Pair<KtExpression, KtExpression>>()
val resultImportFqNames = importFqNames.toMutableSet()
val psiFactory = KtPsiFactory(expression) result.forEachDescendantOfType<KtSimpleNameExpression> { expression ->
expression.forEachDescendantOfType<KtSimpleNameExpression> { expression ->
val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return@forEachDescendantOfType val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return@forEachDescendantOfType
//TODO: other types of references ('[]' etc) //TODO: other types of references ('[]' etc)
if (expression.mainReference.canBeResolvedViaImport(target)) { if (expression.mainReference.canBeResolvedViaImport(target)) {
resultImportFqNames.add(target.importableFqName!!) result.fqNamesToImport.add(target.importableFqName!!)
} }
if (expression.getReceiverExpression() == null) { if (expression.getReceiverExpression() == null) {
if (target is ValueParameterDescriptor && target.containingDeclaration == targetCallable) { if (target is ValueParameterDescriptor && target.containingDeclaration == targetCallable) {
expression.putCopyableUserData(ReplacementExpression.PARAMETER_USAGE_KEY, target.name) expression.putCopyableUserData(ReplacementCode.PARAMETER_USAGE_KEY, target.name)
} }
else if (target is TypeParameterDescriptor && target.containingDeclaration == targetCallable) { else if (target is TypeParameterDescriptor && target.containingDeclaration == targetCallable) {
expression.putCopyableUserData(ReplacementExpression.TYPE_PARAMETER_USAGE_KEY, target.name) expression.putCopyableUserData(ReplacementCode.TYPE_PARAMETER_USAGE_KEY, target.name)
} }
val resolvedCall = expression.getResolvedCall(bindingContext) val resolvedCall = expression.getResolvedCall(bindingContext)
@@ -104,6 +93,7 @@ class ReplacementBuilder(
else else
resolvedCall.dispatchReceiver resolvedCall.dispatchReceiver
if (receiver is ImplicitReceiver) { if (receiver is ImplicitReceiver) {
val resolutionScope = expression.getResolutionScope(bindingContext, resolutionFacade)
val receiverExpression = receiver.asExpression(resolutionScope, psiFactory) val receiverExpression = receiver.asExpression(resolutionScope, psiFactory)
if (receiverExpression != null) { if (receiverExpression != null) {
receiversToAdd.add(expression to receiverExpression) receiversToAdd.add(expression to receiverExpression)
@@ -116,24 +106,10 @@ class ReplacementBuilder(
// add receivers in reverse order because arguments of a call were processed after the callee's name // add receivers in reverse order because arguments of a call were processed after the callee's name
for ((expr, receiverExpression) in receiversToAdd.asReversed()) { for ((expr, receiverExpression) in receiversToAdd.asReversed()) {
val expressionToReplace = expr.parent as? KtCallExpression ?: expr val expressionToReplace = expr.parent as? KtCallExpression ?: expr
val newExpr = expressionToReplace.replaced(psiFactory.createExpressionByPattern("$0.$1", receiverExpression, expressionToReplace)) result.replaceExpression(expressionToReplace,
if (expressionToReplace == expression) { psiFactory.createExpressionByPattern("$0.$1", receiverExpression, expressionToReplace))
expression = newExpr
}
} }
return ReplacementExpression(expression, resultImportFqNames) return result.toNonMutable()
}
private fun analyzeInContext(expression: KtExpression, scope: LexicalScope, expectedType: KotlinType): BindingContext {
val module = scope.ownerDescriptor.module
val frontendService = if (module.builtIns.builtInsModule == module) {
// TODO: doubtful place, do we require this module or not? Built-ins module doesn't have some necessary components...
resolutionFacade.getFrontendService(ExpressionTypingServices::class.java)
}
else {
resolutionFacade.getFrontendService(module, ExpressionTypingServices::class.java)
}
return expression.analyzeInContext(scope, expectedType = expectedType, expressionTypingServices = frontendService)
} }
} }
@@ -17,9 +17,8 @@
package org.jetbrains.kotlin.idea.replacement package org.jetbrains.kotlin.idea.replacement
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.idea.core.copied import org.jetbrains.kotlin.idea.replacement.ReplacementCode.Companion.PARAMETER_USAGE_KEY
import org.jetbrains.kotlin.idea.replacement.ReplacementExpression.Companion.PARAMETER_USAGE_KEY import org.jetbrains.kotlin.idea.replacement.ReplacementCode.Companion.TYPE_PARAMETER_USAGE_KEY
import org.jetbrains.kotlin.idea.replacement.ReplacementExpression.Companion.TYPE_PARAMETER_USAGE_KEY
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
@@ -31,16 +30,15 @@ import org.jetbrains.kotlin.psi.KtExpression
* * All external symbols to be imported should be either referenced via fully-qualified form or included into [fqNamesToImport] * * All external symbols to be imported should be either referenced via fully-qualified form or included into [fqNamesToImport]
* * All usages of value parameters (of our callable) should be marked with [PARAMETER_USAGE_KEY] copyable user data (holds the name of the corresponding parameter) * * All usages of value parameters (of our callable) should be marked with [PARAMETER_USAGE_KEY] copyable user data (holds the name of the corresponding parameter)
* * All usages of type parameters (of our callable) should be marked with [TYPE_PARAMETER_USAGE_KEY] copyable user data (holds the name of the corresponding type parameter) * * All usages of type parameters (of our callable) should be marked with [TYPE_PARAMETER_USAGE_KEY] copyable user data (holds the name of the corresponding type parameter)
* Use [ReplacementBuilder.buildReplacementExpression]. * Use [ReplacementBuilder.buildReplacementCode].
*/ */
class ReplacementExpression( class ReplacementCode(
val expression: KtExpression, val mainExpression: KtExpression?,
val statementsBefore: List<KtExpression>,
val fqNamesToImport: Collection<FqName> val fqNamesToImport: Collection<FqName>
) { ) {
fun copy() = ReplacementExpression(expression.copied(), fqNamesToImport)
companion object { companion object {
val PARAMETER_USAGE_KEY: Key<Name> = Key("PARAMETER_USAGE") val PARAMETER_USAGE_KEY: Key<Name> = Key("PARAMETER_USAGE")
val TYPE_PARAMETER_USAGE_KEY: Key<Name> = Key("TYPE_PARAMETER_USAGE") val TYPE_PARAMETER_USAGE_KEY: Key<Name> = Key("TYPE_PARAMETER_USAGE")
} }
} }
@@ -52,8 +52,11 @@ object ReplacementEngine {
bindingContext: BindingContext, bindingContext: BindingContext,
resolvedCall: ResolvedCall<out CallableDescriptor>, resolvedCall: ResolvedCall<out CallableDescriptor>,
callElement: TCallElement, callElement: TCallElement,
replacement: ReplacementExpression replacement: ReplacementCode
): KtElement { ): KtElement {
@Suppress("NAME_SHADOWING")
val replacement = replacement.toMutable()
val project = element.project val project = element.project
val psiFactory = KtPsiFactory(project) val psiFactory = KtPsiFactory(project)
val descriptor = resolvedCall.resultingDescriptor val descriptor = resolvedCall.resultingDescriptor
@@ -82,34 +85,34 @@ object ReplacementEngine {
receiver?.mark(RECEIVER_VALUE_KEY) receiver?.mark(RECEIVER_VALUE_KEY)
val codeHolder = when (elementToBeReplaced) { val codeHolder = when (elementToBeReplaced) {
is KtExpression -> ConstructedExpressionHolder(replacement.expression, elementToBeReplaced, bindingContext) is KtExpression -> ConstructedExpressionHolder(replacement, elementToBeReplaced, bindingContext)
is KtAnnotationEntry -> ConstructedAnnotationEntryHolder(replacement.expression, elementToBeReplaced, bindingContext) is KtAnnotationEntry -> ConstructedAnnotationEntryHolder(replacement, elementToBeReplaced, bindingContext)
else -> error("Unsupported element") else -> error("Unsupported element")
} }
//TODO: this@ //TODO: this@
for (thisExpression in replacement.expression.collectDescendantsOfType<KtThisExpression>()) { for (thisExpression in replacement.collectDescendantsOfType<KtThisExpression>()) {
if (receiver != null) { if (receiver != null) {
codeHolder.replaceExpression(thisExpression, receiver) replacement.replaceExpression(thisExpression, receiver)
} }
else { else {
thisExpression.mark(RECEIVER_VALUE_KEY) thisExpression.mark(RECEIVER_VALUE_KEY)
} }
} }
val introduceValuesForParameters = codeHolder.processValueParameterUsages(resolvedCall, project) val introduceValuesForParameters = processValueParameterUsages(replacement, resolvedCall, bindingContext, project)
codeHolder.processTypeParameterUsages(resolvedCall) processTypeParameterUsages(replacement, resolvedCall)
if (qualifiedExpression is KtSafeQualifiedExpression) { if (qualifiedExpression is KtSafeQualifiedExpression) {
(codeHolder as ConstructedExpressionHolder).wrapExpressionForSafeCall(receiver!!, receiverType) (codeHolder as ConstructedExpressionHolder).wrapCodeForSafeCall(receiver!!, receiverType)
} }
else if (callElement is KtBinaryExpression && callElement.operationToken == KtTokens.IDENTIFIER) { else if (callElement is KtBinaryExpression && callElement.operationToken == KtTokens.IDENTIFIER) {
codeHolder.keepInfixFormIfPossible() keepInfixFormIfPossible(replacement)
} }
if (receiver != null && codeHolder is ConstructedExpressionHolder) { if (receiver != null && codeHolder is ConstructedExpressionHolder) {
val thisReplaced = codeHolder.expression.collectDescendantsOfType<KtExpression> { it[RECEIVER_VALUE_KEY] } val thisReplaced = replacement.collectDescendantsOfType<KtExpression> { it[RECEIVER_VALUE_KEY] }
if (receiver.shouldKeepValue(thisReplaced.size)) { if (receiver.shouldKeepValue(thisReplaced.size)) {
codeHolder.introduceValue(receiver, receiverType, thisReplaced) codeHolder.introduceValue(receiver, receiverType, thisReplaced)
} }
@@ -117,7 +120,7 @@ object ReplacementEngine {
if (codeHolder is ConstructedExpressionHolder) { if (codeHolder is ConstructedExpressionHolder) {
for ((parameter, value, valueType) in introduceValuesForParameters) { for ((parameter, value, valueType) in introduceValuesForParameters) {
val usagesReplaced = codeHolder.expression.collectDescendantsOfType<KtExpression> { it[PARAMETER_VALUE_KEY] == parameter } val usagesReplaced = replacement.collectDescendantsOfType<KtExpression> { it[PARAMETER_VALUE_KEY] == parameter }
codeHolder.introduceValue(value, valueType, usagesReplaced, nameSuggestion = parameter.name.asString()) codeHolder.introduceValue(value, valueType, usagesReplaced, nameSuggestion = parameter.name.asString())
} }
} }
@@ -133,8 +136,10 @@ object ReplacementEngine {
} }
} }
private fun ConstructedCodeHolder<*>.processValueParameterUsages( private fun processValueParameterUsages(
replacement: MutableReplacementCode,
resolvedCall: ResolvedCall<out CallableDescriptor>, resolvedCall: ResolvedCall<out CallableDescriptor>,
bindingContext: BindingContext,
project: Project project: Project
): Collection<IntroduceValueForParameter> { ): Collection<IntroduceValueForParameter> {
val introduceValuesForParameters = ArrayList<IntroduceValueForParameter>() val introduceValuesForParameters = ArrayList<IntroduceValueForParameter>()
@@ -147,8 +152,8 @@ object ReplacementEngine {
argument.expression.put(PARAMETER_VALUE_KEY, parameter) argument.expression.put(PARAMETER_VALUE_KEY, parameter)
val parameterName = parameter.name val parameterName = parameter.name
val usages = expression.collectDescendantsOfType<KtExpression> { val usages = replacement.collectDescendantsOfType<KtExpression> {
it[ReplacementExpression.PARAMETER_USAGE_KEY] == parameterName it[ReplacementCode.PARAMETER_USAGE_KEY] == parameterName
} }
usages.forEach { usages.forEach {
val usageArgument = it.parent as? KtValueArgument val usageArgument = it.parent as? KtValueArgument
@@ -158,7 +163,7 @@ object ReplacementEngine {
if (argument.isDefaultValue) { if (argument.isDefaultValue) {
usageArgument?.mark(DEFAULT_PARAMETER_VALUE_KEY) usageArgument?.mark(DEFAULT_PARAMETER_VALUE_KEY)
} }
replaceExpression(it, argument.expression) replacement.replaceExpression(it, argument.expression)
} }
//TODO: sometimes we need to add explicit type arguments here because we don't have expected type in the new context //TODO: sometimes we need to add explicit type arguments here because we don't have expected type in the new context
@@ -176,7 +181,7 @@ object ReplacementEngine {
val value: KtExpression, val value: KtExpression,
val valueType: KotlinType?) val valueType: KotlinType?)
private fun ConstructedCodeHolder<*>.processTypeParameterUsages(resolvedCall: ResolvedCall<out CallableDescriptor>) { private fun processTypeParameterUsages(replacement: MutableReplacementCode, resolvedCall: ResolvedCall<out CallableDescriptor>) {
val typeParameters = resolvedCall.resultingDescriptor.original.typeParameters val typeParameters = resolvedCall.resultingDescriptor.original.typeParameters
val callElement = resolvedCall.call.callElement val callElement = resolvedCall.call.callElement
@@ -186,8 +191,8 @@ object ReplacementEngine {
for ((index, typeParameter) in typeParameters.withIndex()) { for ((index, typeParameter) in typeParameters.withIndex()) {
val parameterName = typeParameter.name val parameterName = typeParameter.name
val usages = expression.collectDescendantsOfType<KtExpression> { val usages = replacement.collectDescendantsOfType<KtExpression> {
it[ReplacementExpression.TYPE_PARAMETER_USAGE_KEY] == parameterName it[ReplacementCode.TYPE_PARAMETER_USAGE_KEY] == parameterName
} }
val factory = KtPsiFactory(callElement) val factory = KtPsiFactory(callElement)
@@ -209,7 +214,7 @@ object ReplacementEngine {
val arguments = val arguments =
if (typeElement is KtUserType && KotlinBuiltIns.isArray(type)) typeElement.typeArgumentList?.text.orEmpty() if (typeElement is KtUserType && KotlinBuiltIns.isArray(type)) typeElement.typeArgumentList?.text.orEmpty()
else "" else ""
replaceExpression(usage, KtPsiFactory(usage).createExpression( replacement.replaceExpression(usage, KtPsiFactory(usage).createExpression(
IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(typeClassifier) + arguments IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(typeClassifier) + arguments
)) ))
} }
@@ -218,36 +223,54 @@ object ReplacementEngine {
} }
else { else {
//TODO: tests for this? //TODO: tests for this?
replaceExpression(usage, KtPsiFactory(usage).createExpression(typeElement.text)) replacement.replaceExpression(usage, KtPsiFactory(usage).createExpression(typeElement.text))
} }
} }
} }
} }
private fun ConstructedExpressionHolder.wrapExpressionForSafeCall(receiver: KtExpression, receiverType: KotlinType?) { private fun ConstructedExpressionHolder.wrapCodeForSafeCall(receiver: KtExpression, receiverType: KotlinType?) {
val qualified = expression as? KtQualifiedExpression if (replacement.statementsBefore.isEmpty()) {
if (qualified != null) { val qualified = replacement.mainExpression as? KtQualifiedExpression
if (qualified.receiverExpression[RECEIVER_VALUE_KEY]) { if (qualified != null) {
if (qualified is KtSafeQualifiedExpression) return // already safe if (qualified.receiverExpression[RECEIVER_VALUE_KEY]) {
val selector = qualified.selectorExpression if (qualified is KtSafeQualifiedExpression) return // already safe
if (selector != null) { val selector = qualified.selectorExpression
expression = psiFactory.createExpressionByPattern("$0?.$1", receiver, selector) if (selector != null) {
return replacement.mainExpression = KtPsiFactory(receiver).createExpressionByPattern("$0?.$1", receiver, selector)
return
}
} }
} }
} }
if (elementToBeReplaced.isUsedAsExpression(bindingContext)) { if (elementToBeReplaced.isUsedAsExpression(bindingContext)) {
val thisReplaced = expression.collectDescendantsOfType<KtExpression> { it[RECEIVER_VALUE_KEY] } val thisReplaced = replacement.collectDescendantsOfType<KtExpression> { it[RECEIVER_VALUE_KEY] }
introduceValue(receiver, receiverType, thisReplaced, safeCall = true) introduceValue(receiver, receiverType, thisReplaced, safeCall = true)
} }
else { else {
expression = psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", receiver, expression) val ifExpression = KtPsiFactory(receiver).buildExpression {
appendFixedText("if (")
appendExpression(receiver)
appendFixedText("!=null) {")
replacement.statementsBefore.forEach {
appendExpression(it)
appendFixedText("\n")
}
replacement.mainExpression?.let {
appendExpression(it)
appendFixedText("\n")
}
appendFixedText("}")
}
replacement.mainExpression = ifExpression
replacement.statementsBefore.clear()
} }
} }
private fun ConstructedCodeHolder<*>.keepInfixFormIfPossible() { private fun keepInfixFormIfPossible(replacement: MutableReplacementCode) {
val dotQualified = expression as? KtDotQualifiedExpression ?: return if (replacement.statementsBefore.isNotEmpty()) return
val dotQualified = replacement.mainExpression as? KtDotQualifiedExpression ?: return
val receiver = dotQualified.receiverExpression val receiver = dotQualified.receiverExpression
if (!receiver[RECEIVER_VALUE_KEY]) return if (!receiver[RECEIVER_VALUE_KEY]) return
val call = dotQualified.selectorExpression as? KtCallExpression ?: return val call = dotQualified.selectorExpression as? KtCallExpression ?: return
@@ -255,7 +278,7 @@ object ReplacementEngine {
val argument = call.valueArguments.singleOrNull() ?: return val argument = call.valueArguments.singleOrNull() ?: return
if (argument.getArgumentName() != null) return if (argument.getArgumentName() != null) return
val argumentExpression = argument.getArgumentExpression() ?: return val argumentExpression = argument.getArgumentExpression() ?: return
expression = psiFactory.createExpressionByPattern("$0 ${nameExpression.text} $1", receiver, argumentExpression) replacement.mainExpression = KtPsiFactory(receiver).createExpressionByPattern("$0 ${nameExpression.text} $1", receiver, argumentExpression)
} }
private fun KtExpression?.shouldKeepValue(usageCount: Int): Boolean { private fun KtExpression?.shouldKeepValue(usageCount: Int): Boolean {
@@ -305,13 +328,13 @@ object ReplacementEngine {
val (expression, parameterUsages) = defaultValue val (expression, parameterUsages) = defaultValue
for ((param, usages) in parameterUsages) { for ((param, usages) in parameterUsages) {
usages.forEach { it.put(ReplacementExpression.PARAMETER_USAGE_KEY, param.name) } usages.forEach { it.put(ReplacementCode.PARAMETER_USAGE_KEY, param.name) }
} }
val expressionCopy = expression.copied() val expressionCopy = expression.copied()
// clean up user data in original // clean up user data in original
expression.forEachDescendantOfType<KtExpression> { it.clear(ReplacementExpression.PARAMETER_USAGE_KEY) } expression.forEachDescendantOfType<KtExpression> { it.clear(ReplacementCode.PARAMETER_USAGE_KEY) }
return Argument(expressionCopy, null/*TODO*/, isDefaultValue = true) return Argument(expressionCopy, null/*TODO*/, isDefaultValue = true)
} }
@@ -382,8 +405,8 @@ object ReplacementEngine {
// clean up user data // clean up user data
it.forEachDescendantOfType<KtExpression> { it.forEachDescendantOfType<KtExpression> {
it.clear(USER_CODE_KEY) it.clear(USER_CODE_KEY)
it.clear(ReplacementExpression.PARAMETER_USAGE_KEY) it.clear(ReplacementCode.PARAMETER_USAGE_KEY)
it.clear(ReplacementExpression.TYPE_PARAMETER_USAGE_KEY) it.clear(ReplacementCode.TYPE_PARAMETER_USAGE_KEY)
it.clear(PARAMETER_VALUE_KEY) it.clear(PARAMETER_VALUE_KEY)
it.clear(RECEIVER_VALUE_KEY) it.clear(RECEIVER_VALUE_KEY)
it.clear(WAS_FUNCTION_LITERAL_ARGUMENT_KEY) it.clear(WAS_FUNCTION_LITERAL_ARGUMENT_KEY)
@@ -17,11 +17,12 @@
package org.jetbrains.kotlin.idea.quickfix.replaceWith package org.jetbrains.kotlin.idea.quickfix.replaceWith
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.replacement.ReplacementBuilder import org.jetbrains.kotlin.idea.replacement.ReplacementBuilder
import org.jetbrains.kotlin.idea.replacement.ReplacementExpression import org.jetbrains.kotlin.idea.replacement.ReplacementCode
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.frontendService import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
@@ -39,6 +40,7 @@ import org.jetbrains.kotlin.resolve.scopes.*
import org.jetbrains.kotlin.resolve.scopes.utils.chainImportingScopes import org.jetbrains.kotlin.resolve.scopes.utils.chainImportingScopes
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import java.util.* import java.util.*
data class ReplaceWith(val pattern: String, val imports: List<String>) data class ReplaceWith(val pattern: String, val imports: List<String>)
@@ -48,7 +50,7 @@ object ReplaceWithAnnotationAnalyzer {
annotation: ReplaceWith, annotation: ReplaceWith,
symbolDescriptor: CallableDescriptor, symbolDescriptor: CallableDescriptor,
resolutionFacade: ResolutionFacade resolutionFacade: ResolutionFacade
): ReplacementExpression? { ): ReplacementCode? {
val originalDescriptor = (if (symbolDescriptor is CallableMemberDescriptor) val originalDescriptor = (if (symbolDescriptor is CallableMemberDescriptor)
DescriptorUtils.unwrapFakeOverride(symbolDescriptor) DescriptorUtils.unwrapFakeOverride(symbolDescriptor)
else else
@@ -60,7 +62,7 @@ object ReplaceWithAnnotationAnalyzer {
annotation: ReplaceWith, annotation: ReplaceWith,
symbolDescriptor: CallableDescriptor, symbolDescriptor: CallableDescriptor,
resolutionFacade: ResolutionFacade resolutionFacade: ResolutionFacade
): ReplacementExpression? { ): ReplacementCode? {
val psiFactory = KtPsiFactory(resolutionFacade.project) val psiFactory = KtPsiFactory(resolutionFacade.project)
val expression = try { val expression = try {
psiFactory.createExpression(annotation.pattern) psiFactory.createExpression(annotation.pattern)
@@ -75,8 +77,20 @@ object ReplaceWithAnnotationAnalyzer {
val scope = getResolutionScope(symbolDescriptor, symbolDescriptor, val scope = getResolutionScope(symbolDescriptor, symbolDescriptor,
listOf(explicitImportsScope) + defaultImportsScopes) ?: return null listOf(explicitImportsScope) + defaultImportsScopes) ?: return null
val expressionTypingServices = if (module.builtIns.builtInsModule == module) {
// TODO: doubtful place, do we require this module or not? Built-ins module doesn't have some necessary components...
resolutionFacade.getFrontendService(ExpressionTypingServices::class.java)
}
else {
resolutionFacade.getFrontendService(module, ExpressionTypingServices::class.java)
}
fun analyzeExpression(): BindingContext {
return expression.analyzeInContext(scope, expressionTypingServices = expressionTypingServices)
}
return ReplacementBuilder(symbolDescriptor, resolutionFacade) return ReplacementBuilder(symbolDescriptor, resolutionFacade)
.buildReplacementExpression(expression, scope, importFqNames = importFqNames(annotation), copyExpression = false) .buildReplacementCode(expression, emptyList(), ::analyzeExpression, importFqNames = importFqNames(annotation))
} }
fun analyzeClassReplacement( fun analyzeClassReplacement(
@@ -25,13 +25,16 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.replacement.CallableUsageReplacementStrategy import org.jetbrains.kotlin.idea.replacement.CallableUsageReplacementStrategy
import org.jetbrains.kotlin.idea.replacement.ReplacementBuilder import org.jetbrains.kotlin.idea.replacement.ReplacementBuilder
import org.jetbrains.kotlin.idea.replacement.replaceUsagesInWholeProject import org.jetbrains.kotlin.idea.replacement.replaceUsagesInWholeProject
import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
class KotlinInlineFunctionHandler: InlineActionHandler() { class KotlinInlineFunctionHandler: InlineActionHandler() {
@@ -53,8 +56,15 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
descriptor.returnType ?: TypeUtils.NO_EXPECTED_TYPE descriptor.returnType ?: TypeUtils.NO_EXPECTED_TYPE
else else
TypeUtils.NO_EXPECTED_TYPE TypeUtils.NO_EXPECTED_TYPE
val expression = bodyExpression.copied()
fun analyzeExpression(): BindingContext {
return expression.analyzeInContext(bodyExpression.getResolutionScope(), contextExpression = bodyExpression, expectedType = expectedType)
}
val replacement = ReplacementBuilder(descriptor, element.getResolutionFacade()) val replacement = ReplacementBuilder(descriptor, element.getResolutionFacade())
.buildReplacementExpression(bodyExpression, bodyExpression.getResolutionScope(), expectedType) .buildReplacementCode(expression, emptyList(), ::analyzeExpression)
val commandName = RefactoringBundle.message("inline.command", element.name) val commandName = RefactoringBundle.message("inline.command", element.name)
CallableUsageReplacementStrategy(replacement) CallableUsageReplacementStrategy(replacement)