diff --git a/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt new file mode 100644 index 00000000000..4eda66c40c9 --- /dev/null +++ b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt @@ -0,0 +1,75 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER +// FILE: A.java + +import jspecify.annotations.*; + +public class A { +} + +// FILE: B.java + +import jspecify.annotations.*; + +public class B { + public void superAsIs(A a) {} + public void superNotNull(A a) {} + public void superNullable(A a) {} + + public void extendsAsIs(A a) {} + public void extendsNotNull(A a) {} + public void extendsNullable(A a) {} + + public void noBounds(A a) {} +} + +// FILE: main.kt + +fun main( + aNotNullNotNullNotNull: A, + aNotNullNotNullNull: A, + aNotNullNullNotNull: A, + aNotNullNullNull: A, + + aAnyNotNullNotNullNotNull: A, + aAnyNotNullNotNullNull: A, + aAnyNotNullNullNotNull: A, + aAnyNotNullNullNull: A, + b: B +) { + b.superAsIs(aAnyNotNullNotNullNotNull) + b.superAsIs(aAnyNotNullNotNullNull) + b.superAsIs(aAnyNotNullNullNotNull) + b.superAsIs(aAnyNotNullNullNull) + + b.superNotNull(aAnyNotNullNotNullNotNull) + b.superNotNull(aAnyNotNullNotNullNull) + b.superNotNull(aAnyNotNullNullNotNull) + b.superNotNull(aAnyNotNullNullNull) + + // TODO: Bound for the first argument in "superNullable" contradicts to declared nullability of the parameter + // Do we need to ignore such arguments' nullability? + b.superNullable(aAnyNotNullNotNullNotNull) + b.superNullable(aAnyNotNullNotNullNull) + b.superNullable(aAnyNotNullNullNotNull) + b.superNullable(aAnyNotNullNullNull) + + b.extendsAsIs(aNotNullNotNullNotNull) + b.extendsAsIs(aNotNullNotNullNull) + b.extendsAsIs(aNotNullNullNotNull) + b.extendsAsIs(aNotNullNullNull) + + b.extendsNotNull(aNotNullNotNullNotNull) + b.extendsNotNull(aNotNullNotNullNull) + b.extendsNotNull(aNotNullNullNotNull) + b.extendsNotNull(aNotNullNullNull) + + b.extendsNullable(aNotNullNotNullNotNull) + b.extendsNullable(aNotNullNotNullNull) + b.extendsNullable(aNotNullNullNotNull) + b.extendsNullable(aNotNullNullNull) + + b.noBounds(aNotNullNotNullNotNull) + b.noBounds(aNotNullNotNullNull) + b.noBounds(aNotNullNullNotNull) + b.noBounds(aNotNullNullNull) +} diff --git a/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.txt b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.txt new file mode 100644 index 00000000000..368fd756247 --- /dev/null +++ b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.txt @@ -0,0 +1,24 @@ +package + +public fun main(/*0*/ aNotNullNotNullNotNull: A, /*1*/ aNotNullNotNullNull: A, /*2*/ aNotNullNullNotNull: A, /*3*/ aNotNullNullNull: A, /*4*/ aAnyNotNullNotNullNotNull: A, /*5*/ aAnyNotNullNotNullNull: A, /*6*/ aAnyNotNullNullNotNull: A, /*7*/ aAnyNotNullNullNull: A, /*8*/ b: B): 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 +} + +public open class B { + public constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun extendsAsIs(/*0*/ a: A!): kotlin.Unit + public open fun extendsNotNull(/*0*/ a: A!): kotlin.Unit + public open fun extendsNullable(/*0*/ a: A!): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open fun noBounds(/*0*/ a: A<*, *, *>!): kotlin.Unit + public open fun superAsIs(/*0*/ a: A!): kotlin.Unit + public open fun superNotNull(/*0*/ a: A!): kotlin.Unit + public open fun superNullable(/*0*/ a: A!): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt new file mode 100644 index 00000000000..ae07d1a15a3 --- /dev/null +++ b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt @@ -0,0 +1,38 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER +// FILE: A.java + +import jspecify.annotations.*; + +public class A { +} + +// FILE: B.java + +import jspecify.annotations.*; + +public class B { + @DefaultNotNull + public void noBoundsNotNull(A a) {} + @DefaultNullable + public void noBoundsNullable(A a) {} +} + +// FILE: main.kt + +fun main( + aNotNullNotNullNotNull: A, + aNotNullNotNullNull: A, + aNotNullNullNotNull: A, + aNotNullNullNull: A, + b: B +) { + b.noBoundsNotNull(aNotNullNotNullNotNull) + b.noBoundsNotNull(aNotNullNotNullNull) + b.noBoundsNotNull(aNotNullNullNotNull) + b.noBoundsNotNull(aNotNullNullNull) + + b.noBoundsNullable(aNotNullNotNullNotNull) + b.noBoundsNullable(aNotNullNotNullNull) + b.noBoundsNullable(aNotNullNullNotNull) + b.noBoundsNullable(aNotNullNullNull) +} diff --git a/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.txt b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.txt new file mode 100644 index 00000000000..a15fa4185c5 --- /dev/null +++ b/compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.txt @@ -0,0 +1,19 @@ +package + +public fun main(/*0*/ aNotNullNotNullNotNull: A, /*1*/ aNotNullNotNullNull: A, /*2*/ aNotNullNullNotNull: A, /*3*/ aNotNullNullNull: A, /*4*/ b: B): 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 +} + +public open class B { + public constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + @jspecify.annotations.DefaultNotNull public open fun noBoundsNotNull(/*0*/ a: A): kotlin.Unit + @jspecify.annotations.DefaultNullable public open fun noBoundsNullable(/*0*/ a: A?): kotlin.Unit + 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 a133554c520..ce5956267bd 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated.java @@ -43,6 +43,29 @@ public class ForeignJava8AnnotationsNoAnnotationInClasspathTestGenerated extends runTest("compiler/testData/foreignAnnotationsJava8/tests/typeUseOnObject.kt"); } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Codeanalysis extends AbstractForeignJava8AnnotationsNoAnnotationInClasspathTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInCodeanalysis() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("annotatedWildcards.kt") + public void testAnnotatedWildcards() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt"); + } + + @TestMetadata("wildcardsWithDefault.kt") + public void testWildcardsWithDefault() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt"); + } + } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/jspecify") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) 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 4f63f304ee0..b29dc5154d8 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTestGenerated.java @@ -43,6 +43,29 @@ public class ForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTe runTest("compiler/testData/foreignAnnotationsJava8/tests/typeUseOnObject.kt"); } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Codeanalysis extends AbstractForeignJava8AnnotationsNoAnnotationInClasspathWithPsiClassReadingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInCodeanalysis() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("annotatedWildcards.kt") + public void testAnnotatedWildcards() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt"); + } + + @TestMetadata("wildcardsWithDefault.kt") + public void testWildcardsWithDefault() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt"); + } + } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/jspecify") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) 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 53e3557d16f..ae3d22c9826 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/checkers/ForeignJava8AnnotationsTestGenerated.java @@ -43,6 +43,29 @@ public class ForeignJava8AnnotationsTestGenerated extends AbstractForeignJava8An runTest("compiler/testData/foreignAnnotationsJava8/tests/typeUseOnObject.kt"); } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Codeanalysis extends AbstractForeignJava8AnnotationsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInCodeanalysis() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("annotatedWildcards.kt") + public void testAnnotatedWildcards() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt"); + } + + @TestMetadata("wildcardsWithDefault.kt") + public void testWildcardsWithDefault() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt"); + } + } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/jspecify") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) 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 3d793c38a09..409336684e5 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 @@ -43,6 +43,29 @@ public class JavacForeignJava8AnnotationsTestGenerated extends AbstractJavacFore runTest("compiler/testData/foreignAnnotationsJava8/tests/typeUseOnObject.kt"); } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Codeanalysis extends AbstractJavacForeignJava8AnnotationsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInCodeanalysis() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("annotatedWildcards.kt") + public void testAnnotatedWildcards() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/annotatedWildcards.kt"); + } + + @TestMetadata("wildcardsWithDefault.kt") + public void testWildcardsWithDefault() throws Exception { + runTest("compiler/testData/foreignAnnotationsJava8/tests/codeanalysis/wildcardsWithDefault.kt"); + } + } + @TestMetadata("compiler/testData/foreignAnnotationsJava8/tests/jspecify") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) 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 cb8c7012e9c..5fca4049c8a 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 @@ -313,7 +313,8 @@ class SignatureEnhancement( private fun KotlinType.extractQualifiersFromAnnotations( isHeadTypeConstructor: Boolean, defaultQualifiersForType: JavaDefaultQualifiers?, - typeParameterForArgument: TypeParameterDescriptor? + typeParameterForArgument: TypeParameterDescriptor?, + isFromStarProjection: Boolean ): JavaTypeQualifiers { val composedAnnotation = if (isHeadTypeConstructor && typeContainer != null) @@ -337,7 +338,7 @@ class SignatureEnhancement( val (nullabilityFromBoundsForTypeBasedOnTypeParameter, isTypeParameterWithNotNullableBounds) = nullabilityInfoBoundsForTypeParameterUsage() - val annotationsNullability = composedAnnotation.extractNullability() + val annotationsNullability = composedAnnotation.extractNullability()?.takeUnless { isFromStarProjection } val nullabilityInfo = annotationsNullability ?: computeNullabilityInfoInTheAbsenceOfExplicitAnnotation( @@ -447,11 +448,13 @@ class SignatureEnhancement( val isHeadTypeConstructor = index == 0 assert(isHeadTypeConstructor || !onlyHeadTypeConstructor) { "Only head type constructors should be computed" } - val (qualifiers, defaultQualifiers, typeParameterForArgument) = indexedThisType[index] + val (qualifiers, defaultQualifiers, typeParameterForArgument, isFromStarProjection) = indexedThisType[index] val verticalSlice = indexedFromSupertypes.mapNotNull { it.getOrNull(index)?.type } // Only the head type constructor is safely co-variant - qualifiers.computeQualifiersForOverride(verticalSlice, defaultQualifiers, isHeadTypeConstructor, typeParameterForArgument) + qualifiers.computeQualifiersForOverride( + verticalSlice, defaultQualifiers, isHeadTypeConstructor, typeParameterForArgument, isFromStarProjection + ) } return { index -> computedResult.getOrElse(index) { JavaTypeQualifiers.NONE } } @@ -464,24 +467,26 @@ class SignatureEnhancement( fun add(type: KotlinType, ownerContext: LazyJavaResolverContext, typeParameterForArgument: TypeParameterDescriptor?) { val c = ownerContext.copyWithNewDefaultTypeQualifiers(type.annotations) + val defaultQualifiers = c.defaultTypeQualifiers + ?.get( + if (typeParameterBounds) + AnnotationQualifierApplicabilityType.TYPE_PARAMETER_BOUNDS + else + AnnotationQualifierApplicabilityType.TYPE_USE + ) list.add( TypeAndDefaultQualifiers( type, - c.defaultTypeQualifiers - ?.get( - if (typeParameterBounds) - AnnotationQualifierApplicabilityType.TYPE_PARAMETER_BOUNDS - else - AnnotationQualifierApplicabilityType.TYPE_USE - ), - typeParameterForArgument + defaultQualifiers, + typeParameterForArgument, + isFromStarProjection = false ) ) 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, parameter)) + list.add(TypeAndDefaultQualifiers(arg.type, defaultQualifiers, parameter, isFromStarProjection = true)) } else { add(arg.type, c, parameter) } @@ -496,7 +501,8 @@ class SignatureEnhancement( fromSupertypes: Collection, defaultQualifiersForType: JavaDefaultQualifiers?, isHeadTypeConstructor: Boolean, - typeParameterForArgument: TypeParameterDescriptor? + typeParameterForArgument: TypeParameterDescriptor?, + isFromStarProjection: Boolean ): JavaTypeQualifiers { val superQualifiers = fromSupertypes.map { it.extractQualifiers() } val mutabilityFromSupertypes = superQualifiers.mapNotNull { it.mutability }.toSet() @@ -505,7 +511,10 @@ class SignatureEnhancement( .mapNotNull { it.unwrapEnhancement().extractQualifiers().nullability } .toSet() - val own = extractQualifiersFromAnnotations(isHeadTypeConstructor, defaultQualifiersForType, typeParameterForArgument) + val own = + extractQualifiersFromAnnotations( + isHeadTypeConstructor, defaultQualifiersForType, typeParameterForArgument, isFromStarProjection + ) val ownNullability = own.takeIf { !it.isNullabilityQualifierForWarning }?.nullability val ownNullabilityForWarning = own.nullability @@ -589,7 +598,8 @@ class SignatureEnhancement( private data class TypeAndDefaultQualifiers( val type: KotlinType, val defaultQualifiers: JavaDefaultQualifiers?, - val typeParameterForArgument: TypeParameterDescriptor? + val typeParameterForArgument: TypeParameterDescriptor?, + val isFromStarProjection: Boolean ) private fun KotlinType.isNullabilityFlexible(): Boolean { diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeEnhancement.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeEnhancement.kt index 5b1dad8017b..f5ff66f6d4c 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeEnhancement.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeEnhancement.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.types.checker.SimpleClassicTypeSystemContext import org.jetbrains.kotlin.types.refinement.TypeRefinement import org.jetbrains.kotlin.types.typeUtil.createProjection import org.jetbrains.kotlin.types.typeUtil.isTypeParameter +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable fun KotlinType.hasEnhancedNullability(): Boolean = SimpleClassicTypeSystemContext.hasEnhancedNullability(this)