From e3df8ed2fe672a3830c9399e8ab8507e45ff8755 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 26 Sep 2016 19:52:51 +0300 Subject: [PATCH] Introduce SinceKotlin annotation, check validity of its value #KT-14297 Fixed --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 + .../rendering/DefaultErrorMessages.java | 2 + .../kotlin/resolve/TargetPlatform.kt | 4 +- .../SinceKotlinAnnotationValueChecker.kt | 39 ++++++++++ .../kotlin/resolve/sinceKotlinUtil.kt | 26 +++++++ .../builtin-classes/default/kotlin.txt | 6 ++ .../testData/builtin-classes/java6/kotlin.txt | 6 ++ .../testData/builtin-classes/java8/kotlin.txt | 6 ++ .../builtin-classes/newMethods/kotlin.txt | 6 ++ .../annotations/illegalSinceKotlinValue.kt | 74 +++++++++++++++++++ .../annotations/illegalSinceKotlinValue.txt | 26 +++++++ .../checkers/DiagnosticsTestGenerated.java | 6 ++ core/builtins/src/kotlin/Annotations.kt | 14 +++- 13 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SinceKotlinAnnotationValueChecker.kt create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt create mode 100644 compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt create mode 100644 compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index caf7c08156a..06b990407ab 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -176,6 +176,8 @@ public interface Errors { DiagnosticFactory0 ANNOTATION_PARAMETER_MUST_BE_ENUM_CONST = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 ILLEGAL_SINCE_KOTLIN_VALUE = DiagnosticFactory0.create(ERROR); + // Const DiagnosticFactory0 CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CONST_VAL_WITH_GETTER = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 33c03172d55..8a7e24a5edf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -161,6 +161,8 @@ public class DefaultErrorMessages { MAP.put(INAPPLICABLE_PARAM_TARGET, "'@param:' annotations could be applied only to primary constructor parameters"); MAP.put(REDUNDANT_ANNOTATION_TARGET, "Redundant annotation target ''{0}''", STRING); + MAP.put(ILLEGAL_SINCE_KOTLIN_VALUE, "Invalid @SinceKotlin annotation value (should be 'major.minor' or 'major.minor.patch')"); + MAP.put(REDUNDANT_MODIFIER, "Modifier ''{0}'' is redundant because ''{1}'' is present", TO_STRING, TO_STRING); MAP.put(REDUNDANT_OPEN_IN_INTERFACE, "Modifier 'open' is redundant for abstract interface members"); MAP.put(REDUNDANT_MODIFIER_IN_GETTER, "Visibility modifiers are redundant in getter"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt index c6104350db3..e45eea384b1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt @@ -64,7 +64,9 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf( InlineParameterChecker, InfixModifierChecker(), SuspendModifierChecker, - CoroutineModifierChecker) + CoroutineModifierChecker, + SinceKotlinAnnotationValueChecker +) private val DEFAULT_CALL_CHECKERS = listOf( CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker(), SafeCallChecker(), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SinceKotlinAnnotationValueChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SinceKotlinAnnotationValueChecker.kt new file mode 100644 index 00000000000..b7498f3e06c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SinceKotlinAnnotationValueChecker.kt @@ -0,0 +1,39 @@ +/* + * 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.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.diagnostics.Errors +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 : SimpleDeclarationChecker { + 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 + ) { + val annotation = descriptor.getSinceKotlinAnnotation() ?: return + val version = annotation.allValueArguments.values.singleOrNull()?.value as? String ?: return + if (!version.matches(regex)) { + diagnosticHolder.report(Errors.ILLEGAL_SINCE_KOTLIN_VALUE.on(annotation.source.getPsi() ?: declaration)) + } + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt new file mode 100644 index 00000000000..9254c5c1700 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.name.FqName + +private val SINCE_KOTLIN_FQ_NAME = FqName("kotlin.SinceKotlin") + +internal fun DeclarationDescriptor.getSinceKotlinAnnotation(): AnnotationDescriptor? = + annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME) diff --git a/compiler/testData/builtin-classes/default/kotlin.txt b/compiler/testData/builtin-classes/default/kotlin.txt index 5aae1957b2b..09618dc87ad 100644 --- a/compiler/testData/builtin-classes/default/kotlin.txt +++ b/compiler/testData/builtin-classes/default/kotlin.txt @@ -647,6 +647,12 @@ public final class ShortArray : kotlin.Cloneable { public final operator fun set(/*0*/ index: kotlin.Int, /*1*/ value: kotlin.Short): kotlin.Unit } +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) @kotlin.annotation.MustBeDocumented() public final annotation class SinceKotlin : kotlin.Annotation { + /*primary*/ public constructor SinceKotlin(/*0*/ version: kotlin.String) + public final val version: kotlin.String + public final fun (): kotlin.String +} + public final class String : kotlin.Comparable, kotlin.CharSequence { /*primary*/ public constructor String() public open override /*1*/ val length: kotlin.Int diff --git a/compiler/testData/builtin-classes/java6/kotlin.txt b/compiler/testData/builtin-classes/java6/kotlin.txt index feb01f5561a..bfff6c96402 100644 --- a/compiler/testData/builtin-classes/java6/kotlin.txt +++ b/compiler/testData/builtin-classes/java6/kotlin.txt @@ -651,6 +651,12 @@ public final class ShortArray : kotlin.Cloneable, java.io.Serializable { public final operator fun set(/*0*/ index: kotlin.Int, /*1*/ value: kotlin.Short): kotlin.Unit } +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) @kotlin.annotation.MustBeDocumented() public final annotation class SinceKotlin : kotlin.Annotation { + /*primary*/ public constructor SinceKotlin(/*0*/ version: kotlin.String) + public final val version: kotlin.String + public final fun (): kotlin.String +} + public final class String : kotlin.Comparable, kotlin.CharSequence, java.io.Serializable { /*primary*/ public constructor String() public open override /*1*/ val length: kotlin.Int diff --git a/compiler/testData/builtin-classes/java8/kotlin.txt b/compiler/testData/builtin-classes/java8/kotlin.txt index 4b6627caaa4..e71e4aab487 100644 --- a/compiler/testData/builtin-classes/java8/kotlin.txt +++ b/compiler/testData/builtin-classes/java8/kotlin.txt @@ -653,6 +653,12 @@ public final class ShortArray : kotlin.Cloneable, java.io.Serializable { public final operator fun set(/*0*/ index: kotlin.Int, /*1*/ value: kotlin.Short): kotlin.Unit } +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) @kotlin.annotation.MustBeDocumented() public final annotation class SinceKotlin : kotlin.Annotation { + /*primary*/ public constructor SinceKotlin(/*0*/ version: kotlin.String) + public final val version: kotlin.String + public final fun (): kotlin.String +} + public final class String : kotlin.Comparable, kotlin.CharSequence, java.io.Serializable { /*primary*/ public constructor String() public open override /*1*/ val length: kotlin.Int diff --git a/compiler/testData/builtin-classes/newMethods/kotlin.txt b/compiler/testData/builtin-classes/newMethods/kotlin.txt index 99eb447760c..9c0be382d7f 100644 --- a/compiler/testData/builtin-classes/newMethods/kotlin.txt +++ b/compiler/testData/builtin-classes/newMethods/kotlin.txt @@ -651,6 +651,12 @@ public final class ShortArray : kotlin.Cloneable, java.io.Serializable { public final operator fun set(/*0*/ index: kotlin.Int, /*1*/ value: kotlin.Short): kotlin.Unit } +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) @kotlin.annotation.MustBeDocumented() public final annotation class SinceKotlin : kotlin.Annotation { + /*primary*/ public constructor SinceKotlin(/*0*/ version: kotlin.String) + public final val version: kotlin.String + public final fun (): kotlin.String +} + public final class String : kotlin.Comparable, kotlin.CharSequence, java.io.Serializable { /*primary*/ public constructor String() public open override /*1*/ val length: kotlin.Int diff --git a/compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt b/compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt new file mode 100644 index 00000000000..635bb92d4e9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt @@ -0,0 +1,74 @@ +@SinceKotlin("") +fun f01() {} + +@SinceKotlin("x") +fun f02() {} + +@SinceKotlin("1") +fun f03() {} + +@SinceKotlin("1,0") +fun f04() {} + +@SinceKotlin("1,0,1") +fun f05() {} + +@SinceKotlin("a.b") +fun f06() {} + +@SinceKotlin("1.a") +fun f07() {} + +@SinceKotlin("1.0.a") +fun f08() {} + +@SinceKotlin("1.0-beta") +fun f09() {} + +@SinceKotlin("1.1.0-dev-1111") +fun f10() {} + +@SinceKotlin("1.1.0+rc") +fun f11() {} + +@SinceKotlin("1.5.3.7") +fun f12() {} + +@SinceKotlin("01.1") +fun f13() {} + +@SinceKotlin("1.01") +fun f14() {} + +@SinceKotlin("-1.0") +fun f15() {} + +@SinceKotlin("1.-1.0") +fun f16() {} + +@SinceKotlin("0.00.1") +fun f17() {} + +@SinceKotlin("1..0") +fun f18() {} + +@SinceKotlin(" 1.0") +fun f19() {} + +@SinceKotlin("1.0 ") +fun f20() {} + + + + +@SinceKotlin("1.1") +fun ok1() {} + +@SinceKotlin("1.1.0") +fun ok2() {} + +@SinceKotlin("0.0.0") +fun ok3() {} + +@SinceKotlin("123456789012345678901234567890.123456789012345678901234567890.123456789012345678901234567890") +fun ok4() {} diff --git a/compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.txt b/compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.txt new file mode 100644 index 00000000000..8c23022d2c0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.txt @@ -0,0 +1,26 @@ +package + +@kotlin.SinceKotlin(version = "") public fun f01(): kotlin.Unit +@kotlin.SinceKotlin(version = "x") public fun f02(): kotlin.Unit +@kotlin.SinceKotlin(version = "1") public fun f03(): kotlin.Unit +@kotlin.SinceKotlin(version = "1,0") public fun f04(): kotlin.Unit +@kotlin.SinceKotlin(version = "1,0,1") public fun f05(): kotlin.Unit +@kotlin.SinceKotlin(version = "a.b") public fun f06(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.a") public fun f07(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.0.a") public fun f08(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.0-beta") public fun f09(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.1.0-dev-1111") public fun f10(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.1.0+rc") public fun f11(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.5.3.7") public fun f12(): kotlin.Unit +@kotlin.SinceKotlin(version = "01.1") public fun f13(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.01") public fun f14(): kotlin.Unit +@kotlin.SinceKotlin(version = "-1.0") public fun f15(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.-1.0") public fun f16(): kotlin.Unit +@kotlin.SinceKotlin(version = "0.00.1") public fun f17(): kotlin.Unit +@kotlin.SinceKotlin(version = "1..0") public fun f18(): kotlin.Unit +@kotlin.SinceKotlin(version = " 1.0") public fun f19(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.0 ") public fun f20(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.1") public fun ok1(): kotlin.Unit +@kotlin.SinceKotlin(version = "1.1.0") public fun ok2(): kotlin.Unit +@kotlin.SinceKotlin(version = "0.0.0") public fun ok3(): kotlin.Unit +@kotlin.SinceKotlin(version = "123456789012345678901234567890.123456789012345678901234567890.123456789012345678901234567890") public fun ok4(): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 38a55a65663..9a6c5479779 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -981,6 +981,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("illegalSinceKotlinValue.kt") + public void testIllegalSinceKotlinValue() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/illegalSinceKotlinValue.kt"); + doTest(fileName); + } + @TestMetadata("invalidTypesInAnnotationConstructor.kt") public void testInvalidTypesInAnnotationConstructor() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/invalidTypesInAnnotationConstructor.kt"); diff --git a/core/builtins/src/kotlin/Annotations.kt b/core/builtins/src/kotlin/Annotations.kt index a0cbc1c7395..d5ad77a4115 100644 --- a/core/builtins/src/kotlin/Annotations.kt +++ b/core/builtins/src/kotlin/Annotations.kt @@ -86,4 +86,16 @@ public annotation class Suppress(vararg val names: String) @Target(TYPE) @Retention(SOURCE) @MustBeDocumented -public annotation class UnsafeVariance \ No newline at end of file +public annotation class UnsafeVariance + +/** + * Specifies the first version of Kotlin where a declaration has appeared. + * Using the declaration and specifying an older API version (via the `-api-version` command line option) will result in an error. + * + * @property version the version in the following formats: `.` or `..`, where major, minor and patch + * are non-negative integer numbers without leading zeros. + */ +@Target(CLASS, PROPERTY, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +public annotation class SinceKotlin(val version: String)