diff --git a/libraries/kotlinx-metadata/jvm/ChangeLog.md b/libraries/kotlinx-metadata/jvm/ChangeLog.md index 1874bc9e773..61375418dac 100644 --- a/libraries/kotlinx-metadata/jvm/ChangeLog.md +++ b/libraries/kotlinx-metadata/jvm/ChangeLog.md @@ -4,6 +4,8 @@ - kotlinx-metadata-jvm can no longer be used on JVM 1.6, and now requires JVM 1.8 or later. - Add `Flag.Type.IS_DEFINITELY_NON_NULL`. +- Add `KmClass.contextReceiverTypes`, `KmFunction.contextReceiverTypes`, `KmProperty.contextReceiverTypes` + - The API is experimental and requires `@ExperimentalContextReceivers` on the usages. ## 0.4.2 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 96c00612084..5c33b7a1f35 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensionVisitors.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/jvmExtensionVisitors.kt @@ -222,7 +222,7 @@ open class JvmPropertyExtensionVisitor @JvmOverloads constructor( /** * Visits the JVM signature of a synthetic method which is generated when a delegated property's delegate object is * optimized out, e.g. because it is constant; in that case, a copy of that object can be obtained on demand by calling - * this method. It takes up to two arguments - the property's receivers. + * this method. It takes the property's receivers as arguments. * * Example: `JvmMethodSignature("getX$delegate", "(LMyClass;)LMyDelegate;")` * diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/OptInMarkers.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/OptInMarkers.kt new file mode 100644 index 00000000000..98e95b42945 --- /dev/null +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/OptInMarkers.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2010-2022 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 kotlinx.metadata + +@RequiresOptIn( + "The API uses experimental feature \"context receivers\" (see KEEP-259) and may be changed or removed in any future release." +) +annotation class ExperimentalContextReceivers diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/readers.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/readers.kt index 676bd68613f..05afdc5f529 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/readers.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/readers.kt @@ -46,6 +46,7 @@ class ReadContext( } } +@OptIn(ExperimentalContextReceivers::class) fun ProtoBuf.Class.accept( v: KmClassVisitor, strings: NameResolver, @@ -100,6 +101,10 @@ fun ProtoBuf.Class.accept( v.visitInlineClassUnderlyingType(underlyingType.typeFlags)?.let { underlyingType.accept(it, c) } } + for (contextReceiverType in contextReceiverTypes(c.types)) { + v.visitContextReceiverType(contextReceiverType.typeFlags)?.let { contextReceiverType.accept(it, c) } + } + for (versionRequirement in versionRequirementList) { v.visitVersionRequirement()?.let { acceptVersionRequirementVisitor(versionRequirement, it, c) } } @@ -214,6 +219,7 @@ private fun ProtoBuf.Constructor.accept(v: KmConstructorVisitor, c: ReadContext) v.visitEnd() } +@OptIn(ExperimentalContextReceivers::class) private fun ProtoBuf.Function.accept(v: KmFunctionVisitor, outer: ReadContext) { val c = outer.withTypeParameters(typeParameterList) @@ -225,6 +231,10 @@ private fun ProtoBuf.Function.accept(v: KmFunctionVisitor, outer: ReadContext) { v.visitReceiverParameterType(receiverType.typeFlags)?.let { receiverType.accept(it, c) } } + for (contextReceiverType in contextReceiverTypes(c.types)) { + v.visitContextReceiverType(contextReceiverType.typeFlags)?.let { contextReceiverType.accept(it, c) } + } + for (parameter in valueParameterList) { v.visitValueParameter(parameter.flags, c[parameter.name])?.let { parameter.accept(it, c) } } @@ -248,6 +258,7 @@ private fun ProtoBuf.Function.accept(v: KmFunctionVisitor, outer: ReadContext) { v.visitEnd() } +@OptIn(ExperimentalContextReceivers::class) fun ProtoBuf.Property.accept(v: KmPropertyVisitor, outer: ReadContext) { val c = outer.withTypeParameters(typeParameterList) @@ -259,6 +270,10 @@ fun ProtoBuf.Property.accept(v: KmPropertyVisitor, outer: ReadContext) { v.visitReceiverParameterType(receiverType.typeFlags)?.let { receiverType.accept(it, c) } } + for (contextReceiverType in contextReceiverTypes(c.types)) { + v.visitContextReceiverType(contextReceiverType.typeFlags)?.let { contextReceiverType.accept(it, c) } + } + if (hasSetterValueParameter()) { val parameter = setterValueParameter v.visitSetterParameter(parameter.flags, c[parameter.name])?.let { parameter.accept(it, c) } diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/writers.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/writers.kt index 402d3d21dea..165fea6b672 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/writers.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/impl/writers.kt @@ -158,6 +158,10 @@ private fun writeFunction(c: WriteContext, flags: Flags, name: String, output: ( override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? = writeType(c, flags) { t.receiverType = it.build() } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + writeType(c, flags) { t.addContextReceiverType(it) } + override fun visitValueParameter(flags: Flags, name: String): KmValueParameterVisitor? = writeValueParameter(c, flags, name) { t.addValueParameter(it) } @@ -195,6 +199,10 @@ fun writeProperty( override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? = writeType(c, flags) { t.receiverType = it.build() } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + writeType(c, flags) { t.addContextReceiverType(it) } + override fun visitSetterParameter(flags: Flags, name: String): KmValueParameterVisitor? = writeValueParameter(c, flags, name) { t.setterValueParameter = it.build() } @@ -464,6 +472,10 @@ open class ClassWriter(stringTable: StringTable, contextExtensions: List = ArrayList(0) + /** * Version requirements on this class. */ @@ -157,6 +163,10 @@ class KmClass : KmClassVisitor(), KmDeclarationContainer { override fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor = KmType(flags).also { inlineClassUnderlyingType = it } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + KmType(flags).addTo(contextReceiverTypes) + override fun visitVersionRequirement(): KmVersionRequirementVisitor = KmVersionRequirement().addTo(versionRequirements) @@ -168,6 +178,7 @@ class KmClass : KmClassVisitor(), KmDeclarationContainer { * * @param visitor the visitor which will visit data in this class */ + @OptIn(ExperimentalContextReceivers::class) fun accept(visitor: KmClassVisitor) { visitor.visit(flags, name) typeParameters.forEach { visitor.visitTypeParameter(it.flags, it.name, it.id, it.variance)?.let(it::accept) } @@ -182,6 +193,7 @@ class KmClass : KmClassVisitor(), KmDeclarationContainer { sealedSubclasses.forEach(visitor::visitSealedSubclass) inlineClassUnderlyingPropertyName?.let(visitor::visitInlineClassUnderlyingPropertyName) inlineClassUnderlyingType?.let { visitor.visitInlineClassUnderlyingType(it.flags)?.let(it::accept) } + contextReceiverTypes.forEach { visitor.visitContextReceiverType(it.flags)?.let(it::accept) } versionRequirements.forEach { visitor.visitVersionRequirement()?.let(it::accept) } extensions.forEach { visitor.visitExtensions(it.type)?.let(it::accept) } visitor.visitEnd() @@ -360,6 +372,12 @@ class KmFunction( */ var receiverParameterType: KmType? = null + /** + * Types of context receivers of the function. + */ + @ExperimentalContextReceivers + val contextReceiverTypes: MutableList = ArrayList(0) + /** * Value parameters of the function. */ @@ -389,6 +407,10 @@ class KmFunction( override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor = KmType(flags).also { receiverParameterType = it } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + KmType(flags).addTo(contextReceiverTypes) + override fun visitValueParameter(flags: Flags, name: String): KmValueParameterVisitor = KmValueParameter(flags, name).addTo(valueParameters) @@ -409,9 +431,11 @@ class KmFunction( * * @param visitor the visitor which will visit data in this function */ + @OptIn(ExperimentalContextReceivers::class) fun accept(visitor: KmFunctionVisitor) { typeParameters.forEach { visitor.visitTypeParameter(it.flags, it.name, it.id, it.variance)?.let(it::accept) } receiverParameterType?.let { visitor.visitReceiverParameterType(it.flags)?.let(it::accept) } + contextReceiverTypes.forEach { visitor.visitContextReceiverType(it.flags)?.let(it::accept) } valueParameters.forEach { visitor.visitValueParameter(it.flags, it.name)?.let(it::accept) } visitor.visitReturnType(returnType.flags)?.let(returnType::accept) versionRequirements.forEach { visitor.visitVersionRequirement()?.let(it::accept) } @@ -447,6 +471,12 @@ class KmProperty( */ var receiverParameterType: KmType? = null + /** + * Types of context receivers of the property. + */ + @ExperimentalContextReceivers + val contextReceiverTypes: MutableList = ArrayList(0) + /** * Value parameter of the setter of this property, if this is a `var` property. */ @@ -471,6 +501,10 @@ class KmProperty( override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor = KmType(flags).also { receiverParameterType = it } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + KmType(flags).addTo(contextReceiverTypes) + override fun visitSetterParameter(flags: Flags, name: String): KmValueParameterVisitor = KmValueParameter(flags, name).also { setterParameter = it } @@ -488,9 +522,11 @@ class KmProperty( * * @param visitor the visitor which will visit data in this property */ + @OptIn(ExperimentalContextReceivers::class) fun accept(visitor: KmPropertyVisitor) { typeParameters.forEach { visitor.visitTypeParameter(it.flags, it.name, it.id, it.variance)?.let(it::accept) } receiverParameterType?.let { visitor.visitReceiverParameterType(it.flags)?.let(it::accept) } + contextReceiverTypes.forEach { visitor.visitContextReceiverType(it.flags)?.let(it::accept) } setterParameter?.let { visitor.visitSetterParameter(it.flags, it.name)?.let(it::accept) } visitor.visitReturnType(returnType.flags)?.let(returnType::accept) versionRequirements.forEach { visitor.visitVersionRequirement()?.let(it::accept) } diff --git a/libraries/kotlinx-metadata/src/kotlinx/metadata/visitors.kt b/libraries/kotlinx-metadata/src/kotlinx/metadata/visitors.kt index 2863b893a6f..4689cbb1e1a 100644 --- a/libraries/kotlinx-metadata/src/kotlinx/metadata/visitors.kt +++ b/libraries/kotlinx-metadata/src/kotlinx/metadata/visitors.kt @@ -149,6 +149,15 @@ abstract class KmClassVisitor @JvmOverloads constructor(delegate: KmClassVisitor open fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor? = delegate?.visitInlineClassUnderlyingType(flags) + /** + * Visits the type of a context receiver of the class. + * + * @param flags type flags, consisting of [Flag.Type] flags + */ + @ExperimentalContextReceivers + open fun visitContextReceiverType(flags: Flags): KmTypeVisitor? = + delegate?.visitContextReceiverType(flags) + /** * Visits the version requirement on this class. */ @@ -319,6 +328,15 @@ abstract class KmFunctionVisitor @JvmOverloads constructor(private val delegate: open fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? = delegate?.visitReceiverParameterType(flags) + /** + * Visits the type of a context receiver of the function. + * + * @param flags type flags, consisting of [Flag.Type] flags + */ + @ExperimentalContextReceivers + open fun visitContextReceiverType(flags: Flags): KmTypeVisitor? = + delegate?.visitContextReceiverType(flags) + /** * Visits a value parameter of the function. * @@ -391,6 +409,15 @@ abstract class KmPropertyVisitor @JvmOverloads constructor(private val delegate: open fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? = delegate?.visitReceiverParameterType(flags) + /** + * Visits the type of a context receiver of the property. + * + * @param flags type flags, consisting of [Flag.Type] flags + */ + @ExperimentalContextReceivers + open fun visitContextReceiverType(flags: Flags): KmTypeVisitor? = + delegate?.visitContextReceiverType(flags) + /** * Visits a value parameter of the setter of this property, if this is a `var` property. * diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt index af7151006b5..512939010ea 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/printers.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.kotlinp import kotlinx.metadata.* import kotlinx.metadata.jvm.* -import java.util.* private object SpecialCharacters { const val TYPE_ALIAS_MARKER = '^' @@ -18,6 +17,7 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F val typeParams = mutableListOf() val params = mutableListOf() var receiverParameterType: String? = null + val contextReceiverTypes = mutableListOf() var returnType: String? = null val versionRequirements = mutableListOf() var jvmSignature: JvmMemberSignature? = null @@ -27,6 +27,10 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? = printType(flags) { receiverParameterType = it } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + printType(flags) { contextReceiverTypes.add(it) } + override fun visitTypeParameter( flags: Flags, name: String, id: Int, variance: KmVariance ): KmTypeParameterVisitor? = @@ -68,6 +72,9 @@ private fun visitFunction(settings: KotlinpSettings, sb: StringBuilder, flags: F if (jvmSignature != null) { sb.appendLine(" // signature: $jvmSignature") } + if (contextReceiverTypes.isNotEmpty()) { + sb.appendLine(contextReceiverTypes.joinToString(prefix = " context(", postfix = ")")) + } sb.append(" ") sb.appendFlags(flags, FUNCTION_FLAGS_MAP) sb.append("fun ") @@ -96,6 +103,7 @@ private fun visitProperty( object : KmPropertyVisitor() { val typeParams = mutableListOf() var receiverParameterType: String? = null + val contextReceiverTypes = mutableListOf() var returnType: String? = null var setterParameter: String? = null val versionRequirements = mutableListOf() @@ -109,6 +117,10 @@ private fun visitProperty( override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor? = printType(flags) { receiverParameterType = it } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + printType(flags) { contextReceiverTypes.add(it) } + override fun visitTypeParameter(flags: Flags, name: String, id: Int, variance: KmVariance): KmTypeParameterVisitor? = printTypeParameter(settings, flags, name, id, variance) { typeParams.add(it) } @@ -169,6 +181,9 @@ private fun visitProperty( if (isMovedFromInterfaceCompanion) { sb.appendLine(" // is moved from interface companion") } + if (contextReceiverTypes.isNotEmpty()) { + sb.appendLine(contextReceiverTypes.joinToString(prefix = " context(", postfix = ")")) + } sb.append(" ") sb.appendFlags(flags, PROPERTY_FLAGS_MAP) sb.append(if (Flag.Property.IS_VAR(flags)) "var " else "val ") @@ -711,6 +726,7 @@ class ClassPrinter(private val settings: KotlinpSettings) : KmClassVisitor(), Ab private var name: ClassName? = null private val typeParams = mutableListOf() private val supertypes = mutableListOf() + private val contextReceiverTypes = mutableListOf() private val versionRequirements = mutableListOf() private var anonymousObjectOriginName: String? = null @@ -726,6 +742,9 @@ class ClassPrinter(private val settings: KotlinpSettings) : KmClassVisitor(), Ab for (versionRequirement in versionRequirements) { result.appendLine("// $versionRequirement") } + if (contextReceiverTypes.isNotEmpty()) { + result.appendLine(contextReceiverTypes.joinToString(prefix = "context(", postfix = ")")) + } result.appendFlags(flags!!, CLASS_FLAGS_MAP) result.append(name) if (typeParams.isNotEmpty()) { @@ -789,6 +808,10 @@ class ClassPrinter(private val settings: KotlinpSettings) : KmClassVisitor(), Ab sb.appendLine(" // underlying type: $it") } + @ExperimentalContextReceivers + override fun visitContextReceiverType(flags: Flags): KmTypeVisitor = + printType(flags) { contextReceiverTypes.add(it) } + override fun visitVersionRequirement(): KmVersionRequirementVisitor? = printVersionRequirement { versionRequirements.add(it) } diff --git a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java index 0e014f4950f..8b82dfcff23 100644 --- a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestGenerated.java @@ -34,6 +34,11 @@ public class KotlinpTestGenerated extends AbstractKotlinpTest { runTest("libraries/tools/kotlinp/testData/Annotations.kt"); } + @TestMetadata("ContextReceivers.kt") + public void testContextReceivers() throws Exception { + runTest("libraries/tools/kotlinp/testData/ContextReceivers.kt"); + } + @TestMetadata("Contracts.kt") public void testContracts() throws Exception { runTest("libraries/tools/kotlinp/testData/Contracts.kt"); diff --git a/libraries/tools/kotlinp/testData/ContextReceivers.kt b/libraries/tools/kotlinp/testData/ContextReceivers.kt new file mode 100644 index 00000000000..c11b12c4497 --- /dev/null +++ b/libraries/tools/kotlinp/testData/ContextReceivers.kt @@ -0,0 +1,11 @@ +// !LANGUAGE: +ContextReceivers + +interface A +interface B + +context(A) class C { + context(B) fun f() {} +} + +context(A) fun g() {} +context(B) val h: Int get() = 42 diff --git a/libraries/tools/kotlinp/testData/ContextReceivers.txt b/libraries/tools/kotlinp/testData/ContextReceivers.txt new file mode 100644 index 00000000000..f3c7bd5d3b3 --- /dev/null +++ b/libraries/tools/kotlinp/testData/ContextReceivers.txt @@ -0,0 +1,50 @@ +// A.class +// ------------------------------------------ +public abstract interface A : kotlin/Any { + + // module name: test-module +} +// B.class +// ------------------------------------------ +public abstract interface B : kotlin/Any { + + // module name: test-module +} +// C.class +// ------------------------------------------ +// requires compiler version 1.6.20 (level=ERROR) +context(A) +public final class C : kotlin/Any { + + // signature: (LA;)V + public constructor() + + // requires compiler version 1.6.20 (level=ERROR) + // signature: f(LB;)V + context(B) + public final fun f(): kotlin/Unit + + // module name: test-module +} +// ContextReceiversKt.class +// ------------------------------------------ +package { + + // requires compiler version 1.6.20 (level=ERROR) + // signature: g(LA;)V + context(A) + public final fun g(): kotlin/Unit + + // requires compiler version 1.6.20 (level=ERROR) + // getter: getH(LB;)I + context(B) + public final val h: kotlin/Int + public final /* non-default */ get +} +// META-INF/test-module.kotlin_module +// ------------------------------------------ +module { + package { + ContextReceiversKt + } +}