Forbid non-intended usages of Experimental and markers

Experimental and UseExperimental can only be used as annotations or
qualifiers (to allow "Experimental.Level.*"); experimental markers can
only be used as annotations or qualifiers, or as left-hand side of a
::class literal in arguments to UseExperimental/WasExperimental.

This is needed because we're not yet sure of the design of this feature
and would like to retain the possibility to drop these declarations
(Experimental, UseExperimental) altogether. If they were going to be
used as types, it would be problematic because we can't simply delete
something from stdlib, should deprecate it first. With this change,
these declarations can only be used if the user has opted into using the
experimental API somehow (for example, with
`-Xuse-experimental=kotlin.Experimental`), so we won't stop conforming
to our deprecation policy if we decide to remove these declarations in
the future
This commit is contained in:
Alexander Udalov
2018-04-27 19:01:17 +02:00
parent ed6f044fb0
commit 9995438b37
10 changed files with 177 additions and 9 deletions
@@ -241,6 +241,10 @@ public interface Errors {
DiagnosticFactory2<PsiElement, FqName, DeclarationDescriptor> EXPERIMENTAL_OVERRIDE_ERROR = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<PsiElement> EXPERIMENTAL_IS_NOT_ENABLED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement>
EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> USE_EXPERIMENTAL_WITHOUT_ARGUMENTS = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<KtAnnotationEntry, FqName> USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<KtAnnotationEntry, String> EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET = DiagnosticFactory1.create(ERROR);
@@ -147,6 +147,9 @@ public class DefaultErrorMessages {
MAP.put(EXPERIMENTAL_OVERRIDE_ERROR, "This declaration overrides experimental member of supertype ''{1}'' and must be annotated with ''@{0}''", TO_STRING, NAME);
MAP.put(EXPERIMENTAL_IS_NOT_ENABLED, "This class can only be used with the compiler argument '-Xuse-experimental=kotlin.Experimental'");
MAP.put(EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION, "This class can only be used as an annotation");
MAP.put(EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL, "This class can only be used as an annotation or as an argument to @UseExperimental");
MAP.put(USE_EXPERIMENTAL_WITHOUT_ARGUMENTS, "@UseExperimental without any arguments has no effect");
MAP.put(USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER, "Annotation ''{0}'' is not an experimental API marker, therefore its usage in @UseExperimental is ignored", TO_STRING);
MAP.put(EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET, "Experimental annotation cannot be used on the following code elements: {0}. Please remove these targets", STRING);
@@ -26,9 +26,9 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentQualifiedExpressionForSelector
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
@@ -245,13 +245,63 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
val experimentalities = targetDescriptor.loadExperimentalities(moduleAnnotationsResolver)
reportNotAcceptedExperimentalities(experimentalities, element, context)
val targetClass = when (targetDescriptor) {
is ClassDescriptor -> targetDescriptor
is TypeAliasDescriptor -> targetDescriptor.classDescriptor
else -> null
}
if (targetClass != null && targetClass.loadExperimentalityForMarkerAnnotation() != null) {
if (!element.isUsageAsAnnotationOrQualifier() &&
!element.isUsageAsUseExperimentalArgument(context.trace.bindingContext)
) {
context.trace.report(
Errors.EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL.on(element)
)
}
}
}
private fun checkUsageOfKotlinExperimentalOrUseExperimental(element: PsiElement, context: CheckerContext) {
if (EXPERIMENTAL_FQ_NAME.asString() !in context.languageVersionSettings.getFlag(AnalysisFlag.useExperimental)) {
context.trace.report(Errors.EXPERIMENTAL_IS_NOT_ENABLED.on(element))
}
if (!element.isUsageAsAnnotationOrQualifier()) {
context.trace.report(Errors.EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION.on(element))
}
}
private fun PsiElement.isUsageAsAnnotationOrQualifier(): Boolean {
val parent = parent
if (parent is KtUserType) {
return parent.parent is KtTypeReference &&
parent.parent.parent is KtConstructorCalleeExpression &&
parent.parent.parent.parent is KtAnnotationEntry
}
if (this is KtSimpleNameExpression) {
val qualifier = getTopmostParentQualifiedExpressionForSelector() ?: this
if ((qualifier.parent as? KtDotQualifiedExpression)?.receiverExpression == qualifier) {
return true
}
}
if (getParentOfType<KtImportDirective>(true) != null) return true
return false
}
private fun PsiElement.isUsageAsUseExperimentalArgument(bindingContext: BindingContext): Boolean =
parent is KtClassLiteralExpression &&
parent.parent is KtValueArgument &&
parent.parent.parent is KtValueArgumentList &&
parent.parent.parent.parent.let { entry ->
entry is KtAnnotationEntry && bindingContext.get(BindingContext.ANNOTATION, entry)?.let { annotation ->
annotation.fqName == USE_EXPERIMENTAL_FQ_NAME || annotation.fqName == WAS_EXPERIMENTAL_FQ_NAME
} == true
}
}
class Overrides(project: Project) : DeclarationChecker {
@@ -1,7 +1,9 @@
// !USE_EXPERIMENTAL: kotlin.Experimental
annotation class NotAMarker
<!USE_EXPERIMENTAL_WITHOUT_ARGUMENTS!>@UseExperimental<!>
fun f1() {}
<!USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER!>@UseExperimental(UseExperimental::class)<!>
<!USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER!>@UseExperimental(NotAMarker::class)<!>
fun f2() {}
@@ -1,4 +1,11 @@
package
@kotlin.UseExperimental(markerClass = {}) public fun f1(): kotlin.Unit
@kotlin.UseExperimental(markerClass = {kotlin.UseExperimental::class}) public fun f2(): kotlin.Unit
@kotlin.UseExperimental(markerClass = {NotAMarker::class}) public fun f2(): kotlin.Unit
public final annotation class NotAMarker : kotlin.Annotation {
public constructor NotAMarker()
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,54 @@
// !USE_EXPERIMENTAL: kotlin.Experimental
// !DIAGNOSTICS: -UNUSED_PARAMETER
package test
import kotlin.reflect.KClass
// Usages in import should be OK
import kotlin.Experimental.Level.*
import kotlin.Experimental.Level
import kotlin.Experimental
// Usages with FQ names should be OK
@kotlin.Experimental(kotlin.Experimental.Level.ERROR)
annotation class M
// Usages as types should be errors
fun f1(e: <!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>Experimental<!>) {}
fun f2(u: <!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>UseExperimental<!>?) {}
typealias Experimental0 = <!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>Experimental<!>
typealias UseExperimental0 = <!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>UseExperimental<!>
fun f3(e: Experimental0 /* TODO */) {}
fun f4(u: UseExperimental0 /* TODO */) {}
// Usages as ::class literals should be errors
annotation class VarargKClasses(vararg val k: KClass<*>)
@VarargKClasses(
<!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>Experimental<!>::class,
<!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>UseExperimental<!>::class,
kotlin.<!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>Experimental<!>::class,
kotlin.<!EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION!>UseExperimental<!>::class
)
fun f5() {}
// Usages of markers as types should be errors
@Experimental
annotation class Marker
fun f6(m: <!EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL!>Marker<!>) {}
fun f7(): List<<!EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL!>Marker<!>>? = null
fun f8(): test.<!EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL!>Marker<!>? = null
typealias Marker0 = <!EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL!>Marker<!>
fun f9(m: <!EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL!>Marker0<!>) {}
@@ -0,0 +1,38 @@
package
package test {
public fun f1(/*0*/ e: kotlin.Experimental): kotlin.Unit
public fun f2(/*0*/ u: kotlin.UseExperimental?): kotlin.Unit
public fun f3(/*0*/ e: test.Experimental0 /* = kotlin.Experimental */): kotlin.Unit
public fun f4(/*0*/ u: test.UseExperimental0 /* = kotlin.UseExperimental */): kotlin.Unit
@test.VarargKClasses(k = {kotlin.Experimental::class, kotlin.UseExperimental::class, kotlin.Experimental::class, kotlin.UseExperimental::class}) public fun f5(): kotlin.Unit
public fun f6(/*0*/ m: test.Marker): kotlin.Unit
public fun f7(): kotlin.collections.List<test.Marker>?
public fun f8(): test.Marker?
public fun f9(/*0*/ m: test.Marker0 /* = test.Marker */): kotlin.Unit
@kotlin.Experimental(level = Level.ERROR) public final annotation class M : kotlin.Annotation {
public constructor M()
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.Experimental public final annotation class Marker : kotlin.Annotation {
public constructor Marker()
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 annotation class VarargKClasses : kotlin.Annotation {
public constructor VarargKClasses(/*0*/ vararg k: kotlin.reflect.KClass<*> /*kotlin.Array<out kotlin.reflect.KClass<*>>*/)
public final val k: kotlin.Array<out kotlin.reflect.KClass<*>>
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 typealias Experimental0 = kotlin.Experimental
public typealias Marker0 = test.Marker
public typealias UseExperimental0 = kotlin.UseExperimental
}
@@ -1,6 +1,6 @@
// !API_VERSION: 1.2
// !USE_EXPERIMENTAL: kotlin.Experimental
// !DIAGNOSTICS: -INVISIBLE_MEMBER -INVISIBLE_REFERENCE -NEWER_VERSION_IN_SINCE_KOTLIN -UNUSED_PARAMETER
// !DIAGNOSTICS: -INVISIBLE_MEMBER -INVISIBLE_REFERENCE -NEWER_VERSION_IN_SINCE_KOTLIN
@SinceKotlin("1.3")
fun newPublishedFun() {}
@@ -21,7 +21,7 @@ val newValExperimentalInThePast = ""
@WasExperimental(Marker::class)
class NewClassExperimentalInThePast
fun use1(c: <!UNRESOLVED_REFERENCE!>NewClassExperimentalInThePast<!>) {
fun use1() {
<!UNRESOLVED_REFERENCE!>newPublishedFun<!>()
<!UNRESOLVED_REFERENCE!>newFunExperimentalInThePast<!>()
<!UNRESOLVED_REFERENCE!>newValExperimentalInThePast<!>
@@ -29,7 +29,7 @@ fun use1(c: <!UNRESOLVED_REFERENCE!>NewClassExperimentalInThePast<!>) {
}
@UseExperimental(Marker::class)
fun use2(c: NewClassExperimentalInThePast) {
fun use2() {
<!UNRESOLVED_REFERENCE!>newPublishedFun<!>()
newFunExperimentalInThePast()
newValExperimentalInThePast
@@ -37,7 +37,7 @@ fun use2(c: NewClassExperimentalInThePast) {
}
@Marker
fun use3(c: NewClassExperimentalInThePast) {
fun use3() {
<!UNRESOLVED_REFERENCE!>newPublishedFun<!>()
newFunExperimentalInThePast()
newValExperimentalInThePast
@@ -2172,6 +2172,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/typealias.kt");
}
@TestMetadata("usageNotAsAnnotation.kt")
public void testUsageNotAsAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt");
}
@TestMetadata("useExperimentalOnFile.kt")
public void testUseExperimentalOnFile() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnFile.kt");
@@ -2172,6 +2172,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/typealias.kt");
}
@TestMetadata("usageNotAsAnnotation.kt")
public void testUsageNotAsAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/usageNotAsAnnotation.kt");
}
@TestMetadata("useExperimentalOnFile.kt")
public void testUseExperimentalOnFile() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnFile.kt");