diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/Jsr305Parser.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/Jsr305Parser.kt index b21136a7779..f72fa5bfc10 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/Jsr305Parser.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/Jsr305Parser.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.utils.Jsr305State import org.jetbrains.kotlin.utils.ReportLevel class Jsr305Parser(private val collector: MessageCollector) { - fun parse(value: Array?): Jsr305State { + fun parse(value: Array?, supportCompatqualCheckerFrameworkAnnotations: String?): Jsr305State { var global: ReportLevel? = null var migration: ReportLevel? = null val userDefined = mutableMapOf() @@ -70,7 +70,25 @@ class Jsr305Parser(private val collector: MessageCollector) { } } - val state = Jsr305State(global ?: ReportLevel.WARN, migration, userDefined) + val enableCompatqualCheckerFrameworkAnnotations = when (supportCompatqualCheckerFrameworkAnnotations) { + "enable" -> true + "disable" -> false + null -> null + else -> { + collector.report( + CompilerMessageSeverity.ERROR, + "Unrecognized -Xsupport-compatqual-checker-framework-annotations option: $supportCompatqualCheckerFrameworkAnnotations. Possible values are 'enable'/'disable'" + ) + null + } + } + + val state = Jsr305State( + global ?: ReportLevel.WARN, migration, userDefined, + enableCompatqualCheckerFrameworkAnnotations = + enableCompatqualCheckerFrameworkAnnotations + ?: Jsr305State.COMPATQUAL_CHECKER_FRAMEWORK_ANNOTATIONS_SUPPORT_DEFAULT_VALUE + ) return if (state == Jsr305State.DISABLED) Jsr305State.DISABLED else state } 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 36bedc3d3a3..1bf2f2f99d0 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 @@ -16,13 +16,10 @@ package org.jetbrains.kotlin.cli.common.arguments -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.config.AnalysisFlag import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode import org.jetbrains.kotlin.config.JvmTarget -import org.jetbrains.kotlin.utils.Jsr305State -import org.jetbrains.kotlin.utils.ReportLevel class K2JVMCompilerArguments : CommonCompilerArguments() { companion object { @@ -191,6 +188,17 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { ) var jsr305: Array? by FreezableVar(null) + @Argument( + value = "-Xsupport-compatqual-checker-framework-annotations", + valueDescription = "enable|disable", + description = + """ +Specify behavior for Checker Framework compatqual annotations (NullableDecl/NonNullDecl). +Default value is 'enable' +""" + ) + var supportCompatqualCheckerFrameworkAnnotations: String? by FreezableVar(null) + @Argument( value = "-Xno-exception-on-explicit-equals-for-boxed-null", description = "Do not throw NPE on explicit 'equals' call for null receiver of platform boxed primitive type" @@ -202,7 +210,10 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { override fun configureAnalysisFlags(collector: MessageCollector): MutableMap, Any> { val result = super.configureAnalysisFlags(collector) - result[AnalysisFlag.jsr305] = Jsr305Parser(collector).parse(jsr305) + result[AnalysisFlag.jsr305] = Jsr305Parser(collector).parse( + jsr305, + supportCompatqualCheckerFrameworkAnnotations + ) return result } } diff --git a/compiler/testData/cli/jvm/compatqual/A.java b/compiler/testData/cli/jvm/compatqual/A.java new file mode 100644 index 00000000000..b0bc053d23f --- /dev/null +++ b/compiler/testData/cli/jvm/compatqual/A.java @@ -0,0 +1,3 @@ +public class A { + public void foo(@org.checkerframework.checker.nullness.compatqual.NonNullDecl String x) {} +} diff --git a/compiler/testData/cli/jvm/compatqualDefault.args b/compiler/testData/cli/jvm/compatqualDefault.args new file mode 100644 index 00000000000..497381767fa --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualDefault.args @@ -0,0 +1,5 @@ +$TESTDATA_DIR$/compatqualUsage.kt +$TESTDATA_DIR$/compatqual +$FOREIGN_ANNOTATIONS_DIR$ +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/compatqualDefault.out b/compiler/testData/cli/jvm/compatqualDefault.out new file mode 100644 index 00000000000..f3ff8034e89 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualDefault.out @@ -0,0 +1,4 @@ +compiler/testData/cli/jvm/compatqualUsage.kt:2:11: error: null can not be a value of a non-null type String + a.foo(null) + ^ +COMPILATION_ERROR diff --git a/compiler/testData/cli/jvm/compatqualDisable.args b/compiler/testData/cli/jvm/compatqualDisable.args new file mode 100644 index 00000000000..980a43d36c3 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualDisable.args @@ -0,0 +1,6 @@ +-Xsupport-compatqual-checker-framework-annotations=disable +$TESTDATA_DIR$/compatqualUsage.kt +$TESTDATA_DIR$/compatqual +$FOREIGN_ANNOTATIONS_DIR$ +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/compatqualDisable.out b/compiler/testData/cli/jvm/compatqualDisable.out new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualDisable.out @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/cli/jvm/compatqualEnable.args b/compiler/testData/cli/jvm/compatqualEnable.args new file mode 100644 index 00000000000..99dde6de749 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualEnable.args @@ -0,0 +1,6 @@ +-Xsupport-compatqual-checker-framework-annotations=enable +$TESTDATA_DIR$/compatqualUsage.kt +$TESTDATA_DIR$/compatqual +$FOREIGN_ANNOTATIONS_DIR$ +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/compatqualEnable.out b/compiler/testData/cli/jvm/compatqualEnable.out new file mode 100644 index 00000000000..f3ff8034e89 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualEnable.out @@ -0,0 +1,4 @@ +compiler/testData/cli/jvm/compatqualUsage.kt:2:11: error: null can not be a value of a non-null type String + a.foo(null) + ^ +COMPILATION_ERROR diff --git a/compiler/testData/cli/jvm/compatqualUsage.kt b/compiler/testData/cli/jvm/compatqualUsage.kt new file mode 100644 index 00000000000..bb806147851 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualUsage.kt @@ -0,0 +1,3 @@ +fun bar(a: A) { + a.foo(null) +} diff --git a/compiler/testData/cli/jvm/compatqualWrong.args b/compiler/testData/cli/jvm/compatqualWrong.args new file mode 100644 index 00000000000..f05608f4664 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualWrong.args @@ -0,0 +1,6 @@ +-Xsupport-compatqual-checker-framework-annotations=true +$TESTDATA_DIR$/compatqualUsage.kt +$TESTDATA_DIR$/compatqual +$FOREIGN_ANNOTATIONS_DIR$ +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/compatqualWrong.out b/compiler/testData/cli/jvm/compatqualWrong.out new file mode 100644 index 00000000000..d2044714ec9 --- /dev/null +++ b/compiler/testData/cli/jvm/compatqualWrong.out @@ -0,0 +1,2 @@ +error: unrecognized -Xsupport-compatqual-checker-framework-annotations option: true. Possible values are 'enable'/'disable' +COMPILATION_ERROR diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index c2d84955e4c..be0578bff13 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -35,6 +35,11 @@ where advanced options include: -Xskip-runtime-version-check Allow Kotlin runtime libraries of incompatible versions in the classpath -Xstrict-java-nullability-assertions Generate nullability assertions for non-null Java expressions + -Xsupport-compatqual-checker-framework-annotations=enable|disable + + Specify behavior for Checker Framework compatqual annotations (NullableDecl/NonNullDecl). + Default value is 'enable' + -Xuse-javac Use javac for Java source and class files analysis -Xuse-old-class-files-reading Use old class files reading implementation (may slow down the build and should be used in case of problems with the new implementation) -Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java index 02f6253d819..7605ffa4971 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -99,6 +99,30 @@ public class CliTestGenerated extends AbstractCliTest { doJvmTest(fileName); } + @TestMetadata("compatqualDefault.args") + public void testCompatqualDefault() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/compatqualDefault.args"); + doJvmTest(fileName); + } + + @TestMetadata("compatqualDisable.args") + public void testCompatqualDisable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/compatqualDisable.args"); + doJvmTest(fileName); + } + + @TestMetadata("compatqualEnable.args") + public void testCompatqualEnable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/compatqualEnable.args"); + doJvmTest(fileName); + } + + @TestMetadata("compatqualWrong.args") + public void testCompatqualWrong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/compatqualWrong.args"); + doJvmTest(fileName); + } + @TestMetadata("conflictingOverloads.args") public void testConflictingOverloads() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/conflictingOverloads.args"); diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.kt index ade63ee0c04..f83e3af6e4f 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.kt @@ -24,7 +24,6 @@ val NULLABLE_ANNOTATIONS = listOf( FqName("com.android.annotations.Nullable"), FqName("org.eclipse.jdt.annotation.Nullable"), FqName("org.checkerframework.checker.nullness.qual.Nullable"), - FqName("org.checkerframework.checker.nullness.compatqual.NullableDecl"), FqName("javax.annotation.Nullable"), FqName("javax.annotation.CheckForNull"), FqName("edu.umd.cs.findbugs.annotations.CheckForNull"), @@ -43,11 +42,13 @@ val NOT_NULL_ANNOTATIONS = listOf( FqName("com.android.annotations.NonNull"), FqName("org.eclipse.jdt.annotation.NonNull"), FqName("org.checkerframework.checker.nullness.qual.NonNull"), - FqName("org.checkerframework.checker.nullness.compatqual.NonNullDecl"), FqName("lombok.NonNull"), FqName("io.reactivex.annotations.NonNull") ) +val COMPATQUAL_NULLABLE_ANNOTATION = FqName("org.checkerframework.checker.nullness.compatqual.NullableDecl") +val COMPATQUAL_NONNULL_ANNOTATION = FqName("org.checkerframework.checker.nullness.compatqual.NonNullDecl") + val READ_ONLY_ANNOTATIONS = listOf( JvmAnnotationNames.JETBRAINS_READONLY_ANNOTATION, JvmAnnotationNames.READONLY_ANNOTATION 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 8fe579ddc7a..98aa35220f3 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 @@ -31,13 +31,10 @@ import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.platform.JavaToKotlinClassMap import org.jetbrains.kotlin.resolve.descriptorUtil.firstArgumentValue -import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeUtils -import org.jetbrains.kotlin.types.asFlexibleType +import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker -import org.jetbrains.kotlin.types.isFlexible import org.jetbrains.kotlin.types.typeUtil.isTypeParameter -import org.jetbrains.kotlin.types.unwrapEnhancement +import org.jetbrains.kotlin.utils.Jsr305State import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -46,7 +43,10 @@ data class NullabilityQualifierWithMigrationStatus( val isForWarningOnly: Boolean = false ) -class SignatureEnhancement(private val annotationTypeQualifierResolver: AnnotationTypeQualifierResolver) { +class SignatureEnhancement( + private val annotationTypeQualifierResolver: AnnotationTypeQualifierResolver, + private val jsr305State: Jsr305State +) { private fun AnnotationDescriptor.extractNullabilityTypeFromArgument(): NullabilityQualifierWithMigrationStatus? { val enumEntryDescriptor = firstArgumentValue() @@ -81,10 +81,16 @@ class SignatureEnhancement(private val annotationTypeQualifierResolver: Annotati ): NullabilityQualifierWithMigrationStatus? { val annotationFqName = annotationDescriptor.fqName ?: return null - return when (annotationFqName) { - in NULLABLE_ANNOTATIONS -> NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NULLABLE) - in NOT_NULL_ANNOTATIONS -> NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL) - JAVAX_NONNULL_ANNOTATION -> annotationDescriptor.extractNullabilityTypeFromArgument() + return when { + annotationFqName in NULLABLE_ANNOTATIONS -> NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NULLABLE) + annotationFqName in NOT_NULL_ANNOTATIONS -> NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL) + annotationFqName == JAVAX_NONNULL_ANNOTATION -> annotationDescriptor.extractNullabilityTypeFromArgument() + + annotationFqName == COMPATQUAL_NULLABLE_ANNOTATION && jsr305State.enableCompatqualCheckerFrameworkAnnotations -> + NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NULLABLE) + + annotationFqName == COMPATQUAL_NONNULL_ANNOTATION && jsr305State.enableCompatqualCheckerFrameworkAnnotations -> + NullabilityQualifierWithMigrationStatus(NullabilityQualifier.NOT_NULL) else -> null } } diff --git a/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimeModuleData.kt b/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimeModuleData.kt index 7fea1b8f848..9c52dd12553 100644 --- a/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimeModuleData.kt +++ b/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimeModuleData.kt @@ -68,7 +68,7 @@ class RuntimeModuleData private constructor( runtimePackagePartProvider, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module, ReflectionTypes(module, notFoundClasses), annotationTypeQualifierResolver, - SignatureEnhancement(annotationTypeQualifierResolver), + SignatureEnhancement(annotationTypeQualifierResolver, Jsr305State.DISABLED), JavaClassesTracker.Default ) diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/Jsr305State.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/Jsr305State.kt index 0f9885a2e7d..7602d4c1442 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/Jsr305State.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/Jsr305State.kt @@ -33,7 +33,8 @@ enum class ReportLevel(val description: String) { data class Jsr305State( val global: ReportLevel, val migration: ReportLevel?, - val user: Map + val user: Map, + val enableCompatqualCheckerFrameworkAnnotations: Boolean = COMPATQUAL_CHECKER_FRAMEWORK_ANNOTATIONS_SUPPORT_DEFAULT_VALUE ) { val description: Array by lazy { val result = mutableListOf() @@ -51,6 +52,8 @@ data class Jsr305State( val disabled: Boolean get() = this === DISABLED companion object { + const val COMPATQUAL_CHECKER_FRAMEWORK_ANNOTATIONS_SUPPORT_DEFAULT_VALUE = true + @JvmField val DEFAULT: Jsr305State = Jsr305State(ReportLevel.WARN, null, emptyMap()) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IDELanguageSettingsProvider.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IDELanguageSettingsProvider.kt index 619dc8769e5..9da7b2ca812 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IDELanguageSettingsProvider.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IDELanguageSettingsProvider.kt @@ -47,7 +47,10 @@ object IDELanguageSettingsProvider : LanguageSettingsProvider { val settings = KotlinFacetSettingsProvider.getInstance(project).getSettings(module) ?: continue val compilerArguments = settings.mergedCompilerArguments as? K2JVMCompilerArguments ?: continue - val jsr305State = Jsr305Parser(MessageCollector.NONE).parse(compilerArguments.jsr305) + val jsr305State = Jsr305Parser(MessageCollector.NONE).parse( + compilerArguments.jsr305, + compilerArguments.supportCompatqualCheckerFrameworkAnnotations + ) map.put(AnalysisFlag.jsr305, jsr305State) } return map