diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml
index f8b0e4f6439..ed85296b866 100644
--- a/idea/resources/META-INF/plugin-common.xml
+++ b/idea/resources/META-INF/plugin-common.xml
@@ -3355,6 +3355,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/resources/inspectionDescriptions/ReplaceNotNullAssertionWithElvisReturn.html b/idea/resources/inspectionDescriptions/ReplaceNotNullAssertionWithElvisReturn.html
new file mode 100644
index 00000000000..0577164d742
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/ReplaceNotNullAssertionWithElvisReturn.html
@@ -0,0 +1,13 @@
+
+
+This inspection reports not-null assertion (!!) calls that can be replaced with elvis and return (?: return). For example:
+
+
+val number: Int? = 42
+fun foo() {
+ val a = number!! // Replace '!!' with '?: return'
+ println(1 + a)
+}
+
+
+
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNotNullAssertionWithElvisReturnInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNotNullAssertionWithElvisReturnInspection.kt
new file mode 100644
index 00000000000..f312a980b89
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNotNullAssertionWithElvisReturnInspection.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
+ * that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.idea.inspections
+
+import com.intellij.codeInsight.intention.LowPriorityAction
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.diagnostics.Errors
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.core.replaced
+import org.jetbrains.kotlin.idea.intentions.getParentLambdaLabelName
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes
+import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+import org.jetbrains.kotlin.types.isNullable
+import org.jetbrains.kotlin.types.typeUtil.isUnit
+
+class ReplaceNotNullAssertionWithElvisReturnInspection : AbstractKotlinInspection() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = postfixExpressionVisitor(fun(postfix) {
+ if (postfix.baseExpression == null) return
+ val operationReference = postfix.operationReference
+ if (operationReference.getReferencedNameElementType() != KtTokens.EXCLEXCL) return
+
+ if ((postfix.getTopmostParentOfType() ?: postfix).parent is KtReturnExpression) return
+
+ val parent = postfix.getParentOfTypes(true, KtLambdaExpression::class.java, KtNamedFunction::class.java)
+ if (parent !is KtNamedFunction && parent !is KtLambdaExpression) return
+ val context = postfix.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
+ val (isNullable, returnLabelName) = when (parent) {
+ is KtNamedFunction -> {
+ val returnType = parent.descriptor(context)?.returnType ?: return
+ val isNullable = returnType.isNullable()
+ if (!returnType.isUnit() && !isNullable) return
+ isNullable to null
+ }
+ is KtLambdaExpression -> {
+ val functionLiteral = parent.functionLiteral
+ val returnType = functionLiteral.descriptor(context)?.returnType ?: return
+ if (!returnType.isUnit()) return
+ val lambdaLabelName = functionLiteral.bodyBlockExpression?.getParentLambdaLabelName() ?: return
+ false to lambdaLabelName
+ }
+ else -> return
+ }
+
+ if (context.diagnostics.forElement(operationReference).any { it.factory == Errors.UNNECESSARY_NOT_NULL_ASSERTION }) return
+
+ holder.registerProblem(
+ postfix.operationReference,
+ "Replace '!!' with '?: return'",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ ReplaceWithElvisReturnFix(isNullable, returnLabelName)
+ )
+ })
+
+ private fun KtFunction.descriptor(context: BindingContext): FunctionDescriptor? {
+ return context[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as? FunctionDescriptor
+ }
+
+ private class ReplaceWithElvisReturnFix(
+ private val returnNull: Boolean,
+ private val returnLabelName: String?
+ ) : LocalQuickFix, LowPriorityAction {
+ override fun getName() = "Replace with '?: return${if (returnNull) " null" else ""}'"
+ override fun getFamilyName() = name
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val postfix = descriptor.psiElement.parent as? KtPostfixExpression ?: return
+ val base = postfix.baseExpression ?: return
+ val psiFactory = KtPsiFactory(postfix)
+ postfix.replaced(
+ psiFactory.createExpressionByPattern(
+ "$0 ?: return$1$2",
+ base,
+ returnLabelName?.let { "@$it" } ?: "",
+ if (returnNull) " null" else ""
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/.inspection b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/.inspection
new file mode 100644
index 00000000000..30474f8901a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.ReplaceNotNullAssertionWithElvisReturnInspection
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt
new file mode 100644
index 00000000000..3c596344e6d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt
@@ -0,0 +1,3 @@
+fun test(i: Int?) {
+ val x = i!!
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt.after
new file mode 100644
index 00000000000..80dba28f1ac
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt.after
@@ -0,0 +1,3 @@
+fun test(i: Int?) {
+ val x = i ?: return
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt
new file mode 100644
index 00000000000..8b720b34350
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt
@@ -0,0 +1,5 @@
+fun foo(): Int? = null
+
+fun test() {
+ foo()!!
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt.after
new file mode 100644
index 00000000000..5e8deb99f4b
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt.after
@@ -0,0 +1,5 @@
+fun foo(): Int? = null
+
+fun test() {
+ foo() ?: return
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt
new file mode 100644
index 00000000000..15de7bf7b53
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt
@@ -0,0 +1,6 @@
+class Foo(val i: Int?)
+
+fun test(foo: Foo): Int? {
+ val x = foo.i!!
+ return x
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt.after
new file mode 100644
index 00000000000..f2b8b9c6c10
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt.after
@@ -0,0 +1,6 @@
+class Foo(val i: Int?)
+
+fun test(foo: Foo): Int? {
+ val x = foo.i ?: return null
+ return x
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt
new file mode 100644
index 00000000000..b83e67d6ed2
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt
@@ -0,0 +1,8 @@
+class Foo {
+ fun baz(): Int? = null
+}
+
+fun test(foo: Foo): Int? {
+ foo.baz()!!
+ return 0
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt.after
new file mode 100644
index 00000000000..a28f4e3b1eb
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt.after
@@ -0,0 +1,8 @@
+class Foo {
+ fun baz(): Int? = null
+}
+
+fun test(foo: Foo): Int? {
+ foo.baz() ?: return null
+ return 0
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt
new file mode 100644
index 00000000000..65a5ad0d810
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt
@@ -0,0 +1,3 @@
+fun test(i: Int?) {
+ val x = i!! + 1
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt.after
new file mode 100644
index 00000000000..395950d389d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt.after
@@ -0,0 +1,3 @@
+fun test(i: Int?) {
+ val x = (i ?: return) + 1
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt
new file mode 100644
index 00000000000..f827a310ad8
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+fun test(list: List, number: Int?) {
+ list.forEach {
+ number!!
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt.after
new file mode 100644
index 00000000000..c6a258ae7d8
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+fun test(list: List, number: Int?) {
+ list.forEach {
+ number ?: return@forEach
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda2.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda2.kt
new file mode 100644
index 00000000000..3f976940848
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda2.kt
@@ -0,0 +1,7 @@
+// PROBLEM: none
+// WITH_RUNTIME
+fun test(list: List, number: Int?) {
+ val x: List = list.map {
+ number!!
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/notNullFunction.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/notNullFunction.kt
new file mode 100644
index 00000000000..782a37f4332
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/notNullFunction.kt
@@ -0,0 +1,5 @@
+// PROBLEM: none
+fun test(i: Int?): Int {
+ val x = i!!
+ return x
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn.kt
new file mode 100644
index 00000000000..76b422c8b47
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+fun test(i: Int?): Int? {
+ return i!!
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn2.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn2.kt
new file mode 100644
index 00000000000..7a8cd1b9607
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn2.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+fun test(i: Int?): Int? {
+ return (i!!)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt
new file mode 100644
index 00000000000..a4db7cb0bf5
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt
@@ -0,0 +1,5 @@
+fun test(a: Int?): Int? {
+ return check(a!!)
+}
+
+fun check(i: Int): Int = i
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt.after b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt.after
new file mode 100644
index 00000000000..3829dddde31
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt.after
@@ -0,0 +1,5 @@
+fun test(a: Int?): Int? {
+ return check(a ?: return null)
+}
+
+fun check(i: Int): Int = i
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/unnecessaryNotNullAssertion.kt b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/unnecessaryNotNullAssertion.kt
new file mode 100644
index 00000000000..19b786f8ecb
--- /dev/null
+++ b/idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/unnecessaryNotNullAssertion.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+fun test(i: Int) {
+ val x = i!!
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index 03b371efe49..0cf7b0a8c2e 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -7117,6 +7117,79 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
+ @TestMetadata("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ReplaceNotNullAssertionWithElvisReturn extends AbstractLocalInspectionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInReplaceNotNullAssertionWithElvisReturn() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("basic.kt")
+ public void testBasic() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic.kt");
+ }
+
+ @TestMetadata("basic2.kt")
+ public void testBasic2() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic2.kt");
+ }
+
+ @TestMetadata("basic3.kt")
+ public void testBasic3() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic3.kt");
+ }
+
+ @TestMetadata("basic4.kt")
+ public void testBasic4() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic4.kt");
+ }
+
+ @TestMetadata("basic5.kt")
+ public void testBasic5() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/basic5.kt");
+ }
+
+ @TestMetadata("inLambda.kt")
+ public void testInLambda() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda.kt");
+ }
+
+ @TestMetadata("inLambda2.kt")
+ public void testInLambda2() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/inLambda2.kt");
+ }
+
+ @TestMetadata("notNullFunction.kt")
+ public void testNotNullFunction() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/notNullFunction.kt");
+ }
+
+ @TestMetadata("onReturn.kt")
+ public void testOnReturn() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn.kt");
+ }
+
+ @TestMetadata("onReturn2.kt")
+ public void testOnReturn2() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn2.kt");
+ }
+
+ @TestMetadata("onReturn3.kt")
+ public void testOnReturn3() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/onReturn3.kt");
+ }
+
+ @TestMetadata("unnecessaryNotNullAssertion.kt")
+ public void testUnnecessaryNotNullAssertion() throws Exception {
+ runTest("idea/testData/inspectionsLocal/replaceNotNullAssertionWithElvisReturn/unnecessaryNotNullAssertion.kt");
+ }
+ }
+
@TestMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)