[Jspecify] Use bounds of type parameters for warnings reporting as well

This commit is contained in:
Victor Petukhov
2021-05-12 19:32:01 +03:00
parent 0c4cba549d
commit df2c95e444
8 changed files with 82 additions and 69 deletions
@@ -14,23 +14,23 @@ public class Test {}
// FILE: main.kt // FILE: main.kt
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit { fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit {
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
a1.foo(null) a1.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.foo(1) a1.foo(1)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
a2.foo(null) a2.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a2.foo(1) a2.foo(1)
// jspecify_nullness_mismatch, jspecify_nullness_mismatch
a1.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
a1.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, null) a1.bar(x, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute}
a1.bar(x, null)
a1.bar(x, 1) a1.bar(x, 1)
// jspecify_nullness_mismatch, jspecify_nullness_mismatch
a2.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
a2.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, null) a2.bar(x, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute}
a2.bar(x, null)
a2.bar(x, 1) a2.bar(x, 1)
} }
@@ -14,22 +14,23 @@ public class Test {}
// FILE: main.kt // FILE: main.kt
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit { fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit {
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
a1.foo(null) a1.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.foo(1) a1.foo(1)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
a2.foo(null) a2.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a2.foo(1) a2.foo(1)
// jspecify_nullness_mismatch, jspecify_nullness_mismatch
a1.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
a1.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, null) a1.bar(x, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.bar(x, null)
a1.bar(x, 1) a1.bar(x, 1)
// jspecify_nullness_mismatch, jspecify_nullness_mismatch
a2.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
a2.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, null) a2.bar(x, <!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute}
a2.bar(x, null)
a2.bar(x, 1) a2.bar(x, 1)
} }
@@ -37,20 +37,20 @@ public class CKN extends C<@Nullable CK> {}
// FILE: main.kt // FILE: main.kt
fun main(ak: AK, akn: AKN, bk: BK, ck: CK, ckn: CKN): Unit { fun main(ak: AK, akn: AKN, bk: BK, ck: CK, ckn: CKN): Unit {
ak.foo(ak) ak.foo(ak)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
ak.foo(null) ak.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
akn.foo(null) akn.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
bk.foo(bk) bk.foo(bk)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
bk.foo(null) bk.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
ck.foo(ck) ck.foo(ck)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
ck.foo(null) ck.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
ckn.foo(null) ckn.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
} }
@@ -37,20 +37,20 @@ public class CKN extends C<@Nullable CK> {}
// FILE: main.kt // FILE: main.kt
fun main(ak: AK, akn: AKN, bk: BK, ck: CK, ckn: CKN): Unit { fun main(ak: AK, akn: AKN, bk: BK, ck: CK, ckn: CKN): Unit {
ak.foo(ak) ak.foo(ak)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
ak.foo(null) ak.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
akn.foo(null) akn.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
bk.foo(bk) bk.foo(bk)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
bk.foo(null) bk.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
ck.foo(ck) ck.foo(ck)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
ck.foo(null) ck.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
ckn.foo(null) ckn.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
} }
@@ -38,10 +38,10 @@ fun main(
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
a.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNullNull<!>) a.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNullNull<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
b.bar(aNotNullNotNullNotNull) b.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNotNullNotNull<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
b.bar(aNotNullNotNullNull) b.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNotNullNull<!>)
b.bar(aNotNullNullNotNull) b.bar(aNotNullNullNotNull)
b.bar(aNotNullNullNull) b.bar(aNotNullNullNull)
} }
@@ -38,10 +38,10 @@ fun main(
// jspecify_nullness_mismatch // jspecify_nullness_mismatch
a.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNullNull<!>) a.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNullNull<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
b.bar(aNotNullNotNullNotNull) b.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNotNullNotNull<!>)
// jspecify_nullness_mismatch{mute} // jspecify_nullness_mismatch
b.bar(aNotNullNotNullNull) b.bar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>aNotNullNotNullNull<!>)
b.bar(aNotNullNullNotNull) b.bar(aNotNullNullNotNull)
b.bar(aNotNullNullNull) b.bar(aNotNullNullNull)
} }
@@ -14,5 +14,5 @@ typealias C2<T> = MapLike<T>
val test1 = C(1, C(2, null)) val test1 = C(1, C(2, null))
val test2 = C(1, <!TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!>C(<!TYPE_MISMATCH, TYPE_MISMATCH!>""<!>, null)<!>) val test2 = C(1, <!TYPE_MISMATCH, TYPE_MISMATCH, TYPE_MISMATCH!>C(<!TYPE_MISMATCH, TYPE_MISMATCH!>""<!>, null)<!>)
val test23 = <!UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS!>C2(if (true) 1 else null)<!> val test23 = <!UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS!>C2(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>if (true) 1 else null<!>)<!>
val test234 = C2(<!UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS!>C2(if (true) 1 else null)<!>) val test234 = C2(<!UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS!>C2(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>if (true) 1 else null<!>)<!>)
@@ -411,23 +411,24 @@ class SignatureEnhancement(
} }
val boundsFromTypeParameterForArgument = typeParameterForArgument?.boundsNullability() ?: return result val boundsFromTypeParameterForArgument = typeParameterForArgument?.boundsNullability() ?: return result
if (result == null) return NullabilityQualifierWithMigrationStatus(boundsFromTypeParameterForArgument) if (result == null) return boundsFromTypeParameterForArgument
return NullabilityQualifierWithMigrationStatus( return mostSpecific(boundsFromTypeParameterForArgument, result)
mostSpecific(boundsFromTypeParameterForArgument, result.qualifier)
)
} }
private fun mostSpecific(a: NullabilityQualifier, b: NullabilityQualifier): NullabilityQualifier { private fun mostSpecific(
if (a == NullabilityQualifier.FORCE_FLEXIBILITY) return b a: NullabilityQualifierWithMigrationStatus,
if (b == NullabilityQualifier.FORCE_FLEXIBILITY) return a b: NullabilityQualifierWithMigrationStatus
if (a == NullabilityQualifier.NULLABLE) return b ): NullabilityQualifierWithMigrationStatus {
if (b == NullabilityQualifier.NULLABLE) return a if (a.qualifier == NullabilityQualifier.FORCE_FLEXIBILITY) return b
assert(a == b && a == NullabilityQualifier.NOT_NULL) { if (b.qualifier == NullabilityQualifier.FORCE_FLEXIBILITY) return a
if (a.qualifier == NullabilityQualifier.NULLABLE) return b
if (b.qualifier == NullabilityQualifier.NULLABLE) return a
assert(a.qualifier == b.qualifier && a.qualifier == NullabilityQualifier.NOT_NULL) {
"Expected everything is NOT_NULL, but $a and $b are found" "Expected everything is NOT_NULL, but $a and $b are found"
} }
return NullabilityQualifier.NOT_NULL return NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL)
} }
private fun KotlinType.nullabilityInfoBoundsForTypeParameterUsage(): Pair<NullabilityQualifierWithMigrationStatus?, Boolean> { private fun KotlinType.nullabilityInfoBoundsForTypeParameterUsage(): Pair<NullabilityQualifierWithMigrationStatus?, Boolean> {
@@ -440,20 +441,31 @@ class SignatureEnhancement(
// void foo(T t); // should be loaded as "fun foo(t: T)" but not as "fun foo(t: T?)" // void foo(T t); // should be loaded as "fun foo(t: T)" but not as "fun foo(t: T?)"
// } // }
return Pair( return Pair(
NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL), NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL, typeParameterBoundsNullability.isForWarningOnly),
typeParameterBoundsNullability == NullabilityQualifier.NOT_NULL typeParameterBoundsNullability.qualifier == NullabilityQualifier.NOT_NULL
) )
} }
private fun TypeParameterDescriptor.boundsNullability(): NullabilityQualifier? { private fun TypeParameterDescriptor.boundsNullability(): NullabilityQualifierWithMigrationStatus? {
// Do not use bounds from Kotlin-defined type parameters // Do not use bounds from Kotlin-defined type parameters
if (this !is LazyJavaTypeParameterDescriptor) return null if (this !is LazyJavaTypeParameterDescriptor || upperBounds.all(KotlinType::isError)) return null
return when {
upperBounds.all(KotlinType::isError) -> null if (upperBounds.all(KotlinType::isNullabilityFlexible)) {
upperBounds.all(KotlinType::isNullabilityFlexible) -> null if (upperBounds.any { it is FlexibleTypeWithEnhancement && !it.enhancement.isNullable() }) {
upperBounds.any { !it.isNullable() } -> NullabilityQualifier.NOT_NULL return NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL, isForWarningOnly = true)
else -> NullabilityQualifier.NULLABLE }
if (upperBounds.any { it is FlexibleTypeWithEnhancement && it.enhancement.isNullable() }) {
return NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NULLABLE, isForWarningOnly = true)
}
return null
} }
val resultingQualifier =
if (upperBounds.any { !it.isNullable() }) NullabilityQualifier.NOT_NULL else NullabilityQualifier.NULLABLE
return NullabilityQualifierWithMigrationStatus(resultingQualifier)
} }
private fun Annotations.extractNullability( private fun Annotations.extractNullability(