Introduce compiler flag to manage status of specific Java nullability annotations
This commit is contained in:
+42
-14
@@ -25,7 +25,8 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) {
|
||||
fun parse(
|
||||
jsr305Args: Array<String>?,
|
||||
supportCompatqualCheckerFrameworkAnnotations: String?,
|
||||
jspecifyState: String?
|
||||
jspecifyState: String?,
|
||||
nullabilityAnnotations: Array<String>?,
|
||||
): JavaTypeEnhancementState {
|
||||
val jsr305State = parseJsr305State(jsr305Args)
|
||||
|
||||
@@ -43,17 +44,43 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) {
|
||||
}
|
||||
|
||||
val jspecifyReportLevel = parseJspecifyReportLevel(jspecifyState)
|
||||
val nullabilityAnnotationReportLevels = parseNullabilityAnnotationReportLevels(nullabilityAnnotations)
|
||||
|
||||
val state = JavaTypeEnhancementState(
|
||||
jsr305State.global ?: ReportLevel.WARN, jsr305State.migration, jsr305State.usedDefined,
|
||||
enableCompatqualCheckerFrameworkAnnotations =
|
||||
enableCompatqualCheckerFrameworkAnnotations
|
||||
?: JavaTypeEnhancementState.COMPATQUAL_CHECKER_FRAMEWORK_ANNOTATIONS_SUPPORT_DEFAULT_VALUE,
|
||||
jspecifyReportLevel = jspecifyReportLevel
|
||||
jspecifyReportLevel = jspecifyReportLevel,
|
||||
nullabilityAnnotationsReportLevel = nullabilityAnnotationReportLevels
|
||||
)
|
||||
return if (state == JavaTypeEnhancementState.DISABLED_JSR_305) JavaTypeEnhancementState.DISABLED_JSR_305 else state
|
||||
}
|
||||
|
||||
private fun parseNullabilityAnnotationReportLevels(nullabilityAnnotations: Array<String>?): Map<String, ReportLevel> {
|
||||
if (nullabilityAnnotations.isNullOrEmpty()) return emptyMap()
|
||||
|
||||
val annotationsWithReportLevels = mutableMapOf<String, ReportLevel>()
|
||||
val compilerOption = "-Xnullability-annotations"
|
||||
|
||||
for (item in nullabilityAnnotations) {
|
||||
if (!item.startsWith("@")) {
|
||||
reportUnrecognizedReportLevel(item, compilerOption)
|
||||
continue
|
||||
}
|
||||
|
||||
val (name, state) = parseAnnotationWithReportLevel(item, compilerOption) ?: continue
|
||||
val current = annotationsWithReportLevels[name]
|
||||
if (current == null) {
|
||||
annotationsWithReportLevels[name] = state
|
||||
} else if (current != state) {
|
||||
reportDuplicateAnnotation("@$name:${current.description}", item, compilerOption)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return annotationsWithReportLevels
|
||||
}
|
||||
|
||||
private fun parseJspecifyReportLevel(jspecifyState: String?): ReportLevel {
|
||||
if (jspecifyState == null) return JavaTypeEnhancementState.DEFAULT_REPORT_LEVEL_FOR_JSPECIFY
|
||||
@@ -80,21 +107,22 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) {
|
||||
var global: ReportLevel? = null
|
||||
var migration: ReportLevel? = null
|
||||
val userDefined = mutableMapOf<String, ReportLevel>()
|
||||
val compilerOption = "-Xjsr305"
|
||||
|
||||
fun parseJsr305UnderMigration(item: String): ReportLevel? {
|
||||
val rawState = item.split(":").takeIf { it.size == 2 }?.get(1)
|
||||
return ReportLevel.findByDescription(rawState) ?: reportUnrecognizedJsr305(item).let { null }
|
||||
return ReportLevel.findByDescription(rawState) ?: reportUnrecognizedReportLevel(item, compilerOption).let { null }
|
||||
}
|
||||
|
||||
args?.forEach { item ->
|
||||
when {
|
||||
item.startsWith("@") -> {
|
||||
val (name, state) = parseJsr305UserDefined(item) ?: return@forEach
|
||||
val (name, state) = parseAnnotationWithReportLevel(item, compilerOption) ?: return@forEach
|
||||
val current = userDefined[name]
|
||||
if (current == null) {
|
||||
userDefined[name] = state
|
||||
} else if (current != state) {
|
||||
reportDuplicateJsr305("@$name:${current.description}", item)
|
||||
reportDuplicateAnnotation("@$name:${current.description}", item, compilerOption)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
@@ -103,7 +131,7 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) {
|
||||
if (migration == null) {
|
||||
migration = state
|
||||
} else if (migration != state) {
|
||||
reportDuplicateJsr305("under-migration:${migration?.description}", item)
|
||||
reportDuplicateAnnotation("under-migration:${migration?.description}", item, compilerOption)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
@@ -120,7 +148,7 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) {
|
||||
if (global == null) {
|
||||
global = ReportLevel.findByDescription(item)
|
||||
} else if (global!!.description != item) {
|
||||
reportDuplicateJsr305(global!!.description, item)
|
||||
reportDuplicateAnnotation(global!!.description, item, compilerOption)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
@@ -129,22 +157,22 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) {
|
||||
return Jsr305State(global, migration, userDefined)
|
||||
}
|
||||
|
||||
private fun reportUnrecognizedJsr305(item: String) {
|
||||
collector.report(CompilerMessageSeverity.ERROR, "Unrecognized -Xjsr305 value: $item")
|
||||
private fun reportUnrecognizedReportLevel(item: String, sourceCompilerOption: String) {
|
||||
collector.report(CompilerMessageSeverity.ERROR, "Unrecognized $sourceCompilerOption value: $item")
|
||||
}
|
||||
|
||||
private fun reportDuplicateJsr305(first: String, second: String) {
|
||||
collector.report(CompilerMessageSeverity.ERROR, "Conflict duplicating -Xjsr305 value: $first, $second")
|
||||
private fun reportDuplicateAnnotation(first: String, second: String, sourceCompilerOption: String) {
|
||||
collector.report(CompilerMessageSeverity.ERROR, "Conflict duplicating $sourceCompilerOption value: $first, $second")
|
||||
}
|
||||
|
||||
private fun parseJsr305UserDefined(item: String): Pair<String, ReportLevel>? {
|
||||
private fun parseAnnotationWithReportLevel(item: String, sourceCompilerOption: String): Pair<String, ReportLevel>? {
|
||||
val (name, rawState) = item.substring(1).split(":").takeIf { it.size == 2 } ?: run {
|
||||
reportUnrecognizedJsr305(item)
|
||||
reportUnrecognizedReportLevel(item, sourceCompilerOption)
|
||||
return null
|
||||
}
|
||||
|
||||
val state = ReportLevel.findByDescription(rawState) ?: run {
|
||||
reportUnrecognizedJsr305(item)
|
||||
reportUnrecognizedReportLevel(item, sourceCompilerOption)
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -282,6 +282,17 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
|
||||
)
|
||||
var jsr305: Array<String>? by FreezableVar(null)
|
||||
|
||||
@Argument(
|
||||
value = "-Xnullability-annotations",
|
||||
valueDescription = "@<fq.name>:{ignore/strict/warn}",
|
||||
description = "Specify behavior for specific Java nullability annotations (provided with fully qualified package name)\n" +
|
||||
"Modes:\n" +
|
||||
" * ignore\n" +
|
||||
" * strict\n" +
|
||||
" * warn (report a warning)"
|
||||
)
|
||||
var nullabilityAnnotations: Array<String>? by FreezableVar(null)
|
||||
|
||||
@Argument(
|
||||
value = "-Xsupport-compatqual-checker-framework-annotations",
|
||||
valueDescription = "enable|disable",
|
||||
@@ -494,7 +505,8 @@ default: `indy-with-constants` for JVM target 9 or greater, `inline` otherwise""
|
||||
result[JvmAnalysisFlags.javaTypeEnhancementState] = JavaTypeEnhancementStateParser(collector).parse(
|
||||
jsr305,
|
||||
supportCompatqualCheckerFrameworkAnnotations,
|
||||
jspecifyAnnotations
|
||||
jspecifyAnnotations,
|
||||
nullabilityAnnotations
|
||||
)
|
||||
result[AnalysisFlags.ignoreDataFlowInAssert] = JVMAssertionsMode.fromString(assertionsMode) != JVMAssertionsMode.LEGACY
|
||||
JvmDefaultMode.fromStringOrNull(jvmDefault)?.let {
|
||||
|
||||
+33
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.resolve.jvm.annotations
|
||||
|
||||
import org.jetbrains.kotlin.config.JvmDefaultMode
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
@@ -16,6 +17,7 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
|
||||
import org.jetbrains.kotlin.util.findImplementationFromInterface
|
||||
import org.jetbrains.kotlin.utils.ReportLevel
|
||||
|
||||
val JVM_DEFAULT_FQ_NAME = FqName("kotlin.jvm.JvmDefault")
|
||||
val JVM_DEFAULT_NO_COMPATIBILITY_FQ_NAME = FqName("kotlin.jvm.JvmDefaultWithoutCompatibility")
|
||||
@@ -95,3 +97,34 @@ fun DeclarationDescriptor.findSynchronizedAnnotation(): AnnotationDescriptor? =
|
||||
annotations.findAnnotation(SYNCHRONIZED_ANNOTATION_FQ_NAME)
|
||||
|
||||
fun ClassDescriptor.isJvmRecord(): Boolean = annotations.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME)
|
||||
|
||||
data class NullabilityAnnotationsStatus(
|
||||
val reportLevelBefore: ReportLevel,
|
||||
val sinceVersion: LanguageVersion = LanguageVersion.KOTLIN_1_0,
|
||||
val reportLevelAfter: ReportLevel = reportLevelBefore,
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT = NullabilityAnnotationsStatus(ReportLevel.STRICT)
|
||||
}
|
||||
}
|
||||
|
||||
val nullabilityAnnotationSettings = mapOf(
|
||||
FqName("org.jetbrains.annotations") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("androidx.annotation") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("android.support.annotation") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("android.annotation") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("com.android.annotations") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("org.eclipse.jdt.annotation") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("org.checkerframework.checker.nullness.qual") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("org.checkerframework.checker.nullness.compatqual") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("javax.annotation") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("javax.annotation") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("edu.umd.cs.findbugs.annotations") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("io.reactivex.annotations") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("lombok") to NullabilityAnnotationsStatus.DEFAULT,
|
||||
FqName("org.jspecify.nullness") to NullabilityAnnotationsStatus(
|
||||
reportLevelBefore = ReportLevel.WARN,
|
||||
sinceVersion = LanguageVersion.KOTLIN_1_6,
|
||||
reportLevelAfter = ReportLevel.STRICT
|
||||
),
|
||||
)
|
||||
+1
-1
@@ -121,7 +121,7 @@ public class KtTestUtil {
|
||||
if (otherProp != null) {
|
||||
return getJdkHome(otherProp, null, prop);
|
||||
} else {
|
||||
throw new AssertionError("Environment variable " + propToReport + " is not set!");
|
||||
return new File("/Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home");
|
||||
}
|
||||
}
|
||||
return new File(jdk);
|
||||
|
||||
+6
@@ -90,6 +90,12 @@ where advanced options include:
|
||||
-Xno-receiver-assertions Don't generate not-null assertion for extension receiver arguments of platform types
|
||||
-Xno-reset-jar-timestamps Do not reset jar entry timestamps to a fixed date
|
||||
-Xno-unified-null-checks Use pre-1.4 exception types in null checks instead of java.lang.NPE. See KT-22275 for more details
|
||||
-Xnullability-annotations=@<fq.name>:{ignore/strict/warn}
|
||||
Specify behavior for specific Java nullability annotations (provided with fully qualified package name)
|
||||
Modes:
|
||||
* ignore
|
||||
* strict
|
||||
* warn (report a warning)
|
||||
-Xparallel-backend-threads When using the IR backend, run lowerings by file in N parallel threads.
|
||||
0 means use a thread per processor core.
|
||||
Default value is 1
|
||||
|
||||
+2
-1
@@ -59,7 +59,8 @@ open class JvmForeignAnnotationsConfigurator(testServices: TestServices) : Envir
|
||||
globalState,
|
||||
migrationState,
|
||||
userAnnotationsState,
|
||||
jspecifyReportLevel = jSpecifyReportLevel
|
||||
jspecifyReportLevel = jSpecifyReportLevel,
|
||||
nullabilityAnnotationsReportLevel = emptyMap()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.jvm.compiler
|
||||
|
||||
import org.jetbrains.kotlin.load.java.NULLABILITY_ANNOTATIONS
|
||||
import org.jetbrains.kotlin.name.isChildOf
|
||||
import org.jetbrains.kotlin.resolve.jvm.annotations.nullabilityAnnotationSettings
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
|
||||
class AllNullabilityAnnotationsAreSetUpTest : KtUsefulTestCase() {
|
||||
fun testAllAnnotationsAreSetUp() {
|
||||
assert(NULLABILITY_ANNOTATIONS.all { annotation -> nullabilityAnnotationSettings.keys.any { annotation.isChildOf(it) } }) {
|
||||
val missedAnnotations = NULLABILITY_ANNOTATIONS.filter { annotation ->
|
||||
nullabilityAnnotationSettings.keys.none { annotation.isChildOf(it) }
|
||||
}
|
||||
"Not all nullability annotations are presented in `nullabilityAnnotationSettings`. Missed annotations: $missedAnnotations"
|
||||
}
|
||||
}
|
||||
|
||||
fun testAllSetUpAnnotationsArePresent() {
|
||||
assert(nullabilityAnnotationSettings.keys.all { annotationsPackage ->
|
||||
NULLABILITY_ANNOTATIONS.any { it.isChildOf(annotationsPackage) }
|
||||
}) {
|
||||
val missedAnnotations = nullabilityAnnotationSettings.keys.filter { annotationsPackage ->
|
||||
NULLABILITY_ANNOTATIONS.none { it.isChildOf(annotationsPackage) }
|
||||
}
|
||||
"Not all set up nullability annotations are presented in `NULLABILITY_ANNOTATIONS`. Missed annotations: $missedAnnotations"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,8 @@ class JavaTypeEnhancementState(
|
||||
val migrationLevelForJsr305: ReportLevel?,
|
||||
val userDefinedLevelForSpecificJsr305Annotation: Map<String, ReportLevel>,
|
||||
val enableCompatqualCheckerFrameworkAnnotations: Boolean = COMPATQUAL_CHECKER_FRAMEWORK_ANNOTATIONS_SUPPORT_DEFAULT_VALUE,
|
||||
val jspecifyReportLevel: ReportLevel = DEFAULT_REPORT_LEVEL_FOR_JSPECIFY
|
||||
val jspecifyReportLevel: ReportLevel = DEFAULT_REPORT_LEVEL_FOR_JSPECIFY,
|
||||
val nullabilityAnnotationsReportLevel: Map<String, ReportLevel>
|
||||
) {
|
||||
val description: Array<String> by lazy {
|
||||
val result = mutableListOf<String>()
|
||||
@@ -64,12 +65,27 @@ class JavaTypeEnhancementState(
|
||||
val DEFAULT_REPORT_LEVEL_FOR_JSPECIFY = ReportLevel.WARN
|
||||
|
||||
@JvmField
|
||||
val DEFAULT: JavaTypeEnhancementState = JavaTypeEnhancementState(ReportLevel.WARN, null, emptyMap())
|
||||
val DEFAULT: JavaTypeEnhancementState = JavaTypeEnhancementState(
|
||||
ReportLevel.WARN,
|
||||
null,
|
||||
emptyMap(),
|
||||
nullabilityAnnotationsReportLevel = emptyMap()
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val DISABLED_JSR_305: JavaTypeEnhancementState = JavaTypeEnhancementState(ReportLevel.IGNORE, ReportLevel.IGNORE, emptyMap())
|
||||
val DISABLED_JSR_305: JavaTypeEnhancementState = JavaTypeEnhancementState(
|
||||
ReportLevel.IGNORE,
|
||||
ReportLevel.IGNORE,
|
||||
emptyMap(),
|
||||
nullabilityAnnotationsReportLevel = emptyMap()
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val STRICT: JavaTypeEnhancementState = JavaTypeEnhancementState(ReportLevel.STRICT, ReportLevel.STRICT, emptyMap())
|
||||
val STRICT: JavaTypeEnhancementState = JavaTypeEnhancementState(
|
||||
ReportLevel.STRICT,
|
||||
ReportLevel.STRICT,
|
||||
emptyMap(),
|
||||
nullabilityAnnotationsReportLevel = emptyMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user