Load module annotations for JVM and JS modules in compiler
#KT-22759 In Progress
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.jvm.compiler
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
||||||
|
import org.jetbrains.kotlin.js.resolve.getAnnotationsOnContainingJsModule
|
||||||
|
import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
|
||||||
|
|
||||||
|
class CliModuleAnnotationsResolver : ModuleAnnotationsResolver {
|
||||||
|
private val packagePartProviders = mutableListOf<PackagePartProvider>()
|
||||||
|
|
||||||
|
fun addPackagePartProvider(packagePartProvider: PackagePartProvider) {
|
||||||
|
packagePartProviders += packagePartProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAnnotationsOnContainingModule(descriptor: DeclarationDescriptor): List<ClassId> {
|
||||||
|
getAnnotationsOnContainingJsModule(descriptor)?.let { return it }
|
||||||
|
|
||||||
|
val moduleName = getJvmModuleNameForDeserializedDescriptor(descriptor) ?: return emptyList()
|
||||||
|
return packagePartProviders.flatMap { it.getAnnotationsOnBinaryModule(moduleName) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.config.LanguageVersionSettings
|
|||||||
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
||||||
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
||||||
import org.jetbrains.kotlin.load.kotlin.PackageParts
|
import org.jetbrains.kotlin.load.kotlin.PackageParts
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
|
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
|
||||||
import java.io.EOFException
|
import java.io.EOFException
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ class JvmPackagePartProvider(
|
|||||||
languageVersionSettings: LanguageVersionSettings,
|
languageVersionSettings: LanguageVersionSettings,
|
||||||
private val scope: GlobalSearchScope
|
private val scope: GlobalSearchScope
|
||||||
) : PackagePartProvider {
|
) : PackagePartProvider {
|
||||||
private data class ModuleMappingInfo(val root: VirtualFile, val mapping: ModuleMapping)
|
private data class ModuleMappingInfo(val root: VirtualFile, val mapping: ModuleMapping, val name: String)
|
||||||
|
|
||||||
private val deserializationConfiguration = CompilerDeserializationConfiguration(languageVersionSettings)
|
private val deserializationConfiguration = CompilerDeserializationConfiguration(languageVersionSettings)
|
||||||
|
|
||||||
@@ -68,6 +69,12 @@ class JvmPackagePartProvider(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
|
||||||
|
return loadedModules.mapNotNull { (_, mapping, name) ->
|
||||||
|
if (name == moduleName) mapping.moduleData.annotations else null
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
fun addRoots(roots: List<JavaRoot>) {
|
fun addRoots(roots: List<JavaRoot>) {
|
||||||
for ((root, type) in roots) {
|
for ((root, type) in roots) {
|
||||||
if (type != JavaRoot.RootType.BINARY) continue
|
if (type != JavaRoot.RootType.BINARY) continue
|
||||||
@@ -83,7 +90,7 @@ class JvmPackagePartProvider(
|
|||||||
catch (e: EOFException) {
|
catch (e: EOFException) {
|
||||||
throw RuntimeException("Error on reading package parts from $moduleFile in $root", e)
|
throw RuntimeException("Error on reading package parts from $moduleFile in $root", e)
|
||||||
}
|
}
|
||||||
loadedModules.add(ModuleMappingInfo(root, mapping))
|
loadedModules.add(ModuleMappingInfo(root, mapping, moduleFile.nameWithoutExtension))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ import com.intellij.ide.plugins.PluginManagerCore
|
|||||||
import com.intellij.lang.MetaLanguage
|
import com.intellij.lang.MetaLanguage
|
||||||
import com.intellij.lang.java.JavaParserDefinition
|
import com.intellij.lang.java.JavaParserDefinition
|
||||||
import com.intellij.lang.jvm.facade.JvmElementProvider
|
import com.intellij.lang.jvm.facade.JvmElementProvider
|
||||||
import com.intellij.lang.jvm.facade.JvmFacade
|
|
||||||
import com.intellij.lang.jvm.facade.JvmFacadeImpl
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.application.TransactionGuard
|
import com.intellij.openapi.application.TransactionGuard
|
||||||
import com.intellij.openapi.application.TransactionGuardImpl
|
import com.intellij.openapi.application.TransactionGuardImpl
|
||||||
@@ -101,6 +99,7 @@ import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
|
|||||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
|
import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
|
||||||
|
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
|
||||||
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
|
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
|
||||||
import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade
|
import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade
|
||||||
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
|
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
|
||||||
@@ -277,6 +276,7 @@ class KotlinCoreEnvironment private constructor(
|
|||||||
return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply {
|
return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply {
|
||||||
addRoots(initialRoots)
|
addRoots(initialRoots)
|
||||||
packagePartProviders += this
|
packagePartProviders += this
|
||||||
|
(ModuleAnnotationsResolver.getInstance(project) as CliModuleAnnotationsResolver).addPackagePartProvider(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,6 +569,7 @@ class KotlinCoreEnvironment private constructor(
|
|||||||
registerService(ScriptDependenciesProvider::class.java, CliScriptDependenciesProvider(projectEnvironment.project, scriptDefinitionProvider))
|
registerService(ScriptDependenciesProvider::class.java, CliScriptDependenciesProvider(projectEnvironment.project, scriptDefinitionProvider))
|
||||||
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
|
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
|
||||||
registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this))
|
registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this))
|
||||||
|
registerService(ModuleAnnotationsResolver::class.java, CliModuleAnnotationsResolver())
|
||||||
if (messageCollector != null) {
|
if (messageCollector != null) {
|
||||||
registerService(ScriptReportSink::class.java, CliScriptReportSink(messageCollector))
|
registerService(ScriptReportSink::class.java, CliScriptReportSink(messageCollector))
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.load.kotlin.incremental
|
|||||||
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
||||||
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
|
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
|
||||||
import org.jetbrains.kotlin.storage.StorageManager
|
import org.jetbrains.kotlin.storage.StorageManager
|
||||||
|
|
||||||
@@ -42,4 +43,8 @@ class IncrementalPackagePartProvider(
|
|||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
override fun findMetadataPackageParts(packageFqName: String): List<String> = TODO()
|
override fun findMetadataPackageParts(packageFqName: String): List<String> = TODO()
|
||||||
|
|
||||||
|
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
|
||||||
|
return parent.getAnnotationsOnBinaryModule(moduleName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.load.kotlin
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassOrPackageFragmentDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
|
||||||
|
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
|
||||||
|
|
||||||
|
fun getJvmModuleNameForDeserializedDescriptor(descriptor: DeclarationDescriptor): String? {
|
||||||
|
val parent = DescriptorUtils.getParentOfType(descriptor, ClassOrPackageFragmentDescriptor::class.java, false)
|
||||||
|
|
||||||
|
when {
|
||||||
|
parent is DeserializedClassDescriptor -> {
|
||||||
|
val classProto = parent.classProto
|
||||||
|
val nameResolver = parent.c.nameResolver
|
||||||
|
return classProto.getExtension(JvmProtoBuf.classModuleName)
|
||||||
|
?.takeIf { classProto.hasExtension(JvmProtoBuf.classModuleName) }
|
||||||
|
?.let(nameResolver::getString)
|
||||||
|
?: JvmAbi.DEFAULT_MODULE_NAME
|
||||||
|
}
|
||||||
|
descriptor is DeserializedMemberDescriptor -> {
|
||||||
|
val source = descriptor.containerSource
|
||||||
|
if (source is JvmPackagePartSource) {
|
||||||
|
val packageProto = source.packageProto
|
||||||
|
val nameResolver = source.nameResolver
|
||||||
|
return packageProto.getExtension(JvmProtoBuf.packageModuleName)
|
||||||
|
?.takeIf { packageProto.hasExtension(JvmProtoBuf.packageModuleName) }
|
||||||
|
?.let(nameResolver::getString)
|
||||||
|
?: JvmAbi.DEFAULT_MODULE_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.incremental.components.LookupTracker
|
|||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.*
|
import org.jetbrains.kotlin.resolve.*
|
||||||
import org.jetbrains.kotlin.resolve.calls.tower.KotlinResolutionStatelessCallbacksImpl
|
import org.jetbrains.kotlin.resolve.calls.tower.KotlinResolutionStatelessCallbacksImpl
|
||||||
|
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
|
||||||
import org.jetbrains.kotlin.resolve.lazy.*
|
import org.jetbrains.kotlin.resolve.lazy.*
|
||||||
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
|
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
|
||||||
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
|
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
|
||||||
@@ -63,6 +64,10 @@ fun StorageComponentContainer.configureModule(
|
|||||||
private fun StorageComponentContainer.configurePlatformIndependentComponents() {
|
private fun StorageComponentContainer.configurePlatformIndependentComponents() {
|
||||||
useImpl<SupertypeLoopCheckerImpl>()
|
useImpl<SupertypeLoopCheckerImpl>()
|
||||||
useImpl<KotlinResolutionStatelessCallbacksImpl>()
|
useImpl<KotlinResolutionStatelessCallbacksImpl>()
|
||||||
|
|
||||||
|
useImpl<ExperimentalUsageChecker>()
|
||||||
|
useImpl<ExperimentalUsageChecker.Overrides>()
|
||||||
|
useImpl<ExperimentalUsageChecker.ClassifierUsage>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun StorageComponentContainer.configureModule(
|
fun StorageComponentContainer.configureModule(
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.resolve
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.ServiceManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
|
||||||
|
interface ModuleAnnotationsResolver {
|
||||||
|
fun getAnnotationsOnContainingModule(descriptor: DeclarationDescriptor): List<ClassId>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getInstance(project: Project): ModuleAnnotationsResolver =
|
||||||
|
ServiceManager.getService(project, ModuleAnnotationsResolver::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,8 +81,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
|
|||||||
DelegationChecker(),
|
DelegationChecker(),
|
||||||
KClassWithIncorrectTypeArgumentChecker,
|
KClassWithIncorrectTypeArgumentChecker,
|
||||||
SuspendOperatorsCheckers,
|
SuspendOperatorsCheckers,
|
||||||
InlineClassDeclarationChecker,
|
InlineClassDeclarationChecker
|
||||||
ExperimentalUsageChecker.Overrides
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val DEFAULT_CALL_CHECKERS = listOf(
|
private val DEFAULT_CALL_CHECKERS = listOf(
|
||||||
@@ -92,12 +91,11 @@ private val DEFAULT_CALL_CHECKERS = listOf(
|
|||||||
CoroutineSuspendCallChecker, BuilderFunctionsCallChecker, DslScopeViolationCallChecker, MissingDependencyClassChecker,
|
CoroutineSuspendCallChecker, BuilderFunctionsCallChecker, DslScopeViolationCallChecker, MissingDependencyClassChecker,
|
||||||
CallableReferenceCompatibilityChecker(), LateinitIntrinsicApplicabilityChecker,
|
CallableReferenceCompatibilityChecker(), LateinitIntrinsicApplicabilityChecker,
|
||||||
UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(),
|
UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(),
|
||||||
PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker, ExperimentalUsageChecker
|
PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker
|
||||||
)
|
)
|
||||||
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
||||||
private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf(
|
private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf(
|
||||||
DeprecatedClassifierUsageChecker(), ApiVersionClassifierUsageChecker, MissingDependencyClassChecker.ClassifierUsage,
|
DeprecatedClassifierUsageChecker(), ApiVersionClassifierUsageChecker, MissingDependencyClassChecker.ClassifierUsage
|
||||||
ExperimentalUsageChecker.ClassifierUsage
|
|
||||||
)
|
)
|
||||||
private val DEFAULT_ANNOTATION_CHECKERS = listOf<AdditionalAnnotationChecker>(
|
private val DEFAULT_ANNOTATION_CHECKERS = listOf<AdditionalAnnotationChecker>(
|
||||||
ExperimentalMarkerDeclarationAnnotationChecker
|
ExperimentalMarkerDeclarationAnnotationChecker
|
||||||
|
|||||||
+220
-199
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.checkers
|
package org.jetbrains.kotlin.resolve.checkers
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.config.AnalysisFlag
|
import org.jetbrains.kotlin.config.AnalysisFlag
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
@@ -26,8 +27,7 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
|||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.*
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
|
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
|
||||||
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
|
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
@@ -41,19 +41,8 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
|||||||
import org.jetbrains.kotlin.utils.SmartSet
|
import org.jetbrains.kotlin.utils.SmartSet
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
object ExperimentalUsageChecker : CallChecker {
|
class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||||
internal val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental")
|
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
|
||||||
internal val USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
|
|
||||||
internal val USE_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
|
||||||
|
|
||||||
private val LEVEL = Name.identifier("level")
|
|
||||||
private val WARNING_LEVEL = Name.identifier("WARNING")
|
|
||||||
private val ERROR_LEVEL = Name.identifier("ERROR")
|
|
||||||
|
|
||||||
internal val IMPACT = Name.identifier("changesMayBreak")
|
|
||||||
private val COMPILATION_IMPACT = Name.identifier("COMPILATION")
|
|
||||||
private val LINKAGE_IMPACT = Name.identifier("LINKAGE")
|
|
||||||
private val RUNTIME_IMPACT = Name.identifier("RUNTIME")
|
|
||||||
|
|
||||||
internal data class Experimentality(
|
internal data class Experimentality(
|
||||||
val markerDescriptor: ClassDescriptor,
|
val markerDescriptor: ClassDescriptor,
|
||||||
@@ -73,235 +62,267 @@ object ExperimentalUsageChecker : CallChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
||||||
checkExperimental(resolvedCall.resultingDescriptor, reportOn, context)
|
checkExperimental(resolvedCall.resultingDescriptor, reportOn, context, moduleAnnotationsResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, context: CheckerContext) {
|
companion object {
|
||||||
val experimentalities = descriptor.loadExperimentalities()
|
internal val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental")
|
||||||
if (experimentalities.isNotEmpty()) {
|
internal val USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
|
||||||
checkExperimental(
|
internal val USE_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
||||||
experimentalities, element, context.trace.bindingContext, context.languageVersionSettings,
|
|
||||||
context.moduleDescriptor
|
private val LEVEL = Name.identifier("level")
|
||||||
) { experimentality, isBodyUsageOfSourceOnlyExperimentality ->
|
private val WARNING_LEVEL = Name.identifier("WARNING")
|
||||||
val diagnostic = when (experimentality.severity) {
|
private val ERROR_LEVEL = Name.identifier("ERROR")
|
||||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
|
|
||||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
|
internal val IMPACT = Name.identifier("changesMayBreak")
|
||||||
|
private val COMPILATION_IMPACT = Name.identifier("COMPILATION")
|
||||||
|
private val LINKAGE_IMPACT = Name.identifier("LINKAGE")
|
||||||
|
private val RUNTIME_IMPACT = Name.identifier("RUNTIME")
|
||||||
|
|
||||||
|
private fun checkExperimental(
|
||||||
|
descriptor: DeclarationDescriptor,
|
||||||
|
element: PsiElement,
|
||||||
|
context: CheckerContext,
|
||||||
|
moduleAnnotationsResolver: ModuleAnnotationsResolver
|
||||||
|
) {
|
||||||
|
val experimentalities = descriptor.loadExperimentalities(moduleAnnotationsResolver)
|
||||||
|
if (experimentalities.isNotEmpty()) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
context.trace.report(diagnostic.on(element, experimentality.annotationFqName, isBodyUsageOfSourceOnlyExperimentality))
|
||||||
}
|
}
|
||||||
context.trace.report(diagnostic.on(element, experimentality.annotationFqName, isBodyUsageOfSourceOnlyExperimentality))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkExperimental(
|
private fun checkExperimental(
|
||||||
experimentalities: Collection<Experimentality>,
|
experimentalities: Collection<Experimentality>,
|
||||||
element: PsiElement,
|
element: PsiElement,
|
||||||
bindingContext: BindingContext,
|
bindingContext: BindingContext,
|
||||||
languageVersionSettings: LanguageVersionSettings,
|
languageVersionSettings: LanguageVersionSettings,
|
||||||
module: ModuleDescriptor,
|
module: ModuleDescriptor,
|
||||||
report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit
|
report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
val isBodyUsageExceptPublicInline = element.isBodyUsage(bindingContext, allowPublicInline = false)
|
val isBodyUsageExceptPublicInline = element.isBodyUsage(bindingContext, allowPublicInline = false)
|
||||||
val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(bindingContext, allowPublicInline = true)
|
val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(bindingContext, allowPublicInline = true)
|
||||||
|
|
||||||
for (experimentality in experimentalities) {
|
for (experimentality in experimentalities) {
|
||||||
val isBodyUsageOfCompilationExperimentality =
|
val isBodyUsageOfCompilationExperimentality =
|
||||||
experimentality.isCompilationOnly && isBodyUsage
|
experimentality.isCompilationOnly && isBodyUsage
|
||||||
|
|
||||||
val isBodyUsageInSameModule =
|
val isBodyUsageInSameModule =
|
||||||
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
|
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
|
||||||
|
|
||||||
val annotationFqName = experimentality.annotationFqName
|
val annotationFqName = experimentality.annotationFqName
|
||||||
val isExperimentalityAccepted =
|
val isExperimentalityAccepted =
|
||||||
isBodyUsageInSameModule ||
|
isBodyUsageInSameModule ||
|
||||||
(isBodyUsageOfCompilationExperimentality &&
|
(isBodyUsageOfCompilationExperimentality &&
|
||||||
element.hasContainerAnnotatedWithUseExperimental(annotationFqName, bindingContext, languageVersionSettings)) ||
|
element.hasContainerAnnotatedWithUseExperimental(annotationFqName, bindingContext, languageVersionSettings)) ||
|
||||||
element.propagates(annotationFqName, bindingContext, languageVersionSettings)
|
element.propagates(annotationFqName, bindingContext, languageVersionSettings)
|
||||||
|
|
||||||
if (!isExperimentalityAccepted) {
|
if (!isExperimentalityAccepted) {
|
||||||
report(experimentality, isBodyUsageOfCompilationExperimentality)
|
report(experimentality, isBodyUsageOfCompilationExperimentality)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun DeclarationDescriptor.loadExperimentalities(): Set<Experimentality> {
|
private fun DeclarationDescriptor.loadExperimentalities(
|
||||||
val result = SmartSet.create<Experimentality>()
|
moduleAnnotationsResolver: ModuleAnnotationsResolver
|
||||||
|
): Set<Experimentality> {
|
||||||
|
val result = SmartSet.create<Experimentality>()
|
||||||
|
|
||||||
for (annotation in annotations) {
|
for (annotation in annotations) {
|
||||||
result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation())
|
|
||||||
}
|
|
||||||
|
|
||||||
val container = containingDeclaration
|
|
||||||
if (container is ClassDescriptor && this !is ConstructorDescriptor) {
|
|
||||||
for (annotation in container.annotations) {
|
|
||||||
result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation())
|
result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val container = containingDeclaration
|
||||||
|
if (container is ClassDescriptor && this !is ConstructorDescriptor) {
|
||||||
|
for (annotation in container.annotations) {
|
||||||
|
result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (moduleAnnotationClassId in moduleAnnotationsResolver.getAnnotationsOnContainingModule(this)) {
|
||||||
|
val annotationClass = module.findClassAcrossModuleDependencies(moduleAnnotationClassId)
|
||||||
|
result.addIfNotNull(annotationClass?.loadExperimentalityForMarkerAnnotation())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
internal fun ClassDescriptor.loadExperimentalityForMarkerAnnotation(): Experimentality? {
|
||||||
}
|
val experimental = annotations.findAnnotation(EXPERIMENTAL_FQ_NAME) ?: return null
|
||||||
|
|
||||||
internal fun ClassDescriptor.loadExperimentalityForMarkerAnnotation(): Experimentality? {
|
val severity = when ((experimental.allValueArguments[LEVEL] as? EnumValue)?.enumEntryName) {
|
||||||
val experimental = annotations.findAnnotation(EXPERIMENTAL_FQ_NAME) ?: return null
|
WARNING_LEVEL -> Experimentality.Severity.WARNING
|
||||||
|
ERROR_LEVEL -> Experimentality.Severity.ERROR
|
||||||
|
else -> Experimentality.DEFAULT_SEVERITY
|
||||||
|
}
|
||||||
|
|
||||||
val severity = when ((experimental.allValueArguments[LEVEL] as? EnumValue)?.enumEntryName) {
|
val impact = (experimental.allValueArguments[IMPACT] as? ArrayValue)?.value?.mapNotNull { impact ->
|
||||||
WARNING_LEVEL -> Experimentality.Severity.WARNING
|
when ((impact as? EnumValue)?.enumEntryName) {
|
||||||
ERROR_LEVEL -> Experimentality.Severity.ERROR
|
COMPILATION_IMPACT -> Experimentality.Impact.COMPILATION
|
||||||
else -> Experimentality.DEFAULT_SEVERITY
|
LINKAGE_IMPACT, RUNTIME_IMPACT -> Experimentality.Impact.LINKAGE_OR_RUNTIME
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} ?: Experimentality.DEFAULT_IMPACT
|
||||||
|
|
||||||
|
return Experimentality(this, fqNameSafe, severity, impact)
|
||||||
}
|
}
|
||||||
|
|
||||||
val impact = (experimental.allValueArguments[IMPACT] as? ArrayValue)?.value?.mapNotNull { impact ->
|
// Returns true if this element appears in the body of some function and is not visible in any non-local declaration signature.
|
||||||
when ((impact as? EnumValue)?.enumEntryName) {
|
// If that's the case, one can opt-in to using the corresponding experimental API by annotating this element (or any of its
|
||||||
COMPILATION_IMPACT -> Experimentality.Impact.COMPILATION
|
// enclosing declarations) with @UseExperimental(X::class), not requiring propagation of the experimental annotation to the call sites.
|
||||||
LINKAGE_IMPACT, RUNTIME_IMPACT -> Experimentality.Impact.LINKAGE_OR_RUNTIME
|
// (Note that this is allowed only if X's impact is [COMPILATION].)
|
||||||
|
private fun PsiElement.isBodyUsage(bindingContext: BindingContext, allowPublicInline: Boolean): Boolean {
|
||||||
|
return anyParentMatches { element, parent ->
|
||||||
|
element == (parent as? KtDeclarationWithBody)?.bodyExpression?.takeIf {
|
||||||
|
allowPublicInline || !parent.isPublicInline(bindingContext)
|
||||||
|
} ||
|
||||||
|
element == (parent as? KtDeclarationWithInitializer)?.initializer ||
|
||||||
|
element == (parent as? KtClassInitializer)?.body ||
|
||||||
|
element == (parent as? KtParameter)?.defaultValue ||
|
||||||
|
element == (parent as? KtSuperTypeCallEntry)?.valueArgumentList ||
|
||||||
|
element == (parent as? KtDelegatedSuperTypeEntry)?.delegateExpression ||
|
||||||
|
element == (parent as? KtPropertyDelegate)?.expression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.isPublicInline(bindingContext: BindingContext): Boolean {
|
||||||
|
val descriptor = when (this) {
|
||||||
|
is KtFunction -> bindingContext.get(BindingContext.FUNCTION, this)
|
||||||
|
is KtPropertyAccessor -> bindingContext.get(BindingContext.PROPERTY_ACCESSOR, this)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
} ?: Experimentality.DEFAULT_IMPACT
|
return descriptor != null && descriptor.isInline && descriptor.effectiveVisibility().let {
|
||||||
|
it == EffectiveVisibility.Public ||
|
||||||
return Experimentality(this, fqNameSafe, severity, impact)
|
it == EffectiveVisibility.ProtectedBound ||
|
||||||
}
|
it is EffectiveVisibility.Protected
|
||||||
|
}
|
||||||
// Returns true if this element appears in the body of some function and is not visible in any non-local declaration signature.
|
|
||||||
// If that's the case, one can opt-in to using the corresponding experimental API by annotating this element (or any of its
|
|
||||||
// enclosing declarations) with @UseExperimental(X::class), not requiring propagation of the experimental annotation to the call sites.
|
|
||||||
// (Note that this is allowed only if X's impact is [COMPILATION].)
|
|
||||||
private fun PsiElement.isBodyUsage(bindingContext: BindingContext, allowPublicInline: Boolean): Boolean {
|
|
||||||
return anyParentMatches { element, parent ->
|
|
||||||
element == (parent as? KtDeclarationWithBody)?.bodyExpression?.takeIf {
|
|
||||||
allowPublicInline || !parent.isPublicInline(bindingContext)
|
|
||||||
} ||
|
|
||||||
element == (parent as? KtDeclarationWithInitializer)?.initializer ||
|
|
||||||
element == (parent as? KtClassInitializer)?.body ||
|
|
||||||
element == (parent as? KtParameter)?.defaultValue ||
|
|
||||||
element == (parent as? KtSuperTypeCallEntry)?.valueArgumentList ||
|
|
||||||
element == (parent as? KtDelegatedSuperTypeEntry)?.delegateExpression ||
|
|
||||||
element == (parent as? KtPropertyDelegate)?.expression
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun PsiElement.isPublicInline(bindingContext: BindingContext): Boolean {
|
// Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring
|
||||||
val descriptor = when (this) {
|
// propagation for the experimental annotation to the call sites
|
||||||
is KtFunction -> bindingContext.get(BindingContext.FUNCTION, this)
|
private fun PsiElement.propagates(
|
||||||
is KtPropertyAccessor -> bindingContext.get(BindingContext.PROPERTY_ACCESSOR, this)
|
annotationFqName: FqName,
|
||||||
else -> null
|
bindingContext: BindingContext,
|
||||||
}
|
languageVersionSettings: LanguageVersionSettings
|
||||||
return descriptor != null && descriptor.isInline && descriptor.effectiveVisibility().let {
|
): Boolean =
|
||||||
it == EffectiveVisibility.Public ||
|
annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.experimental) ||
|
||||||
it == EffectiveVisibility.ProtectedBound ||
|
anyParentMatches { element, _ ->
|
||||||
it is EffectiveVisibility.Protected
|
if (element is KtDeclaration) {
|
||||||
}
|
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||||
}
|
descriptor != null && !DescriptorUtils.isLocal(descriptor) &&
|
||||||
|
descriptor.annotations.hasAnnotation(annotationFqName)
|
||||||
// Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring
|
} else false
|
||||||
// propagation for the experimental annotation to the call sites
|
|
||||||
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,
|
|
||||||
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 {
|
// Checks whether there's an element lexically above the tree, that is annotated with `@UseExperimental(X::class)`
|
||||||
var element = this
|
// where annotationFqName is the FQ name of X
|
||||||
while (true) {
|
private fun PsiElement.hasContainerAnnotatedWithUseExperimental(
|
||||||
val parent = element.parent
|
annotationFqName: FqName,
|
||||||
if (predicate(element, parent)) return true
|
bindingContext: BindingContext,
|
||||||
element = parent ?: return false
|
languageVersionSettings: LanguageVersionSettings
|
||||||
}
|
): Boolean =
|
||||||
}
|
annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.useExperimental) ||
|
||||||
|
anyParentMatches { element, _ ->
|
||||||
private fun AnnotationDescriptor.isUseExperimental(annotationFqName: FqName): Boolean {
|
element is KtAnnotated && element.annotationEntries.any { entry ->
|
||||||
if (fqName != USE_EXPERIMENTAL_FQ_NAME) return false
|
bindingContext.get(BindingContext.ANNOTATION, entry)?.isUseExperimental(annotationFqName) == true
|
||||||
|
|
||||||
val annotationClasses = allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS]
|
|
||||||
return annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass ->
|
|
||||||
(annotationClass as? KClassValue)?.value?.constructor?.declarationDescriptor?.fqNameSafe == annotationFqName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkCompilerArguments(
|
|
||||||
module: ModuleDescriptor,
|
|
||||||
languageVersionSettings: LanguageVersionSettings,
|
|
||||||
reportError: (String) -> Unit,
|
|
||||||
reportWarning: (String) -> Unit
|
|
||||||
) {
|
|
||||||
// Ideally, we should run full resolution (with all classifier usage checkers) on classifiers used in "-Xexperimental" and
|
|
||||||
// "-Xuse-experimental" arguments. However, it's not easy to do this. This should be solved in the future with the support of
|
|
||||||
// module annotations. For now, we only check deprecations because this is needed to correctly retire unneeded compiler arguments.
|
|
||||||
val deprecationResolver = DeprecationResolver(LockBasedStorageManager(), languageVersionSettings)
|
|
||||||
|
|
||||||
fun checkAnnotation(fqName: String, allowNonCompilationImpact: Boolean): Boolean {
|
|
||||||
val descriptor = module.resolveClassByFqName(FqName(fqName), NoLookupLocation.FOR_NON_TRACKED_SCOPE)
|
|
||||||
val experimentality = descriptor?.loadExperimentalityForMarkerAnnotation()
|
|
||||||
val message = when {
|
|
||||||
descriptor == null ->
|
|
||||||
"Experimental API marker $fqName is unresolved. " +
|
|
||||||
"Please make sure it's present in the module dependencies"
|
|
||||||
experimentality == null ->
|
|
||||||
"Class $fqName is not an experimental API marker annotation"
|
|
||||||
!allowNonCompilationImpact && !experimentality.impact.all(Experimentality.Impact.COMPILATION::equals) ->
|
|
||||||
"Experimental API marker $fqName has impact other than COMPILATION, " +
|
|
||||||
"therefore it can't be used with -Xuse-experimental"
|
|
||||||
else -> {
|
|
||||||
for (deprecation in deprecationResolver.getDeprecations(descriptor)) {
|
|
||||||
val report = when (deprecation.deprecationLevel) {
|
|
||||||
DeprecationLevelValue.WARNING -> reportWarning
|
|
||||||
DeprecationLevelValue.ERROR, DeprecationLevelValue.HIDDEN -> reportError
|
|
||||||
}
|
}
|
||||||
report("Experimental API marker $fqName is deprecated" + deprecation.message?.let { ". $it" }.orEmpty())
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
private inline fun PsiElement.anyParentMatches(predicate: (element: PsiElement, parent: PsiElement?) -> Boolean): Boolean {
|
||||||
|
var element = this
|
||||||
|
while (true) {
|
||||||
|
val parent = element.parent
|
||||||
|
if (predicate(element, parent)) return true
|
||||||
|
element = parent ?: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AnnotationDescriptor.isUseExperimental(annotationFqName: FqName): Boolean {
|
||||||
|
if (fqName != USE_EXPERIMENTAL_FQ_NAME) return false
|
||||||
|
|
||||||
|
val annotationClasses = allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS]
|
||||||
|
return annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass ->
|
||||||
|
(annotationClass as? KClassValue)?.value?.constructor?.declarationDescriptor?.fqNameSafe == annotationFqName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkCompilerArguments(
|
||||||
|
module: ModuleDescriptor,
|
||||||
|
languageVersionSettings: LanguageVersionSettings,
|
||||||
|
reportError: (String) -> Unit,
|
||||||
|
reportWarning: (String) -> Unit
|
||||||
|
) {
|
||||||
|
// Ideally, we should run full resolution (with all classifier usage checkers) on classifiers used in "-Xexperimental" and
|
||||||
|
// "-Xuse-experimental" arguments. However, it's not easy to do this. This should be solved in the future with the support of
|
||||||
|
// module annotations. For now, we only check deprecations because this is needed to correctly retire unneeded compiler arguments.
|
||||||
|
val deprecationResolver = DeprecationResolver(LockBasedStorageManager(), languageVersionSettings)
|
||||||
|
|
||||||
|
fun checkAnnotation(fqName: String, allowNonCompilationImpact: Boolean): Boolean {
|
||||||
|
val descriptor = module.resolveClassByFqName(FqName(fqName), NoLookupLocation.FOR_NON_TRACKED_SCOPE)
|
||||||
|
val experimentality = descriptor?.loadExperimentalityForMarkerAnnotation()
|
||||||
|
val message = when {
|
||||||
|
descriptor == null ->
|
||||||
|
"Experimental API marker $fqName is unresolved. " +
|
||||||
|
"Please make sure it's present in the module dependencies"
|
||||||
|
experimentality == null ->
|
||||||
|
"Class $fqName is not an experimental API marker annotation"
|
||||||
|
!allowNonCompilationImpact && !experimentality.impact.all(Experimentality.Impact.COMPILATION::equals) ->
|
||||||
|
"Experimental API marker $fqName has impact other than COMPILATION, " +
|
||||||
|
"therefore it can't be used with -Xuse-experimental"
|
||||||
|
else -> {
|
||||||
|
for (deprecation in deprecationResolver.getDeprecations(descriptor)) {
|
||||||
|
val report = when (deprecation.deprecationLevel) {
|
||||||
|
DeprecationLevelValue.WARNING -> reportWarning
|
||||||
|
DeprecationLevelValue.ERROR, DeprecationLevelValue.HIDDEN -> reportError
|
||||||
|
}
|
||||||
|
report("Experimental API marker $fqName is deprecated" + deprecation.message?.let { ". $it" }.orEmpty())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reportError(message)
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
reportError(message)
|
val validExperimental =
|
||||||
|
languageVersionSettings.getFlag(AnalysisFlag.experimental)
|
||||||
|
.filter { checkAnnotation(it, allowNonCompilationImpact = true) }
|
||||||
|
val validUseExperimental =
|
||||||
|
languageVersionSettings.getFlag(AnalysisFlag.useExperimental)
|
||||||
|
.filter { checkAnnotation(it, allowNonCompilationImpact = false) }
|
||||||
|
|
||||||
return false
|
for (fqName in validExperimental.intersect(validUseExperimental)) {
|
||||||
}
|
reportError("'-Xuse-experimental=$fqName' has no effect because '-Xexperimental=$fqName' is used")
|
||||||
|
}
|
||||||
val validExperimental =
|
|
||||||
languageVersionSettings.getFlag(AnalysisFlag.experimental)
|
|
||||||
.filter { checkAnnotation(it, allowNonCompilationImpact = true) }
|
|
||||||
val validUseExperimental =
|
|
||||||
languageVersionSettings.getFlag(AnalysisFlag.useExperimental)
|
|
||||||
.filter { checkAnnotation(it, allowNonCompilationImpact = false) }
|
|
||||||
|
|
||||||
for (fqName in validExperimental.intersect(validUseExperimental)) {
|
|
||||||
reportError("'-Xuse-experimental=$fqName' has no effect because '-Xexperimental=$fqName' is used")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ClassifierUsage : ClassifierUsageChecker {
|
class ClassifierUsage(project: Project) : ClassifierUsageChecker {
|
||||||
|
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
|
||||||
|
|
||||||
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
|
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
|
||||||
checkExperimental(targetDescriptor, element, context)
|
checkExperimental(targetDescriptor, element, context, moduleAnnotationsResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Overrides : DeclarationChecker {
|
class Overrides(project: Project) : DeclarationChecker {
|
||||||
|
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
|
||||||
|
|
||||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||||
if (descriptor !is CallableMemberDescriptor) return
|
if (descriptor !is CallableMemberDescriptor) return
|
||||||
|
|
||||||
val experimentalOverridden = descriptor.overriddenDescriptors.flatMap { member ->
|
val experimentalOverridden = descriptor.overriddenDescriptors.flatMap { member ->
|
||||||
member.loadExperimentalities().map { experimentality -> experimentality to member }
|
member.loadExperimentalities(moduleAnnotationsResolver).map { experimentality -> experimentality to member }
|
||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
val module = descriptor.module
|
val module = descriptor.module
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
@Experimental
|
||||||
|
annotation class ExperimentalAPI
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
compiler/testData/experimental/jsExperimentalModule/usage.kt:5:15: error: this declaration is experimental and its usage must be marked with '@lib.ExperimentalAPI'
|
||||||
|
fun fail(foo: Foo) {
|
||||||
|
^
|
||||||
|
compiler/testData/experimental/jsExperimentalModule/usage.kt:6:5: error: this declaration is experimental and its usage must be marked with '@lib.ExperimentalAPI'
|
||||||
|
bar()
|
||||||
|
^
|
||||||
|
COMPILATION_ERROR
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
import lib.*
|
||||||
|
|
||||||
|
fun fail(foo: Foo) {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalAPI
|
||||||
|
fun ok(foo: Foo) {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
@Experimental
|
||||||
|
annotation class ExperimentalAPI
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
compiler/testData/experimental/jvmExperimentalModule/usage.kt:5:15: error: this declaration is experimental and its usage must be marked with '@lib.ExperimentalAPI'
|
||||||
|
fun fail(foo: Foo) {
|
||||||
|
^
|
||||||
|
compiler/testData/experimental/jvmExperimentalModule/usage.kt:6:5: error: this declaration is experimental and its usage must be marked with '@lib.ExperimentalAPI'
|
||||||
|
bar()
|
||||||
|
^
|
||||||
|
COMPILATION_ERROR
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
import lib.*
|
||||||
|
|
||||||
|
fun fail(foo: Foo) {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalAPI
|
||||||
|
fun ok(foo: Foo) {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
+9
-3
@@ -99,10 +99,16 @@ abstract class AbstractKotlinCompilerIntegrationTest : TestCaseWithTmpdir() {
|
|||||||
*
|
*
|
||||||
* @return the path to the corresponding .meta.js file, i.e. "[libraryName].meta.js"
|
* @return the path to the corresponding .meta.js file, i.e. "[libraryName].meta.js"
|
||||||
*/
|
*/
|
||||||
protected fun compileJsLibrary(libraryName: String): File {
|
protected fun compileJsLibrary(
|
||||||
|
libraryName: String,
|
||||||
|
additionalOptions: List<String> = emptyList(),
|
||||||
|
checkKotlinOutput: (String) -> Unit = { actual -> assertEquals(normalizeOutput("" to ExitCode.OK), actual) }
|
||||||
|
): File {
|
||||||
val destination = File(tmpdir, "$libraryName.js")
|
val destination = File(tmpdir, "$libraryName.js")
|
||||||
val output = compileKotlin(libraryName, destination, compiler = K2JSCompiler(), expectedFileName = null)
|
val output = compileKotlin(
|
||||||
assertEquals(normalizeOutput("" to ExitCode.OK), normalizeOutput(output))
|
libraryName, destination, compiler = K2JSCompiler(), additionalOptions = additionalOptions, expectedFileName = null
|
||||||
|
)
|
||||||
|
checkKotlinOutput(normalizeOutput(output))
|
||||||
return File(tmpdir, "$libraryName.meta.js")
|
return File(tmpdir, "$libraryName.meta.js")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.jvm.compiler
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.cli.js.K2JSCompiler
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class ExperimentalIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
|
||||||
|
override val testDataPath: String
|
||||||
|
get() = "compiler/testData/experimental/"
|
||||||
|
|
||||||
|
fun testJvmExperimentalModule() {
|
||||||
|
val lib = compileLibrary(
|
||||||
|
"lib", additionalOptions = listOf(
|
||||||
|
"-Xskip-runtime-version-check",
|
||||||
|
"-language-version",
|
||||||
|
"1.3",
|
||||||
|
"-Xexperimental=lib.ExperimentalAPI"
|
||||||
|
),
|
||||||
|
checkKotlinOutput = { output -> output.trimEnd().endsWith("OK") }
|
||||||
|
)
|
||||||
|
compileKotlin("usage.kt", tmpdir, listOf(lib), additionalOptions = listOf("-Xskip-metadata-version-check"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testJsExperimentalModule() {
|
||||||
|
val lib = compileJsLibrary(
|
||||||
|
"lib", additionalOptions = listOf(
|
||||||
|
"-Xskip-runtime-version-check",
|
||||||
|
"-language-version",
|
||||||
|
"1.3",
|
||||||
|
"-Xexperimental=lib.ExperimentalAPI"
|
||||||
|
),
|
||||||
|
checkKotlinOutput = { output -> output.trimEnd().endsWith("OK") }
|
||||||
|
)
|
||||||
|
compileKotlin(
|
||||||
|
"usage.kt", File(tmpdir, "usage.js"), listOf(lib), K2JSCompiler(),
|
||||||
|
additionalOptions = listOf("-Xskip-metadata-version-check")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -18,6 +18,7 @@ package kotlin.reflect.jvm.internal.components
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
||||||
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
|
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@@ -44,4 +45,9 @@ class RuntimePackagePartProvider(private val classLoader: ClassLoader) : Package
|
|||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
override fun findMetadataPackageParts(packageFqName: String): List<String> = TODO()
|
override fun findMetadataPackageParts(packageFqName: String): List<String> = TODO()
|
||||||
|
|
||||||
|
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
|
||||||
|
// TODO: load annotations from resource files
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.descriptors
|
package org.jetbrains.kotlin.descriptors
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
|
||||||
interface PackagePartProvider {
|
interface PackagePartProvider {
|
||||||
/**
|
/**
|
||||||
* @return JVM internal names of package parts existing in the package with the given FQ name.
|
* @return JVM internal names of package parts existing in the package with the given FQ name.
|
||||||
@@ -31,9 +33,13 @@ interface PackagePartProvider {
|
|||||||
*/
|
*/
|
||||||
fun findMetadataPackageParts(packageFqName: String): List<String>
|
fun findMetadataPackageParts(packageFqName: String): List<String>
|
||||||
|
|
||||||
|
fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId>
|
||||||
|
|
||||||
object Empty : PackagePartProvider {
|
object Empty : PackagePartProvider {
|
||||||
override fun findPackageParts(packageFqName: String): List<String> = emptyList()
|
override fun findPackageParts(packageFqName: String): List<String> = emptyList()
|
||||||
|
|
||||||
override fun findMetadataPackageParts(packageFqName: String): List<String> = emptyList()
|
override fun findMetadataPackageParts(packageFqName: String): List<String> = emptyList()
|
||||||
|
|
||||||
|
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -21,6 +21,7 @@ import com.intellij.util.indexing.FileBasedIndex
|
|||||||
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
import org.jetbrains.kotlin.descriptors.PackagePartProvider
|
||||||
import org.jetbrains.kotlin.idea.vfilefinder.KotlinModuleMappingIndex
|
import org.jetbrains.kotlin.idea.vfilefinder.KotlinModuleMappingIndex
|
||||||
import org.jetbrains.kotlin.load.kotlin.PackageParts
|
import org.jetbrains.kotlin.load.kotlin.PackageParts
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
|
||||||
class IDEPackagePartProvider(val scope: GlobalSearchScope) : PackagePartProvider {
|
class IDEPackagePartProvider(val scope: GlobalSearchScope) : PackagePartProvider {
|
||||||
override fun findPackageParts(packageFqName: String): List<String> =
|
override fun findPackageParts(packageFqName: String): List<String> =
|
||||||
@@ -31,4 +32,8 @@ class IDEPackagePartProvider(val scope: GlobalSearchScope) : PackagePartProvider
|
|||||||
|
|
||||||
private fun getPackageParts(packageFqName: String): MutableList<PackageParts> =
|
private fun getPackageParts(packageFqName: String): MutableList<PackageParts> =
|
||||||
FileBasedIndex.getInstance().getValues(KotlinModuleMappingIndex.KEY, packageFqName, scope)
|
FileBasedIndex.getInstance().getValues(KotlinModuleMappingIndex.KEY, packageFqName, scope)
|
||||||
|
|
||||||
|
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.js.resolve
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassOrPackageFragmentDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
|
||||||
|
import org.jetbrains.kotlin.serialization.js.KotlinJavascriptPackageFragment
|
||||||
|
|
||||||
|
fun getAnnotationsOnContainingJsModule(descriptor: DeclarationDescriptor): List<ClassId>? {
|
||||||
|
val parent = DescriptorUtils.getParentOfType(descriptor, ClassOrPackageFragmentDescriptor::class.java, false) ?: return emptyList()
|
||||||
|
|
||||||
|
val parentSource = (descriptor as? DeserializedMemberDescriptor)?.containerSource ?: parent.source
|
||||||
|
return (parentSource as? KotlinJavascriptPackageFragment.JsContainerSource)?.annotations
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user