diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantWithInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantWithInspection.kt index f3446fb23fee..74358056a15b 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantWithInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantWithInspection.kt @@ -7,23 +7,28 @@ import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.codeinsight.utils.findExistingEditor import org.jetbrains.kotlin.idea.core.moveCaret +import org.jetbrains.kotlin.idea.core.setType import org.jetbrains.kotlin.idea.inspections.UnusedLambdaExpressionBodyInspection.Companion.replaceBlockExpressionWithLambdaBody +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.allChildren +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.psi2ir.deparenthesize import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression -import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.typeUtil.isUnit class RedundantWithInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = @@ -38,38 +43,25 @@ class RedundantWithInspection : AbstractKotlinInspection() { val lambdaBody = lambda.bodyExpression ?: return val context = callExpression.analyze(BodyResolveMode.PARTIAL_WITH_CFA) - if (lambdaBody.statements.size > 1 && callExpression.isUsedAsExpression(context)) return - if (callExpression.getResolvedCall(context)?.resultingDescriptor?.fqNameSafe != FqName("kotlin.with")) return + if (!callExpression.isCalling(FqName("kotlin.with"), context)) return - val lambdaDescriptor = context[BindingContext.FUNCTION, lambda.functionLiteral] ?: return + if (lambdaBody.statements.size > 1 && + callExpression.isUsedAsExpression(context) && + callExpression.getStrictParentOfType()?.bodyExpression?.deparenthesize() != callExpression + ) return - var used = false - lambda.functionLiteral.acceptChildren(object : KtVisitorVoid() { - override fun visitKtElement(element: KtElement) { - if (used) return - element.acceptChildren(this) - - if (element is KtReturnExpression && element.getLabelName() == "with") { - used = true - return - } - - if (isUsageOfDescriptor(lambdaDescriptor, element, context)) { - used = true - } - } - }) + val functionLiteral = lambda.functionLiteral + val lambdaDescriptor = context[BindingContext.FUNCTION, functionLiteral] ?: return + val used = functionLiteral.anyDescendantOfType { + (it as? KtReturnExpression)?.getLabelName() == "with" || isUsageOfDescriptor(lambdaDescriptor, it, context) + } if (!used) { val quickfix = when (receiver) { is KtSimpleNameExpression, is KtStringTemplateExpression, is KtConstantExpression -> arrayOf(RemoveRedundantWithFix()) else -> LocalQuickFix.EMPTY_ARRAY } - holder.registerProblem( - callee, - KotlinBundle.message("inspection.redundant.with.display.name"), - *quickfix - ) + holder.registerProblem(callee, KotlinBundle.message("inspection.redundant.with.display.name"), *quickfix) } }) } @@ -88,17 +80,27 @@ private class RemoveRedundantWithFix : LocalQuickFix { val lambdaBody = lambdaExpression.bodyExpression ?: return val function = callExpression.getStrictParentOfType() - val functionBody = KtPsiUtil.deparenthesize(function?.bodyExpression) + val functionBody = function?.bodyExpression - val replaced = if (function?.equalsToken != null && functionBody == callExpression) { - val singleStatement = lambdaBody.statements.singleOrNull()?.let { - (it as? KtReturnExpression)?.returnedExpression ?: it - } + val replaced = if (functionBody?.deparenthesize() == callExpression) { + val singleStatement = lambdaBody.statements.singleOrNull() if (singleStatement != null) { - callExpression.replaced(singleStatement) + callExpression.replaced( + (singleStatement as? KtReturnExpression)?.returnedExpression ?: singleStatement + ) } else { + val returnType = (function.descriptor as? FunctionDescriptor)?.returnType + if (returnType != null && !returnType.isUnit()) { + function.setType(returnType, shortenReferences = true) + } + val lastStatement = lambdaBody.statements.lastOrNull() + if (lastStatement != null && lastStatement !is KtReturnExpression) { + lastStatement.replaced( + KtPsiFactory(project).createExpressionByPattern("return $0", lastStatement) + ) + } function.replaceBlockExpressionWithLambdaBody(lambdaBody) - function.bodyExpression + functionBody } } else { val result = lambdaBody.allChildren.takeUnless { it.isEmpty }?.let { range -> diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index c17c01419ffc..5086a6e73477 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -10412,24 +10412,29 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } - @TestMetadata("asInitializer.kt") - public void testAsInitializer() throws Exception { - runTest("testData/inspectionsLocal/redundantWith/asInitializer.kt"); - } - - @TestMetadata("asInitializerWithSingleReturn.kt") - public void testAsInitializerWithSingleReturn() throws Exception { - runTest("testData/inspectionsLocal/redundantWith/asInitializerWithSingleReturn.kt"); - } - @TestMetadata("emptyExpressionInReturn.kt") public void testEmptyExpressionInReturn() throws Exception { runTest("testData/inspectionsLocal/redundantWith/emptyExpressionInReturn.kt"); } - @TestMetadata("expressionBodyFunction.kt") - public void testExpressionBodyFunction() throws Exception { - runTest("testData/inspectionsLocal/redundantWith/expressionBodyFunction.kt"); + @TestMetadata("functionBody.kt") + public void testFunctionBody() throws Exception { + runTest("testData/inspectionsLocal/redundantWith/functionBody.kt"); + } + + @TestMetadata("functionBodyWithMultiStatement.kt") + public void testFunctionBodyWithMultiStatement() throws Exception { + runTest("testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt"); + } + + @TestMetadata("functionBodyWithMultiStatementAndReturn.kt") + public void testFunctionBodyWithMultiStatementAndReturn() throws Exception { + runTest("testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt"); + } + + @TestMetadata("functionBodyWithReturn.kt") + public void testFunctionBodyWithReturn() throws Exception { + runTest("testData/inspectionsLocal/redundantWith/functionBodyWithReturn.kt"); } @TestMetadata("nested.kt") @@ -10477,11 +10482,6 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/redundantWith/notApplicable_inBinaryExpression.kt"); } - @TestMetadata("notApplicable_inFunctionBody.kt") - public void testNotApplicable_inFunctionBody() throws Exception { - runTest("testData/inspectionsLocal/redundantWith/notApplicable_inFunctionBody.kt"); - } - @TestMetadata("notApplicable_inProperty.kt") public void testNotApplicable_inProperty() throws Exception { runTest("testData/inspectionsLocal/redundantWith/notApplicable_inProperty.kt"); diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/expressionBodyFunction.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBody.kt similarity index 100% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/expressionBodyFunction.kt rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBody.kt diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/expressionBodyFunction.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBody.kt.after similarity index 100% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/expressionBodyFunction.kt.after rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBody.kt.after diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/notApplicable_inFunctionBody.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt similarity index 80% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/notApplicable_inFunctionBody.kt rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt index 71bf68e326a3..e2c7224cdb6f 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/notApplicable_inFunctionBody.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt @@ -1,4 +1,3 @@ -// PROBLEM: none // WITH_STDLIB fun test() = with("") { println() diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt.after new file mode 100644 index 000000000000..0d82f468f9da --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatement.kt.after @@ -0,0 +1,5 @@ +// WITH_STDLIB +fun test(): Int { + println() + return 1 +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializer.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt similarity index 69% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializer.kt rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt index af95fb0a596c..1d496690c712 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializer.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt @@ -1,6 +1,4 @@ -// PROBLEM: none // WITH_STDLIB -// See KTIJ-19621 fun test(): Int = with("") { println() return 42 diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt.after new file mode 100644 index 000000000000..5e3d8e8a8244 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithMultiStatementAndReturn.kt.after @@ -0,0 +1,5 @@ +// WITH_STDLIB +fun test(): Int { + println() + return 42 +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializerWithSingleReturn.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithReturn.kt similarity index 100% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializerWithSingleReturn.kt rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithReturn.kt diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializerWithSingleReturn.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithReturn.kt.after similarity index 100% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/asInitializerWithSingleReturn.kt.after rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantWith/functionBodyWithReturn.kt.after