Load module annotations for JVM and JS modules in compiler

#KT-22759 In Progress
This commit is contained in:
Alexander Udalov
2018-01-23 13:33:47 +01:00
parent 396a5ab352
commit 890374a42a
21 changed files with 478 additions and 211 deletions
@@ -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))
} }
@@ -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
@@ -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()
}
@@ -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")
)
}
}
@@ -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()
} }
} }
@@ -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
}