diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt index 07576db4c0c..f93b41edf3c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt @@ -61,7 +61,7 @@ class FlatMapTransformation( if (iterableType.checkIsSuperTypeOf(nestedSequenceType) == null) return null val nestedLoopBody = nestedLoop.body ?: return null - if (state.workingVariable.hasUsages(listOf(nestedLoopBody))) return null // workingVariable is still needed - cannot transform + if (state.workingVariable.hasUsages(nestedLoopBody)) return null // workingVariable is still needed - cannot transform val newWorkingVariable = nestedLoop.loopParameter ?: return null val transformation = FlatMapTransformation(state.workingVariable, transform) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt index 2812e6127ab..6b72827fbb6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt @@ -87,6 +87,10 @@ fun KtExpression?.isSimpleName(name: Name): Boolean { return this is KtNameReferenceExpression && this.getQualifiedExpressionForSelector() == null && this.getReferencedNameAsName() == name } +fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean { + return hasUsages(listOf(inElement)) +} + fun KtCallableDeclaration.hasUsages(inElements: Collection): Boolean { // TODO: it's a temporary workaround about strange dead-lock when running inspections return inElements.any { ReferencesSearch.search(this, LocalSearchScope(it)).any() } @@ -127,10 +131,23 @@ fun buildFindOperationGenerator( valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> "none" - workingVariable.hasUsages(listOf(valueIfFound)) -> { + workingVariable.hasUsages(valueIfFound) -> { if (!valueIfNotFound.isNullExpression()) return null //TODO 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(workingVariable) && selector != null && !workingVariable.hasUsages(selector)) { + return { chainedCallGenerator, filter -> + val findFirstCall = generateChainedCall("firstOrNull", chainedCallGenerator, filter) + KtPsiFactory(findFirstCall).createExpressionByPattern("$0?.$1", findFirstCall, selector) + } + } + } + // in case of nullable working variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found if ((workingVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL) return null diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_let2.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_let2.kt new file mode 100644 index 00000000000..e9937f7bc83 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_let2.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s.length > 0) { + result = s.substring(0, s.length - 1) + break + } + } +} + +fun bar(s: String): String = s diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_let2.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_let2.kt.after new file mode 100644 index 00000000000..1e25b004c78 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_let2.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun foo(list: List) { + val result: String? = list.firstOrNull { it.length > 0 }?.let { it.substring(0, it.length - 1) } +} + +fun bar(s: String): String = s diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after index d11920f4f17..a8416cb456e 100644 --- a/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after @@ -1,4 +1,4 @@ // WITH_RUNTIME fun foo(list: List): Int? { - return list.firstOrNull { it.isNotEmpty() }?.let { it.length } + return list.firstOrNull { it.isNotEmpty() }?.length } \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_safeDotExpression.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_safeDotExpression.kt new file mode 100644 index 00000000000..28c60180bfb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_safeDotExpression.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s != "") { + result = s?.substring(1) + break + } + } +} diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_safeDotExpression.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_safeDotExpression.kt.after new file mode 100644 index 00000000000..5674bd90763 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_safeDotExpression.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List) { + val result: String? = list.firstOrNull { it != "" }?.substring(1) +}