Annotation option 'mustBeDocumented': definition, mapping from Kotlin to Java Documented and back, tests
This commit is contained in:
@@ -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");
|
||||||
|
|||||||
+15
-4
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user