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 f5e3266ce24..2812e6127ab 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt @@ -21,9 +21,11 @@ import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.references.KtSimpleNameReference @@ -36,6 +38,8 @@ import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.typeUtil.TypeNullability +import org.jetbrains.kotlin.types.typeUtil.nullability import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull @@ -123,7 +127,19 @@ fun buildFindOperationGenerator( valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> "none" - workingVariable.hasUsages(listOf(valueIfFound)) -> /*TODO*/ return null + workingVariable.hasUsages(listOf(valueIfFound)) -> { + if (!valueIfNotFound.isNullExpression()) return null //TODO + if (!findFirst) return null // too dangerous because of side effects + + // 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 + + return { chainedCallGenerator, filter -> + val findFirstCall = generateChainedCall("firstOrNull", chainedCallGenerator, filter) + val letBody = generateLambda(workingVariable, valueIfFound) + KtPsiFactory(findFirstCall).createExpressionByPattern("$0?.let $1:'{}'", findFirstCall, letBody) + } + } // initial value is compile-time constant ConstantExpressionEvaluator.getConstant(valueIfNotFound, valueIfNotFound.analyze(BodyResolveMode.PARTIAL)) != null -> { diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_cannotUseLet.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_cannotUseLet.kt new file mode 100644 index 00000000000..8cc66ab7cdf --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_cannotUseLet.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s != null && s.length > 0) { + result = bar(s) + break + } + } +} + +fun bar(s: String?): String? = s \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_let.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_let.kt new file mode 100644 index 00000000000..dc125a973f3 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_let.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s.length > 0) { + result = bar(s) + break + } + } +} + +fun bar(s: String): String = s diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_let.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_let.kt.after new file mode 100644 index 00000000000..22ac38a35a0 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_let.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun foo(list: List) { + val result: String? = list.firstOrNull { it.length > 0 }?.let { bar(it) } +} + +fun bar(s: String): String = s diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt index 0b5313bec07..e95eb77b314 100644 --- a/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt @@ -1,5 +1,4 @@ // WITH_RUNTIME -// IS_APPLICABLE: false fun foo(list: List): Int? { for (s in list) { if (s.isNotEmpty()) { diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after new file mode 100644 index 00000000000..d11920f4f17 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list.firstOrNull { it.isNotEmpty() }?.let { it.length } +} \ No newline at end of file