Support type arguments enhancement from type parameters bounds

Currently, only works for codeanalysis annotations because
type parameters bounds are enhanced only for them
This commit is contained in:
Denis Zharkov
2019-08-12 12:43:02 +03:00
committed by Victor Petukhov
parent e9e05c53e1
commit 392ef9aa2b
8 changed files with 180 additions and 26 deletions
@@ -0,0 +1,59 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER
// FILE: A.java
import jspecify.annotations.*;
public class A<T extends @NotNull Object, E extends @Nullable Object, F extends @UnknownNullness Object> {
}
// FILE: B.java
import jspecify.annotations.*;
@DefaultNullable
public class B {
public void bar(A<String, String, String> a) {}
}
// FILE: C.java
import jspecify.annotations.*;
@DefaultNotNull
public class C {
public void bar(A<String, String, String> a) {}
}
// FILE: D.java
import jspecify.annotations.*;
@DefaultUnknownNullness
public class D {
public void bar(A<String, String, String> a) {}
}
// FILE: main.kt
fun main(
aNotNullNotNullNotNull: A<String, String, String>,
aNotNullNotNullNull: A<String, String, String?>,
aNotNullNullNotNull: A<String, String?, String>,
aNotNullNullNull: A<String, String?, String?>,
b: B, c: C, d: D
) {
b.bar(<!TYPE_MISMATCH!>aNotNullNotNullNotNull<!>)
b.bar(<!TYPE_MISMATCH!>aNotNullNotNullNull<!>)
b.bar(<!TYPE_MISMATCH!>aNotNullNullNotNull<!>)
b.bar(aNotNullNullNull)
c.bar(aNotNullNotNullNotNull)
c.bar(<!TYPE_MISMATCH!>aNotNullNotNullNull<!>)
c.bar(<!TYPE_MISMATCH!>aNotNullNullNotNull<!>)
c.bar(<!TYPE_MISMATCH!>aNotNullNullNull<!>)
d.bar(<!TYPE_MISMATCH!>aNotNullNotNullNotNull<!>)
d.bar(<!TYPE_MISMATCH!>aNotNullNotNullNull<!>)
d.bar(aNotNullNullNotNull)
d.bar(aNotNullNullNull)
}
@@ -0,0 +1,34 @@
package
public fun main(/*0*/ aNotNullNotNullNotNull: A<kotlin.String, kotlin.String, kotlin.String>, /*1*/ aNotNullNotNullNull: A<kotlin.String, kotlin.String, kotlin.String?>, /*2*/ aNotNullNullNotNull: A<kotlin.String, kotlin.String?, kotlin.String>, /*3*/ aNotNullNullNull: A<kotlin.String, kotlin.String?, kotlin.String?>, /*4*/ b: B, /*5*/ c: C, /*6*/ d: D): kotlin.Unit
public open class A</*0*/ T : @jspecify.annotations.NotNull kotlin.Any, /*1*/ E, /*2*/ F : @jspecify.annotations.UnknownNullness kotlin.Any!> {
public constructor A</*0*/ T : @jspecify.annotations.NotNull kotlin.Any, /*1*/ E, /*2*/ F : @jspecify.annotations.UnknownNullness kotlin.Any!>()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@jspecify.annotations.DefaultNullable public open class B {
public constructor B()
public open fun bar(/*0*/ a: A<kotlin.String, kotlin.String?, kotlin.String?>?): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@jspecify.annotations.DefaultNotNull public open class C {
public constructor C()
public open fun bar(/*0*/ a: A<kotlin.String, kotlin.String, kotlin.String>): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@DefaultUnknownNullness /* annotation class not found */ public open class D {
public constructor D()
public open fun bar(/*0*/ a: A<kotlin.String, kotlin.String?, kotlin.String!>!): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -70,6 +70,11 @@ public class ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated extends
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/simple.kt");
}
@TestMetadata("typeArgumentsFromParameterBounds.kt")
public void testTypeArgumentsFromParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt");
}
@TestMetadata("typeParameterBounds.kt")
public void testTypeParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeParameterBounds.kt");
@@ -70,6 +70,11 @@ public class ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTe
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/simple.kt");
}
@TestMetadata("typeArgumentsFromParameterBounds.kt")
public void testTypeArgumentsFromParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt");
}
@TestMetadata("typeParameterBounds.kt")
public void testTypeParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeParameterBounds.kt");
@@ -70,6 +70,11 @@ public class ForeignJava8AnnotationsTestGenerated extends AbstractForeignJava8An
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/simple.kt");
}
@TestMetadata("typeArgumentsFromParameterBounds.kt")
public void testTypeArgumentsFromParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt");
}
@TestMetadata("typeParameterBounds.kt")
public void testTypeParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeParameterBounds.kt");
@@ -70,6 +70,11 @@ public class JavacForeignJava8AnnotationsTestGenerated extends AbstractJavacFore
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/simple.kt");
}
@TestMetadata("typeArgumentsFromParameterBounds.kt")
public void testTypeArgumentsFromParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt");
}
@TestMetadata("typeParameterBounds.kt")
public void testTypeParameterBounds() throws Exception {
runTest("compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeParameterBounds.kt");
@@ -27,7 +27,8 @@ val DEFAULT_JSPECIFY_APPLICABILITY = listOf(
AnnotationQualifierApplicabilityType.FIELD,
AnnotationQualifierApplicabilityType.METHOD_RETURN_TYPE,
AnnotationQualifierApplicabilityType.VALUE_PARAMETER,
AnnotationQualifierApplicabilityType.TYPE_PARAMETER_BOUNDS
AnnotationQualifierApplicabilityType.TYPE_PARAMETER_BOUNDS,
AnnotationQualifierApplicabilityType.TYPE_USE
)
val BUILT_IN_TYPE_QUALIFIER_DEFAULT_ANNOTATIONS = mapOf(
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.load.java.*
import org.jetbrains.kotlin.load.java.descriptors.*
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
import org.jetbrains.kotlin.load.java.lazy.copyWithNewDefaultTypeQualifiers
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaTypeParameterDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.isJavaField
import org.jetbrains.kotlin.load.kotlin.SignatureBuildingComponents
import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor
@@ -311,7 +312,8 @@ class SignatureEnhancement(
private fun KotlinType.extractQualifiersFromAnnotations(
isHeadTypeConstructor: Boolean,
defaultQualifiersForType: JavaDefaultQualifiers?
defaultQualifiersForType: JavaDefaultQualifiers?,
typeParameterForArgument: TypeParameterDescriptor?
): JavaTypeQualifiers {
val composedAnnotation =
if (isHeadTypeConstructor && typeContainer != null)
@@ -338,13 +340,11 @@ class SignatureEnhancement(
val annotationsNullability = composedAnnotation.extractNullability()
val nullabilityInfo =
annotationsNullability
?: nullabilityFromBoundsForTypeBasedOnTypeParameter
?: defaultTypeQualifier?.nullabilityQualifier?.let { nullabilityQualifierWithMigrationStatus ->
NullabilityQualifierWithMigrationStatus(
nullabilityQualifierWithMigrationStatus.qualifier,
nullabilityQualifierWithMigrationStatus.isForWarningOnly
)
}
?: computeNullabilityInfoInTheAbsenceOfExplicitAnnotation(
nullabilityFromBoundsForTypeBasedOnTypeParameter,
defaultTypeQualifier,
typeParameterForArgument
)
val isNotNullTypeParameter =
if (annotationsNullability != null)
@@ -367,12 +367,45 @@ class SignatureEnhancement(
)
}
private fun computeNullabilityInfoInTheAbsenceOfExplicitAnnotation(
nullabilityFromBoundsForTypeBasedOnTypeParameter: NullabilityQualifierWithMigrationStatus?,
defaultTypeQualifier: JavaDefaultQualifiers?,
typeParameterForArgument: TypeParameterDescriptor?
): NullabilityQualifierWithMigrationStatus? {
val result =
nullabilityFromBoundsForTypeBasedOnTypeParameter
?: defaultTypeQualifier?.nullabilityQualifier?.let { nullabilityQualifierWithMigrationStatus ->
NullabilityQualifierWithMigrationStatus(
nullabilityQualifierWithMigrationStatus.qualifier,
nullabilityQualifierWithMigrationStatus.isForWarningOnly
)
}
val boundsFromTypeParameterForArgument = typeParameterForArgument?.boundsNullability() ?: return result
if (result == null) return NullabilityQualifierWithMigrationStatus(boundsFromTypeParameterForArgument)
return NullabilityQualifierWithMigrationStatus(
mostSpecific(boundsFromTypeParameterForArgument, result.qualifier)
)
}
private fun mostSpecific(a: NullabilityQualifier, b: NullabilityQualifier): NullabilityQualifier {
if (a == NullabilityQualifier.FORCE_FLEXIBILITY) return b
if (b == NullabilityQualifier.FORCE_FLEXIBILITY) return a
if (a == NullabilityQualifier.NULLABLE) return b
if (b == NullabilityQualifier.NULLABLE) return a
assert(a == b && a == NullabilityQualifier.NOT_NULL) {
"Expected everything is NOT_NULL, but $a and $b are found"
}
return NullabilityQualifier.NOT_NULL
}
private fun KotlinType.nullabilityInfoBoundsForTypeParameterUsage(): Pair<NullabilityQualifierWithMigrationStatus?, Boolean> {
val typeParameterBoundsNullability =
(constructor.declarationDescriptor as? TypeParameterDescriptor)?.boundsNullability() ?: return Pair(null, false)
if (typeParameterBoundsNullability == NullabilityQualifier.FORCE_FLEXIBILITY) return Pair(null, false)
// If type parameter has a nullable (non-flexible) upper bound
// We shouldn't mark its type usages as nullable:
// interface A<T extends @Nullable Object> {
@@ -384,10 +417,14 @@ class SignatureEnhancement(
)
}
private fun TypeParameterDescriptor.boundsNullability(): NullabilityQualifier = when {
upperBounds.all(KotlinType::isNullabilityFlexible) -> NullabilityQualifier.FORCE_FLEXIBILITY
upperBounds.any { !it.isNullable() } -> NullabilityQualifier.NOT_NULL
else -> NullabilityQualifier.NULLABLE
private fun TypeParameterDescriptor.boundsNullability(): NullabilityQualifier? {
// Do not use bounds from Kotlin-defined type parameters
if (this !is LazyJavaTypeParameterDescriptor) return null
return when {
upperBounds.all(KotlinType::isNullabilityFlexible) -> null
upperBounds.any { !it.isNullable() } -> NullabilityQualifier.NOT_NULL
else -> NullabilityQualifier.NULLABLE
}
}
private fun Annotations.extractNullability(): NullabilityQualifierWithMigrationStatus? =
@@ -410,11 +447,11 @@ class SignatureEnhancement(
val isHeadTypeConstructor = index == 0
assert(isHeadTypeConstructor || !onlyHeadTypeConstructor) { "Only head type constructors should be computed" }
val (qualifiers, defaultQualifiers) = indexedThisType[index]
val (qualifiers, defaultQualifiers, typeParameterForArgument) = indexedThisType[index]
val verticalSlice = indexedFromSupertypes.mapNotNull { it.getOrNull(index)?.type }
// Only the head type constructor is safely co-variant
qualifiers.computeQualifiersForOverride(verticalSlice, defaultQualifiers, isHeadTypeConstructor)
qualifiers.computeQualifiersForOverride(verticalSlice, defaultQualifiers, isHeadTypeConstructor, typeParameterForArgument)
}
return { index -> computedResult.getOrElse(index) { JavaTypeQualifiers.NONE } }
@@ -424,7 +461,7 @@ class SignatureEnhancement(
private fun KotlinType.toIndexed(): List<TypeAndDefaultQualifiers> {
val list = ArrayList<TypeAndDefaultQualifiers>(1)
fun add(type: KotlinType, ownerContext: LazyJavaResolverContext) {
fun add(type: KotlinType, ownerContext: LazyJavaResolverContext, typeParameterForArgument: TypeParameterDescriptor?) {
val c = ownerContext.copyWithNewDefaultTypeQualifiers(type.annotations)
list.add(
@@ -436,28 +473,30 @@ class SignatureEnhancement(
AnnotationQualifierApplicabilityType.TYPE_PARAMETER_BOUNDS
else
AnnotationQualifierApplicabilityType.TYPE_USE
)
),
typeParameterForArgument
)
)
for (arg in type.arguments) {
for ((arg, parameter) in type.arguments.zip(type.constructor.parameters)) {
if (arg.isStarProjection) {
// TODO: sort out how to handle wildcards
list.add(TypeAndDefaultQualifiers(arg.type, null))
list.add(TypeAndDefaultQualifiers(arg.type, null, parameter))
} else {
add(arg.type, c)
add(arg.type, c, parameter)
}
}
}
add(this, containerContext)
add(this, containerContext, typeParameterForArgument = null)
return list
}
private fun KotlinType.computeQualifiersForOverride(
fromSupertypes: Collection<KotlinType>,
defaultQualifiersForType: JavaDefaultQualifiers?,
isHeadTypeConstructor: Boolean
isHeadTypeConstructor: Boolean,
typeParameterForArgument: TypeParameterDescriptor?
): JavaTypeQualifiers {
val superQualifiers = fromSupertypes.map { it.extractQualifiers() }
val mutabilityFromSupertypes = superQualifiers.mapNotNull { it.mutability }.toSet()
@@ -466,7 +505,7 @@ class SignatureEnhancement(
.mapNotNull { it.unwrapEnhancement().extractQualifiers().nullability }
.toSet()
val own = extractQualifiersFromAnnotations(isHeadTypeConstructor, defaultQualifiersForType)
val own = extractQualifiersFromAnnotations(isHeadTypeConstructor, defaultQualifiersForType, typeParameterForArgument)
val ownNullability = own.takeIf { !it.isNullabilityQualifierForWarning }?.nullability
val ownNullabilityForWarning = own.nullability
@@ -549,7 +588,8 @@ class SignatureEnhancement(
private data class TypeAndDefaultQualifiers(
val type: KotlinType,
val defaultQualifiers: JavaDefaultQualifiers?
val defaultQualifiers: JavaDefaultQualifiers?,
val typeParameterForArgument: TypeParameterDescriptor?
)
private fun KotlinType.isNullabilityFlexible(): Boolean {