From ac87ad422d0e9a9d4dbc952ee33fe471bd5c1d3a Mon Sep 17 00:00:00 2001 From: Chris Povirk Date: Mon, 4 Dec 2017 17:22:54 -0500 Subject: [PATCH] Recognize Checker Framework declaration annotations. We are migrating Guava to use these annotations rather than jsr305's @Nullable. We can't use the Checker Framework's _@Nullable_ yet because we promise compatibility with Java 7, which doesn't support type annotations. This is related to but distinct from https://youtrack.jetbrains.com/issue/KT-21408, which is about a different jsr305 annotation we use, @ParametersAreNonnullByDefault. I've also updated some docs to mention Kotlin's existing support for the Checker Framework _@NonNull_. --- .../tests/checkerFramework.kt | 33 +++++++++++++++++++ .../tests/checkerFramework.txt | 13 ++++++++ ...sNoAnnotationInClasspathTestGenerated.java | 6 ++++ ...pathWithFastClassReadingTestGenerated.java | 6 ++++ .../ForeignAnnotationsTestGenerated.java | 6 ++++ .../JavacForeignAnnotationsTestGenerated.java | 6 ++++ .../kotlin/load/java/JvmAnnotationNames.kt | 2 ++ spec-docs/flexible-java-types.md | 6 +++- .../nullness/compatqual/NonNullDecl.java | 15 +++++++++ .../nullness/compatqual/NullableDecl.java | 15 +++++++++ 10 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 compiler/testData/foreignAnnotations/tests/checkerFramework.kt create mode 100644 compiler/testData/foreignAnnotations/tests/checkerFramework.txt create mode 100644 third-party/annotations/org/checkerframework/checker/nullness/compatqual/NonNullDecl.java create mode 100644 third-party/annotations/org/checkerframework/checker/nullness/compatqual/NullableDecl.java diff --git a/compiler/testData/foreignAnnotations/tests/checkerFramework.kt b/compiler/testData/foreignAnnotations/tests/checkerFramework.kt new file mode 100644 index 00000000000..6649ad634c6 --- /dev/null +++ b/compiler/testData/foreignAnnotations/tests/checkerFramework.kt @@ -0,0 +1,33 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER +// FILE: A.java + +import org.checkerframework.checker.nullness.compatqual.*; + +public class A { + @NullableDecl public String field = null; + + @NullableDecl + public String foo(@NonNullDecl String x, @NullableDecl CharSequence y) { + return ""; + } + + @NonNullDecl + public String bar() { + return ""; + } + +} + +// FILE: main.kt + +fun main(a: A) { + a.foo("", null)?.length + a.foo("", null).length + a.foo(null, "").length + + a.bar().length + a.bar()!!.length + + a.field?.length + a.field.length +} diff --git a/compiler/testData/foreignAnnotations/tests/checkerFramework.txt b/compiler/testData/foreignAnnotations/tests/checkerFramework.txt new file mode 100644 index 00000000000..fc2fd111586 --- /dev/null +++ b/compiler/testData/foreignAnnotations/tests/checkerFramework.txt @@ -0,0 +1,13 @@ +package + +public fun main(/*0*/ a: A): kotlin.Unit + +public open class A { + public constructor A() + @org.checkerframework.checker.nullness.compatqual.NullableDecl public final var field: kotlin.String? + @org.checkerframework.checker.nullness.compatqual.NonNullDecl public open fun bar(): kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @org.checkerframework.checker.nullness.compatqual.NullableDecl public open fun foo(/*0*/ @org.checkerframework.checker.nullness.compatqual.NonNullDecl x: kotlin.String, /*1*/ @org.checkerframework.checker.nullness.compatqual.NullableDecl y: kotlin.CharSequence?): kotlin.String? + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathTestGenerated.java index 64ff45a2d22..79cf0855ab4 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathTestGenerated.java @@ -48,6 +48,12 @@ public class ForeignAnnotationsNoAnnotationInClasspathTestGenerated extends Abst doTest(fileName); } + @TestMetadata("checkerFramework.kt") + public void testCheckerFramework() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/checkerFramework.kt"); + doTest(fileName); + } + @TestMetadata("eclipse.kt") public void testEclipse() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/eclipse.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTestGenerated.java index 23c90871576..7a195a573f6 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTestGenerated.java @@ -48,6 +48,12 @@ public class ForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTestGe doTest(fileName); } + @TestMetadata("checkerFramework.kt") + public void testCheckerFramework() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/checkerFramework.kt"); + doTest(fileName); + } + @TestMetadata("eclipse.kt") public void testEclipse() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/eclipse.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java index 611c700d4ed..eb8ca04d278 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java @@ -48,6 +48,12 @@ public class ForeignAnnotationsTestGenerated extends AbstractForeignAnnotationsT doTest(fileName); } + @TestMetadata("checkerFramework.kt") + public void testCheckerFramework() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/checkerFramework.kt"); + doTest(fileName); + } + @TestMetadata("eclipse.kt") public void testEclipse() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/eclipse.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignAnnotationsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignAnnotationsTestGenerated.java index e4a9dd4b0e8..cfe27c71e53 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignAnnotationsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/JavacForeignAnnotationsTestGenerated.java @@ -48,6 +48,12 @@ public class JavacForeignAnnotationsTestGenerated extends AbstractJavacForeignAn doTest(fileName); } + @TestMetadata("checkerFramework.kt") + public void testCheckerFramework() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/checkerFramework.kt"); + doTest(fileName); + } + @TestMetadata("eclipse.kt") public void testEclipse() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/eclipse.kt"); 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 675a4d453f5..ade63ee0c04 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,6 +24,7 @@ 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"), @@ -42,6 +43,7 @@ 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") ) diff --git a/spec-docs/flexible-java-types.md b/spec-docs/flexible-java-types.md index 071eb9374ed..ac1f354a7d0 100644 --- a/spec-docs/flexible-java-types.md +++ b/spec-docs/flexible-java-types.md @@ -498,4 +498,8 @@ We can also support the following annotations out-of-the-box: * `NotNull` and `NotNull.List` * [Project Lombok](http://projectlombok.org/features/NonNull.html) * [`org.eclipse.jdt.annotation`](https://wiki.eclipse.org/JDT_Core/Null_Analysis) -* [`org.checkerframework.checker.nullness.qual`](http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#nullness-checker) +* [`org.checkerframework.checker.nullness`](https://checkerframework.org/manual/#nullness-checker) + * [`*.qual.Nullable`](https://checkerframework.org/api/org/checkerframework/checker/nullness/qual/Nullable.html) + * [`*.qual.NonNull`](https://checkerframework.org/api/org/checkerframework/checker/nullness/qual/NonNull.html) + * [`*.compatqual.NullableDecl`](https://checkerframework.org/api/org/checkerframework/checker/nullness/compatqual/NullableDecl.html) + * [`*.compatqual.NonNullDecl`](https://checkerframework.org/api/org/checkerframework/checker/nullness/compatqual/NonNullDecl.html) diff --git a/third-party/annotations/org/checkerframework/checker/nullness/compatqual/NonNullDecl.java b/third-party/annotations/org/checkerframework/checker/nullness/compatqual/NonNullDecl.java new file mode 100644 index 00000000000..bab17a5ef6e --- /dev/null +++ b/third-party/annotations/org/checkerframework/checker/nullness/compatqual/NonNullDecl.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.nullness.compatqual; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Java 7 compatibility annotation without dependency on Java 8 classes. + * + * @see org.checkerframework.checker.nullness.qual.NonNull + * @checker_framework.manual #nullness-checker Nullness Checker + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface NonNullDecl {} diff --git a/third-party/annotations/org/checkerframework/checker/nullness/compatqual/NullableDecl.java b/third-party/annotations/org/checkerframework/checker/nullness/compatqual/NullableDecl.java new file mode 100644 index 00000000000..30ac96b20de --- /dev/null +++ b/third-party/annotations/org/checkerframework/checker/nullness/compatqual/NullableDecl.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.nullness.compatqual; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Java 7 compatibility annotation without dependency on Java 8 classes. + * + * @see org.checkerframework.checker.nullness.qual.Nullable + * @checker_framework.manual #nullness-checker Nullness Checker + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface NullableDecl {}