Introduce inspection for redundant not-null extension receiver of inline

Related to KT-22303
This commit is contained in:
Mikhail Glukhikh
2018-01-19 16:14:30 +03:00
parent ad48099ca6
commit 7995ae05c5
8 changed files with 231 additions and 0 deletions
@@ -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
@@ -0,0 +1,7 @@
<html>
<body>
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.
</body>
</html>
+9
View File
@@ -2682,6 +2682,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantNotNullExtensionReceiverOfInlineInspection"
displayName="Not-null extension receiver of inline function can be made nullable"
groupPath="Kotlin"
groupName="Java interop issues"
enabledByDefault="false"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -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<KtExpression> {
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
)
})
}
@@ -0,0 +1,42 @@
<problems>
<problem>
<file>test.kt</file>
<line>2</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Not-null extension receiver of inline function can be made nullable</problem_class>
<description>This type probably can be changed to nullable</description>
</problem>
<problem>
<file>test.kt</file>
<line>4</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Not-null extension receiver of inline function can be made nullable</problem_class>
<description>This type probably can be changed to nullable</description>
</problem>
<problem>
<file>test.kt</file>
<line>6</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Not-null extension receiver of inline function can be made nullable</problem_class>
<description>This type probably can be changed to nullable</description>
</problem>
<problem>
<file>test.kt</file>
<line>7</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Not-null extension receiver of inline function can be made nullable</problem_class>
<description>This type probably can be changed to nullable</description>
</problem>
<problem>
<file>test.kt</file>
<line>8</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Not-null extension receiver of inline function can be made nullable</problem_class>
<description>This type probably can be changed to nullable</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantNotNullExtensionReceiverOfInlineInspection
@@ -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 <reified T> List<T>.count() {
return size
}
// No problem (in this)
inline fun <reified T> List<T>.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
@@ -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");