Support -Xexperimental/-Xuse-experimental in ExperimentalUsageChecker
#KT-22759 In Progress
This commit is contained in:
+34
-20
@@ -78,8 +78,10 @@ object ExperimentalUsageChecker : CallChecker {
|
||||
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, context: CheckerContext) {
|
||||
val experimentalities = descriptor.loadExperimentalities()
|
||||
if (experimentalities.isNotEmpty()) {
|
||||
checkExperimental(experimentalities, element, context.trace.bindingContext, context.moduleDescriptor) {
|
||||
experimentality, isBodyUsageOfSourceOnlyExperimentality ->
|
||||
checkExperimental(
|
||||
experimentalities, element, context.trace.bindingContext, context.languageVersionSettings,
|
||||
context.moduleDescriptor
|
||||
) { experimentality, isBodyUsageOfSourceOnlyExperimentality ->
|
||||
val diagnostic = when (experimentality.severity) {
|
||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
|
||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
|
||||
@@ -93,6 +95,7 @@ object ExperimentalUsageChecker : CallChecker {
|
||||
experimentalities: Collection<Experimentality>,
|
||||
element: PsiElement,
|
||||
bindingContext: BindingContext,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
module: ModuleDescriptor,
|
||||
report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit
|
||||
) {
|
||||
@@ -106,11 +109,12 @@ object ExperimentalUsageChecker : CallChecker {
|
||||
val isBodyUsageInSameModule =
|
||||
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
|
||||
|
||||
val annotationFqName = experimentality.annotationFqName
|
||||
val isExperimentalityAccepted =
|
||||
isBodyUsageInSameModule ||
|
||||
(isBodyUsageOfCompilationExperimentality &&
|
||||
element.hasContainerAnnotatedWithUseExperimental(experimentality.annotationFqName, bindingContext)) ||
|
||||
element.propagates(experimentality.annotationFqName, bindingContext)
|
||||
element.hasContainerAnnotatedWithUseExperimental(annotationFqName, bindingContext, languageVersionSettings)) ||
|
||||
element.propagates(annotationFqName, bindingContext, languageVersionSettings)
|
||||
|
||||
if (!isExperimentalityAccepted) {
|
||||
report(experimentality, isBodyUsageOfCompilationExperimentality)
|
||||
@@ -188,24 +192,32 @@ object ExperimentalUsageChecker : CallChecker {
|
||||
|
||||
// Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring
|
||||
// propagation for the experimental annotation to the call sites
|
||||
private fun PsiElement.propagates(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
|
||||
return anyParentMatches { element, _ ->
|
||||
if (element is KtDeclaration) {
|
||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||
descriptor != null && !DescriptorUtils.isLocal(descriptor) && descriptor.annotations.hasAnnotation(annotationFqName)
|
||||
} else false
|
||||
}
|
||||
}
|
||||
private fun PsiElement.propagates(
|
||||
annotationFqName: FqName,
|
||||
bindingContext: BindingContext,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Boolean =
|
||||
annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.experimental) ||
|
||||
anyParentMatches { element, _ ->
|
||||
if (element is KtDeclaration) {
|
||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||
descriptor != null && !DescriptorUtils.isLocal(descriptor) && descriptor.annotations.hasAnnotation(annotationFqName)
|
||||
} else false
|
||||
}
|
||||
|
||||
// Checks whether there's an element lexically above the tree, that is annotated with `@UseExperimental(X::class)`
|
||||
// where annotationFqName is the FQ name of X
|
||||
private fun PsiElement.hasContainerAnnotatedWithUseExperimental(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
|
||||
return anyParentMatches { element, _ ->
|
||||
element is KtAnnotated && element.annotationEntries.any { entry ->
|
||||
bindingContext.get(BindingContext.ANNOTATION, entry)?.isUseExperimental(annotationFqName) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun PsiElement.hasContainerAnnotatedWithUseExperimental(
|
||||
annotationFqName: FqName,
|
||||
bindingContext: BindingContext,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Boolean =
|
||||
annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.useExperimental) ||
|
||||
anyParentMatches { element, _ ->
|
||||
element is KtAnnotated && element.annotationEntries.any { entry ->
|
||||
bindingContext.get(BindingContext.ANNOTATION, entry)?.isUseExperimental(annotationFqName) == true
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun PsiElement.anyParentMatches(predicate: (element: PsiElement, parent: PsiElement?) -> Boolean): Boolean {
|
||||
var element = this
|
||||
@@ -273,7 +285,9 @@ object ExperimentalUsageChecker : CallChecker {
|
||||
val module = descriptor.module
|
||||
|
||||
for ((experimentality, member) in experimentalOverridden) {
|
||||
checkExperimental(listOf(experimentality), declaration, context.trace.bindingContext, module) { _, _ ->
|
||||
checkExperimental(
|
||||
listOf(experimentality), declaration, context.trace.bindingContext, context.languageVersionSettings, module
|
||||
) { _, _ ->
|
||||
val diagnostic = when (experimentality.severity) {
|
||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_OVERRIDE
|
||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_OVERRIDE_ERROR
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// !API_VERSION: 1.3
|
||||
// !EXPERIMENTAL: api.ExperimentalAPI
|
||||
// MODULE: api
|
||||
// FILE: api.kt
|
||||
|
||||
package api
|
||||
|
||||
@Experimental
|
||||
annotation class ExperimentalAPI
|
||||
|
||||
@ExperimentalAPI
|
||||
fun function(): String = ""
|
||||
|
||||
// MODULE: usage(api)
|
||||
// FILE: usage.kt
|
||||
|
||||
package usage
|
||||
|
||||
import api.*
|
||||
|
||||
fun use() {
|
||||
function()
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// -- Module: <api> --
|
||||
package
|
||||
|
||||
package api {
|
||||
@api.ExperimentalAPI public fun function(): kotlin.String
|
||||
|
||||
@kotlin.Experimental public final annotation class ExperimentalAPI : kotlin.Annotation {
|
||||
public constructor ExperimentalAPI()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -- Module: <usage> --
|
||||
package
|
||||
|
||||
package api {
|
||||
}
|
||||
|
||||
package usage {
|
||||
public fun use(): kotlin.Unit
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// !API_VERSION: 1.3
|
||||
// !USE_EXPERIMENTAL: api.ExperimentalAPI
|
||||
// MODULE: api
|
||||
// FILE: api.kt
|
||||
|
||||
package api
|
||||
|
||||
@Experimental(Experimental.Level.ERROR, [Experimental.Impact.COMPILATION])
|
||||
annotation class ExperimentalAPI
|
||||
|
||||
@ExperimentalAPI
|
||||
fun function(): String = ""
|
||||
|
||||
// MODULE: usage(api)
|
||||
// FILE: usage.kt
|
||||
|
||||
package usage
|
||||
|
||||
import api.*
|
||||
|
||||
fun use() {
|
||||
function()
|
||||
}
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
// -- Module: <api> --
|
||||
package
|
||||
|
||||
package api {
|
||||
@api.ExperimentalAPI public fun function(): kotlin.String
|
||||
|
||||
@kotlin.Experimental(changesMayBreak = {Impact.COMPILATION}, level = Level.ERROR) public final annotation class ExperimentalAPI : kotlin.Annotation {
|
||||
public constructor ExperimentalAPI()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -- Module: <usage> --
|
||||
package
|
||||
|
||||
package api {
|
||||
}
|
||||
|
||||
package usage {
|
||||
public fun use(): kotlin.Unit
|
||||
}
|
||||
+13
-4
@@ -26,6 +26,9 @@ import java.util.regex.Pattern
|
||||
const val LANGUAGE_DIRECTIVE = "LANGUAGE"
|
||||
const val API_VERSION_DIRECTIVE = "API_VERSION"
|
||||
|
||||
const val EXPERIMENTAL_DIRECTIVE = "EXPERIMENTAL"
|
||||
const val USE_EXPERIMENTAL_DIRECTIVE = "USE_EXPERIMENTAL"
|
||||
|
||||
data class CompilerTestLanguageVersionSettings(
|
||||
private val initialLanguageFeatures: Map<LanguageFeature, LanguageFeature.State>,
|
||||
override val apiVersion: ApiVersion,
|
||||
@@ -51,15 +54,21 @@ private fun specificFeaturesForTests(): Map<LanguageFeature, LanguageFeature.Sta
|
||||
|
||||
fun parseLanguageVersionSettings(directiveMap: Map<String, String>): LanguageVersionSettings? {
|
||||
val apiVersionString = directiveMap[API_VERSION_DIRECTIVE]
|
||||
val directives = directiveMap[LANGUAGE_DIRECTIVE]
|
||||
if (apiVersionString == null && directives == null) return null
|
||||
val languageFeaturesString = directiveMap[LANGUAGE_DIRECTIVE]
|
||||
val experimental = directiveMap[EXPERIMENTAL_DIRECTIVE]?.split(' ')?.let { AnalysisFlag.experimental to it }
|
||||
val useExperimental = directiveMap[USE_EXPERIMENTAL_DIRECTIVE]?.split(' ')?.let { AnalysisFlag.useExperimental to it }
|
||||
|
||||
if (apiVersionString == null && languageFeaturesString == null && experimental == null && useExperimental == null) return null
|
||||
|
||||
val apiVersion = (if (apiVersionString != null) ApiVersion.parse(apiVersionString) else ApiVersion.LATEST_STABLE)
|
||||
?: error("Unknown API version: $apiVersionString")
|
||||
|
||||
val languageFeatures = directives?.let(::collectLanguageFeatureMap).orEmpty()
|
||||
val languageFeatures = languageFeaturesString?.let(::collectLanguageFeatureMap).orEmpty()
|
||||
|
||||
return CompilerTestLanguageVersionSettings(languageFeatures, apiVersion, LanguageVersion.LATEST_STABLE)
|
||||
return CompilerTestLanguageVersionSettings(
|
||||
languageFeatures, apiVersion, LanguageVersion.LATEST_STABLE,
|
||||
mapOf(*listOfNotNull(experimental, useExperimental).toTypedArray())
|
||||
)
|
||||
}
|
||||
|
||||
fun setupLanguageVersionSettingsForCompilerTests(originalFileText: String, environment: KotlinCoreEnvironment) {
|
||||
|
||||
+12
@@ -1845,6 +1845,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("experimentalOnWholeModule.kt")
|
||||
public void testExperimentalOnWholeModule() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalOnWholeModule.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("experimentalWithNoImpact.kt")
|
||||
public void testExperimentalWithNoImpact() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalWithNoImpact.kt");
|
||||
@@ -1899,6 +1905,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("useExperimentalOnWholeModule.kt")
|
||||
public void testUseExperimentalOnWholeModule() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnWholeModule.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("useExperimentalTargets.kt")
|
||||
public void testUseExperimentalTargets() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalTargets.kt");
|
||||
|
||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+12
@@ -1845,6 +1845,12 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("experimentalOnWholeModule.kt")
|
||||
public void testExperimentalOnWholeModule() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalOnWholeModule.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("experimentalWithNoImpact.kt")
|
||||
public void testExperimentalWithNoImpact() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalWithNoImpact.kt");
|
||||
@@ -1899,6 +1905,12 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("useExperimentalOnWholeModule.kt")
|
||||
public void testUseExperimentalOnWholeModule() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalOnWholeModule.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("useExperimentalTargets.kt")
|
||||
public void testUseExperimentalTargets() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalTargets.kt");
|
||||
|
||||
Reference in New Issue
Block a user