Introduce compiler flag to manage status of specific Java nullability annotations

This commit is contained in:
Victor Petukhov
2021-05-26 19:21:56 +03:00
parent c3a3e99724
commit c8af1b735f
8 changed files with 150 additions and 21 deletions
@@ -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
}
@@ -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 {
@@ -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
),
)
@@ -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
View File
@@ -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
@@ -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()
)
}
}