Skip to content

Commit 8319741

Browse files
bnormSpace Team
authored andcommitted
[FIR] Treat package-info.java nullness annotations as warnings
With the fix of reading package-info.java nullness annotations in more situations, many new errors are introduced which could disrupt mixed Java and Kotlin projects. As such, these errors should temporarily be treated as warnings, even when the mode is set to strict. Since they weren't previously reported, we need a short migration period. ^KT-77729
1 parent 806f68b commit 8319741

File tree

10 files changed

+78
-27
lines changed

10 files changed

+78
-27
lines changed

compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex
3131
import org.jetbrains.kotlin.load.java.JavaClassFinder
3232
import org.jetbrains.kotlin.load.java.structure.JavaClass
3333
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
34+
import org.jetbrains.kotlin.load.java.structure.impl.source.SingleFileRootPsiPackage
3435
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
3536
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
3637
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
@@ -245,27 +246,20 @@ class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJ
245246
if (!found) {
246247
found = packagePartProviders.any { it.findPackageParts(packageName).isNotEmpty() }
247248
}
248-
if (!found) {
249-
found = singleJavaFileRootsIndex.hasPackage(packageFqName)
249+
if (!found && singleJavaFileRootsIndex.hasPackage(packageFqName)) {
250+
val packageInfoClassId = ClassId(packageFqName, SingleJavaFileRootsIndex.PACKAGE_INFO_CLASS_NAME)
251+
val packageInfoVirtualFile = singleJavaFileRootsIndex.findJavaSourceClass(packageInfoClassId)
252+
val packageInfoPsiFile = packageInfoVirtualFile?.let { myPsiManager.findFile(it) } as? PsiJavaFile
253+
val annotationsList = packageInfoPsiFile?.packageStatement?.annotationList
254+
return SingleFileRootPsiPackage(myPsiManager, packageName, annotationsList)
250255
}
251256

252257
if (!found) return null
253258

254259
return object : PsiPackageImpl(myPsiManager, packageName) {
255-
private val packageInfoAnnotations by lazy {
256-
val packageInfoClassId = ClassId(packageFqName, SingleJavaFileRootsIndex.PACKAGE_INFO_CLASS_NAME)
257-
val packageInfoVirtualFile = singleJavaFileRootsIndex.findJavaSourceClass(packageInfoClassId)
258-
val packageInfoPsiFile = packageInfoVirtualFile?.let { myPsiManager.findFile(it) } as? PsiJavaFile
259-
packageInfoPsiFile?.packageStatement?.annotationList
260-
}
261-
262260
// Do not check validness for packages we just made sure are actually present
263261
// It might be important for source roots that have non-trivial package prefix
264262
override fun isValid() = true
265-
266-
override fun getAnnotationList(): PsiModifierList? {
267-
return packageInfoAnnotations ?: super.getAnnotationList()
268-
}
269263
}
270264
}
271265

compiler/fir/fir-jvm/src/org/jetbrains/kotlin/fir/java/enhancement/FirAnnotationTypeQualifierResolver.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.jetbrains.kotlin.fir.java.enhancement
77

88
import org.jetbrains.kotlin.KtFakeSourceElementKind
9+
import org.jetbrains.kotlin.config.LanguageFeature
910
import org.jetbrains.kotlin.fakeElement
1011
import org.jetbrains.kotlin.fir.FirSession
1112
import org.jetbrains.kotlin.fir.FirSessionComponent
@@ -14,13 +15,16 @@ import org.jetbrains.kotlin.fir.declarations.extractEnumValueArgumentInfo
1415
import org.jetbrains.kotlin.fir.expressions.*
1516
import org.jetbrains.kotlin.fir.java.convertAnnotationsToFir
1617
import org.jetbrains.kotlin.fir.java.declarations.FirJavaClass
18+
import org.jetbrains.kotlin.fir.languageVersionSettings
1719
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
1820
import org.jetbrains.kotlin.fir.resolve.toSymbol
1921
import org.jetbrains.kotlin.load.java.AbstractAnnotationTypeQualifierResolver
2022
import org.jetbrains.kotlin.load.java.JavaModuleAnnotationsProvider
2123
import org.jetbrains.kotlin.load.java.JavaTypeEnhancementState
2224
import org.jetbrains.kotlin.load.java.JavaTypeQualifiersByElementType
2325
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME
26+
import org.jetbrains.kotlin.load.java.structure.impl.JavaElementImpl
27+
import org.jetbrains.kotlin.load.java.structure.impl.source.SingleFileRootPsiPackage
2428
import org.jetbrains.kotlin.name.FqName
2529

2630
class FirAnnotationTypeQualifierResolver(
@@ -64,10 +68,15 @@ class FirAnnotationTypeQualifierResolver(
6468
?.let { extractDefaultQualifiers(it) }
6569
} else {
6670
val fakeSource = firClass.source?.fakeElement(KtFakeSourceElementKind.Enhancement)
67-
val forModule = javaModuleAnnotationsProvider.getAnnotationsForModuleOwnerOfClass(classId)
68-
?.let { extractAndMergeDefaultQualifiers(null, it.convertAnnotationsToFir(session, fakeSource)) }
69-
val forPackage = (firClass as? FirJavaClass)?.javaPackage
70-
?.let { extractAndMergeDefaultQualifiers(forModule, it.convertAnnotationsToFir(session, fakeSource)) }
71+
val forModule = javaModuleAnnotationsProvider.getAnnotationsForModuleOwnerOfClass(classId)?.let {
72+
extractAndMergeDefaultQualifiers(null, it.convertAnnotationsToFir(session, fakeSource))
73+
}
74+
val forPackage = (firClass as? FirJavaClass)?.javaPackage?.let {
75+
val isForWarningOnly =
76+
!session.languageVersionSettings.supportsFeature(LanguageFeature.CheckPackageInfoNullnessAnnotations) &&
77+
(it as? JavaElementImpl<*>)?.psi is SingleFileRootPsiPackage
78+
extractAndMergeDefaultQualifiers(forModule, it.convertAnnotationsToFir(session, fakeSource), isForWarningOnly)
79+
}
7180
forPackage ?: forModule
7281
}
7382
return extractAndMergeDefaultQualifiers(parentQualifiers, firClass.annotations)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.load.java.structure.impl.source
7+
8+
import com.intellij.psi.PsiManager
9+
import com.intellij.psi.PsiModifierList
10+
import com.intellij.psi.impl.file.PsiPackageImpl
11+
12+
class SingleFileRootPsiPackage(
13+
manager: PsiManager,
14+
qualifiedName: String,
15+
private val annotationsList: PsiModifierList?,
16+
) : PsiPackageImpl(manager, qualifiedName) {
17+
// Do not check validness for packages we just made sure are actually present
18+
// It might be important for source roots that have non-trivial package prefix
19+
override fun isValid() = true
20+
21+
override fun getAnnotationList(): PsiModifierList? {
22+
return annotationsList
23+
}
24+
}

compiler/testData/cli/jvm/XjspecifyAnnotation/jspecifyFileRoots.args renamed to compiler/testData/cli/jvm/XjspecifyAnnotation/jspecifyFileRootsProgressive.args

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ $TESTDATA_DIR$/jspecify/test/package-info.java
44
$FOREIGN_JAVA8_ANNOTATIONS_DIR$
55
-d
66
$TEMP_DIR$
7+
-progressive
File renamed without changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$TESTDATA_DIR$/jspecifyUsage.kt
2+
$TESTDATA_DIR$/jspecify/test/A.java
3+
$TESTDATA_DIR$/jspecify/test/package-info.java
4+
$FOREIGN_JAVA8_ANNOTATIONS_DIR$
5+
-d
6+
$TEMP_DIR$
7+
-Xreport-all-warnings
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
compiler/testData/cli/jvm/XjspecifyAnnotation/jspecifyUsage.kt:4:11: warning: Java type mismatch: inferred type is 'Nothing?', but 'String' was expected.
2+
a.foo(null)
3+
^^^^
4+
compiler/testData/cli/jvm/XjspecifyAnnotation/jspecifyUsage.kt:5:12: error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'.
5+
a.bar().length
6+
^
7+
COMPILATION_ERROR

compiler/tests-integration/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ enum class LanguageFeature(
455455
ForbidInlineEnumEntries(KOTLIN_2_4, enabledInProgressiveMode = true, "KTLC-361"),
456456
CheckOptInOnPureEnumEntries(KOTLIN_2_4, enabledInProgressiveMode = true, "KTLC-359"),
457457
ForbidExposingPackagePrivateInInternal(KOTLIN_2_4, enabledInProgressiveMode = true, "KTLC-271"),
458+
CheckPackageInfoNullnessAnnotations(KOTLIN_2_4, enabledInProgressiveMode = true, "KT-77729"),
458459

459460
// 2.5
460461

core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AbstractAnnotationTypeQualifierResolver.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,16 @@ abstract class AbstractAnnotationTypeQualifierResolver<TAnnotation : Any>(
4646
}
4747
}
4848

49-
private fun resolveQualifierBuiltInDefaultAnnotation(annotation: TAnnotation): JavaDefaultQualifiers? {
49+
private fun resolveQualifierBuiltInDefaultAnnotation(annotation: TAnnotation, isForWarningOnly: Boolean): JavaDefaultQualifiers? {
5050
if (javaTypeEnhancementState.disabledDefaultAnnotations) {
5151
return null
5252
}
5353

5454
return BUILT_IN_TYPE_QUALIFIER_DEFAULT_ANNOTATIONS[annotation.fqName]?.let { qualifierForDefaultingAnnotation ->
5555
val state = resolveDefaultAnnotationState(annotation).takeIf { it != ReportLevel.IGNORE } ?: return null
5656
qualifierForDefaultingAnnotation.copy(
57-
nullabilityQualifier = qualifierForDefaultingAnnotation.nullabilityQualifier.copy(isForWarningOnly = state.isWarning)
57+
nullabilityQualifier = qualifierForDefaultingAnnotation.nullabilityQualifier
58+
.copy(isForWarningOnly = isForWarningOnly || state.isWarning)
5859
)
5960
}
6061
}
@@ -156,8 +157,8 @@ abstract class AbstractAnnotationTypeQualifierResolver<TAnnotation : Any>(
156157
}.also { if (found != null && found != it) return null /* inconsistent */ }
157158
}
158159

159-
private fun extractDefaultQualifiers(annotation: TAnnotation): JavaDefaultQualifiers? {
160-
resolveQualifierBuiltInDefaultAnnotation(annotation)?.let { return it }
160+
private fun extractDefaultQualifiers(annotation: TAnnotation, isForWarningOnly: Boolean): JavaDefaultQualifiers? {
161+
resolveQualifierBuiltInDefaultAnnotation(annotation, isForWarningOnly)?.let { return it }
161162

162163
val (typeQualifier, applicability) = resolveTypeQualifierDefaultAnnotation(annotation)
163164
?: return null
@@ -166,15 +167,17 @@ abstract class AbstractAnnotationTypeQualifierResolver<TAnnotation : Any>(
166167
// TODO: since we override the warning status, whether we force it in `extractNullability` is irrelevant.
167168
// However, this is probably not what was intended.
168169
val nullabilityQualifier = extractNullability(typeQualifier) { false } ?: return null
169-
return JavaDefaultQualifiers(nullabilityQualifier.copy(isForWarningOnly = jsr305State.isWarning), applicability)
170+
return JavaDefaultQualifiers(nullabilityQualifier.copy(isForWarningOnly = isForWarningOnly || jsr305State.isWarning), applicability)
170171
}
171172

172173
fun extractAndMergeDefaultQualifiers(
173-
oldQualifiers: JavaTypeQualifiersByElementType?, annotations: Iterable<TAnnotation>
174+
oldQualifiers: JavaTypeQualifiersByElementType?,
175+
annotations: Iterable<TAnnotation>,
176+
isForWarningOnly: Boolean = false,
174177
): JavaTypeQualifiersByElementType? {
175178
if (javaTypeEnhancementState.disabledDefaultAnnotations) return oldQualifiers
176179

177-
val extractedQualifiers = annotations.mapNotNull { extractDefaultQualifiers(it) }
180+
val extractedQualifiers = annotations.mapNotNull { extractDefaultQualifiers(it, isForWarningOnly) }
178181
if (extractedQualifiers.isEmpty()) return oldQualifiers
179182

180183
val newQualifiers = QualifierByApplicabilityType(AnnotationQualifierApplicabilityType::class.java)

0 commit comments

Comments
 (0)