diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/findOperationGeneration.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/findOperationGeneration.kt deleted file mode 100644 index 4bef0f032e1..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/findOperationGeneration.kt +++ /dev/null @@ -1,190 +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.intentions.loopToCallChain - -import org.jetbrains.kotlin.descriptors.VariableDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.types.typeUtil.TypeNullability -import org.jetbrains.kotlin.types.typeUtil.nullability - -interface FindOperatorGenerator { - val functionName: String - - val hasFilter: Boolean - - val presentation: String - get() = functionName + (if (hasFilter) "{}" else "()") - - val shouldUseInputVariable: Boolean - - fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression - - val chainCallCount: Int - get() = 1 -} - -fun buildFindOperationGenerator( - valueIfFound: KtExpression, - valueIfNotFound: KtExpression, - inputVariable: KtCallableDeclaration, - indexVariable: KtCallableDeclaration?, - filterTransformation: FilterTransformation?, - findFirst: Boolean -): FindOperatorGenerator? { - assert(valueIfFound.isPhysical) - assert(valueIfNotFound.isPhysical) - - val filterCondition = filterTransformation?.effectiveCondition() - - fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { - return if (filter == null) { - chainedCallGenerator.generate("$stdlibFunName()") - } - else { - val lambda = generateLambda(inputVariable, filter) - chainedCallGenerator.generate("$stdlibFunName $0:'{}'", lambda) - } - } - - class SimpleGenerator( - override val functionName: String, - override val shouldUseInputVariable: Boolean - ) : FindOperatorGenerator { - override val hasFilter: Boolean - get() = filterCondition != null - - override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - return generateChainedCall(functionName, chainedCallGenerator, filterCondition) - } - } - - if (indexVariable != null) { - if (filterTransformation == null) return null // makes no sense, indexVariable must be always 0 - if (filterTransformation.indexVariable != null) return null // cannot use index in condition for indexOfFirst/indexOfLast - - //TODO: what if value when not found is not "-1"? - if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") { - return SimpleGenerator(if (findFirst) "indexOfFirst" else "indexOfLast", shouldUseInputVariable = false) - } - - return null - - } - else { - val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL - - fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? { - if (valueIfNotFound.isNullExpression()) return this - - // we cannot use ?: if found value can be null - if (inputVariableCanHoldNull) return null - - return object: FindOperatorGenerator by this { - override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator) - return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound) - } - } - } - - when { - valueIfFound.isVariableReference(inputVariable) -> { - val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true) - return generator.useElvisOperatorIfNeeded() - } - - valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false) - - valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false) - - inputVariable.hasUsages(valueIfFound) -> { - if (!findFirst) return null // too dangerous because of side effects - - // specially handle the case when the result expression is "." or "?." - val qualifiedExpression = valueIfFound as? KtQualifiedExpression - if (qualifiedExpression != null) { - val receiver = qualifiedExpression.receiverExpression - val selector = qualifiedExpression.selectorExpression - if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) { - return object: FindOperatorGenerator { - override val functionName: String - get() = "firstOrNull" - - override val hasFilter: Boolean - get() = filterCondition != null - - override val shouldUseInputVariable: Boolean - get() = true - - override val chainCallCount: Int - get() = 2 - - override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition) - return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true) - } - }.useElvisOperatorIfNeeded() - } - } - - // in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found - if (inputVariableCanHoldNull) return null - - return object: FindOperatorGenerator { - override val functionName: String - get() = "firstOrNull" - - override val hasFilter: Boolean - get() = filterCondition != null - - override val shouldUseInputVariable: Boolean - get() = true - - override val chainCallCount: Int - get() = 2 // also includes "let" - - override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition) - val letBody = generateLambda(inputVariable, valueIfFound) - return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true) - } - }.useElvisOperatorIfNeeded() - } - - else -> { - return object: FindOperatorGenerator { - override val functionName: String - get() = "any" - - override val hasFilter: Boolean - get() = filterCondition != null - - override val shouldUseInputVariable: Boolean - get() = false - - override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition) - return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound) - } - } - } - } - } -} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt deleted file mode 100644 index 0448608f9c8..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt +++ /dev/null @@ -1,44 +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.intentions.loopToCallChain.result - -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.AssignToVariableResultTransformation -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.ChainedCallGenerator -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.FindOperatorGenerator -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.VariableInitialization -import org.jetbrains.kotlin.psi.KtExpression -import org.jetbrains.kotlin.psi.KtForExpression - -class FindAndAssignTransformation( - loop: KtForExpression, - private val generator: FindOperatorGenerator, - initialization: VariableInitialization -) : AssignToVariableResultTransformation(loop, initialization) { - - override val presentation: String - get() = generator.presentation - - override val chainCallCount: Int - get() = generator.chainCallCount - - override val shouldUseInputVariable: Boolean - get() = generator.shouldUseInputVariable - - override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { - return generator.generate(chainedCallGenerator) - } -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt deleted file mode 100644 index 8975109e309..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt +++ /dev/null @@ -1,51 +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.intentions.loopToCallChain.result - -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* -import org.jetbrains.kotlin.psi.KtExpression -import org.jetbrains.kotlin.psi.KtForExpression -import org.jetbrains.kotlin.psi.KtReturnExpression -import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange - -class FindAndReturnTransformation( - override val loop: KtForExpression, - private val generator: FindOperatorGenerator, - private val endReturn: KtReturnExpression -) : ResultTransformation { - - override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn) - - override val presentation: String - get() = generator.presentation - - override val chainCallCount: Int - get() = generator.chainCallCount - - override val shouldUseInputVariable: Boolean - get() = generator.shouldUseInputVariable - - override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { - return generator.generate(chainedCallGenerator) - } - - override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression { - endReturn.returnedExpression!!.replace(resultCallChain) - loop.deleteWithLabels() - return endReturn - } -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt index 2ea55bc1ef3..ac40a980eee 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt @@ -16,12 +16,16 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtBinaryExpression -import org.jetbrains.kotlin.psi.KtBreakExpression -import org.jetbrains.kotlin.psi.KtReturnExpression +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange +import org.jetbrains.kotlin.types.typeUtil.TypeNullability +import org.jetbrains.kotlin.types.typeUtil.nullability /** * Matches: @@ -101,4 +105,217 @@ object FindTransformationMatcher : TransformationMatcher { val transformation = FindAndReturnTransformation(state.outerLoop, generator, returnAfterLoop) return TransformationMatch.Result(transformation) } -} \ No newline at end of file + + private class FindAndReturnTransformation( + override val loop: KtForExpression, + private val generator: FindOperationGenerator, + private val endReturn: KtReturnExpression + ) : ResultTransformation { + + override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn) + + override val presentation: String + get() = generator.presentation + + override val chainCallCount: Int + get() = generator.chainCallCount + + override val shouldUseInputVariable: Boolean + get() = generator.shouldUseInputVariable + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return generator.generate(chainedCallGenerator) + } + + override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression { + endReturn.returnedExpression!!.replace(resultCallChain) + loop.deleteWithLabels() + return endReturn + } + } + + private class FindAndAssignTransformation( + loop: KtForExpression, + private val generator: FindOperationGenerator, + initialization: VariableInitialization + ) : AssignToVariableResultTransformation(loop, initialization) { + + override val presentation: String + get() = generator.presentation + + override val chainCallCount: Int + get() = generator.chainCallCount + + override val shouldUseInputVariable: Boolean + get() = generator.shouldUseInputVariable + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return generator.generate(chainedCallGenerator) + } + } + + private interface FindOperationGenerator { + val functionName: String + + val hasFilter: Boolean + + val presentation: String + get() = functionName + (if (hasFilter) "{}" else "()") + + val shouldUseInputVariable: Boolean + + fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression + + val chainCallCount: Int + get() = 1 + } + + private fun buildFindOperationGenerator( + valueIfFound: KtExpression, + valueIfNotFound: KtExpression, + inputVariable: KtCallableDeclaration, + indexVariable: KtCallableDeclaration?, + filterTransformation: FilterTransformation?, + findFirst: Boolean + ): FindOperationGenerator? { + assert(valueIfFound.isPhysical) + assert(valueIfNotFound.isPhysical) + + val filterCondition = filterTransformation?.effectiveCondition() + + fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression { + return if (filter == null) { + chainedCallGenerator.generate("$stdlibFunName()") + } + else { + val lambda = org.jetbrains.kotlin.idea.intentions.loopToCallChain.generateLambda(inputVariable, filter) + chainedCallGenerator.generate("$stdlibFunName $0:'{}'", lambda) + } + } + + class SimpleGenerator( + override val functionName: String, + override val shouldUseInputVariable: Boolean + ) : FindOperationGenerator { + override val hasFilter: Boolean + get() = filterCondition != null + + override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return generateChainedCall(functionName, chainedCallGenerator, filterCondition) + } + } + + if (indexVariable != null) { + if (filterTransformation == null) return null // makes no sense, indexVariable must be always 0 + if (filterTransformation.indexVariable != null) return null // cannot use index in condition for indexOfFirst/indexOfLast + + //TODO: what if value when not found is not "-1"? + if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") { + return SimpleGenerator(if (findFirst) "indexOfFirst" else "indexOfLast", shouldUseInputVariable = false) + } + + return null + + } + else { + val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL + + fun FindOperationGenerator.useElvisOperatorIfNeeded(): FindOperationGenerator? { + if (valueIfNotFound.isNullExpression()) return this + + // we cannot use ?: if found value can be null + if (inputVariableCanHoldNull) return null + + return object: FindOperationGenerator by this { + override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator) + return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound) + } + } + } + + when { + valueIfFound.isVariableReference(inputVariable) -> { + val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true) + return generator.useElvisOperatorIfNeeded() + } + + valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false) + + valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false) + + inputVariable.hasUsages(valueIfFound) -> { + if (!findFirst) return null // too dangerous because of side effects + + // specially handle the case when the result expression is "." or "?." + val qualifiedExpression = valueIfFound as? KtQualifiedExpression + if (qualifiedExpression != null) { + val receiver = qualifiedExpression.receiverExpression + val selector = qualifiedExpression.selectorExpression + if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) { + return object: FindOperationGenerator { + override val functionName: String + get() = "firstOrNull" + + override val hasFilter: Boolean + get() = filterCondition != null + + override val shouldUseInputVariable: Boolean + get() = true + + override val chainCallCount: Int + get() = 2 + + override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition) + return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true) + } + }.useElvisOperatorIfNeeded() + } + } + + // in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found + if (inputVariableCanHoldNull) return null + + return object: FindOperationGenerator { + override val functionName: String + get() = "firstOrNull" + + override val hasFilter: Boolean + get() = filterCondition != null + + override val shouldUseInputVariable: Boolean + get() = true + + override val chainCallCount: Int + get() = 2 // also includes "let" + + override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition) + val letBody = generateLambda(inputVariable, valueIfFound) + return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true) + } + }.useElvisOperatorIfNeeded() + } + + else -> { + return object: FindOperationGenerator { + override val functionName: String + get() = "any" + + override val hasFilter: Boolean + get() = filterCondition != null + + override val shouldUseInputVariable: Boolean + get() = false + + override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition) + return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound) + } + } + } + } + } + } +}