diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/JvmFlag.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/JvmFlag.kt index 2ab38a36565..c359b36fa88 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/JvmFlag.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/JvmFlag.kt @@ -31,6 +31,31 @@ object JvmFlag { val IS_MOVED_FROM_INTERFACE_COMPANION = booleanFlag(JF.IS_MOVED_FROM_INTERFACE_COMPANION) } + /** + * JVM-specific class flags in addition to common class flags declared in [Flag.Class]. + */ + object Class { + /** + * Applied to an interface compiled with -Xjvm-default=all or all-compatibility. + * + * Without this flag or a `@JvmDefault` annotation on individual interface methods + * the Kotlin compiler moves all interface method bodies into a nested `DefaultImpls` + * class. + */ + @JvmField + val HAS_METHOD_BODIES_IN_INTERFACE = booleanFlag(JF.ARE_INTERFACE_METHOD_BODIES_INSIDE) + + /** + * Applied to an interface compiled with -Xjvm-default=all-compatibility. + * + * In compatibility mode we generate method bodies directly in the interface, + * but we also generate bridges in a nested `DefaultImpls` class for use by + * clients compiled without all-compatibility. + */ + @JvmField + val IS_COMPILED_IN_COMPATIBILITY_MODE = booleanFlag(JF.IS_ALL_COMPATIBILITY_MODE) + } + private fun booleanFlag(f: F.BooleanFlagField): Flag = Flag(f.offset, f.bitWidth, 1) } diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/JvmMetadataExtensions.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/JvmMetadataExtensions.kt index 1875c862660..5f3db7631f6 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/JvmMetadataExtensions.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/JvmMetadataExtensions.kt @@ -31,6 +31,8 @@ internal class JvmMetadataExtensions : MetadataExtensions { ext.visitModuleName(proto.getExtensionOrNull(JvmProtoBuf.classModuleName)?.let(c::get) ?: JvmProtoBufUtil.DEFAULT_MODULE_NAME) + proto.getExtensionOrNull(JvmProtoBuf.jvmClassFlags)?.let(ext::visitJvmFlags) + ext.visitEnd() } @@ -133,6 +135,12 @@ internal class JvmMetadataExtensions : MetadataExtensions { proto.setExtension(JvmProtoBuf.classModuleName, c[name]) } } + + override fun visitJvmFlags(flags: Flags) { + if (flags != 0) { + proto.setExtension(JvmProtoBuf.jvmClassFlags, flags) + } + } } } diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/jvmExtensionNodes.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/jvmExtensionNodes.kt index 067af5e1e04..a75c2b7b44a 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/jvmExtensionNodes.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/impl/jvmExtensionNodes.kt @@ -35,6 +35,7 @@ internal class JvmClassExtension : JvmClassExtensionVisitor(), KmClassExtension val localDelegatedProperties: MutableList = ArrayList(0) var moduleName: String? = null var anonymousObjectOriginName: String? = null + var jvmFlags: Flags = 0 override fun visitLocalDelegatedProperty(flags: Flags, name: String, getterFlags: Flags, setterFlags: Flags): KmPropertyVisitor = KmProperty(flags, name, getterFlags, setterFlags).also { localDelegatedProperties.add(it) } @@ -47,6 +48,10 @@ internal class JvmClassExtension : JvmClassExtensionVisitor(), KmClassExtension this.anonymousObjectOriginName = internalName } + override fun visitJvmFlags(flags: Flags) { + this.jvmFlags = flags + } + override fun accept(visitor: KmClassExtensionVisitor) { require(visitor is JvmClassExtensionVisitor) localDelegatedProperties.forEach { @@ -54,6 +59,7 @@ internal class JvmClassExtension : JvmClassExtensionVisitor(), KmClassExtension } moduleName?.let(visitor::visitModuleName) anonymousObjectOriginName?.let(visitor::visitAnonymousObjectOriginName) + jvmFlags.takeIf { it != 0 }?.let(visitor::visitJvmFlags) visitor.visitEnd() } } diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensionVisitors.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensionVisitors.kt index b13d851384f..a526b6773f5 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensionVisitors.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensionVisitors.kt @@ -61,6 +61,13 @@ open class JvmClassExtensionVisitor @JvmOverloads constructor( delegate?.visitAnonymousObjectOriginName(internalName) } + /** + * Visits the JVM-specific flags of the class, consisting of [JvmFlag.Class] flags. + */ + open fun visitJvmFlags(flags: Flags) { + delegate?.visitJvmFlags(flags) + } + /** * Visits the end of JVM extensions for the class. */ diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensions.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensions.kt index c86a8900b16..b5f87ef8772 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensions.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensions.kt @@ -41,6 +41,15 @@ var KmClass.anonymousObjectOriginName: String? jvm.anonymousObjectOriginName = value } +/** + * JVM-specific flags of the class, consisting of [JvmFlag.Class] flags. + */ +var KmClass.jvmFlags: Flags + get() = jvm.jvmFlags + set(value) { + jvm.jvmFlags = value + } + /** * Metadata of local delegated properties used somewhere inside this package fragment (but not in any class). * Note that for classes produced by the Kotlin compiler, such properties will have default accessors. diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt index 81b1da18e03..5c2ae3c19e5 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt @@ -214,4 +214,26 @@ class MetadataSmokeTest { KotlinModuleMetadata.Writer().write(mv) } + + @Test + fun jvmClassFlags() { + // Test that we can (de-)serialize the jvmClassFlags extension. All the flags that currently + // exist are controlled by compiler options, so we have to manually create metadata with the + // flags set. Since the current flags only apply to interfaces with default functions we modify + // the metadata for the kotlin.coroutines.CoroutineContext interface. + val jvmClassFlags: Flags = flagsOf( + JvmFlag.Class.IS_COMPILED_IN_COMPATIBILITY_MODE, + JvmFlag.Class.HAS_METHOD_BODIES_IN_INTERFACE + ) + + val metadata = CoroutineContext::class.java.readMetadata() + val kmClass = (KotlinClassMetadata.read(metadata) as KotlinClassMetadata.Class).toKmClass() + kmClass.jvmFlags = jvmClassFlags + + val kmClassCopy = KotlinClassMetadata.Class.Writer() + .also { kmClass.accept(it) } + .write(metadata.metadataVersion, metadata.extraInt) + .toKmClass() + assertEquals(kmClassCopy.jvmFlags, jvmClassFlags) + } }