Introduce internal RequireKotlin annotation

This commit is contained in:
Alexander Udalov
2017-09-28 14:48:40 +03:00
parent 8962911503
commit 4532f7556c
4 changed files with 132 additions and 12 deletions
@@ -28,7 +28,10 @@ import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry
import org.jetbrains.kotlin.resolve.MemberComparator
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.IntValue
import org.jetbrains.kotlin.resolve.constants.NullValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.serialization.deserialization.descriptors.VersionRequirement
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.contains
@@ -134,6 +137,11 @@ class DescriptorSerializer private constructor(
builder.typeTable = typeTableProto
}
val requirement = serializeVersionRequirement(classDescriptor)
if (requirement != null) {
builder.versionRequirement = requirement
}
val versionRequirementTableProto = versionRequirementTable.serialize()
if (versionRequirementTableProto != null) {
builder.versionRequirementTable = versionRequirementTableProto
@@ -216,7 +224,11 @@ class DescriptorSerializer private constructor(
}
}
if (descriptor.isSuspendOrHasSuspendTypesInSignature()) {
val requirement = serializeVersionRequirement(descriptor)
if (requirement != null) {
builder.versionRequirement = requirement
}
else if (descriptor.isSuspendOrHasSuspendTypesInSignature()) {
builder.versionRequirement = writeVersionRequirement(LanguageFeature.Coroutines)
}
@@ -273,7 +285,11 @@ class DescriptorSerializer private constructor(
}
}
if (descriptor.isSuspendOrHasSuspendTypesInSignature()) {
val requirement = serializeVersionRequirement(descriptor)
if (requirement != null) {
builder.versionRequirement = requirement
}
else if (descriptor.isSuspendOrHasSuspendTypesInSignature()) {
builder.versionRequirement = writeVersionRequirement(LanguageFeature.Coroutines)
}
@@ -296,7 +312,11 @@ class DescriptorSerializer private constructor(
builder.addValueParameter(local.valueParameter(valueParameterDescriptor))
}
if (descriptor.isSuspendOrHasSuspendTypesInSignature()) {
val requirement = serializeVersionRequirement(descriptor)
if (requirement != null) {
builder.versionRequirement = requirement
}
else if (descriptor.isSuspendOrHasSuspendTypesInSignature()) {
builder.versionRequirement = writeVersionRequirement(LanguageFeature.Coroutines)
}
@@ -346,6 +366,11 @@ class DescriptorSerializer private constructor(
builder.setExpandedType(local.type(expandedType))
}
val requirement = serializeVersionRequirement(descriptor)
if (requirement != null) {
builder.versionRequirement = requirement
}
builder.addAllAnnotation(descriptor.annotations.map { extension.annotationSerializer.serializeAnnotation(it) })
return builder
@@ -578,6 +603,44 @@ class DescriptorSerializer private constructor(
return versionRequirementTable[requirement]
}
// Returns index into versionRequirementTable, or null if there's no @RequireKotlin on the descriptor
private fun serializeVersionRequirement(descriptor: DeclarationDescriptor): Int? {
val annotation = descriptor.annotations.findAnnotation(RequireKotlinNames.FQ_NAME) ?: return null
val args = annotation.allValueArguments
val versionString = (args[RequireKotlinNames.VERSION] as? StringValue)?.value ?: return null
val matchResult = RequireKotlinNames.VERSION_REGEX.matchEntire(versionString) ?: return null
val major = matchResult.groupValues.getOrNull(1)?.toIntOrNull() ?: return null
val minor = matchResult.groupValues.getOrNull(2)?.toIntOrNull() ?: 0
val patch = matchResult.groupValues.getOrNull(3)?.toIntOrNull() ?: 0
val proto = ProtoBuf.VersionRequirement.newBuilder()
VersionRequirement.Version(major, minor, patch).encode(
writeVersion = { proto.version = it },
writeVersionFull = { proto.versionFull = it }
)
val message = (args[RequireKotlinNames.MESSAGE] as? StringValue)?.value
if (message != null) {
proto.message = stringTable.getStringIndex(message)
}
val level = (args[RequireKotlinNames.LEVEL] as? EnumValue)?.value?.name?.asString()
when (level) {
DeprecationLevel.ERROR.toString() -> { /* ERROR is the default level */ }
DeprecationLevel.WARNING.toString() -> proto.level = ProtoBuf.VersionRequirement.Level.WARNING
DeprecationLevel.HIDDEN.toString() -> proto.level = ProtoBuf.VersionRequirement.Level.HIDDEN
}
val errorCode = (args[RequireKotlinNames.ERROR_CODE] as? IntValue)?.value
if (errorCode != null && errorCode != -1) {
proto.errorCode = errorCode
}
return versionRequirementTable[proto]
}
private fun getClassifierId(descriptor: ClassifierDescriptorWithTypeParameters): Int =
stringTable.getFqNameIndex(descriptor)
@@ -587,6 +650,17 @@ class DescriptorSerializer private constructor(
private fun getTypeParameterId(descriptor: TypeParameterDescriptor): Int =
typeParameters.intern(descriptor)
private object RequireKotlinNames {
val FQ_NAME = FqName("kotlin.internal.RequireKotlin")
val VERSION = Name.identifier("version")
val MESSAGE = Name.identifier("message")
val LEVEL = Name.identifier("level")
val ERROR_CODE = Name.identifier("errorCode")
val VERSION_REGEX: Regex = "(0|[1-9][0-9]*)".let { number -> Regex("$number\\.$number(\\.$number)?") }
}
companion object {
@JvmStatic
fun createTopLevel(extension: SerializerExtension): DescriptorSerializer {
@@ -0,0 +1,18 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package test
import kotlin.internal.RequireKotlin
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, 42)
class Klass
class Konstructor @RequireKotlin("1.1", "message", DeprecationLevel.WARNING, 42) constructor()
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, 42)
typealias Typealias = String
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, 42)
fun function() {}
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, 42)
val property = ""
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.VersionRequirement
import org.jetbrains.kotlin.test.ConfigurationKind
@@ -36,7 +37,6 @@ import java.io.File
class VersionRequirementTest : TestCaseWithTmpdir() {
fun doTest(
fileName: String,
expectedVersionRequirement: VersionRequirement.Version,
expectedLevel: DeprecationLevel,
expectedMessage: String?,
@@ -44,7 +44,7 @@ class VersionRequirementTest : TestCaseWithTmpdir() {
vararg fqNames: String
) {
LoadDescriptorUtil.compileKotlinToDirAndGetModule(
listOf(File(fileName)), tmpdir,
listOf(File("compiler/testData/versionRequirement/${getTestName(true)}.kt")), tmpdir,
KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(testRootDisposable)
)
@@ -57,11 +57,11 @@ class VersionRequirementTest : TestCaseWithTmpdir() {
)
fun check(descriptor: DeclarationDescriptor) {
if (descriptor !is DeserializedMemberDescriptor) {
throw AssertionError("Not a deserialized descriptor: $descriptor")
}
val requirement = descriptor.versionRequirement ?: throw AssertionError("No VersionRequirement for $descriptor")
val requirement = when (descriptor) {
is DeserializedMemberDescriptor -> descriptor.versionRequirement
is DeserializedClassDescriptor -> descriptor.versionRequirement
else -> throw AssertionError("Unknown descriptor: $descriptor")
} ?: throw AssertionError("No VersionRequirement for $descriptor")
assertEquals(expectedVersionRequirement, requirement.version)
assertEquals(expectedLevel, requirement.level)
@@ -96,8 +96,7 @@ class VersionRequirementTest : TestCaseWithTmpdir() {
}
fun testSuspendFun() {
doTest("compiler/testData/versionRequirement/suspendFun.kt",
VersionRequirement.Version(1, 1), DeprecationLevel.ERROR, null, null,
doTest(VersionRequirement.Version(1, 1), DeprecationLevel.ERROR, null, null,
"test.topLevel",
"test.Foo.member",
"test.Foo.<init>",
@@ -108,4 +107,14 @@ class VersionRequirementTest : TestCaseWithTmpdir() {
"test.asyncVal"
)
}
fun testLanguageVersionViaAnnotation() {
doTest(VersionRequirement.Version(1, 1), DeprecationLevel.WARNING, "message", 42,
"test.Klass",
"test.Konstructor.<init>",
"test.Typealias",
"test.function",
"test.property"
)
}
}
@@ -65,3 +65,22 @@ internal annotation class InlineOnly
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.BINARY)
internal annotation class DynamicExtension
/**
* Specifies that this declaration is only completely supported since the specified version.
*
* The Kotlin compiler of an earlier version is going to report a diagnostic on usages of this declaration.
* The diagnostic message can be specified with [message], or via [errorCode] (takes less space, but might not be immediately clear
* to the user). The diagnostic severity can be specified with [level]: WARNING/ERROR mean that either a warning or an error
* is going to be reported, HIDDEN means that the declaration is going to be removed from resolution completely.
*
* This annotation is erased at compile time; its arguments are stored in a more compact form in the Kotlin metadata.
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.TYPEALIAS)
@Retention(AnnotationRetention.SOURCE)
internal annotation class RequireKotlin(
val version: String,
val message: String = "",
val level: DeprecationLevel = DeprecationLevel.ERROR,
val errorCode: Int = -1
)