diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/KotlinType.kt b/core/descriptors/src/org/jetbrains/kotlin/types/KotlinType.kt
index 3dcab44929b..25232e74673 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/types/KotlinType.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/types/KotlinType.kt
@@ -66,6 +66,8 @@ sealed class KotlinType : Annotated {
}
}
+fun KotlinType.isNullable(): Boolean = TypeUtils.isNullableType(this)
+
abstract class WrappedType : KotlinType() {
open fun isComputed(): Boolean = true
protected abstract val delegate: KotlinType
diff --git a/idea/resources/inspectionDescriptions/RedundantNotNullExtensionReceiverOfInline.html b/idea/resources/inspectionDescriptions/RedundantNotNullExtensionReceiverOfInline.html
new file mode 100644
index 00000000000..75df227e05b
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/RedundantNotNullExtensionReceiverOfInline.html
@@ -0,0 +1,7 @@
+
+
+Reports inline functions with not-null extension receivers which does not use the fact that extension receiver is not null.
+This function are dangerous to call in Kotlin 1.2 on actual nullable flexible receiver type.
+Consider making receiver type nullable.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 4e86a9ea8c9..14f2ea8a811 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2682,6 +2682,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantNotNullExtensionReceiverOfInlineInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantNotNullExtensionReceiverOfInlineInspection.kt
new file mode 100644
index 00000000000..920ca0eed6f
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantNotNullExtensionReceiverOfInlineInspection.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2000-2018 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.codeInspection.LocalInspectionToolSession
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import org.jetbrains.kotlin.config.LanguageFeature
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
+import org.jetbrains.kotlin.idea.project.languageVersionSettings
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
+import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
+import org.jetbrains.kotlin.types.isNullable
+
+class RedundantNotNullExtensionReceiverOfInlineInspection : AbstractKotlinInspection() {
+
+ private fun ReceiverValue?.isThisExpressionReceiver(): Boolean =
+ this is ExpressionReceiver && this.expression is KtThisExpression
+
+ private fun ResolvedCall<*>?.usesNotNullThisReceiverIn(expression: KtExpression, thisReceiverValue: ReceiverValue): Boolean {
+ if (this == null || expression.parent is KtThisExpression || call.isSafeCall()) {
+ return false
+ }
+ val descriptor = resultingDescriptor
+ val extensionReceiverType = descriptor?.extensionReceiverParameter?.type
+ return if (extensionReceiverType != null) {
+ !extensionReceiverType.isNullable() && (extensionReceiver == thisReceiverValue || extensionReceiver.isThisExpressionReceiver())
+ } else {
+ dispatchReceiver == thisReceiverValue || dispatchReceiver.isThisExpressionReceiver()
+ }
+ }
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) =
+ namedFunctionVisitor(fun(function) {
+
+ val receiverTypeReference = function.receiverTypeReference ?: return
+ if (!function.hasModifier(KtTokens.INLINE_KEYWORD) || !function.hasBody()) return
+ if (!function.languageVersionSettings.supportsFeature(LanguageFeature.NullabilityAssertionOnExtensionReceiver)) {
+ return
+ }
+
+ val context = function.analyzeFully()
+ val functionDescriptor = context[BindingContext.FUNCTION, function] ?: return
+ val receiverParameter = functionDescriptor.extensionReceiverParameter ?: return
+ val receiverValue = receiverParameter.value
+ val receiverType = receiverParameter.type
+ if (receiverType.isNullable()) return
+ if ((receiverType.constructor.declarationDescriptor as? ClassDescriptor)?.isCompanionObject == true) return
+
+ if (function.anyDescendantOfType {
+ when (it) {
+ is KtNameReferenceExpression -> {
+ val resolvedCall = it.getResolvedCall(context)
+ resolvedCall.usesNotNullThisReceiverIn(it, receiverValue)
+ }
+ is KtThisExpression -> {
+ val expectedType = context[BindingContext.EXPECTED_EXPRESSION_TYPE, it]
+ expectedType != null && !expectedType.isNullable()
+ }
+ is KtBinaryExpressionWithTypeRHS -> {
+ val type = context[BindingContext.TYPE, it.right]
+ it.left is KtThisExpression && type != null && !type.isNullable()
+ && it.operationReference.getReferencedNameElementType() == KtTokens.AS_KEYWORD
+ }
+ is KtForExpression -> {
+ it.loopRange is KtThisExpression
+ }
+ is KtBinaryExpression -> {
+ if (it.operationToken == KtTokens.EQEQ ||
+ it.operationToken == KtTokens.EQEQEQ ||
+ it.operationToken == KtTokens.EXCLEQ ||
+ it.operationToken == KtTokens.EXCLEQEQEQ
+ ) {
+ false
+ } else {
+ val resolvedCall = it.operationReference.getResolvedCall(context)
+ resolvedCall.usesNotNullThisReceiverIn(it, receiverValue)
+ }
+ }
+ is KtOperationReferenceExpression -> {
+ if (it.parent is KtBinaryExpression) {
+ false
+ } else {
+ val resolvedCall = it.getResolvedCall(context)
+ resolvedCall.usesNotNullThisReceiverIn(it, receiverValue)
+ }
+ }
+ is KtArrayAccessExpression -> {
+ val resolvedCall = it.getResolvedCall(context)
+ resolvedCall.usesNotNullThisReceiverIn(it, receiverValue)
+ }
+ else -> false
+ }
+ }) return
+
+ holder.registerProblem(
+ receiverTypeReference,
+ "This type probably can be changed to nullable",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING
+ )
+ })
+}
\ No newline at end of file
diff --git a/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/expected.xml b/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/expected.xml
new file mode 100644
index 00000000000..d024464271c
--- /dev/null
+++ b/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/expected.xml
@@ -0,0 +1,42 @@
+
+
+ test.kt
+ 2
+ light_idea_test_case
+
+ Not-null extension receiver of inline function can be made nullable
+ This type probably can be changed to nullable
+
+
+ test.kt
+ 4
+ light_idea_test_case
+
+ Not-null extension receiver of inline function can be made nullable
+ This type probably can be changed to nullable
+
+
+ test.kt
+ 6
+ light_idea_test_case
+
+ Not-null extension receiver of inline function can be made nullable
+ This type probably can be changed to nullable
+
+
+ test.kt
+ 7
+ light_idea_test_case
+
+ Not-null extension receiver of inline function can be made nullable
+ This type probably can be changed to nullable
+
+
+ test.kt
+ 8
+ light_idea_test_case
+
+ Not-null extension receiver of inline function can be made nullable
+ This type probably can be changed to nullable
+
+
\ No newline at end of file
diff --git a/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/inspections.test b/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/inspections.test
new file mode 100644
index 00000000000..14acd36549d
--- /dev/null
+++ b/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/inspections.test
@@ -0,0 +1 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantNotNullExtensionReceiverOfInlineInspection
diff --git a/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/test.kt b/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/test.kt
new file mode 100644
index 00000000000..77792c30bdd
--- /dev/null
+++ b/idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/test.kt
@@ -0,0 +1,51 @@
+// Typical true positive
+inline fun String.foo() = foo(this)
+fun String.notInlineFoo() = foo(this)
+inline fun String.eq(other: Any?) = this == other
+// Other positives
+inline fun String.fooo1() = fooo()
+inline fun String.fooo2() = this.fooo()
+inline fun String.fooo3() = this?.foo()
+
+// Just functions
+fun foo(s: String?) {}
+fun String?.fooo() {}
+fun bar(s: String) {}
+fun String.baz() = this
+
+inline fun String.bar() = bar(this) // No problem
+inline fun String.bazz() = baz() // No problem
+inline fun String.bazzx() = baz().baz() // No problem
+inline fun String.bazzz() = this.baz() // No problem
+
+interface My {
+ inline fun String.bar() // No problem (no body)
+}
+
+inline fun String.myLength() = length // No problem (this.length)
+
+// No problem (this.size)
+inline fun List.count() {
+ return size
+}
+
+// No problem (in this)
+inline fun List.foo() {
+ for (element in this) {}
+}
+
+// No problem (cast)
+inline fun String.cast(): Any = this as Any
+
+class Some {
+ companion object {}
+}
+
+fun Some.Companion.foo() {} // No problem (on companion)
+inline fun Int.baz(x: Int) = this + x // No problem
+inline fun Int.bar(x: Int) = x - this // No problem
+
+inline fun Int.unary() = -this // No problem
+inline fun String.indexed(arg: Int) = this[arg] // No problem
+
+
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
index 506d46b2122..cc461ca36ad 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -288,6 +288,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
doTest(fileName);
}
+ @TestMetadata("redundantNotNullExtensionReceiverOfInline/inspectionData/inspections.test")
+ public void testRedundantNotNullExtensionReceiverOfInline_inspectionData_Inspections_test() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantNotNullExtensionReceiverOfInline/inspectionData/inspections.test");
+ doTest(fileName);
+ }
+
@TestMetadata("redundantSamConstructor/inspectionData/inspections.test")
public void testRedundantSamConstructor_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantSamConstructor/inspectionData/inspections.test");