[kotlin-dfa] Correct type of as? argument
authorTagir Valeev <Tagir.Valeev@jetbrains.com>
Fri, 24 Sep 2021 08:25:41 +0000 (15:25 +0700)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Fri, 24 Sep 2021 09:24:36 +0000 (09:24 +0000)
GitOrigin-RevId: 00bde638717e2bd5d39d60495734da38df1383a2

plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/KtDfaHelpers.kt
plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/KtDataFlowInspectionTest.kt
plugins/kotlin/idea/tests/testData/inspections/dfa/CastGenericMethodReturn.kt [new file with mode: 0644]

index d76dfe49843f0214745b0b37b7f7dda71029de7c..83c62a59b50bd8e4ea5db4b0d6653e3c55b077b3 100644 (file)
@@ -20,10 +20,7 @@ import com.intellij.psi.tree.IElementType
 import com.intellij.psi.util.PsiUtil
 import org.jetbrains.kotlin.builtins.KotlinBuiltIns
 import org.jetbrains.kotlin.builtins.StandardNames.FqNames
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.descriptors.*
 import org.jetbrains.kotlin.idea.caches.resolve.analyze
 import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
 import org.jetbrains.kotlin.idea.project.builtIns
@@ -73,7 +70,7 @@ private fun KotlinType.toDfTypeNotNullable(context: KtElement): DfType {
         is ClassDescriptor -> when (val fqNameUnsafe = descriptor.fqNameUnsafe) {
             FqNames._boolean -> DfTypes.BOOLEAN
             FqNames._byte -> DfTypes.intRange(LongRangeSet.range(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()))
-            FqNames._char -> DfTypes.intRange(LongRangeSet.range(Character.MIN_VALUE.toLong(), Character.MAX_VALUE.toLong()))
+            FqNames._char -> DfTypes.intRange(LongRangeSet.range(Character.MIN_VALUE.code.toLong(), Character.MAX_VALUE.code.toLong()))
             FqNames._short -> DfTypes.intRange(LongRangeSet.range(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()))
             FqNames._int -> DfTypes.INT
             FqNames._long -> DfTypes.LONG
@@ -172,7 +169,34 @@ internal fun getConstant(expr: KtConstantExpression): DfType {
     }
 }
 
-internal fun KtExpression.getKotlinType(): KotlinType? = analyze(BodyResolveMode.PARTIAL).getType(this)
+internal fun KtExpression.getKotlinType(): KotlinType? {
+    var parent = this.parent
+    if (parent is KtDotQualifiedExpression && parent.selectorExpression == this) {
+        parent = parent.parent
+    }
+    while (parent is KtParenthesizedExpression) {
+        parent = parent.parent
+    }
+    // In (call() as? X), the call() type might be inferred to be X due to peculiarities
+    // of Kotlin type system. This produces an unpleasant effect for data flow analysis:
+    // it assumes that this cast never fails, thus result is never null, which is actually wrong
+    // So we have to patch the original call type, widening it to its upper bound.
+    // Current implementation is not always precise and may result in skipping a useful warning.
+    if (parent is KtBinaryExpressionWithTypeRHS && parent.operationReference.text == "as?") {
+        val call = resolveToCall()
+        if (call != null) {
+            val descriptor = call.resultingDescriptor
+            val typeDescriptor = descriptor.original.returnType?.constructor?.declarationDescriptor
+            if (typeDescriptor is TypeParameterDescriptor) {
+                val upperBound = typeDescriptor.upperBounds.singleOrNull()
+                if (upperBound != null) {
+                    return upperBound
+                }
+            }
+        }
+    }
+    return analyze(BodyResolveMode.PARTIAL).getType(this)
+}
 
 /**
  * JVM-patched array element type (e.g. Int? for Array<Int>)
index aabc8c920eb320df244a71c2a62852e92d3d9a50..df9aac03883b301c56a662da8597e17693401c25 100644 (file)
@@ -14,6 +14,7 @@ class KtDataFlowInspectionTest : KotlinLightCodeInsightFixtureTestCase() {
     fun testBoolean() = doTest()
     fun testBooleanConst() = doTest()
     fun testBoxedInt() = doTest()
+    fun testCastGenericMethodReturn() = doTest()
     fun testClassRef() = doTest()
     fun testCollectionConstructors() = doTest()
     fun testComparison() = doTest()
diff --git a/plugins/kotlin/idea/tests/testData/inspections/dfa/CastGenericMethodReturn.kt b/plugins/kotlin/idea/tests/testData/inspections/dfa/CastGenericMethodReturn.kt
new file mode 100644 (file)
index 0000000..951f180
--- /dev/null
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+fun test(t: Test) {
+    val result = t.returnT() as? String
+    if (result == null) {}
+}
+
+interface Test {
+    fun <T:Any> returnT() : T
+}