From c8af1b735f4bbd923defa08a0c9e145d21049e63 Mon Sep 17 00:00:00 2001 From: Victor Petukhov Date: Wed, 26 May 2021 19:21:56 +0300 Subject: [PATCH] Introduce compiler flag to manage status of specific Java nullability annotations --- .../JavaTypeEnhancementStateParser.kt | 56 ++++++++++++++----- .../arguments/K2JVMCompilerArguments.kt | 14 ++++- .../jvm/annotations/jvmAnnotationUtil.kt | 33 +++++++++++ .../kotlin/test/util/KtTestUtil.java | 2 +- compiler/testData/cli/jvm/extraHelp.out | 6 ++ .../JvmForeignAnnotationsConfigurator.kt | 3 +- .../AllNullabilityAnnotationsAreSetUp.kt | 33 +++++++++++ .../kotlin/utils/JavaTypeEnhancementState.kt | 24 ++++++-- 8 files changed, 150 insertions(+), 21 deletions(-) create mode 100644 compiler/tests/org/jetbrains/kotlin/jvm/compiler/AllNullabilityAnnotationsAreSetUp.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/JavaTypeEnhancementStateParser.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/JavaTypeEnhancementStateParser.kt index 3f02f9abdc0..96756352dd0 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/JavaTypeEnhancementStateParser.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/JavaTypeEnhancementStateParser.kt @@ -25,7 +25,8 @@ class JavaTypeEnhancementStateParser(private val collector: MessageCollector) { fun parse( jsr305Args: Array?, supportCompatqualCheckerFrameworkAnnotations: String?, - jspecifyState: String? + jspecifyState: String?, + nullabilityAnnotations: Array?, ): 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?): Map { + if (nullabilityAnnotations.isNullOrEmpty()) return emptyMap() + + val annotationsWithReportLevels = mutableMapOf() + 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() + 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? { + private fun parseAnnotationWithReportLevel(item: String, sourceCompilerOption: String): Pair? { 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 } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index 086bd202c6f..8b3cb275122 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -282,6 +282,17 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { ) var jsr305: Array? by FreezableVar(null) + @Argument( + value = "-Xnullability-annotations", + valueDescription = "@:{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? 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 { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt index c0b9bdc1a7a..b1259fc9bc4 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt @@ -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 + ), +) \ No newline at end of file diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/KtTestUtil.java b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/KtTestUtil.java index 7e4380b4c45..c257e37521c 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/KtTestUtil.java +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/KtTestUtil.java @@ -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); diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index 184d830eb66..d93c49e7c3d 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -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=@:{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 diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmForeignAnnotationsConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmForeignAnnotationsConfigurator.kt index 1776c552ce7..08011f471fb 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmForeignAnnotationsConfigurator.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmForeignAnnotationsConfigurator.kt @@ -59,7 +59,8 @@ open class JvmForeignAnnotationsConfigurator(testServices: TestServices) : Envir globalState, migrationState, userAnnotationsState, - jspecifyReportLevel = jSpecifyReportLevel + jspecifyReportLevel = jSpecifyReportLevel, + nullabilityAnnotationsReportLevel = emptyMap() ) ) } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AllNullabilityAnnotationsAreSetUp.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AllNullabilityAnnotationsAreSetUp.kt new file mode 100644 index 00000000000..827ed98e65e --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AllNullabilityAnnotationsAreSetUp.kt @@ -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" + } + } +} diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/JavaTypeEnhancementState.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/JavaTypeEnhancementState.kt index 6a3fc3d8a7c..94b93613961 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/JavaTypeEnhancementState.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/JavaTypeEnhancementState.kt @@ -35,7 +35,8 @@ class JavaTypeEnhancementState( val migrationLevelForJsr305: ReportLevel?, val userDefinedLevelForSpecificJsr305Annotation: Map, 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 ) { val description: Array by lazy { val result = mutableListOf() @@ -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() + ) } }