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) {
|
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, context: CheckerContext) {
|
||||||
val experimentalities = descriptor.loadExperimentalities()
|
val experimentalities = descriptor.loadExperimentalities()
|
||||||
if (experimentalities.isNotEmpty()) {
|
if (experimentalities.isNotEmpty()) {
|
||||||
checkExperimental(experimentalities, element, context.trace.bindingContext, context.moduleDescriptor) {
|
checkExperimental(
|
||||||
experimentality, isBodyUsageOfSourceOnlyExperimentality ->
|
experimentalities, element, context.trace.bindingContext, context.languageVersionSettings,
|
||||||
|
context.moduleDescriptor
|
||||||
|
) { experimentality, isBodyUsageOfSourceOnlyExperimentality ->
|
||||||
val diagnostic = when (experimentality.severity) {
|
val diagnostic = when (experimentality.severity) {
|
||||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
|
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
|
||||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
|
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
|
||||||
@@ -93,6 +95,7 @@ object ExperimentalUsageChecker : CallChecker {
|
|||||||
experimentalities: Collection<Experimentality>,
|
experimentalities: Collection<Experimentality>,
|
||||||
element: PsiElement,
|
element: PsiElement,
|
||||||
bindingContext: BindingContext,
|
bindingContext: BindingContext,
|
||||||
|
languageVersionSettings: LanguageVersionSettings,
|
||||||
module: ModuleDescriptor,
|
module: ModuleDescriptor,
|
||||||
report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit
|
report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -106,11 +109,12 @@ object ExperimentalUsageChecker : CallChecker {
|
|||||||
val isBodyUsageInSameModule =
|
val isBodyUsageInSameModule =
|
||||||
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
|
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
|
||||||
|
|
||||||
|
val annotationFqName = experimentality.annotationFqName
|
||||||
val isExperimentalityAccepted =
|
val isExperimentalityAccepted =
|
||||||
isBodyUsageInSameModule ||
|
isBodyUsageInSameModule ||
|
||||||
(isBodyUsageOfCompilationExperimentality &&
|
(isBodyUsageOfCompilationExperimentality &&
|
||||||
element.hasContainerAnnotatedWithUseExperimental(experimentality.annotationFqName, bindingContext)) ||
|
element.hasContainerAnnotatedWithUseExperimental(annotationFqName, bindingContext, languageVersionSettings)) ||
|
||||||
element.propagates(experimentality.annotationFqName, bindingContext)
|
element.propagates(annotationFqName, bindingContext, languageVersionSettings)
|
||||||
|
|
||||||
if (!isExperimentalityAccepted) {
|
if (!isExperimentalityAccepted) {
|
||||||
report(experimentality, isBodyUsageOfCompilationExperimentality)
|
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
|
// Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring
|
||||||
// propagation for the experimental annotation to the call sites
|
// propagation for the experimental annotation to the call sites
|
||||||
private fun PsiElement.propagates(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
|
private fun PsiElement.propagates(
|
||||||
return anyParentMatches { element, _ ->
|
annotationFqName: FqName,
|
||||||
if (element is KtDeclaration) {
|
bindingContext: BindingContext,
|
||||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
languageVersionSettings: LanguageVersionSettings
|
||||||
descriptor != null && !DescriptorUtils.isLocal(descriptor) && descriptor.annotations.hasAnnotation(annotationFqName)
|
): Boolean =
|
||||||
} else false
|
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)`
|
// 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
|
// where annotationFqName is the FQ name of X
|
||||||
private fun PsiElement.hasContainerAnnotatedWithUseExperimental(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
|
private fun PsiElement.hasContainerAnnotatedWithUseExperimental(
|
||||||
return anyParentMatches { element, _ ->
|
annotationFqName: FqName,
|
||||||
element is KtAnnotated && element.annotationEntries.any { entry ->
|
bindingContext: BindingContext,
|
||||||
bindingContext.get(BindingContext.ANNOTATION, entry)?.isUseExperimental(annotationFqName) == true
|
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 {
|
private inline fun PsiElement.anyParentMatches(predicate: (element: PsiElement, parent: PsiElement?) -> Boolean): Boolean {
|
||||||
var element = this
|
var element = this
|
||||||
@@ -273,7 +285,9 @@ object ExperimentalUsageChecker : CallChecker {
|
|||||||
val module = descriptor.module
|
val module = descriptor.module
|
||||||
|
|
||||||
for ((experimentality, member) in experimentalOverridden) {
|
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) {
|
val diagnostic = when (experimentality.severity) {
|
||||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_OVERRIDE
|
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_OVERRIDE
|
||||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_OVERRIDE_ERROR
|
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 LANGUAGE_DIRECTIVE = "LANGUAGE"
|
||||||
const val API_VERSION_DIRECTIVE = "API_VERSION"
|
const val API_VERSION_DIRECTIVE = "API_VERSION"
|
||||||
|
|
||||||
|
const val EXPERIMENTAL_DIRECTIVE = "EXPERIMENTAL"
|
||||||
|
const val USE_EXPERIMENTAL_DIRECTIVE = "USE_EXPERIMENTAL"
|
||||||
|
|
||||||
data class CompilerTestLanguageVersionSettings(
|
data class CompilerTestLanguageVersionSettings(
|
||||||
private val initialLanguageFeatures: Map<LanguageFeature, LanguageFeature.State>,
|
private val initialLanguageFeatures: Map<LanguageFeature, LanguageFeature.State>,
|
||||||
override val apiVersion: ApiVersion,
|
override val apiVersion: ApiVersion,
|
||||||
@@ -51,15 +54,21 @@ private fun specificFeaturesForTests(): Map<LanguageFeature, LanguageFeature.Sta
|
|||||||
|
|
||||||
fun parseLanguageVersionSettings(directiveMap: Map<String, String>): LanguageVersionSettings? {
|
fun parseLanguageVersionSettings(directiveMap: Map<String, String>): LanguageVersionSettings? {
|
||||||
val apiVersionString = directiveMap[API_VERSION_DIRECTIVE]
|
val apiVersionString = directiveMap[API_VERSION_DIRECTIVE]
|
||||||
val directives = directiveMap[LANGUAGE_DIRECTIVE]
|
val languageFeaturesString = directiveMap[LANGUAGE_DIRECTIVE]
|
||||||
if (apiVersionString == null && directives == null) return null
|
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)
|
val apiVersion = (if (apiVersionString != null) ApiVersion.parse(apiVersionString) else ApiVersion.LATEST_STABLE)
|
||||||
?: error("Unknown API version: $apiVersionString")
|
?: 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) {
|
fun setupLanguageVersionSettingsForCompilerTests(originalFileText: String, environment: KotlinCoreEnvironment) {
|
||||||
|
|||||||
+12
@@ -1845,6 +1845,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("experimentalWithNoImpact.kt")
|
||||||
public void testExperimentalWithNoImpact() throws Exception {
|
public void testExperimentalWithNoImpact() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalWithNoImpact.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalWithNoImpact.kt");
|
||||||
@@ -1899,6 +1905,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("useExperimentalTargets.kt")
|
||||||
public void testUseExperimentalTargets() throws Exception {
|
public void testUseExperimentalTargets() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalTargets.kt");
|
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);
|
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")
|
@TestMetadata("experimentalWithNoImpact.kt")
|
||||||
public void testExperimentalWithNoImpact() throws Exception {
|
public void testExperimentalWithNoImpact() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalWithNoImpact.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalWithNoImpact.kt");
|
||||||
@@ -1899,6 +1905,12 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("useExperimentalTargets.kt")
|
||||||
public void testUseExperimentalTargets() throws Exception {
|
public void testUseExperimentalTargets() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalTargets.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/experimental/useExperimentalTargets.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user