From 392ef9aa2b31014d1d4c7046391e733f9ccc7a4f Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Mon, 12 Aug 2019 12:43:02 +0300 Subject: [PATCH] Support type arguments enhancement from type parameters bounds Currently, only works for codeanalysis annotations because type parameters bounds are enhanced only for them --- .../typeArgumentsFromParameterBounds.kt | 59 ++++++++++++ .../typeArgumentsFromParameterBounds.txt | 34 +++++++ ...sNoAnnotationInClasspathTestGenerated.java | 5 ++ ...spathWithPsiClassReadingTestGenerated.java | 5 ++ .../ForeignJava8AnnotationsTestGenerated.java | 5 ++ ...cForeignJava8AnnotationsTestGenerated.java | 5 ++ .../load/java/AnnotationQualifiersFqNames.kt | 3 +- .../typeEnhancement/signatureEnhancement.kt | 90 +++++++++++++------ 8 files changed, 180 insertions(+), 26 deletions(-) create mode 100644 compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt create mode 100644 compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.txt diff --git a/compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt b/compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt new file mode 100644 index 00000000000..bcdc30223d2 --- /dev/null +++ b/compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.kt @@ -0,0 +1,59 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER +// FILE: A.java + +import jspecify.annotations.*; + +public class A { +} + +// FILE: B.java + +import jspecify.annotations.*; + +@DefaultNullable +public class B { + public void bar(A a) {} +} + +// FILE: C.java + +import jspecify.annotations.*; + +@DefaultNotNull +public class C { + public void bar(A a) {} +} + +// FILE: D.java + +import jspecify.annotations.*; + +@DefaultUnknownNullness +public class D { + public void bar(A a) {} +} + +// FILE: main.kt + +fun main( + aNotNullNotNullNotNull: A, + aNotNullNotNullNull: A, + aNotNullNullNotNull: A, + aNotNullNullNull: A, + b: B, c: C, d: D +) { + b.bar(aNotNullNotNullNotNull) + b.bar(aNotNullNotNullNull) + b.bar(aNotNullNullNotNull) + b.bar(aNotNullNullNull) + + c.bar(aNotNullNotNullNotNull) + c.bar(aNotNullNotNullNull) + c.bar(aNotNullNullNotNull) + c.bar(aNotNullNullNull) + + d.bar(aNotNullNotNullNotNull) + d.bar(aNotNullNotNullNull) + d.bar(aNotNullNullNotNull) + d.bar(aNotNullNullNull) +} diff --git a/compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.txt b/compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.txt new file mode 100644 index 00000000000..7e05124989d --- /dev/null +++ b/compiler/testData/foreignAnnotationsJava8/tests/jspecify/typeArgumentsFromParameterBounds.txt @@ -0,0 +1,34 @@ +package + +public fun main(/*0*/ aNotNullNotNullNotNull: A, /*1*/ aNotNullNotNullNull: A, /*2*/ aNotNullNullNotNull: A, /*3*/ aNotNullNullNull: A, /*4*/ b: B, /*5*/ c: C, /*6*/ d: D): kotlin.Unit + +public open class A { + public constructor A() + 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.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.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.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 +} diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated.java index 83f24d49ea0..a133554c520 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated.java @@ -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"); diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTestGenerated.java index ce6652f4a9b..4f63f304ee0 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTestGenerated.java @@ -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"); diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsTestGenerated.java index 1a494f92359..53e3557d16f 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsTestGenerated.java @@ -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"); diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignJava8AnnotationsTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignJava8AnnotationsTestGenerated.java index 010f7b303f4..3d793c38a09 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignJava8AnnotationsTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignJava8AnnotationsTestGenerated.java @@ -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"); diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifiersFqNames.kt b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifiersFqNames.kt index 2b67336b40c..f228f165fd0 100644 --- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifiersFqNames.kt +++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifiersFqNames.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( diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt index ba5efaa32dd..cb8c7012e9c 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt @@ -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 { 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 { @@ -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 { val list = ArrayList(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, 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 {