KT-13998 Loop to call chain converter produces 'none' instead of 'all'

#KT-13998 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-10-17 16:48:22 +03:00
parent 89099b93b1
commit e5fcfae969
8 changed files with 87 additions and 16 deletions
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.Condition
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformationBase import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformationBase
import org.jetbrains.kotlin.idea.intentions.negate import org.jetbrains.kotlin.idea.intentions.negate
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
@@ -225,7 +226,7 @@ object FindTransformationMatcher : TransformationMatcher {
assert(valueIfFound.isPhysical) assert(valueIfFound.isPhysical)
assert(valueIfNotFound.isPhysical) assert(valueIfNotFound.isPhysical)
val filter = filterTransformation?.effectiveCondition?.asExpression() val filterCondition = filterTransformation?.effectiveCondition
if (indexVariable != null) { if (indexVariable != null) {
if (filterTransformation == null) return null // makes no sense, indexVariable must be always null if (filterTransformation == null) return null // makes no sense, indexVariable must be always null
@@ -233,14 +234,15 @@ object FindTransformationMatcher : TransformationMatcher {
//TODO: what if value when not found is not "-1"? //TODO: what if value when not found is not "-1"?
if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") { if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") {
val containsArgument = filter!!.isFilterForContainsOperation(inputVariable, loop) val filterExpression = filterCondition!!.asExpression()
val containsArgument = filterExpression.isFilterForContainsOperation(inputVariable, loop)
if (containsArgument != null) { if (containsArgument != null) {
val functionName = if (findFirst) "indexOf" else "lastIndexOf" val functionName = if (findFirst) "indexOf" else "lastIndexOf"
return SimpleGenerator(functionName, inputVariable, null, containsArgument) return SimpleGenerator(functionName, inputVariable, null, containsArgument)
} }
else { else {
val functionName = if (findFirst) "indexOfFirst" else "indexOfLast" val functionName = if (findFirst) "indexOfFirst" else "indexOfLast"
return SimpleGenerator(functionName, inputVariable, filter) return SimpleGenerator(functionName, inputVariable, filterExpression)
} }
} }
@@ -267,16 +269,16 @@ object FindTransformationMatcher : TransformationMatcher {
when { when {
valueIfFound.isVariableReference(inputVariable) -> { valueIfFound.isVariableReference(inputVariable) -> {
val functionName = if (findFirst) "firstOrNull" else "lastOrNull" val functionName = if (findFirst) "firstOrNull" else "lastOrNull"
val generator = SimpleGenerator(functionName, inputVariable, filter) val generator = SimpleGenerator(functionName, inputVariable, filterCondition?.asExpression())
return generator.useElvisOperatorIfNeeded() return generator.useElvisOperatorIfNeeded()
} }
valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> { valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> {
return buildFoundFlagGenerator(loop, inputVariable, filter, negated = false) return buildFoundFlagGenerator(loop, inputVariable, filterCondition, negated = false)
} }
valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> { valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> {
return buildFoundFlagGenerator(loop, inputVariable, filter, negated = true) return buildFoundFlagGenerator(loop, inputVariable, filterCondition, negated = true)
} }
inputVariable.hasUsages(valueIfFound) -> { inputVariable.hasUsages(valueIfFound) -> {
@@ -288,9 +290,9 @@ object FindTransformationMatcher : TransformationMatcher {
val receiver = qualifiedExpression.receiverExpression val receiver = qualifiedExpression.receiverExpression
val selector = qualifiedExpression.selectorExpression val selector = qualifiedExpression.selectorExpression
if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) { if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) {
return object: FindOperationGenerator("firstOrNull", filter != null, chainCallCount = 2) { return object: FindOperationGenerator("firstOrNull", filterCondition != null, chainCallCount = 2) {
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filter) val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filterCondition?.asExpression())
return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true) return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true)
} }
}.useElvisOperatorIfNeeded() }.useElvisOperatorIfNeeded()
@@ -300,9 +302,9 @@ object FindTransformationMatcher : TransformationMatcher {
// in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found // 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 if (inputVariableCanHoldNull) return null
return object : FindOperationGenerator("firstOrNull", filter != null, chainCallCount = 2 /* also includes "let" */) { return object : FindOperationGenerator("firstOrNull", filterCondition != null, chainCallCount = 2 /* also includes "let" */) {
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filter) val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filterCondition?.asExpression())
val letBody = generateLambda(inputVariable, valueIfFound) val letBody = generateLambda(inputVariable, valueIfFound)
return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true) return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true)
} }
@@ -310,7 +312,7 @@ object FindTransformationMatcher : TransformationMatcher {
} }
else -> { else -> {
val generator = buildFoundFlagGenerator(loop, inputVariable, filter, negated = false) val generator = buildFoundFlagGenerator(loop, inputVariable, filterCondition, negated = false)
return object : FindOperationGenerator(generator) { return object : FindOperationGenerator(generator) {
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val chainedCall = generator.generate(chainedCallGenerator) val chainedCall = generator.generate(chainedCallGenerator)
@@ -325,14 +327,15 @@ object FindTransformationMatcher : TransformationMatcher {
private fun buildFoundFlagGenerator( private fun buildFoundFlagGenerator(
loop: KtForExpression, loop: KtForExpression,
inputVariable: KtCallableDeclaration, inputVariable: KtCallableDeclaration,
filter: KtExpression?, filter: Condition?,
negated: Boolean negated: Boolean
): FindOperationGenerator { ): FindOperationGenerator {
if (filter == null) { if (filter == null) {
return SimpleGenerator(if (negated) "none" else "any", inputVariable, filter) return SimpleGenerator(if (negated) "none" else "any", inputVariable, null)
} }
val containsArgument = filter.isFilterForContainsOperation(inputVariable, loop) val filterExpression = filter.asExpression()
val containsArgument = filterExpression.isFilterForContainsOperation(inputVariable, loop)
if (containsArgument != null) { if (containsArgument != null) {
val generator = SimpleGenerator("contains", inputVariable, null, containsArgument) val generator = SimpleGenerator("contains", inputVariable, null, containsArgument)
if (negated) { if (negated) {
@@ -347,7 +350,11 @@ object FindTransformationMatcher : TransformationMatcher {
} }
} }
return SimpleGenerator(if (negated) "none" else "any", inputVariable, filter) if (filterExpression is KtPrefixExpression && filterExpression.operationToken == KtTokens.EXCL) {
return SimpleGenerator(if (negated) "any" else "none", inputVariable, filter.asNegatedExpression())
}
return SimpleGenerator(if (negated) "none" else "any", inputVariable, filterExpression)
} }
private fun KtExpression.isFilterForContainsOperation(inputVariable: KtCallableDeclaration, loop: KtForExpression): KtExpression? { private fun KtExpression.isFilterForContainsOperation(inputVariable: KtCallableDeclaration, loop: KtForExpression): KtExpression? {
@@ -83,7 +83,7 @@ class CompositeCondition private constructor(val conditions: List<AtomicConditio
} }
override fun asNegatedExpression(): KtExpression { override fun asNegatedExpression(): KtExpression {
return asExpression().negate() //TODO? return asExpression().negate()
} }
override fun toAtomicConditions() = conditions override fun toAtomicConditions() = conditions
+10
View File
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'any{}'"
// IS_APPLICABLE_2: false
fun foo(): Boolean {
val foo = listOf(true, true)
<caret>for (e in foo) {
if (!e) return false
}
return true
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'any{}'"
// IS_APPLICABLE_2: false
fun foo(): Boolean {
val foo = listOf(true, true)
return foo.any { it }
}
@@ -0,0 +1,13 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'any{}'"
// IS_APPLICABLE_2: false
fun foo(): Boolean {
val foo = listOf(true, true)
<caret>for (e in foo) {
if (!(f1(e) && f2(e))) return false
}
return true
}
fun f1(b: Boolean): Boolean = TODO()
fun f2(b: Boolean): Boolean = TODO()
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'any{}'"
// IS_APPLICABLE_2: false
fun foo(): Boolean {
val foo = listOf(true, true)
return foo.any { f1(it) && f2(it) }
}
fun f1(b: Boolean): Boolean = TODO()
fun f2(b: Boolean): Boolean = TODO()
@@ -181,6 +181,18 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("KT13998.kt")
public void testKT13998() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998.kt");
doTest(fileName);
}
@TestMetadata("KT13998_1.kt")
public void testKT13998_1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998_1.kt");
doTest(fileName);
}
@TestMetadata("none.kt") @TestMetadata("none.kt")
public void testNone() throws Exception { public void testNone() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/none.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/none.kt");
@@ -8089,6 +8089,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("KT13998.kt")
public void testKT13998() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998.kt");
doTest(fileName);
}
@TestMetadata("KT13998_1.kt")
public void testKT13998_1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998_1.kt");
doTest(fileName);
}
@TestMetadata("none.kt") @TestMetadata("none.kt")
public void testNone() throws Exception { public void testNone() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/none.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/none.kt");