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:
committed by
Victor Petukhov
parent
e9e05c53e1
commit
392ef9aa2b
Vendored
+59
@@ -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)
|
||||
}
|
||||
Vendored
+34
@@ -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
|
||||
}
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Generated
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+2
-1
@@ -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(
|
||||
|
||||
+65
-25
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user