diff --git a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallKindHandler.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallKindHandler.kt deleted file mode 100644 index 2ea112870f1..00000000000 --- a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallKindHandler.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 org.jetbrains.kotlin.idea.core.replaced -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis - -interface CallKindHandler { - fun elementToReplace(callElement: TCallElement): KtElement - - fun precheckReplacementPattern(pattern: ReplacementExpression): Boolean - - fun wrapGeneratedExpression(expression: KtExpression): TCallElement - - fun unwrapResult(result: TCallElement): TCallElement -} - -object CallExpressionHandler : CallKindHandler { - override fun elementToReplace(callElement: KtExpression) = callElement.getQualifiedExpressionForSelectorOrThis() - - override fun precheckReplacementPattern(pattern: ReplacementExpression) = true - - override fun wrapGeneratedExpression(expression: KtExpression) = expression - - override fun unwrapResult(result: KtExpression) = result -} - -object AnnotationEntryHandler : CallKindHandler { - override fun elementToReplace(callElement: KtAnnotationEntry) = callElement - - override fun precheckReplacementPattern(pattern: ReplacementExpression): Boolean { - //TODO - return true - } - - //TODO: how to prohibit wrapping replacement expression into anything? - - override fun wrapGeneratedExpression(expression: KtExpression): KtAnnotationEntry { - return createByPattern("@Dummy($0)", expression) { KtPsiFactory(expression).createAnnotationEntry(it) } - } - - override fun unwrapResult(result: KtAnnotationEntry): KtAnnotationEntry { - val text = result.valueArguments.single().getArgumentExpression()!!.text - return result.replaced(KtPsiFactory(result).createAnnotationEntry("@" + text)) - } -} \ No newline at end of file diff --git a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt index da184b6ccf9..38d696f6565 100644 --- a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt +++ b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt @@ -34,9 +34,9 @@ class CallableUsageReplacementStrategy( if (!resolvedCall.isReallySuccess()) return null val callElement = resolvedCall.call.callElement - val callTypeHandler = callTypeHandler(callElement) ?: return null + if (callElement !is KtExpression && callElement !is KtAnnotationEntry) return null - if (!callTypeHandler.precheckReplacementPattern(replacement)) return null + //TODO: precheck pattern correctness for annotation entry return { if (usage is KtOperationReferenceExpression && usage.getReferencedNameElementType() != KtTokens.IDENTIFIER) { @@ -46,19 +46,9 @@ class CallableUsageReplacementStrategy( else { // copy replacement expression because it is modified by performCallReplacement @Suppress("UNCHECKED_CAST") - ReplacementEngine.performCallReplacement(usage, bindingContext, resolvedCall, callElement, - callTypeHandler as CallKindHandler, - replacement.copy()) + ReplacementEngine.performCallReplacement(usage, bindingContext, resolvedCall, callElement, replacement.copy()) } } } - - private fun callTypeHandler(callElement: KtElement): CallKindHandler<*>? { - return when (callElement) { - is KtExpression -> CallExpressionHandler - is KtAnnotationEntry -> AnnotationEntryHandler - else -> null - } - } } diff --git a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ConstructedExpressionWrapperWithIntroduceFeature.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ConstructedExpressionHolder.kt similarity index 57% rename from idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ConstructedExpressionWrapperWithIntroduceFeature.kt rename to idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ConstructedExpressionHolder.kt index e90d0819601..7850ccafafd 100644 --- a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ConstructedExpressionWrapperWithIntroduceFeature.kt +++ b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ConstructedExpressionHolder.kt @@ -19,10 +19,12 @@ package org.jetbrains.kotlin.idea.replacement import org.jetbrains.kotlin.idea.analysis.computeTypeInContext import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.setType import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression import org.jetbrains.kotlin.psi.psiUtil.isAncestor @@ -34,8 +36,9 @@ import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.KotlinType import java.util.* -internal open class ConstructedExpressionWrapper( +internal abstract class ConstructedCodeHolder( var expression: KtExpression, + val elementToBeReplaced: TElement, val bindingContext: BindingContext ) { val psiFactory = KtPsiFactory(expression) @@ -50,14 +53,68 @@ internal open class ConstructedExpressionWrapper( return oldExpression.replace(newExpression) as KtExpression } } + + abstract fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): TElement } -internal class ConstructedExpressionWrapperWithIntroduceFeature( +internal class ConstructedAnnotationEntryHolder( expression: KtExpression, - bindingContext: BindingContext, - val expressionToBeReplaced: KtExpression -) : ConstructedExpressionWrapper(expression, bindingContext) { - val addedStatements = ArrayList() + elementToBeReplaced: KtAnnotationEntry, + bindingContext: BindingContext +) : ConstructedCodeHolder(expression, elementToBeReplaced, bindingContext) { + + override fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): KtAnnotationEntry { + val dummyAnnotationEntry = createByPattern("@Dummy($0)", expression) { psiFactory.createAnnotationEntry(it) } + val replaced = elementToBeReplaced.replace(dummyAnnotationEntry) + var range = PsiChildRange.singleElement(replaced) + range = postProcessing(range) + + assert(range.first == range.last) + assert(range.first is KtAnnotationEntry) + val annotationEntry = range.first as KtAnnotationEntry + val text = annotationEntry.valueArguments.single().getArgumentExpression()!!.text + return annotationEntry.replaced(psiFactory.createAnnotationEntry("@" + text)) + } +} + +internal class ConstructedExpressionHolder( + expression: KtExpression, + expressionToBeReplaced: KtExpression, + bindingContext: BindingContext +) : ConstructedCodeHolder(expression, expressionToBeReplaced, bindingContext) { + + private data class StatementToInsert(val statement: TStatement, val postProcessing: (TStatement) -> Unit) + + private val statementsToInsert = ArrayList>() + + private fun addStatementToInsert(statement: TStatement, postProcessing: (TStatement) -> Unit = {}) { + statementsToInsert.add(StatementToInsert(statement, postProcessing)) + } + + override fun finish(postProcessing: (PsiChildRange) -> PsiChildRange): KtExpression { + val insertedStatements = ArrayList() + for (toInsert in statementsToInsert.asReversed()) { //TODO: do we need it? + val block = elementToBeReplaced.parent as KtBlockExpression //TODO + + val inserted = block.addBefore(toInsert.statement, elementToBeReplaced) as KtExpression + block.addBefore(psiFactory.createNewLine(), elementToBeReplaced) + insertedStatements.add(inserted) + + @Suppress("UNCHECKED_CAST") + (toInsert.postProcessing as (KtExpression) -> Unit).invoke(inserted) + } + + val replaced = elementToBeReplaced.replace(expression) + + var range = if (insertedStatements.isEmpty()) + PsiChildRange.singleElement(replaced) + else + PsiChildRange(insertedStatements.first(), replaced) + + range = postProcessing(range) + + return range.last as KtExpression + } fun introduceValue( value: KtExpression, @@ -87,17 +144,17 @@ internal class ConstructedExpressionWrapperWithIntroduceFeature( fun isNameUsed(name: String) = collectNameUsages(expression, name).any { nameUsage -> usages.none { it.isAncestor(nameUsage) } } if (!safeCall) { - val block = expressionToBeReplaced.parent as? KtBlockExpression + val block = elementToBeReplaced.parent as? KtBlockExpression if (block != null) { - val resolutionScope = expressionToBeReplaced.getResolutionScope(bindingContext, expressionToBeReplaced.getResolutionFacade()) + val resolutionScope = elementToBeReplaced.getResolutionScope(bindingContext, elementToBeReplaced.getResolutionFacade()) if (usages.isNotEmpty()) { var explicitType: KotlinType? = null if (valueType != null && !ErrorUtils.containsErrorType(valueType)) { val valueTypeWithoutExpectedType = value.computeTypeInContext( resolutionScope, - expressionToBeReplaced, - dataFlowInfo = bindingContext.getDataFlowInfo(expressionToBeReplaced) + elementToBeReplaced, + dataFlowInfo = bindingContext.getDataFlowInfo(elementToBeReplaced) ) if (valueTypeWithoutExpectedType == null || ErrorUtils.containsErrorType(valueTypeWithoutExpectedType)) { explicitType = valueType @@ -108,21 +165,18 @@ internal class ConstructedExpressionWrapperWithIntroduceFeature( resolutionScope.findLocalVariable(Name.identifier(name)) == null && !isNameUsed(name) } - var declaration = psiFactory.createDeclarationByPattern("val $0 = $1", name, value) - declaration = block.addBefore(declaration, expressionToBeReplaced) as KtVariableDeclaration - block.addBefore(psiFactory.createNewLine(), expressionToBeReplaced) - - if (explicitType != null) { - declaration.setType(explicitType) - } + val declaration = psiFactory.createDeclarationByPattern("val $0 = $1", name, value) + addStatementToInsert(declaration, + postProcessing = { + if (explicitType != null) { + it.setType(explicitType!!) + } + }) replaceUsages(name) - - addedStatements.add(declaration) } else { - addedStatements.add(block.addBefore(value, expressionToBeReplaced) as KtExpression) - block.addBefore(psiFactory.createNewLine(), expressionToBeReplaced) + addStatementToInsert(value) } return } diff --git a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ReplacementEngine.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ReplacementEngine.kt index aa390e1a33e..54b48e63247 100644 --- a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ReplacementEngine.kt +++ b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ReplacementEngine.kt @@ -52,15 +52,18 @@ object ReplacementEngine { bindingContext: BindingContext, resolvedCall: ResolvedCall, callElement: TCallElement, - callKindHandler: CallKindHandler, replacement: ReplacementExpression ): KtElement { val project = element.project val psiFactory = KtPsiFactory(project) val descriptor = resolvedCall.resultingDescriptor + val file = element.getContainingKtFile() val qualifiedExpression = callElement.getQualifiedExpressionForSelector() - val elementToBeReplaced = callKindHandler.elementToReplace(callElement) + val elementToBeReplaced = when (callElement) { + is KtExpression -> callElement.getQualifiedExpressionForSelectorOrThis() + else -> callElement + } val commentSaver = CommentSaver(elementToBeReplaced, saveLineBreaks = true) @@ -78,68 +81,59 @@ object ReplacementEngine { receiver?.mark(RECEIVER_VALUE_KEY) - val wrapper = if (elementToBeReplaced is KtExpression) - ConstructedExpressionWrapperWithIntroduceFeature(replacement.expression, bindingContext, elementToBeReplaced) - else - ConstructedExpressionWrapper(replacement.expression, bindingContext) + val codeHolder = when (elementToBeReplaced) { + is KtExpression -> ConstructedExpressionHolder(replacement.expression, elementToBeReplaced, bindingContext) + is KtAnnotationEntry -> ConstructedAnnotationEntryHolder(replacement.expression, elementToBeReplaced, bindingContext) + else -> error("Unsupported element") + } //TODO: this@ for (thisExpression in replacement.expression.collectDescendantsOfType()) { if (receiver != null) { - wrapper.replaceExpression(thisExpression, receiver) + codeHolder.replaceExpression(thisExpression, receiver) } else { thisExpression.mark(RECEIVER_VALUE_KEY) } } - val introduceValuesForParameters = wrapper.processValueParameterUsages(resolvedCall, project) + val introduceValuesForParameters = codeHolder.processValueParameterUsages(resolvedCall, project) - wrapper.processTypeParameterUsages(resolvedCall) + codeHolder.processTypeParameterUsages(resolvedCall) if (qualifiedExpression is KtSafeQualifiedExpression) { - (wrapper as ConstructedExpressionWrapperWithIntroduceFeature).wrapExpressionForSafeCall(receiver!!, receiverType) + (codeHolder as ConstructedExpressionHolder).wrapExpressionForSafeCall(receiver!!, receiverType) } else if (callElement is KtBinaryExpression && callElement.operationToken == KtTokens.IDENTIFIER) { - wrapper.keepInfixFormIfPossible() + codeHolder.keepInfixFormIfPossible() } - if (receiver != null && wrapper is ConstructedExpressionWrapperWithIntroduceFeature) { - val thisReplaced = wrapper.expression.collectDescendantsOfType { it[RECEIVER_VALUE_KEY] } + if (receiver != null && codeHolder is ConstructedExpressionHolder) { + val thisReplaced = codeHolder.expression.collectDescendantsOfType { it[RECEIVER_VALUE_KEY] } if (receiver.shouldKeepValue(thisReplaced.size)) { - wrapper.introduceValue(receiver, receiverType, thisReplaced) + codeHolder.introduceValue(receiver, receiverType, thisReplaced) } } - if (wrapper is ConstructedExpressionWrapperWithIntroduceFeature) { + if (codeHolder is ConstructedExpressionHolder) { for ((parameter, value, valueType) in introduceValuesForParameters) { - val usagesReplaced = wrapper.expression.collectDescendantsOfType { it[PARAMETER_VALUE_KEY] == parameter } - wrapper.introduceValue(value, valueType, usagesReplaced, nameSuggestion = parameter.name.asString()) + val usagesReplaced = codeHolder.expression.collectDescendantsOfType { it[PARAMETER_VALUE_KEY] == parameter } + codeHolder.introduceValue(value, valueType, usagesReplaced, nameSuggestion = parameter.name.asString()) } } - val wrappedExpression = callKindHandler.wrapGeneratedExpression(wrapper.expression) - val result = elementToBeReplaced.replace(wrappedExpression) as KtElement - - val file = result.getContainingKtFile() replacement.fqNamesToImport .flatMap { file.resolveImportReference(it) } .forEach { ImportInsertHelper.getInstance(project).importDescriptor(file, it) } - var resultRange = if (wrapper !is ConstructedExpressionWrapperWithIntroduceFeature || wrapper.addedStatements.isEmpty()) - PsiChildRange.singleElement(result) - else - PsiChildRange(wrapper.addedStatements.first(), result) - - resultRange = postProcessInsertedCode(resultRange) - - commentSaver.restore(resultRange) - - @Suppress("UNCHECKED_CAST") - return callKindHandler.unwrapResult(resultRange.last as TCallElement) + return codeHolder.finish { range -> + val newRange = postProcessInsertedCode(range) + commentSaver.restore(newRange) + newRange + } } - private fun ConstructedExpressionWrapper.processValueParameterUsages( + private fun ConstructedCodeHolder<*>.processValueParameterUsages( resolvedCall: ResolvedCall, project: Project ): Collection { @@ -182,7 +176,7 @@ object ReplacementEngine { val value: KtExpression, val valueType: KotlinType?) - private fun ConstructedExpressionWrapper.processTypeParameterUsages(resolvedCall: ResolvedCall) { + private fun ConstructedCodeHolder<*>.processTypeParameterUsages(resolvedCall: ResolvedCall) { val typeParameters = resolvedCall.resultingDescriptor.original.typeParameters val callElement = resolvedCall.call.callElement @@ -230,7 +224,7 @@ object ReplacementEngine { } } - private fun ConstructedExpressionWrapperWithIntroduceFeature.wrapExpressionForSafeCall(receiver: KtExpression, receiverType: KotlinType?) { + private fun ConstructedExpressionHolder.wrapExpressionForSafeCall(receiver: KtExpression, receiverType: KotlinType?) { val qualified = expression as? KtQualifiedExpression if (qualified != null) { if (qualified.receiverExpression[RECEIVER_VALUE_KEY]) { @@ -243,7 +237,7 @@ object ReplacementEngine { } } - if (expressionToBeReplaced.isUsedAsExpression(bindingContext)) { + if (elementToBeReplaced.isUsedAsExpression(bindingContext)) { val thisReplaced = expression.collectDescendantsOfType { it[RECEIVER_VALUE_KEY] } introduceValue(receiver, receiverType, thisReplaced, safeCall = true) } @@ -252,7 +246,7 @@ object ReplacementEngine { } } - private fun ConstructedExpressionWrapper.keepInfixFormIfPossible() { + private fun ConstructedCodeHolder<*>.keepInfixFormIfPossible() { val dotQualified = expression as? KtDotQualifiedExpression ?: return val receiver = dotQualified.receiverExpression if (!receiver[RECEIVER_VALUE_KEY]) return