Annotation option 'mustBeDocumented': definition, mapping from Kotlin to Java Documented and back, tests

This commit is contained in:
Mikhail Glukhikh
2015-07-30 15:30:15 +03:00
parent dfaed3fef3
commit faac06ff7e
10 changed files with 156 additions and 13 deletions
@@ -33,10 +33,7 @@ import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.TypesPackage; import org.jetbrains.kotlin.types.TypesPackage;
import org.jetbrains.org.objectweb.asm.*; import org.jetbrains.org.objectweb.asm.*;
import java.lang.annotation.ElementType; import java.lang.annotation.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.*; import java.util.*;
import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getClassObjectType; import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getClassObjectType;
@@ -123,6 +120,7 @@ public abstract class AnnotationCodegen {
if (annotated instanceof ClassDescriptor) { if (annotated instanceof ClassDescriptor) {
ClassDescriptor classDescriptor = (ClassDescriptor) annotated; ClassDescriptor classDescriptor = (ClassDescriptor) annotated;
if (classDescriptor.getKind() == ClassKind.ANNOTATION_CLASS) { if (classDescriptor.getKind() == ClassKind.ANNOTATION_CLASS) {
generateDocumentedAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
generateRetentionAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent); generateRetentionAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
generateTargetAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent); generateTargetAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
} }
@@ -218,6 +216,15 @@ public abstract class AnnotationCodegen {
visitor.visitEnd(); visitor.visitEnd();
} }
private void generateDocumentedAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> 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<String> annotationDescriptorsAlreadyPresent, Class<?> annotationClass) { private void generateAnnotationIfNotPresent(Set<String> annotationDescriptorsAlreadyPresent, Class<?> annotationClass) {
String descriptor = Type.getType(annotationClass).getDescriptor(); String descriptor = Type.getType(annotationClass).getDescriptor();
if (!annotationDescriptorsAlreadyPresent.contains(descriptor)) { if (!annotationDescriptorsAlreadyPresent.contains(descriptor)) {
@@ -0,0 +1,7 @@
annotation(mustBeDocumented = true) class DocAnn
annotation class NotDocAnn
DocAnn class My
NotDocAnn class Your
@@ -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
}
@@ -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
@@ -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
}
@@ -1022,6 +1022,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName); 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") @TestMetadata("javaretention.kt")
public void testJavaretention() throws Exception { public void testJavaretention() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/javaretention.kt"); String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/options/javaretention.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 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 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) target(AnnotationTarget.ANNOTATION_CLASS)
public annotation(retention = AnnotationRetention.SOURCE) class annotation ( public annotation(retention = AnnotationRetention.SOURCE) class annotation (
val retention: AnnotationRetention = AnnotationRetention.RUNTIME, val retention: AnnotationRetention = AnnotationRetention.RUNTIME,
val repeatable: Boolean = false val repeatable: Boolean = false,
val mustBeDocumented: Boolean = false
) )
@@ -41,6 +41,7 @@ public final class JvmAnnotationNames {
public static final Name TARGET_ANNOTATION_MEMBER_NAME = Name.identifier("allowedTargets"); 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 RETENTION_ANNOTATION_PARAMETER_NAME = Name.identifier("retention");
public static final Name REPEATABLE_ANNOTATION_PARAMETER_NAME = Name.identifier("repeatable"); 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 TARGET_ANNOTATION = new FqName("java.lang.annotation.Target");
public static final FqName RETENTION_ANNOTATION = new FqName("java.lang.annotation.Retention"); public static final FqName RETENTION_ANNOTATION = new FqName("java.lang.annotation.Retention");
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.constants.* import org.jetbrains.kotlin.resolve.constants.*
import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.ErrorUtils
import java.lang.annotation.Documented
import java.lang.annotation.Retention import java.lang.annotation.Retention
import java.lang.annotation.Target import java.lang.annotation.Target
import java.util.EnumSet import java.util.EnumSet
@@ -40,6 +41,7 @@ public object JavaAnnotationMapper {
private val javaTargetFqName = FqName(javaClass<Target>().canonicalName) private val javaTargetFqName = FqName(javaClass<Target>().canonicalName)
private val javaRetentionFqName = FqName(javaClass<Retention>().canonicalName) private val javaRetentionFqName = FqName(javaClass<Retention>().canonicalName)
private val javaDeprecatedFqName = FqName(javaClass<Deprecated>().canonicalName) private val javaDeprecatedFqName = FqName(javaClass<Deprecated>().canonicalName)
private val javaDocumentedFqName = FqName(javaClass<Documented>().canonicalName)
// Java8-specific thing // Java8-specific thing
private val javaRepeatableFqName = FqName("java.lang.annotation.Repeatable") private val javaRepeatableFqName = FqName("java.lang.annotation.Repeatable")
@@ -59,8 +61,10 @@ public object JavaAnnotationMapper {
// Construct kotlin.annotation.annotation from Retention & Repeatable // Construct kotlin.annotation.annotation from Retention & Repeatable
val retentionAnnotation = annotationOwner.findAnnotation(javaRetentionFqName) val retentionAnnotation = annotationOwner.findAnnotation(javaRetentionFqName)
val repeatableAnnotation = annotationOwner.findAnnotation(javaRepeatableFqName) val repeatableAnnotation = annotationOwner.findAnnotation(javaRepeatableFqName)
return if (retentionAnnotation != null || repeatableAnnotation != null) { val documentedAnnotation = annotationOwner.findAnnotation(javaDocumentedFqName)
JavaRetentionRepeatableAnnotationDescriptor(retentionAnnotation, repeatableAnnotation != null, c) return if (retentionAnnotation != null || repeatableAnnotation != null || documentedAnnotation != null) {
JavaRetentionRepeatableAnnotationDescriptor(retentionAnnotation, repeatableAnnotation != null,
documentedAnnotation != null, c)
} }
else { else {
null null
@@ -82,7 +86,8 @@ public object JavaAnnotationMapper {
mapOf(javaTargetFqName to KotlinBuiltIns.FQ_NAMES.target, mapOf(javaTargetFqName to KotlinBuiltIns.FQ_NAMES.target,
javaRetentionFqName to KotlinBuiltIns.FQ_NAMES.annotation, javaRetentionFqName to KotlinBuiltIns.FQ_NAMES.annotation,
javaDeprecatedFqName to KotlinBuiltIns.FQ_NAMES.deprecated, 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( abstract class AbstractJavaAnnotationDescriptor(
@@ -120,6 +125,7 @@ class JavaDeprecatedAnnotationDescriptor(
class JavaRetentionRepeatableAnnotationDescriptor( class JavaRetentionRepeatableAnnotationDescriptor(
retentionAnnotation: JavaAnnotation?, retentionAnnotation: JavaAnnotation?,
repeatable: Boolean, repeatable: Boolean,
documented: Boolean,
c: LazyJavaResolverContext c: LazyJavaResolverContext
): AbstractJavaAnnotationDescriptor(c, retentionAnnotation, c.module.builtIns.annotationAnnotation) { ): AbstractJavaAnnotationDescriptor(c, retentionAnnotation, c.module.builtIns.annotationAnnotation) {
@@ -135,8 +141,13 @@ class JavaRetentionRepeatableAnnotationDescriptor(
val repeatableParameterDescriptor = valueParameters.first { val repeatableParameterDescriptor = valueParameters.first {
it.name == JvmAnnotationNames.REPEATABLE_ANNOTATION_PARAMETER_NAME 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()) + (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() override fun getAllValueArguments() = valueArguments()
@@ -145,14 +145,18 @@ public fun CallableDescriptor.getOwnerForEffectiveDispatchReceiverParameter(): D
return getDispatchReceiverParameter()?.getContainingDeclaration() return getDispatchReceiverParameter()?.getContainingDeclaration()
} }
public fun Annotated.isRepeatableAnnotation(): Boolean { private fun Annotated.isAnnotationPropertyTrue(name: String, defaultValue: Boolean = false): Boolean {
val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return false val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return defaultValue
val repeatableArgumentValue = annotationEntryDescriptor.allValueArguments.entrySet().firstOrNull { val repeatableArgumentValue = annotationEntryDescriptor.allValueArguments.entrySet().firstOrNull {
"repeatable" == it.key.name.asString() name == it.key.name.asString()
}?.getValue() as? BooleanValue ?: return false // not repeatable by default }?.getValue() as? BooleanValue ?: return defaultValue
return repeatableArgumentValue.value return repeatableArgumentValue.value
} }
public fun Annotated.isRepeatableAnnotation(): Boolean = isAnnotationPropertyTrue("repeatable")
public fun Annotated.isDocumentedAnnotation(): Boolean = isAnnotationPropertyTrue("mustBeDocumented")
public fun Annotated.getAnnotationRetention(): KotlinRetention? { public fun Annotated.getAnnotationRetention(): KotlinRetention? {
val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return null val annotationEntryDescriptor = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation) ?: return null
val retentionArgumentValue = annotationEntryDescriptor.allValueArguments.entrySet().firstOrNull { val retentionArgumentValue = annotationEntryDescriptor.allValueArguments.entrySet().firstOrNull {