Support new scheme of compilation of OptionalExpectation annotations

Instead of generating these annotation classes as package-private on
JVM, serialize their metadata to the .kotlin_module file, and load it
when compiling dependent multiplatform modules.

The problem with generating them as package-private was that
kotlin-stdlib for JVM would end up declaring symbols from other
platforms, which would include some annotations from package
kotlin.native. But using that package is discouraged by some tools
because it has a Java keyword in its name. In particular, jlink refused
to work with such artifact altogether (KT-21266).

 #KT-38652 Fixed
This commit is contained in:
Alexander Udalov
2019-12-17 17:43:42 +01:00
committed by Alexander Udalov
parent 63e355d979
commit 012ffa2993
42 changed files with 1469 additions and 145 deletions
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver;
import org.jetbrains.kotlin.resolve.InlineClassesUtilsKt;
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker;
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.jvm.*;
@@ -433,9 +432,6 @@ public class AsmUtil {
if (descriptor instanceof SyntheticClassDescriptorForLambda) {
return getVisibilityAccessFlagForAnonymous(descriptor);
}
if (ExpectedActualDeclarationChecker.isOptionalAnnotationClass(descriptor)) {
return NO_FLAG_PACKAGE_PRIVATE;
}
if (descriptor.getKind() == ClassKind.ENUM_ENTRY) {
return NO_FLAG_PACKAGE_PRIVATE;
}
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf;
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping;
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMappingKt;
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts;
import org.jetbrains.kotlin.metadata.serialization.StringTable;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
@@ -129,15 +130,18 @@ public class ClassFileFactory implements OutputFileCollection {
JvmModuleProtoBuf.Module.Builder builder = JvmModuleProtoBuf.Module.newBuilder();
String outputFilePath = getMappingFileName(state.getModuleName());
for (PackageParts part : ClassFileUtilsKt.addCompiledPartsAndSort(packagePartRegistry.getParts().values(), state)) {
part.addTo(builder);
}
StringTableImpl stringTable = new StringTableImpl();
ClassFileUtilsKt.addDataFromCompiledModule(builder, packagePartRegistry, stringTable, state);
List<String> experimental = state.getLanguageVersionSettings().getFlag(AnalysisFlags.getExperimental());
if (!experimental.isEmpty()) {
writeExperimentalMarkers(state.getModule(), builder, experimental);
writeExperimentalMarkers(state.getModule(), builder, experimental, stringTable);
}
Pair<ProtoBuf.StringTable, ProtoBuf.QualifiedNameTable> tables = stringTable.buildProto();
builder.setStringTable(tables.getFirst());
builder.setQualifiedNameTable(tables.getSecond());
JvmModuleProtoBuf.Module moduleProto = builder.build();
generators.put(outputFilePath, new OutAndSourceFileList(CollectionsKt.toList(sourceFiles)) {
@@ -160,9 +164,9 @@ public class ClassFileFactory implements OutputFileCollection {
private static void writeExperimentalMarkers(
@NotNull ModuleDescriptor module,
@NotNull JvmModuleProtoBuf.Module.Builder builder,
@NotNull List<String> experimental
@NotNull List<String> experimental,
@NotNull StringTable stringTable
) {
StringTableImpl stringTable = new StringTableImpl();
for (String fqName : experimental) {
ClassDescriptor descriptor =
DescriptorUtilKt.resolveClassByFqName(module, new FqName(fqName), NoLookupLocation.FOR_ALREADY_TRACKED);
@@ -175,9 +179,6 @@ public class ClassFileFactory implements OutputFileCollection {
}
}
}
Pair<ProtoBuf.StringTable, ProtoBuf.QualifiedNameTable> tables = stringTable.buildProto();
builder.setStringTable(tables.getFirst());
builder.setQualifiedNameTable(tables.getSecond());
}
@NotNull
@@ -90,8 +90,12 @@ public class PackageCodegenImpl implements PackageCodegen {
for (KtDeclaration declaration : file.getDeclarations()) {
if (declaration instanceof KtClassOrObject) {
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration);
if (PsiUtilsKt.hasExpectModifier(declaration) &&
(descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) {
if (PsiUtilsKt.hasExpectModifier(declaration)) {
if (descriptor != null && ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor)) {
assert ExpectedActualDeclarationChecker.isOptionalAnnotationClass(descriptor) :
"Expect class should be generated only if it's an optional annotation: " + descriptor;
state.getFactory().getPackagePartRegistry().getOptionalAnnotations().add(descriptor);
}
continue;
}
@@ -16,11 +16,13 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts
import org.jetbrains.kotlin.name.FqName
class PackagePartRegistry {
val parts = mutableMapOf<FqName, PackageParts>()
val optionalAnnotations = mutableListOf<ClassDescriptor>()
fun addPart(packageFqName: FqName, partInternalName: String, facadeInternalName: String?) {
parts.computeIfAbsent(packageFqName) { PackageParts(it.asString()) }.addPart(partInternalName, facadeInternalName)
@@ -18,10 +18,19 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.load.kotlin.loadModuleMapping
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.serialization.DescriptorSerializer
import org.jetbrains.kotlin.serialization.KotlinSerializerExtensionBase
import org.jetbrains.kotlin.serialization.StringTableImpl
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
fun ClassFileFactory.getClassFiles(): Iterable<OutputFile> {
return asList().filterClassFiles()
@@ -31,27 +40,64 @@ fun List<OutputFile>.filterClassFiles(): List<OutputFile> {
return filter { it.relativePath.endsWith(".class") }
}
fun Iterable<PackageParts>.addCompiledPartsAndSort(state: GenerationState): List<PackageParts> =
addCompiledParts(state).sortedBy { it.packageFqName }
private fun Iterable<PackageParts>.addCompiledParts(state: GenerationState): List<PackageParts> {
val incrementalCache = state.incrementalCacheForThisTarget ?: return this.toList()
val moduleMappingData = incrementalCache.getModuleMappingData() ?: return this.toList()
val mapping = ModuleMapping.loadModuleMapping(moduleMappingData, "<incremental>", state.deserializationConfiguration) { version ->
throw IllegalStateException("Version of the generated module cannot be incompatible: $version")
fun JvmModuleProtoBuf.Module.Builder.addDataFromCompiledModule(
registry: PackagePartRegistry, stringTable: StringTableImpl, state: GenerationState
) {
for (part in registry.parts.values.addCompiledPartsAndSort(state)) {
part.addTo(this)
}
incrementalCache.getObsoletePackageParts().forEach { internalName ->
// Take all optional annotation classes from sources, as well as look up all previously compiled optional annotation classes
// by FQ name in the current module. The latter is needed because in incremental compilation scenario, the already compiled
// classes will not be available via sources.
val optionalAnnotationClassDescriptors =
registry.optionalAnnotations.toSet() +
state.loadCompiledModule()?.moduleData?.run {
optionalAnnotations.mapNotNull { proto ->
state.module.findClassAcrossModuleDependencies(
ClassId.fromString(nameResolver.getQualifiedClassName(proto.fqName))
)
}
}.orEmpty()
val serializer = DescriptorSerializer.createTopLevel(JvmOptionalAnnotationSerializerExtension(stringTable))
for (descriptor in optionalAnnotationClassDescriptors) {
addOptionalAnnotationClass(serializer.classProto(descriptor))
}
}
class JvmOptionalAnnotationSerializerExtension(
override val stringTable: StringTableImpl
) : KotlinSerializerExtensionBase(BuiltInSerializerProtocol) {
override val metadataVersion: BinaryVersion
get() = JvmMetadataVersion.INSTANCE
override fun shouldUseTypeTable(): Boolean = true
}
private fun Iterable<PackageParts>.addCompiledPartsAndSort(state: GenerationState): List<PackageParts> =
addCompiledParts(state).sortedBy { it.packageFqName }
private fun Iterable<PackageParts>.addCompiledParts(state: GenerationState): List<PackageParts> {
val mapping = state.loadCompiledModule() ?: return this.toList()
state.incrementalCacheForThisTarget?.getObsoletePackageParts()?.forEach { internalName ->
val qualifier = JvmClassName.byInternalName(internalName).packageFqName.asString()
mapping.findPackageParts(qualifier)?.removePart(internalName)
}
return (this + mapping.packageFqName2Parts.values)
.groupBy { it.packageFqName }
.map { (packageFqName, allOldPackageParts) ->
PackageParts(packageFqName).apply {
allOldPackageParts.forEach { packageParts -> this += packageParts }
}
.groupBy { it.packageFqName }
.map { (packageFqName, allOldPackageParts) ->
PackageParts(packageFqName).apply {
allOldPackageParts.forEach { packageParts -> this += packageParts }
}
}
}
private fun GenerationState.loadCompiledModule(): ModuleMapping? {
val moduleMappingData = incrementalCacheForThisTarget?.getModuleMappingData() ?: return null
return ModuleMapping.loadModuleMapping(moduleMappingData, "<incremental>", deserializationConfiguration) { version ->
throw IllegalStateException("Version of the generated module cannot be incompatible: $version")
}
}
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.konan.properties.propertyList
import org.jetbrains.kotlin.konan.util.KlibMetadataFactories
import org.jetbrains.kotlin.library.KLIB_PROPERTY_DEPENDS
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
import org.jetbrains.kotlin.load.java.JavaClassesTracker
import org.jetbrains.kotlin.load.java.lazy.ModuleClassResolver
import org.jetbrains.kotlin.load.java.structure.JavaClass
@@ -68,10 +69,10 @@ import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.multiplatform.OptionalAnnotationPackageFragmentProvider
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.storage.StorageManager
import java.util.*
@@ -195,7 +196,8 @@ object TopDownAnalyzerFacadeForJVM {
CompositePackageFragmentProvider(
listOf(
moduleClassResolver.compiledCodeResolver.packageFragmentProvider,
dependenciesContainer.get<JvmBuiltInsPackageFragmentProvider>()
dependenciesContainer.get<JvmBuiltInsPackageFragmentProvider>(),
dependenciesContainer.get<OptionalAnnotationPackageFragmentProvider>()
)
)
)
@@ -252,7 +254,10 @@ object TopDownAnalyzerFacadeForJVM {
)
module.initialize(
CompositePackageFragmentProvider(
listOf(container.get<KotlinCodeAnalyzer>().packageFragmentProvider) + additionalProviders
listOf(
container.get<KotlinCodeAnalyzer>().packageFragmentProvider,
container.get<OptionalAnnotationPackageFragmentProvider>()
) + additionalProviders
)
)
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import org.jetbrains.kotlin.resolve.jvm.JvmDiagnosticComponents
import org.jetbrains.kotlin.resolve.jvm.multiplatform.OptionalAnnotationPackageFragmentProvider
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
@@ -121,6 +122,7 @@ fun StorageComponentContainer.configureJavaSpecificComponents(
useInstance((moduleContext.module.builtIns as JvmBuiltIns).settings)
useImpl<JvmBuiltInsPackageFragmentProvider>()
}
useImpl<OptionalAnnotationPackageFragmentProvider>()
useInstance(javaClassTracker ?: JavaClassesTracker.Default)
useInstance(
@@ -16,18 +16,20 @@
package org.jetbrains.kotlin.load.kotlin.incremental
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartProviderBase
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.loadModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.serialization.deserialization.ClassData
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
import org.jetbrains.kotlin.storage.StorageManager
class IncrementalPackagePartProvider(
private val parent: PackagePartProvider,
incrementalCaches: List<IncrementalCache>,
storageManager: StorageManager
private val parent: PackagePartProvider,
incrementalCaches: List<IncrementalCache>,
storageManager: StorageManager
) : PackagePartProvider {
lateinit var deserializationConfiguration: DeserializationConfiguration
@@ -48,4 +50,8 @@ class IncrementalPackagePartProvider(
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
return parent.getAnnotationsOnBinaryModule(moduleName)
}
override fun getAllOptionalAnnotationClasses(): List<ClassData> =
moduleMappings().flatMap((JvmPackagePartProviderBase)::getAllOptionalAnnotationClasses) +
parent.getAllOptionalAnnotationClasses()
}
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.jvm.multiplatform
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
import org.jetbrains.kotlin.resolve.sam.SamConversionResolver
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.serialization.deserialization.*
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.storage.NotNullLazyValue
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.storage.getValue
import org.jetbrains.kotlin.utils.Printer
class OptionalAnnotationPackageFragmentProvider(
module: ModuleDescriptor,
storageManager: StorageManager,
notFoundClasses: NotFoundClasses,
languageVersionSettings: LanguageVersionSettings,
packagePartProvider: PackagePartProvider,
) : PackageFragmentProvider {
val packages: Map<FqName, PackageFragmentDescriptor> by storageManager.createLazyValue p@{
// We call getAllOptionalAnnotationClasses under lazy value only because IncrementalPackagePartProvider requires
// deserializationConfiguration to be injected.
val optionalAnnotationClasses = packagePartProvider.getAllOptionalAnnotationClasses()
if (optionalAnnotationClasses.isEmpty()) return@p emptyMap()
mutableMapOf<FqName, PackageFragmentDescriptor>().also { packages ->
// We use BuiltInSerializerProtocol when serializing optional annotation classes (see
// JvmOptionalAnnotationSerializerExtension). Use it in deserialization as well, to be able to load annotations on
// optional annotation classes and their members (in particular, the `@OptionalExpectation` annotation).
val serializerProtocol = BuiltInSerializerProtocol
val classDataFinder = OptionalAnnotationClassDataFinder(optionalAnnotationClasses)
val components = storageManager.createLazyValue {
DeserializationComponents(
storageManager, module, CompilerDeserializationConfiguration(languageVersionSettings),
classDataFinder,
AnnotationAndConstantLoaderImpl(module, notFoundClasses, serializerProtocol),
this,
LocalClassifierTypeSettings.Default,
ErrorReporter.DO_NOTHING,
LookupTracker.DO_NOTHING,
FlexibleTypeDeserializer.ThrowException,
emptyList(),
notFoundClasses,
ContractDeserializer.DEFAULT,
extensionRegistryLite = serializerProtocol.extensionRegistry,
samConversionResolver = SamConversionResolver.Empty
)
}
for ((packageFqName, classes) in classDataFinder.classIdToData.entries.groupBy { it.key.packageFqName }) {
val classNames = classes.mapNotNull { (classId) ->
classId.shortClassName.takeUnless { classId.isNestedClass }
}.toSet()
// TODO: make this lazy value more granular, e.g. a memoized function ClassId -> ClassDescriptor
val classDescriptors = storageManager.createLazyValue {
classes.mapNotNull { (classId, classData) ->
components().classDeserializer.deserializeClass(classId, classData)
}.associateBy(ClassDescriptor::getName)
}
packages[packageFqName] = PackageFragmentForOptionalAnnotations(module, packageFqName, classNames, classDescriptors)
}
}
}
override fun getPackageFragments(fqName: FqName): List<PackageFragmentDescriptor> =
packages[fqName]?.let(::listOf).orEmpty()
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> =
emptyList()
}
private class OptionalAnnotationClassDataFinder(classes: List<ClassData>) : ClassDataFinder {
val classIdToData = classes.associateBy { (nameResolver, klass) -> nameResolver.getClassId(klass.fqName) }
override fun findClassData(classId: ClassId): ClassData? = classIdToData[classId]
}
private class PackageFragmentForOptionalAnnotations(
module: ModuleDescriptor,
fqName: FqName,
classNames: Set<Name>,
classDescriptors: NotNullLazyValue<Map<Name, ClassDescriptor>>,
) : PackageFragmentDescriptorImpl(module, fqName) {
private val scope = object : MemberScopeImpl() {
override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? = classDescriptors()[name]
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> =
if (kindFilter.acceptsKinds(DescriptorKindFilter.CLASSIFIERS_MASK)) classDescriptors().values else emptyList()
override fun getClassifierNames(): Set<Name> = classNames
override fun printScopeStructure(p: Printer) {
p.print("PackageFragmentForOptionalAnnotations{${classNames.joinToString(transform = Name::asString)}}")
}
}
override fun getMemberScope(): MemberScope = scope
}
@@ -7,19 +7,14 @@ package org.jetbrains.kotlin.backend.common.lower
import org.jetbrains.kotlin.backend.common.BackendContext
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.util.ExpectDeclarationRemover
/**
* This pass removes all declarations with `isExpect == true`.
*/
class ExpectDeclarationsRemoveLowering(context: BackendContext, keepOptionalAnnotations: Boolean = false) : DeclarationTransformer {
private val remover = ExpectDeclarationRemover(
symbolTable = context.ir.symbols.externalSymbolTable,
doRemove = true,
keepOptionalAnnotations = keepOptionalAnnotations
)
class ExpectDeclarationsRemoveLowering(context: BackendContext) : DeclarationTransformer {
private val remover = ExpectDeclarationRemover(context.ir.symbols.externalSymbolTable, true)
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
return remover.transformFlat(declaration)
@@ -75,8 +75,8 @@ private val arrayConstructorPhase = makeIrFilePhase(
description = "Transform `Array(size) { index -> value }` into a loop"
)
private val expectDeclarationsRemovingPhase = makeIrModulePhase<JvmBackendContext>(
{ context -> ExpectDeclarationsRemoveLowering(context, keepOptionalAnnotations = true) },
private val expectDeclarationsRemovingPhase = makeIrModulePhase(
::ExpectDeclarationsRemoveLowering,
name = "ExpectDeclarationsRemoving",
description = "Remove expect declaration from module fragment"
)
@@ -375,6 +375,7 @@ val jvmPhases = namedIrModulePhase(
name = "IrLowering",
description = "IR lowering",
lower = validateIrBeforeLowering then
processOptionalAnnotationsPhase then
expectDeclarationsRemovingPhase then
fileClassPhase then
performByIrFile(lower = jvmFilePhases) then
@@ -372,9 +372,6 @@ internal fun getSignature(
*/
fun IrClass.getVisibilityAccessFlagForClass(): Int {
/* Original had a check for SyntheticClassDescriptorForJava, never invoked in th IR backend. */
if (isOptionalAnnotationClass()) {
return AsmUtil.NO_FLAG_PACKAGE_PRIVATE
}
if (kind == ClassKind.ENUM_ENTRY) {
return AsmUtil.NO_FLAG_PACKAGE_PRIVATE
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.isOptionalAnnotationClass
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.MetadataSource
internal val processOptionalAnnotationsPhase = makeIrModulePhase(
::ProcessOptionalAnnotations,
name = "ProcessOptionalAnnotations",
description = "Record metadata of @OptionalExpectation-annotated classes to backend-specific storage, later written to .kotlin_module"
)
class ProcessOptionalAnnotations(private val context: JvmBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) {
for (declaration in irFile.declarations) {
if (declaration !is IrClass || !declaration.isOptionalAnnotationClass()) continue
val metadataSource = (declaration.metadata as? MetadataSource.Class)?.descriptor ?: continue
context.state.factory.packagePartRegistry.optionalAnnotations += metadataSource
}
}
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.ir.util
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
@@ -23,11 +22,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver
// `doRemove` means should expect-declaration be removed from IR
class ExpectDeclarationRemover(
val symbolTable: ReferenceSymbolTable,
private val doRemove: Boolean,
private val keepOptionalAnnotations: Boolean
) : IrElementVisitorVoid {
class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private val doRemove: Boolean) : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
@@ -58,10 +53,8 @@ class ExpectDeclarationRemover(
}
private fun shouldRemoveTopLevelDeclaration(declaration: IrDeclaration): Boolean {
// TODO: rewrite findCompatibleActualForExpected using IR structures instead of descriptors
val descriptor = declaration.descriptor
return doRemove && descriptor is MemberDescriptor && descriptor.isExpect &&
!(keepOptionalAnnotations && descriptor is ClassDescriptor && ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))
return doRemove && descriptor is MemberDescriptor && descriptor.isExpect
}
private fun tryCopyDefaultArguments(declaration: IrValueParameter) {
@@ -27,7 +27,10 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.*
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrModuleSerializer
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerIr
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.metadata.KlibMetadataIncrementalSerializer
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
@@ -51,8 +54,8 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
import org.jetbrains.kotlin.psi2ir.generators.createGeneratorContext
import org.jetbrains.kotlin.psi2ir.generators.GeneratorExtensions
import org.jetbrains.kotlin.psi2ir.generators.createGeneratorContext
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContextUtils
import org.jetbrains.kotlin.storage.LockBasedStorageManager
@@ -162,7 +165,7 @@ fun generateKLib(
val moduleName = configuration[CommonConfigurationKeys.MODULE_NAME]!!
if (!configuration.expectActualLinker) {
moduleFragment.acceptVoid(ExpectDeclarationRemover(psi2IrContext.symbolTable, doRemove = false, keepOptionalAnnotations = false))
moduleFragment.acceptVoid(ExpectDeclarationRemover(psi2IrContext.symbolTable, false))
}
serializeModuleIntoKlib(
@@ -1,10 +1,3 @@
@java.lang.annotation.Retention
@kotlin.Metadata
@kotlin.OptionalExpectation
annotation class Anno {
public abstract method s(): java.lang.String
}
@kotlin.Metadata
public interface Foo$Nested {
inner class Foo$Nested
@@ -31,8 +31,10 @@ fun box(): String {
val annotations = Test::class.java.declaredMethods.single().annotations.toList()
if (annotations.toString() != "[@a.A(x=42)]") return "Fail 1: $annotations"
// Can't use B::class.java because "Declaration annotated with '@OptionalExpectation' can only be used inside an annotation entry"
if (Modifier.isPublic(Class.forName("a.B").modifiers)) return "Fail 2: optional annotation class should not be public in the bytecode"
return "OK"
try {
Class.forName("a.B")
return "Fail 2: there should be no class file for a.B"
} catch (e: ClassNotFoundException) {
return "OK"
}
}
@@ -1,12 +0,0 @@
// !LANGUAGE: +MultiPlatformProjects
// !USE_EXPERIMENTAL: kotlin.ExperimentalMultiplatform
// WITH_RUNTIME
@file:Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE") // TODO: support common sources in the test infrastructure
@OptionalExpectation
expect annotation class Anno(val s: String)
// TESTED_OBJECT_KIND: class
// TESTED_OBJECTS: Anno
// FLAGS: ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
@@ -895,24 +895,6 @@ public class WriteFlagsTestGenerated extends AbstractWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/multiplatform")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Multiplatform extends AbstractWriteFlagsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInMultiplatform() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/writeFlags/multiplatform"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("optionalExpectation.kt")
public void testOptionalExpectation() throws Exception {
runTest("compiler/testData/writeFlags/multiplatform/optionalExpectation.kt");
}
}
@TestMetadata("compiler/testData/writeFlags/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -895,24 +895,6 @@ public class IrWriteFlagsTestGenerated extends AbstractIrWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/multiplatform")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Multiplatform extends AbstractIrWriteFlagsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInMultiplatform() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/writeFlags/multiplatform"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@TestMetadata("optionalExpectation.kt")
public void testOptionalExpectation() throws Exception {
runTest("compiler/testData/writeFlags/multiplatform/optionalExpectation.kt");
}
}
@TestMetadata("compiler/testData/writeFlags/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)