Introduce internal RequireKotlin annotation
This commit is contained in:
+77
-3
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user