From faac06ff7e428fc31ccdb6c3531b9ea679398f4d Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 30 Jul 2015 15:30:15 +0300 Subject: [PATCH] Annotation option 'mustBeDocumented': definition, mapping from Kotlin to Java Documented and back, tests --- .../kotlin/codegen/AnnotationCodegen.java | 15 ++++-- .../tests/annotations/options/documented.kt | 7 +++ .../tests/annotations/options/documented.txt | 29 +++++++++++ .../annotations/options/javaDocumented.kt | 20 ++++++++ .../annotations/options/javaDocumented.txt | 50 +++++++++++++++++++ .../checkers/JetDiagnosticsTestGenerated.java | 12 +++++ .../src/kotlin/annotation/Annotations.kt | 4 +- .../kotlin/load/java/JvmAnnotationNames.java | 1 + .../java/components/JavaAnnotationMapper.kt | 19 +++++-- .../kotlin/resolve/DescriptorUtils.kt | 12 +++-- 10 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/annotations/options/documented.kt create mode 100644 compiler/testData/diagnostics/tests/annotations/options/documented.txt create mode 100644 compiler/testData/diagnostics/tests/annotations/options/javaDocumented.kt create mode 100644 compiler/testData/diagnostics/tests/annotations/options/javaDocumented.txt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AnnotationCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/AnnotationCodegen.java index 8a2bb9ee0ac..57b22ea7723 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AnnotationCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AnnotationCodegen.java @@ -33,10 +33,7 @@ import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.types.TypesPackage; import org.jetbrains.org.objectweb.asm.*; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; import java.util.*; import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getClassObjectType; @@ -123,6 +120,7 @@ public abstract class AnnotationCodegen { if (annotated instanceof ClassDescriptor) { ClassDescriptor classDescriptor = (ClassDescriptor) annotated; if (classDescriptor.getKind() == ClassKind.ANNOTATION_CLASS) { + generateDocumentedAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent); generateRetentionAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent); generateTargetAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent); } @@ -218,6 +216,15 @@ public abstract class AnnotationCodegen { visitor.visitEnd(); } + private void generateDocumentedAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set annotationDescriptorsAlreadyPresent) { + boolean documented = DescriptorUtilPackage.isDocumentedAnnotation(classDescriptor); + if (!documented) return; + String descriptor = Type.getType(Documented.class).getDescriptor(); + if (!annotationDescriptorsAlreadyPresent.add(descriptor)) return; + AnnotationVisitor visitor = visitAnnotation(descriptor, true); + visitor.visitEnd(); + } + private void generateAnnotationIfNotPresent(Set annotationDescriptorsAlreadyPresent, Class annotationClass) { String descriptor = Type.getType(annotationClass).getDescriptor(); if (!annotationDescriptorsAlreadyPresent.contains(descriptor)) { diff --git a/compiler/testData/diagnostics/tests/annotations/options/documented.kt b/compiler/testData/diagnostics/tests/annotations/options/documented.kt new file mode 100644 index 00000000000..3c57f8a8983 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/options/documented.kt @@ -0,0 +1,7 @@ +annotation(mustBeDocumented = true) class DocAnn + +annotation class NotDocAnn + +DocAnn class My + +NotDocAnn class Your diff --git a/compiler/testData/diagnostics/tests/annotations/options/documented.txt b/compiler/testData/diagnostics/tests/annotations/options/documented.txt new file mode 100644 index 00000000000..affb7a9d5b4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/options/documented.txt @@ -0,0 +1,29 @@ +package + +kotlin.annotation.annotation(mustBeDocumented = true) internal final class DocAnn : kotlin.Annotation { + public constructor DocAnn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +DocAnn() internal final class My { + public constructor My() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +kotlin.annotation.annotation() internal final class NotDocAnn : kotlin.Annotation { + public constructor NotDocAnn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +NotDocAnn() internal final class Your { + public constructor Your() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/annotations/options/javaDocumented.kt b/compiler/testData/diagnostics/tests/annotations/options/javaDocumented.kt new file mode 100644 index 00000000000..f861f90d306 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/options/javaDocumented.kt @@ -0,0 +1,20 @@ +// FILE: DocumentedAnnotations.java + +import java.lang.annotation.*; + +public class DocumentedAnnotations { + + @Documented public @interface DocAnn; + + public @interface NotDocAnn; + + @Documented @Retention(RetentionPolicy.RUNTIME) public @interface RunDocAnn; +} + +// FILE: DocumentedAnnotations.kt + +DocumentedAnnotations.DocAnn class My + +DocumentedAnnotations.NotDocAnn class Your + +DocumentedAnnotations.RunDocAnn class His diff --git a/compiler/testData/diagnostics/tests/annotations/options/javaDocumented.txt b/compiler/testData/diagnostics/tests/annotations/options/javaDocumented.txt new file mode 100644 index 00000000000..88503712f2e --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/options/javaDocumented.txt @@ -0,0 +1,50 @@ +package + +public open class DocumentedAnnotations { + public constructor DocumentedAnnotations() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + java.lang.annotation.Documented() kotlin.annotation.annotation(mustBeDocumented = true) public final class DocAnn : kotlin.Annotation { + public constructor DocAnn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class NotDocAnn : kotlin.Annotation { + public constructor NotDocAnn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + java.lang.annotation.Documented() kotlin.annotation.annotation(mustBeDocumented = true, retention = AnnotationRetention.RUNTIME) public final class RunDocAnn : kotlin.Annotation { + public constructor RunDocAnn() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +DocumentedAnnotations.RunDocAnn() internal final class His { + public constructor His() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +DocumentedAnnotations.DocAnn() internal final class My { + public constructor My() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +DocumentedAnnotations.NotDocAnn() internal final class Your { + public constructor Your() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + 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/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index b84de03e4f0..e7ef5e784c6 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -1022,6 +1022,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("documented.kt") + public void testDocumented() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/documented.kt"); + doTest(fileName); + } + + @TestMetadata("javaDocumented.kt") + public void testJavaDocumented() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/javaDocumented.kt"); + doTest(fileName); + } + @TestMetadata("javaretention.kt") public void testJavaretention() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/javaretention.kt"); diff --git a/core/builtins/src/kotlin/annotation/Annotations.kt b/core/builtins/src/kotlin/annotation/Annotations.kt index 8732725225d..876621e617f 100644 --- a/core/builtins/src/kotlin/annotation/Annotations.kt +++ b/core/builtins/src/kotlin/annotation/Annotations.kt @@ -83,9 +83,11 @@ public annotation class target(vararg val allowedTargets: AnnotationTarget) * * @property retention determines whether the annotation is stored in binary output and visible for reflection. By default, both are true. * @property repeatable true if annotation is repeatable (applicable twice or more on a single code element), otherwise false (default) + * @property mustBeDocumented true if annotation is a part of public API and therefore must be documented, otherwise false (default) */ target(AnnotationTarget.ANNOTATION_CLASS) public annotation(retention = AnnotationRetention.SOURCE) class annotation ( val retention: AnnotationRetention = AnnotationRetention.RUNTIME, - val repeatable: Boolean = false + val repeatable: Boolean = false, + val mustBeDocumented: Boolean = false ) diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java index 6adf563543a..ba120828d71 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java @@ -41,6 +41,7 @@ public final class JvmAnnotationNames { public static final Name TARGET_ANNOTATION_MEMBER_NAME = Name.identifier("allowedTargets"); public static final Name RETENTION_ANNOTATION_PARAMETER_NAME = Name.identifier("retention"); public static final Name REPEATABLE_ANNOTATION_PARAMETER_NAME = Name.identifier("repeatable"); + public static final Name DOCUMENTED_ANNOTATION_PARAMETER_NAME = Name.identifier("mustBeDocumented"); public static final FqName TARGET_ANNOTATION = new FqName("java.lang.annotation.Target"); public static final FqName RETENTION_ANNOTATION = new FqName("java.lang.annotation.Retention"); diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt index 504f7ad49ed..b5ff29246ac 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.constants.* import org.jetbrains.kotlin.types.ErrorUtils +import java.lang.annotation.Documented import java.lang.annotation.Retention import java.lang.annotation.Target import java.util.EnumSet @@ -40,6 +41,7 @@ public object JavaAnnotationMapper { private val javaTargetFqName = FqName(javaClass().canonicalName) private val javaRetentionFqName = FqName(javaClass().canonicalName) private val javaDeprecatedFqName = FqName(javaClass().canonicalName) + private val javaDocumentedFqName = FqName(javaClass().canonicalName) // Java8-specific thing private val javaRepeatableFqName = FqName("java.lang.annotation.Repeatable") @@ -59,8 +61,10 @@ public object JavaAnnotationMapper { // Construct kotlin.annotation.annotation from Retention & Repeatable val retentionAnnotation = annotationOwner.findAnnotation(javaRetentionFqName) val repeatableAnnotation = annotationOwner.findAnnotation(javaRepeatableFqName) - return if (retentionAnnotation != null || repeatableAnnotation != null) { - JavaRetentionRepeatableAnnotationDescriptor(retentionAnnotation, repeatableAnnotation != null, c) + val documentedAnnotation = annotationOwner.findAnnotation(javaDocumentedFqName) + return if (retentionAnnotation != null || repeatableAnnotation != null || documentedAnnotation != null) { + JavaRetentionRepeatableAnnotationDescriptor(retentionAnnotation, repeatableAnnotation != null, + documentedAnnotation != null, c) } else { null @@ -82,7 +86,8 @@ public object JavaAnnotationMapper { mapOf(javaTargetFqName to KotlinBuiltIns.FQ_NAMES.target, javaRetentionFqName to KotlinBuiltIns.FQ_NAMES.annotation, javaDeprecatedFqName to KotlinBuiltIns.FQ_NAMES.deprecated, - javaRepeatableFqName to KotlinBuiltIns.FQ_NAMES.annotation) + javaRepeatableFqName to KotlinBuiltIns.FQ_NAMES.annotation, + javaDocumentedFqName to KotlinBuiltIns.FQ_NAMES.annotation) } abstract class AbstractJavaAnnotationDescriptor( @@ -120,6 +125,7 @@ class JavaDeprecatedAnnotationDescriptor( class JavaRetentionRepeatableAnnotationDescriptor( retentionAnnotation: JavaAnnotation?, repeatable: Boolean, + documented: Boolean, c: LazyJavaResolverContext ): AbstractJavaAnnotationDescriptor(c, retentionAnnotation, c.module.builtIns.annotationAnnotation) { @@ -135,8 +141,13 @@ class JavaRetentionRepeatableAnnotationDescriptor( val repeatableParameterDescriptor = valueParameters.first { it.name == JvmAnnotationNames.REPEATABLE_ANNOTATION_PARAMETER_NAME } + val documentedArgument = if (documented) BooleanValue(true, c.module.builtIns) else null + val documentedParameterDescriptor = valueParameters.first { + it.name == JvmAnnotationNames.DOCUMENTED_ANNOTATION_PARAMETER_NAME + } (retentionArgument?.let { mapOf(retentionParameterDescriptor to it) } ?: emptyMap()) + - (repeatableArgument?.let { mapOf(repeatableParameterDescriptor to it) } ?: emptyMap()) + (repeatableArgument?.let { mapOf(repeatableParameterDescriptor to it) } ?: emptyMap()) + + (documentedArgument?.let { mapOf(documentedParameterDescriptor to it) } ?: emptyMap()) } override fun getAllValueArguments() = valueArguments() diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index 1de7299c12f..0f242b3969a 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -145,14 +145,18 @@ public fun CallableDescriptor.getOwnerForEffectiveDispatchReceiverParameter(): D return getDispatchReceiverParameter()?.getContainingDeclaration() } -public fun Annotated.isRepeatableAnnotation(): Boolean { - val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return false +private fun Annotated.isAnnotationPropertyTrue(name: String, defaultValue: Boolean = false): Boolean { + val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return defaultValue val repeatableArgumentValue = annotationEntryDescriptor.allValueArguments.entrySet().firstOrNull { - "repeatable" == it.key.name.asString() - }?.getValue() as? BooleanValue ?: return false // not repeatable by default + name == it.key.name.asString() + }?.getValue() as? BooleanValue ?: return defaultValue return repeatableArgumentValue.value } +public fun Annotated.isRepeatableAnnotation(): Boolean = isAnnotationPropertyTrue("repeatable") + +public fun Annotated.isDocumentedAnnotation(): Boolean = isAnnotationPropertyTrue("mustBeDocumented") + public fun Annotated.getAnnotationRetention(): KotlinRetention? { val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return null val retentionArgumentValue = annotationEntryDescriptor.allValueArguments.entrySet().firstOrNull {