diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliModuleAnnotationsResolver.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliModuleAnnotationsResolver.kt new file mode 100644 index 00000000000..535aa469ee6 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliModuleAnnotationsResolver.kt @@ -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() + + fun addPackagePartProvider(packagePartProvider: PackagePartProvider) { + packagePartProviders += packagePartProvider + } + + override fun getAnnotationsOnContainingModule(descriptor: DeclarationDescriptor): List { + getAnnotationsOnContainingJsModule(descriptor)?.let { return it } + + val moduleName = getJvmModuleNameForDeserializedDescriptor(descriptor) ?: return emptyList() + return packagePartProviders.flatMap { it.getAnnotationsOnBinaryModule(moduleName) } + } +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmPackagePartProvider.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmPackagePartProvider.kt index 846cefeb996..9dc093dc382 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmPackagePartProvider.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmPackagePartProvider.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.PackagePartProvider import org.jetbrains.kotlin.load.kotlin.ModuleMapping import org.jetbrains.kotlin.load.kotlin.PackageParts +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration import java.io.EOFException @@ -31,7 +32,7 @@ class JvmPackagePartProvider( languageVersionSettings: LanguageVersionSettings, private val scope: GlobalSearchScope ) : 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) @@ -68,6 +69,12 @@ class JvmPackagePartProvider( return result } + override fun getAnnotationsOnBinaryModule(moduleName: String): List { + return loadedModules.mapNotNull { (_, mapping, name) -> + if (name == moduleName) mapping.moduleData.annotations else null + }.flatten() + } + fun addRoots(roots: List) { for ((root, type) in roots) { if (type != JavaRoot.RootType.BINARY) continue @@ -83,7 +90,7 @@ class JvmPackagePartProvider( catch (e: EOFException) { throw RuntimeException("Error on reading package parts from $moduleFile in $root", e) } - loadedModules.add(ModuleMappingInfo(root, mapping)) + loadedModules.add(ModuleMappingInfo(root, mapping, moduleFile.nameWithoutExtension)) } } } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index b5676170dae..39cc92c47d1 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -29,8 +29,6 @@ import com.intellij.ide.plugins.PluginManagerCore import com.intellij.lang.MetaLanguage import com.intellij.lang.java.JavaParserDefinition 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.application.TransactionGuard 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.psi.KtFile import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer +import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension @@ -277,6 +276,7 @@ class KotlinCoreEnvironment private constructor( return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply { addRoots(initialRoots) 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(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this)) registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this)) + registerService(ModuleAnnotationsResolver::class.java, CliModuleAnnotationsResolver()) if (messageCollector != null) { registerService(ScriptReportSink::class.java, CliScriptReportSink(messageCollector)) } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackagePartProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackagePartProvider.kt index 140d42e596c..e5e82e2d3d8 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackagePartProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackagePartProvider.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.load.kotlin.incremental import org.jetbrains.kotlin.descriptors.PackagePartProvider import org.jetbrains.kotlin.load.kotlin.ModuleMapping 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.storage.StorageManager @@ -42,4 +43,8 @@ class IncrementalPackagePartProvider( // TODO override fun findMetadataPackageParts(packageFqName: String): List = TODO() + + override fun getAnnotationsOnBinaryModule(moduleName: String): List { + return parent.getAnnotationsOnBinaryModule(moduleName) + } } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/moduleName.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/moduleName.kt new file mode 100644 index 00000000000..16eb5b50030 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/moduleName.kt @@ -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 +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt b/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt index c2f79f93c21..de0695ef5c9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.* 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.declarations.DeclarationProviderFactory import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory @@ -63,6 +64,10 @@ fun StorageComponentContainer.configureModule( private fun StorageComponentContainer.configurePlatformIndependentComponents() { useImpl() useImpl() + + useImpl() + useImpl() + useImpl() } fun StorageComponentContainer.configureModule( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModuleAnnotationsResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModuleAnnotationsResolver.kt new file mode 100644 index 00000000000..b1e0ea4bf53 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModuleAnnotationsResolver.kt @@ -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 + + companion object { + fun getInstance(project: Project): ModuleAnnotationsResolver = + ServiceManager.getService(project, ModuleAnnotationsResolver::class.java) + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt index c98f2836b34..8a6a55b24d1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt @@ -81,8 +81,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf( DelegationChecker(), KClassWithIncorrectTypeArgumentChecker, SuspendOperatorsCheckers, - InlineClassDeclarationChecker, - ExperimentalUsageChecker.Overrides + InlineClassDeclarationChecker ) private val DEFAULT_CALL_CHECKERS = listOf( @@ -92,12 +91,11 @@ private val DEFAULT_CALL_CHECKERS = listOf( CoroutineSuspendCallChecker, BuilderFunctionsCallChecker, DslScopeViolationCallChecker, MissingDependencyClassChecker, CallableReferenceCompatibilityChecker(), LateinitIntrinsicApplicabilityChecker, UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(), - PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker, ExperimentalUsageChecker + PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker ) private val DEFAULT_TYPE_CHECKERS = emptyList() private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf( - DeprecatedClassifierUsageChecker(), ApiVersionClassifierUsageChecker, MissingDependencyClassChecker.ClassifierUsage, - ExperimentalUsageChecker.ClassifierUsage + DeprecatedClassifierUsageChecker(), ApiVersionClassifierUsageChecker, MissingDependencyClassChecker.ClassifierUsage ) private val DEFAULT_ANNOTATION_CHECKERS = listOf( ExperimentalMarkerDeclarationAnnotationChecker diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt index 8578470eb2b..776c773e835 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.resolve.checkers +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import org.jetbrains.kotlin.config.AnalysisFlag 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.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext 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.addIfNotNull -object ExperimentalUsageChecker : CallChecker { - internal val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental") - 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") +class ExperimentalUsageChecker(project: Project) : CallChecker { + private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project) internal data class Experimentality( val markerDescriptor: ClassDescriptor, @@ -73,235 +62,267 @@ object ExperimentalUsageChecker : CallChecker { } 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) { - val experimentalities = descriptor.loadExperimentalities() - 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 + companion object { + internal val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental") + 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") + + 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( - experimentalities: Collection, - element: PsiElement, - bindingContext: BindingContext, - languageVersionSettings: LanguageVersionSettings, - module: ModuleDescriptor, - report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit - ) { - val isBodyUsageExceptPublicInline = element.isBodyUsage(bindingContext, allowPublicInline = false) - val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(bindingContext, allowPublicInline = true) + private fun checkExperimental( + experimentalities: Collection, + element: PsiElement, + bindingContext: BindingContext, + languageVersionSettings: LanguageVersionSettings, + module: ModuleDescriptor, + report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit + ) { + val isBodyUsageExceptPublicInline = element.isBodyUsage(bindingContext, allowPublicInline = false) + val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(bindingContext, allowPublicInline = true) - for (experimentality in experimentalities) { - val isBodyUsageOfCompilationExperimentality = - experimentality.isCompilationOnly && isBodyUsage + for (experimentality in experimentalities) { + val isBodyUsageOfCompilationExperimentality = + experimentality.isCompilationOnly && isBodyUsage - val isBodyUsageInSameModule = - experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline + val isBodyUsageInSameModule = + experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline - val annotationFqName = experimentality.annotationFqName - val isExperimentalityAccepted = - isBodyUsageInSameModule || - (isBodyUsageOfCompilationExperimentality && - element.hasContainerAnnotatedWithUseExperimental(annotationFqName, bindingContext, languageVersionSettings)) || - element.propagates(annotationFqName, bindingContext, languageVersionSettings) + val annotationFqName = experimentality.annotationFqName + val isExperimentalityAccepted = + isBodyUsageInSameModule || + (isBodyUsageOfCompilationExperimentality && + element.hasContainerAnnotatedWithUseExperimental(annotationFqName, bindingContext, languageVersionSettings)) || + element.propagates(annotationFqName, bindingContext, languageVersionSettings) - if (!isExperimentalityAccepted) { - report(experimentality, isBodyUsageOfCompilationExperimentality) + if (!isExperimentalityAccepted) { + report(experimentality, isBodyUsageOfCompilationExperimentality) + } } } - } - private fun DeclarationDescriptor.loadExperimentalities(): Set { - val result = SmartSet.create() + private fun DeclarationDescriptor.loadExperimentalities( + moduleAnnotationsResolver: ModuleAnnotationsResolver + ): Set { + val result = SmartSet.create() - for (annotation in annotations) { - result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation()) - } - - val container = containingDeclaration - if (container is ClassDescriptor && this !is ConstructorDescriptor) { - for (annotation in container.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()) + } + } + + 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 experimental = annotations.findAnnotation(EXPERIMENTAL_FQ_NAME) ?: return null + val severity = when ((experimental.allValueArguments[LEVEL] as? EnumValue)?.enumEntryName) { + WARNING_LEVEL -> Experimentality.Severity.WARNING + ERROR_LEVEL -> Experimentality.Severity.ERROR + else -> Experimentality.DEFAULT_SEVERITY + } - val severity = when ((experimental.allValueArguments[LEVEL] as? EnumValue)?.enumEntryName) { - WARNING_LEVEL -> Experimentality.Severity.WARNING - ERROR_LEVEL -> Experimentality.Severity.ERROR - else -> Experimentality.DEFAULT_SEVERITY + val impact = (experimental.allValueArguments[IMPACT] as? ArrayValue)?.value?.mapNotNull { impact -> + when ((impact as? EnumValue)?.enumEntryName) { + COMPILATION_IMPACT -> Experimentality.Impact.COMPILATION + 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 -> - when ((impact as? EnumValue)?.enumEntryName) { - COMPILATION_IMPACT -> Experimentality.Impact.COMPILATION - LINKAGE_IMPACT, RUNTIME_IMPACT -> Experimentality.Impact.LINKAGE_OR_RUNTIME + // 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 { + val descriptor = when (this) { + is KtFunction -> bindingContext.get(BindingContext.FUNCTION, this) + is KtPropertyAccessor -> bindingContext.get(BindingContext.PROPERTY_ACCESSOR, this) else -> null } - } ?: Experimentality.DEFAULT_IMPACT - - return Experimentality(this, fqNameSafe, severity, impact) - } - - // 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 + return descriptor != null && descriptor.isInline && descriptor.effectiveVisibility().let { + it == EffectiveVisibility.Public || + it == EffectiveVisibility.ProtectedBound || + it is EffectiveVisibility.Protected + } } - } - 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 - } - return descriptor != null && descriptor.isInline && descriptor.effectiveVisibility().let { - it == EffectiveVisibility.Public || - it == EffectiveVisibility.ProtectedBound || - it is EffectiveVisibility.Protected - } - } - - // Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring - // propagation for the experimental annotation to the call sites - private fun PsiElement.propagates( - annotationFqName: FqName, - bindingContext: BindingContext, - 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 + // Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring + // propagation for the experimental annotation to the call sites + private fun PsiElement.propagates( + annotationFqName: FqName, + bindingContext: BindingContext, + 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 } - } - 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 + // 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 } - 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 - } - - 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") + 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) { - 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) { if (descriptor !is CallableMemberDescriptor) return val experimentalOverridden = descriptor.overriddenDescriptors.flatMap { member -> - member.loadExperimentalities().map { experimentality -> experimentality to member } + member.loadExperimentalities(moduleAnnotationsResolver).map { experimentality -> experimentality to member } }.toMap() val module = descriptor.module diff --git a/compiler/testData/experimental/jsExperimentalModule/lib/lib.kt b/compiler/testData/experimental/jsExperimentalModule/lib/lib.kt new file mode 100644 index 00000000000..920a5a68bf7 --- /dev/null +++ b/compiler/testData/experimental/jsExperimentalModule/lib/lib.kt @@ -0,0 +1,8 @@ +package lib + +@Experimental +annotation class ExperimentalAPI + +class Foo + +fun bar() {} diff --git a/compiler/testData/experimental/jsExperimentalModule/output.txt b/compiler/testData/experimental/jsExperimentalModule/output.txt new file mode 100644 index 00000000000..47b9e72d0da --- /dev/null +++ b/compiler/testData/experimental/jsExperimentalModule/output.txt @@ -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 diff --git a/compiler/testData/experimental/jsExperimentalModule/usage.kt b/compiler/testData/experimental/jsExperimentalModule/usage.kt new file mode 100644 index 00000000000..3fa639fa740 --- /dev/null +++ b/compiler/testData/experimental/jsExperimentalModule/usage.kt @@ -0,0 +1,12 @@ +package usage + +import lib.* + +fun fail(foo: Foo) { + bar() +} + +@ExperimentalAPI +fun ok(foo: Foo) { + bar() +} diff --git a/compiler/testData/experimental/jvmExperimentalModule/lib/lib.kt b/compiler/testData/experimental/jvmExperimentalModule/lib/lib.kt new file mode 100644 index 00000000000..920a5a68bf7 --- /dev/null +++ b/compiler/testData/experimental/jvmExperimentalModule/lib/lib.kt @@ -0,0 +1,8 @@ +package lib + +@Experimental +annotation class ExperimentalAPI + +class Foo + +fun bar() {} diff --git a/compiler/testData/experimental/jvmExperimentalModule/output.txt b/compiler/testData/experimental/jvmExperimentalModule/output.txt new file mode 100644 index 00000000000..fe1e69c9ee1 --- /dev/null +++ b/compiler/testData/experimental/jvmExperimentalModule/output.txt @@ -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 diff --git a/compiler/testData/experimental/jvmExperimentalModule/usage.kt b/compiler/testData/experimental/jvmExperimentalModule/usage.kt new file mode 100644 index 00000000000..3fa639fa740 --- /dev/null +++ b/compiler/testData/experimental/jvmExperimentalModule/usage.kt @@ -0,0 +1,12 @@ +package usage + +import lib.* + +fun fail(foo: Foo) { + bar() +} + +@ExperimentalAPI +fun ok(foo: Foo) { + bar() +} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt index 156bc96e08a..5584735c2b7 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt @@ -99,10 +99,16 @@ abstract class AbstractKotlinCompilerIntegrationTest : TestCaseWithTmpdir() { * * @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 = emptyList(), + checkKotlinOutput: (String) -> Unit = { actual -> assertEquals(normalizeOutput("" to ExitCode.OK), actual) } + ): File { val destination = File(tmpdir, "$libraryName.js") - val output = compileKotlin(libraryName, destination, compiler = K2JSCompiler(), expectedFileName = null) - assertEquals(normalizeOutput("" to ExitCode.OK), normalizeOutput(output)) + val output = compileKotlin( + libraryName, destination, compiler = K2JSCompiler(), additionalOptions = additionalOptions, expectedFileName = null + ) + checkKotlinOutput(normalizeOutput(output)) return File(tmpdir, "$libraryName.meta.js") } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/ExperimentalIntegrationTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/ExperimentalIntegrationTest.kt new file mode 100644 index 00000000000..45bc5bf3673 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/ExperimentalIntegrationTest.kt @@ -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") + ) + } +} diff --git a/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimePackagePartProvider.kt b/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimePackagePartProvider.kt index 6dc04950506..e323500b8a7 100644 --- a/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimePackagePartProvider.kt +++ b/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/components/RuntimePackagePartProvider.kt @@ -18,6 +18,7 @@ package kotlin.reflect.jvm.internal.components import org.jetbrains.kotlin.descriptors.PackagePartProvider import org.jetbrains.kotlin.load.kotlin.ModuleMapping +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration import java.util.concurrent.ConcurrentHashMap @@ -44,4 +45,9 @@ class RuntimePackagePartProvider(private val classLoader: ClassLoader) : Package // TODO override fun findMetadataPackageParts(packageFqName: String): List = TODO() + + override fun getAnnotationsOnBinaryModule(moduleName: String): List { + // TODO: load annotations from resource files + return emptyList() + } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackagePartProvider.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackagePartProvider.kt index 10b7203dd5d..54a1ccb9cd9 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackagePartProvider.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackagePartProvider.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.descriptors +import org.jetbrains.kotlin.name.ClassId + interface PackagePartProvider { /** * @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 + fun getAnnotationsOnBinaryModule(moduleName: String): List + object Empty : PackagePartProvider { override fun findPackageParts(packageFqName: String): List = emptyList() override fun findMetadataPackageParts(packageFqName: String): List = emptyList() + + override fun getAnnotationsOnBinaryModule(moduleName: String): List = emptyList() } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt index 92fbfc4e067..d47ac154f0f 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt @@ -21,6 +21,7 @@ import com.intellij.util.indexing.FileBasedIndex import org.jetbrains.kotlin.descriptors.PackagePartProvider import org.jetbrains.kotlin.idea.vfilefinder.KotlinModuleMappingIndex import org.jetbrains.kotlin.load.kotlin.PackageParts +import org.jetbrains.kotlin.name.ClassId class IDEPackagePartProvider(val scope: GlobalSearchScope) : PackagePartProvider { override fun findPackageParts(packageFqName: String): List = @@ -31,4 +32,8 @@ class IDEPackagePartProvider(val scope: GlobalSearchScope) : PackagePartProvider private fun getPackageParts(packageFqName: String): MutableList = FileBasedIndex.getInstance().getValues(KotlinModuleMappingIndex.KEY, packageFqName, scope) + + override fun getAnnotationsOnBinaryModule(moduleName: String): List { + throw UnsupportedOperationException() + } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/moduleAnnotations.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/moduleAnnotations.kt new file mode 100644 index 00000000000..19282e4f5f9 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/moduleAnnotations.kt @@ -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? { + 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 +}