Check the RequireKotlin annotation value

Similarly to SinceKotlin
This commit is contained in:
Alexander Udalov
2017-10-09 20:48:38 +02:00
parent a96861c353
commit 6f2e6db131
16 changed files with 238 additions and 95 deletions
@@ -234,7 +234,7 @@ public interface Errors {
DiagnosticFactory0<KtAnnotatedExpression> ANNOTATIONS_ON_BLOCK_LEVEL_EXPRESSION_ON_THE_SAME_LINE = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<KtAnnotationEntry> ANNOTATION_USED_AS_ANNOTATION_ARGUMENT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> ILLEGAL_SINCE_KOTLIN_VALUE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, FqName> ILLEGAL_KOTLIN_VERSION_STRING_VALUE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> NEWER_VERSION_IN_SINCE_KOTLIN = DiagnosticFactory1.create(WARNING);
// Const
@@ -163,7 +163,7 @@ public class DefaultErrorMessages {
MAP.put(REDUNDANT_ANNOTATION_TARGET, "Redundant annotation target ''{0}''", STRING);
MAP.put(INAPPLICABLE_FILE_TARGET, "'@file:' annotations can only be applied before package declaration");
MAP.put(ILLEGAL_SINCE_KOTLIN_VALUE, "Invalid @SinceKotlin annotation value (should be 'major.minor' or 'major.minor.patch')");
MAP.put(ILLEGAL_KOTLIN_VERSION_STRING_VALUE, "Invalid @{0} annotation value (should be ''major.minor'' or ''major.minor.patch'')", TO_STRING);
MAP.put(NEWER_VERSION_IN_SINCE_KOTLIN, "The version is greater than the specified API version {0}", STRING);
MAP.put(REDUNDANT_MODIFIER, "Modifier ''{0}'' is redundant because ''{1}'' is present", TO_STRING, TO_STRING);
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
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 VERSION_KIND = Name.identifier("versionKind")
val ERROR_CODE = Name.identifier("errorCode")
}
@@ -85,6 +85,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
InlineParameterChecker,
InfixModifierChecker(),
SinceKotlinAnnotationValueChecker,
RequireKotlinAnnotationValueChecker,
ReifiedTypeParameterAnnotationChecker(),
DynamicReceiverChecker,
DelegationChecker(),
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.RequireKotlinNames
import org.jetbrains.kotlin.resolve.SINCE_KOTLIN_FQ_NAME
import org.jetbrains.kotlin.resolve.source.getPsi
abstract class KotlinVersionStringAnnotationValueChecker(
private val annotationFqName: FqName
) : DeclarationChecker {
override fun check(
declaration: KtDeclaration,
descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext,
languageVersionSettings: LanguageVersionSettings
) {
val annotation = descriptor.annotations.findAnnotation(annotationFqName) ?: return
val version = annotation.allValueArguments.values.singleOrNull()?.value as? String ?: return
if (!version.matches(VERSION_REGEX)) {
diagnosticHolder.report(Errors.ILLEGAL_KOTLIN_VERSION_STRING_VALUE.on(
annotation.source.getPsi() ?: declaration, annotationFqName
))
return
}
extraCheck(declaration, annotation, version, diagnosticHolder, languageVersionSettings)
}
open fun extraCheck(
declaration: KtDeclaration,
annotation: AnnotationDescriptor,
version: String,
diagnosticHolder: DiagnosticSink,
languageVersionSettings: LanguageVersionSettings
) {}
companion object {
val VERSION_REGEX: Regex = "(0|[1-9][0-9]*)".let { number -> Regex("$number\\.$number(\\.$number)?") }
}
}
object SinceKotlinAnnotationValueChecker : KotlinVersionStringAnnotationValueChecker(SINCE_KOTLIN_FQ_NAME) {
override fun extraCheck(
declaration: KtDeclaration,
annotation: AnnotationDescriptor,
version: String,
diagnosticHolder: DiagnosticSink,
languageVersionSettings: LanguageVersionSettings
) {
val apiVersion = ApiVersion.parse(version)
val specified = languageVersionSettings.apiVersion
if (apiVersion != null && apiVersion > specified) {
diagnosticHolder.report(Errors.NEWER_VERSION_IN_SINCE_KOTLIN.on(annotation.source.getPsi() ?: declaration, specified.versionString))
}
}
}
object RequireKotlinAnnotationValueChecker : KotlinVersionStringAnnotationValueChecker(RequireKotlinNames.FQ_NAME)
@@ -1,53 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors.ILLEGAL_SINCE_KOTLIN_VALUE
import org.jetbrains.kotlin.diagnostics.Errors.NEWER_VERSION_IN_SINCE_KOTLIN
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.getSinceKotlinAnnotation
import org.jetbrains.kotlin.resolve.source.getPsi
object SinceKotlinAnnotationValueChecker : DeclarationChecker {
private val regex: Regex = "(0|[1-9][0-9]*)".let { number -> Regex("$number\\.$number(\\.$number)?") }
override fun check(
declaration: KtDeclaration,
descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext,
languageVersionSettings: LanguageVersionSettings
) {
val annotation = descriptor.getSinceKotlinAnnotation() ?: return
val version = annotation.allValueArguments.values.singleOrNull()?.value as? String ?: return
if (!version.matches(regex)) {
diagnosticHolder.report(ILLEGAL_SINCE_KOTLIN_VALUE.on(annotation.source.getPsi() ?: declaration))
return
}
val apiVersion = ApiVersion.parse(version)
val specified = languageVersionSettings.apiVersion
if (apiVersion != null && apiVersion > specified) {
diagnosticHolder.report(NEWER_VERSION_IN_SINCE_KOTLIN.on(annotation.source.getPsi() ?: declaration, specified.versionString))
}
}
}
@@ -19,16 +19,11 @@ package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForTypeAliasObject
private val SINCE_KOTLIN_FQ_NAME = FqName("kotlin.SinceKotlin")
// TODO: use-site targeted annotations
internal fun DeclarationDescriptor.getSinceKotlinAnnotation(): AnnotationDescriptor? =
annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME)
internal val SINCE_KOTLIN_FQ_NAME = FqName("kotlin.SinceKotlin")
/**
* @return true if the descriptor is accessible according to [languageVersionSettings], or false otherwise. The [actionIfInaccessible]
@@ -62,8 +57,10 @@ private fun getSinceKotlinVersionByOverridden(descriptor: CallableMemberDescript
}
private fun DeclarationDescriptor.getOwnSinceKotlinVersion(): ApiVersion? {
// TODO: use-site targeted annotations
fun DeclarationDescriptor.loadAnnotationValue(): ApiVersion? =
(getSinceKotlinAnnotation()?.allValueArguments?.values?.singleOrNull()?.value as? String)?.let(ApiVersion.Companion::parse)
(annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME)?.allValueArguments?.values?.singleOrNull()?.value as? String)
?.let(ApiVersion.Companion::parse)
val ownVersion = loadAnnotationValue()
val ctorClass = (this as? ConstructorDescriptor)?.containingDeclaration?.loadAnnotationValue()
+1
View File
@@ -5,6 +5,7 @@ jvmTarget = "1.6"
dependencies {
compile(project(":compiler:util"))
compile(project(":compiler:frontend"))
compile(project(":core"))
}
@@ -28,6 +28,8 @@ 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.RequireKotlinNames
import org.jetbrains.kotlin.resolve.checkers.KotlinVersionStringAnnotationValueChecker
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.IntValue
import org.jetbrains.kotlin.resolve.constants.NullValue
@@ -609,7 +611,7 @@ class DescriptorSerializer private constructor(
val args = annotation.allValueArguments
val versionString = (args[RequireKotlinNames.VERSION] as? StringValue)?.value ?: return null
val matchResult = RequireKotlinNames.VERSION_REGEX.matchEntire(versionString) ?: return null
val matchResult = KotlinVersionStringAnnotationValueChecker.VERSION_REGEX.matchEntire(versionString) ?: return null
val major = matchResult.groupValues.getOrNull(1)?.toIntOrNull() ?: return null
val minor = matchResult.groupValues.getOrNull(2)?.toIntOrNull() ?: 0
@@ -659,18 +661,6 @@ 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 VERSION_KIND = Name.identifier("versionKind")
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,38 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package test
import kotlin.internal.RequireKotlin
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("")<!>
fun f01() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("x")<!>
fun f02() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("1")<!>
fun f03() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("1.0-beta")<!>
fun f04() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("1.1.0-dev-1111")<!>
fun f05() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("1.5.3.7")<!>
fun f06() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin("1..0")<!>
fun f07() {}
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@RequireKotlin(" 1.0")<!>
fun f08() {}
@RequireKotlin("1.1")
fun ok1() {}
@RequireKotlin("1.1.0")
fun ok2() {}
@RequireKotlin("0.0.0")
fun ok3() {}
@@ -0,0 +1,15 @@
package
package test {
@kotlin.internal.RequireKotlin(version = "") public fun f01(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "x") public fun f02(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1") public fun f03(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1.0-beta") public fun f04(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1.1.0-dev-1111") public fun f05(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1.5.3.7") public fun f06(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1..0") public fun f07(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = " 1.0") public fun f08(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1.1") public fun ok1(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "1.1.0") public fun ok2(): kotlin.Unit
@kotlin.internal.RequireKotlin(version = "0.0.0") public fun ok3(): kotlin.Unit
}
@@ -1,61 +1,61 @@
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("")<!>
fun f01() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("x")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("x")<!>
fun f02() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1")<!>
fun f03() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1,0")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1,0")<!>
fun f04() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1,0,1")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1,0,1")<!>
fun f05() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("a.b")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("a.b")<!>
fun f06() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.a")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.a")<!>
fun f07() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.0.a")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.0.a")<!>
fun f08() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.0-beta")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.0-beta")<!>
fun f09() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.1.0-dev-1111")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.1.0-dev-1111")<!>
fun f10() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.1.0+rc")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.1.0+rc")<!>
fun f11() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.5.3.7")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.5.3.7")<!>
fun f12() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("01.1")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("01.1")<!>
fun f13() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.01")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.01")<!>
fun f14() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("-1.0")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("-1.0")<!>
fun f15() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.-1.0")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.-1.0")<!>
fun f16() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("0.00.1")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("0.00.1")<!>
fun f17() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1..0")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1..0")<!>
fun f18() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin(" 1.0")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin(" 1.0")<!>
fun f19() {}
<!ILLEGAL_SINCE_KOTLIN_VALUE!>@SinceKotlin("1.0 ")<!>
<!ILLEGAL_KOTLIN_VERSION_STRING_VALUE!>@SinceKotlin("1.0 ")<!>
fun f20() {}
@@ -0,0 +1,19 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package test
import kotlin.internal.RequireKotlin
import kotlin.internal.RequireKotlinVersionKind
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, RequireKotlinVersionKind.COMPILER_VERSION, 42)
class Klass
class Konstructor @RequireKotlin("1.1", "message", DeprecationLevel.WARNING, RequireKotlinVersionKind.COMPILER_VERSION, 42) constructor()
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, RequireKotlinVersionKind.COMPILER_VERSION, 42)
typealias Typealias = String
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, RequireKotlinVersionKind.COMPILER_VERSION, 42)
fun function() {}
@RequireKotlin("1.1", "message", DeprecationLevel.WARNING, RequireKotlinVersionKind.COMPILER_VERSION, 42)
val property = ""
@@ -1072,6 +1072,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("illegalRequireKotlinValue.kt")
public void testIllegalRequireKotlinValue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/illegalRequireKotlinValue.kt");
doTest(fileName);
}
@TestMetadata("illegalSinceKotlinValue.kt")
public void testIllegalSinceKotlinValue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt");
@@ -1072,6 +1072,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("illegalRequireKotlinValue.kt")
public void testIllegalRequireKotlinValue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/illegalRequireKotlinValue.kt");
doTest(fileName);
}
@TestMetadata("illegalSinceKotlinValue.kt")
public void testIllegalSinceKotlinValue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt");
@@ -129,4 +129,14 @@ class VersionRequirementTest : TestCaseWithTmpdir() {
"test.property"
)
}
fun testCompilerVersionViaAnnotation() {
doTest(VersionRequirement.Version(1, 1), DeprecationLevel.WARNING, "message", ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION, 42,
"test.Klass",
"test.Konstructor.<init>",
"test.Typealias",
"test.function",
"test.property"
)
}
}