diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt index 90de1d8799e21..91e89658a1ba7 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt @@ -41,7 +41,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.runIf private sealed class BoundValue { class StoredInVariable(val symbol: IrVariable) : BoundValue() - class StoredInField(val symbol: IrField): BoundValue() + class StoredInReceiverField(val symbol: IrField) : BoundValue() + class StoredInBoundContextValuesArray(val arrayField: IrField, val index: Int) : BoundValue() } /** @@ -50,6 +51,9 @@ private sealed class BoundValue { internal class FunctionReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() { private val crossinlineLambdas = HashSet() + private val arrayOfAnyNType: IrType = context.symbols.arrayOfAnyNType + private val arrayGetFunctionSymbol = context.symbols.arrayElementGetter(arrayOfAnyNType, context.irBuiltIns.intType) + private val IrRichFunctionReference.isInlineLambda: Boolean get() = origin == IrStatementOrigin.INLINE_LAMBDA @@ -189,13 +193,20 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) private val isHeavyweightLambda = isLambda && !isLightweightLambda private val isSuspend = irFunctionReference.overriddenFunctionSymbol.isSuspend + private val boundContextArgumentCount: Int = + irFunctionReference.reflectionTargetSymbol?.owner?.parameters?.count { it.kind == IrParameterKind.Context } ?: 0 + private val hasBoundReceiver get() = irFunctionReference.boundValues.size > boundContextArgumentCount + // Only function references can bind a receiver and even then we can only bind either an extension or a dispatch receiver. // However, when we bind a value of an inline class type as a receiver, the receiver will turn into an argument of // the function in question. Yet we still need to record it as the "receiver" in CallableReference in order for reflection // to work correctly. private val boundReceivers: Map = - if (callee.isJvmStaticInObject()) mapOf(createFakeBoundReceiverForJvmStaticInObject()) - else (irFunctionReference.invokeFunction.parameters zip irFunctionReference.boundValues).toMap() + when { + callee.isJvmStaticInObject() -> mapOf(createFakeBoundReceiverForJvmStaticInObject()) + hasBoundReceiver -> mapOf(irFunctionReference.invokeFunction.parameters.last() to irFunctionReference.boundValues.last()) + else -> emptyMap() + } // The type of the reference is KFunction private val parameterTypes = (irFunctionReference.type as IrSimpleType).arguments.map { @@ -281,6 +292,8 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) } } + private val boundContextArgumentsField: IrField = functionReferenceClass.getBoundContextArgumentsField(context) + private fun createFakeFormalTypeParameters(sourceTypeParameters: List, irClass: IrClass): List { if (sourceTypeParameters.isEmpty()) return emptyList() @@ -302,10 +315,9 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) fun build(): IrExpression = context.createJvmIrBuilder(currentScope!!).run { irBlock(irFunctionReference.startOffset, irFunctionReference.endOffset) { val constructor = createConstructor() - require(irFunctionReference.boundValues.size <= 1) { "Function references with multiple bound values are not supported yet" } +functionReferenceClass - // For function references the bound receiver parameter is stored in a field of the superclass. + // For function references the bound receiver and context parameters are stored in a field of the superclass. // For sam references, we just capture the value in a local variable, and LocalDeclarationsLowering // will put it into a field. if (samSuperType != null) { @@ -316,8 +328,14 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) } +irCall(constructor.symbol) } else { - val boundValues = irFunctionReference.boundValues.map { - BoundValue.StoredInField(functionReferenceClass.getReceiverField(backendContext)) + val receiverField = functionReferenceClass.getReceiverField(backendContext) + val boundValues = buildList { + for (index in 0 until boundContextArgumentCount) { + add(BoundValue.StoredInBoundContextValuesArray(boundContextArgumentsField, index)) + } + if (hasBoundReceiver) { + add(BoundValue.StoredInReceiverField(receiverField)) + } } createInvokeMethod(boundValues) +irCall(constructor.symbol).apply { @@ -349,7 +367,7 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) it.parameters.size == 1 + boundReceivers.size + 4 } irCallConstructor(constructor.symbol, emptyList()).apply { - generateConstructorCallArguments(this) { irGet(boundReceiverVars[it].symbol) } + generateConstructorCallArguments(this) { irGet(boundReceiverVars.last().symbol) } } }.generate() } @@ -360,12 +378,15 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) returnType = functionReferenceClass.defaultType isPrimary = true }.apply { + val boundContextValuesParams = mutableListOf() if (samSuperType == null) { - for (index in boundReceivers.entries.indices) { - addValueParameter("receiver$index", context.irBuiltIns.anyNType) + for (contextIndex in 0 until boundContextArgumentCount) { + boundContextValuesParams += addValueParameter($$"context$$$contextIndex", context.irBuiltIns.anyNType) + } + if (hasBoundReceiver) { + addValueParameter("receiver", context.irBuiltIns.anyNType,) } } - // Super constructor: // - For fun interface constructor references, super class is kotlin.jvm.internal.FunInterfaceConstructorReference // with single constructor 'public FunInterfaceConstructorReference(Class funInterface)' @@ -396,10 +417,17 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) irBlockBody(startOffset, endOffset) { +irDelegatingConstructorCall(constructor).also { call -> if (samSuperType == null) { - generateConstructorCallArguments(call) { irGet(parameters.first()) } + generateConstructorCallArguments(call) { irGet(parameters.last()) } } } +IrInstanceInitializerCallImpl(startOffset, endOffset, functionReferenceClass.symbol, context.irBuiltIns.unitType) + if (samSuperType == null && boundContextArgumentCount > 0) { + +irSetField( + irGet(functionReferenceClass.thisReceiver!!), + boundContextArgumentsField, + this@run.irArrayOf(arrayOfAnyNType, boundContextValuesParams.map { irGet(it) }), + ) + } } } } @@ -499,7 +527,7 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) val invokeParameter = invokeFunction.parameters[index] val capturedValueLocal = when (capturedValue) { is BoundValue.StoredInVariable -> capturedValue.symbol - is BoundValue.StoredInField -> irTemporary( + is BoundValue.StoredInReceiverField -> irTemporary( irImplicitCast( irGetField( irGet(dispatchReceiverParameter!!), @@ -508,6 +536,17 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) invokeParameter.type, ) ) + is BoundValue.StoredInBoundContextValuesArray -> irTemporary( + irImplicitCast( + irCallOp( + arrayGetFunctionSymbol.symbol, + context.irBuiltIns.anyNType, + irGetField(irGet(dispatchReceiverParameter!!), capturedValue.arrayField), + irInt(capturedValue.index), + ), + invokeParameter.type, + ) + ) } put(invokeParameter, capturedValueLocal) } @@ -587,12 +626,23 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) }.apply { parent = this@getReceiverField } + + // Same trick as [getReceiverField] for the inherited `kotlin.jvm.internal.CallableReference.boundContextArguments` field, + // which holds the bound context arguments of a reference to a declaration with context parameters. + internal fun IrClass.getBoundContextArgumentsField(context: JvmBackendContext): IrField = + context.irFactory.buildField { + name = Name.identifier("boundContextArguments") + type = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.anyNType) + visibility = DescriptorVisibilities.PROTECTED + }.apply { + parent = this@getBoundContextArgumentsField + } } } data class IndyCallData( val forceSerializability: Boolean, - val plainLambda: Boolean + val plainLambda: Boolean, ) var IrRichFunctionReference.indyCallData by irAttribute<_, IndyCallData>(copyByDefault = true) diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrLowerUtils.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrLowerUtils.kt index f1bf4cf8450b1..91057a955b0b1 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrLowerUtils.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrLowerUtils.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.builders.irCall import org.jetbrains.kotlin.ir.builders.irInt import org.jetbrains.kotlin.ir.builders.irString import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction @@ -21,6 +22,7 @@ import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.util.getPackageFragment import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction import org.jetbrains.kotlin.ir.util.isSuspendFunctionOrKFunction +import org.jetbrains.kotlin.ir.util.resolveFakeOverride import org.jetbrains.kotlin.ir.util.shallowCopyOrNull import org.jetbrains.kotlin.ir.util.statements import org.jetbrains.org.objectweb.asm.Handle @@ -56,6 +58,22 @@ internal fun IrProperty.getRichPropertyReferenceForOptimizableDelegatedProperty( return delegate } +internal val IrRichPropertyReference.boundContextArgumentCount: Int + get() { + val getter = when (val target = reflectionTargetSymbol?.owner) { + is IrProperty -> target.getter?.let { it.resolveFakeOverride() ?: it } + is IrLocalDelegatedProperty -> target.getter + else -> null + } + return getter?.parameters?.count { it.kind == IrParameterKind.Context } ?: 0 + } + +internal val IrRichPropertyReference.hasBoundReceiver: Boolean + get() = boundValues.size > boundContextArgumentCount + +internal val IrRichPropertyReference.boundReceiverOrNull: IrExpression? + get() = if (hasBoundReceiver) boundValues.last() else null + fun IrProperty.getSingletonOrConstantForOptimizableDelegatedProperty(): IrExpression? { fun IrExpression.isInlineable(): Boolean = when (this) { @@ -94,12 +112,5 @@ internal fun JvmIrBuilder.jvmMethodHandle(handle: Handle): IrCall = arguments[4] = irBoolean(handle.isInterface) } -internal val IrRichPropertyReference.singleBoundValueOrNull: IrExpression? - get() = when (boundValues.size) { - 0 -> return null - 1 -> boundValues.first() - else -> error("Property reference can not have more than one bound value, but got: ${boundValues.size}") - } - internal fun IrRichFunctionReference.isSamConversion(): Boolean = - !type.isFunctionOrKFunction() && !type.isSuspendFunctionOrKFunction() \ No newline at end of file + !type.isFunctionOrKFunction() && !type.isSuspendFunctionOrKFunction() diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStaticAnnotationLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStaticAnnotationLowering.kt index 681d6ebe22485..395b507cf8e1a 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStaticAnnotationLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStaticAnnotationLowering.kt @@ -126,12 +126,11 @@ class SingletonObjectJvmStaticTransformer( expression.transformChildrenVoid(this) val property = expression.reflectionTargetSymbol?.owner if (property is IrDeclaration && property.isJvmStaticInObject()) { - val bound = expression.singleBoundValueOrNull ?: return expression + val boundReceiver = expression.boundReceiverOrNull ?: return expression val objectClass = property.parentAsClass val objectValue = IrGetObjectValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, objectClass.defaultType, objectClass.symbol) - expression.boundValues.clear() - expression.boundValues += objectValue - return expression.addEvaluationOfArgIfSideEffects(bound, irBuiltIns) + expression.boundValues[expression.boundValues.lastIndex] = objectValue + return expression.addEvaluationOfArgIfSideEffects(boundReceiver, irBuiltIns) } return expression } diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt index 26580c852114b..ec9cbc4b64cb5 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt @@ -34,8 +34,9 @@ import org.jetbrains.kotlin.name.Name /** * Optimizes `val x by ::y`: instead of constructing a `KProperty` instance and calling `getValue`/`setValue` operators, generates calls - * to the getter/setter of the referenced property directly. If the property reference has a bound receiver which is non-trivial - * (its computation might lead to side effects), we compute the receiver once and store it in a field. + * to the getter/setter of the referenced property directly. If a bound value of the property reference (its receiver, or a context + * argument in the case of a reference to a declaration with context parameters) is non-trivial (its computation might lead to side + * effects), we compute it once and store it in a field. * * Also, generates a `$delegate` method that returns the delegate anyway. This method is supposed to only be used from kotlin-reflect * ([kotlin.reflect.KProperty0.getDelegate]). @@ -109,7 +110,7 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont fun DeclarationIrBuilder.createGetterBody( getter: IrSimpleFunction, delegateReference: IrRichPropertyReference, - receiverProvider: IrBuilder.() -> IrExpression?, + boundValuesProvider: IrBuilder.() -> List, ): IrBody { val constInitializer = delegateReference.constInitializer return if (constInitializer != null) { @@ -119,7 +120,7 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont irExprBody(irBlock { +delegateReference.getterFunction.inline( getter, - createAccessorArgumentsList(getter, delegateReference.getterFunction, isGetter = true, receiverProvider) + createAccessorArgumentsList(getter, delegateReference.getterFunction, isGetter = true, boundValuesProvider) ) }) } @@ -128,30 +129,29 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont fun DeclarationIrBuilder.createSetterBody( setter: IrSimpleFunction, delegateReference: IrRichPropertyReference, - receiverProvider: IrBuilder.() -> IrExpression?, + boundValuesProvider: IrBuilder.() -> List, ): IrBody { val delegateSetter = delegateReference.setterFunction ?: error("delegate was expected to have a setter") return irExprBody(irBlock { +delegateSetter.inline( setter, - createAccessorArgumentsList(setter, delegateSetter, isGetter = false, receiverProvider) + createAccessorArgumentsList(setter, delegateSetter, isGetter = false, boundValuesProvider) ) }) } - fun IrBuilder.createBoundReceiverExpr(accessor: IrSimpleFunction, backingField: IrField?, remappedReceiver: IrExpression?) = - backingField?.run { irGetField(accessor.dispatchReceiverParameter?.let(::irGet), this) } ?: remappedReceiver - fun IrBlockBuilder.createAccessorArgumentsList( accessor: IrSimpleFunction, delegateAccessor: IrSimpleFunction, isGetter: Boolean, - receiverProvider: IrBuilder.() -> IrExpression?, + boundValuesProvider: IrBuilder.() -> List, ): List { - val boundReceiverOrNull = receiverProvider() + val boundValues = boundValuesProvider() val setterParam = if (isGetter) null else accessor.parameters.lastOrNull() ?: error("setter must have at least one parameter") return buildList { - if (boundReceiverOrNull != null) add(createTmpVariable(boundReceiverOrNull.deepCopyWithSymbols(accessor))) + for (boundValue in boundValues) { + add(createTmpVariable(boundValue.deepCopyWithSymbols(accessor))) + } if (size + (if (isGetter) 0 else 1) < delegateAccessor.parameters.size) { val unboundReceiver = accessor.getReceiverParameterOrNull() if (unboundReceiver != null) add(unboundReceiver) @@ -167,24 +167,35 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont private fun IrProperty.transform(): List? { val delegate = getRichPropertyReferenceForOptimizableDelegatedProperty() ?: return null val oldField = backingField ?: return null - val boundValueOrNull = delegate.singleBoundValueOrNull?.transform(this@PropertyReferenceDelegationTransformer, null) - backingField = boundValueOrNull?.takeIf { !it.canInline(parents.toSet()) }?.let { + delegate.boundValues.replaceAll { it.transform(this@PropertyReferenceDelegationTransformer, null) } + val boundValues = delegate.boundValues + val receiverField = delegate.boundReceiverOrNull?.takeIf { !it.canInline(parents.toSet()) }?.let { receiver -> context.irFactory.buildField { updateFrom(oldField) name = Name.identifier("${this@transform.name}\$receiver") - type = boundValueOrNull.type + type = receiver.type }.apply { parent = oldField.parent - initializer = context.irFactory.createExpressionBody(it) + initializer = context.irFactory.createExpressionBody(receiver) correspondingPropertySymbol = oldField.correspondingPropertySymbol } } + backingField = receiverField val originalThis = parentAsClass.thisReceiver - fun remapReceiverIfNeeded(accessor: IrSimpleFunction) = if (backingField == null) { - boundValueOrNull?.remapReceiver(originalThis, accessor.dispatchReceiverParameter) - } else { - null + fun IrBuilder.boundValueExpressions(accessor: IrSimpleFunction): List = buildList { + boundValues.take(delegate.boundContextArgumentCount).mapTo(this) { + it.remapReceiver(originalThis, accessor.dispatchReceiverParameter) + } + val boundReceiver = delegate.boundReceiverOrNull + if (boundReceiver != null) { + add( + if (receiverField != null) + irGetField(accessor.dispatchReceiverParameter?.let(::irGet), receiverField) + else + boundReceiver.remapReceiver(originalThis, accessor.dispatchReceiverParameter) + ) + } } getter?.apply { @@ -192,9 +203,7 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont createGetterBody( getter = this@apply, delegateReference = delegate, - receiverProvider = { - createBoundReceiverExpr(this@apply, backingField, remapReceiverIfNeeded(this@apply)) - } + boundValuesProvider = { boundValueExpressions(this@apply) } ) } } @@ -203,9 +212,7 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont createSetterBody( setter = this@apply, delegateReference = delegate, - receiverProvider = { - createBoundReceiverExpr(this@apply, backingField, remapReceiverIfNeeded(this@apply)) - } + boundValuesProvider = { boundValueExpressions(this@apply) } ) } } @@ -213,31 +220,35 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont // The `$delegate` method is generated as instance method here, see MakePropertyDelegateMethodsStaticLowering. val delegateMethod = context.createSyntheticMethodForPropertyDelegate(this).apply { body = context.createJvmIrBuilder(symbol).run { - val boundReceiver = createBoundReceiverExpr(this@apply, backingField, remapReceiverIfNeeded(this@apply)) + val newBoundValues = boundValueExpressions(this@apply) irExprBody( delegate.deepCopyWithSymbols(parent).apply { origin = PropertyReferenceLowering.REFLECTED_PROPERTY_REFERENCE - if (boundReceiver != null) { - boundValues.clear() - boundValues.add(boundReceiver) - } + this.boundValues.clear() + this.boundValues += newBoundValues }) } } - // When the receiver is inlined, it can have side effects in form of class initialization, so it should be evaluated here. - val receiverBlock = boundValueOrNull.takeIf { backingField == null }?.let { + // When a bound value is inlined, it can have side effects in form of class initialization, so it should be evaluated here. + // The inlined values are evaluated in the initializer block, i.e., after the stored receiver, which is initialized in place + // of the property's backing field; this can only reorder class initialization triggers, since inlinable expressions are + // stable. + val inlinedValues = if (receiverField != null) boundValues.dropLast(1) else boundValues + val initializerBlock = inlinedValues.ifEmpty { null }?.let { values -> val symbol = IrAnonymousInitializerSymbolImpl(parentAsClass.symbol) + // Take both offsets from the same element: bound values can come from different sources (e.g. an inlined context argument + // and a receiver from the source code), and mixing their offsets can produce an invalid UNDEFINED/defined combination. context.irFactory.createAnonymousInitializer( - it.startOffset, - it.endOffset, + delegate.startOffset, + delegate.endOffset, IrDeclarationOrigin.DEFINED, symbol, parentAsClass.isFacadeClass ).apply { - body = context.irFactory.createBlockBody(startOffset, endOffset, listOf(it.remapReceiver(null, null))) + body = context.irFactory.createBlockBody(startOffset, endOffset, values.map { it.remapReceiver(null, null) }) } } - return listOfNotNull(this, delegateMethod, receiverBlock) + return listOfNotNull(this, delegateMethod, initializerBlock) } private fun IrFunction.getReceiverParameterOrNull(): IrValueParameter? { @@ -251,20 +262,22 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont !declaration.getter.returnsResultOfStdlibCall || declaration.setter?.returnsResultOfStdlibCall == false ) return super.visitLocalDelegatedProperty(declaration) - val receiver = delegateInitializer.singleBoundValueOrNull?.let { receiver -> + val boundValueVariables = delegateInitializer.boundValues.mapIndexed { index, boundValue -> with(delegate) { - buildVariable(parent, startOffset, endOffset, origin, name, receiver.type) + val variableName = if (delegateInitializer.boundValues.size > 1) Name.identifier("$name\$$index") else name + buildVariable(parent, startOffset, endOffset, origin, variableName, boundValue.type) }.apply { - initializer = receiver.transform(this@PropertyReferenceDelegationTransformer, null) + initializer = boundValue.transform(this@PropertyReferenceDelegationTransformer, null) } - } // TODO: just like in `PropertyReferenceLowering`, probably better to inline the getter/setter rather than + } + // TODO: just like in `PropertyReferenceLowering`, probably better to inline the getter/setter rather than // generate them as local functions. val getter = declaration.getter.apply { with(context.createIrBuilder(symbol, startOffset, endOffset)) { body = createGetterBody( getter = this@apply, delegateReference = delegateInitializer, - receiverProvider = { receiver?.let { irGet(it) } } + boundValuesProvider = { boundValueVariables.map { irGet(it) } } ) } } @@ -273,11 +286,11 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont body = createSetterBody( setter = this@apply, delegateReference = delegateInitializer, - receiverProvider = { receiver?.let { irGet(it) } } + boundValuesProvider = { boundValueVariables.map { irGet(it) } } ) } } - val statements = listOfNotNull(receiver, getter, setter) + val statements = boundValueVariables + listOfNotNull(getter, setter) return statements.singleOrNull() ?: IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.irBuiltIns.unitType, null, statements) } diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt index af2eee4431280..51c949808a153 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.ir.symbols.IrLocalDelegatedPropertySymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.createType +import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection import org.jetbrains.kotlin.ir.util.* @@ -171,14 +172,8 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle } private fun propertyReferenceClassFor(expression: IrRichPropertyReference): IrClassSymbol { - val boundReceivers = expression.boundValues - val getterFunction = expression.getterFunction - val needReceiversCount = getterFunction.parameters.size - check(boundReceivers.size < 2 && boundReceivers.size <= needReceiversCount) { - "Property reference with two and more receivers is not supported: ${expression.dump()}" - } val mutable = expression.setterFunction != null - val unboundParameterCount = needReceiversCount - boundReceivers.size + val unboundParameterCount = expression.getterFunction.parameters.size - expression.boundValues.size check(unboundParameterCount in 0..2) { "Incorrect number of receivers ($unboundParameterCount) for property reference: ${expression.render()}" } return context.symbols.getPropertyReferenceClass(mutable, unboundParameterCount, true) } @@ -316,16 +311,24 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle // does not support local variables and is slower, but takes up less space in the output binary. // Example: `C::property` -> `Reflection.property1(PropertyReference1Impl(C::class, "property", "getProperty()LType;"))`. private fun createReflectedKProperty(expression: IrRichPropertyReference): IrExpression { - require(expression.boundValues.size <= 1) { "Property references can not capture more than one receiver: ${expression.dump()}" } - val boundReceiver = expression.boundValues.firstOrNull() + val boundContextArguments = expression.boundValues.take(expression.boundContextArgumentCount) + val boundReceiver = expression.boundReceiverOrNull val referenceClass = propertyReferenceClassFor(expression) return context.createJvmIrBuilder(currentScope!!, expression).run { - val arity = when { - boundReceiver != null -> 5 // (receiver, jClass, name, desc, flags) - else -> 4 // (jClass, name, desc, flags) + // Possible shapes: + // (jClass, name, desc, flags), + // (receiver, jClass, name, desc, flags), + // (contextArguments, jClass, name, desc, flags), + // (contextArguments, receiver, jClass, name, desc, flags). + // The two 5-argument shapes are distinguished by the first parameter. + val arity = 4 + (if (boundContextArguments.isNotEmpty()) 1 else 0) + (if (boundReceiver != null) 1 else 0) + val constructor = referenceClass.constructors.single { + val parameters = it.owner.parameters + parameters.size == arity && + (parameters.first().name.asString() == "contextArguments") == boundContextArguments.isNotEmpty() } - irCall(referenceClass.constructors.single { it.owner.parameters.size == arity }).apply { - fillReflectedPropertyArguments(this, expression, boundReceiver) + irCall(constructor).apply { + fillReflectedPropertyArguments(this, expression, boundContextArguments, boundReceiver) } } } @@ -333,14 +336,17 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle private fun JvmIrBuilder.fillReflectedPropertyArguments( call: IrFunctionAccessExpression, expression: IrRichPropertyReference, + contextArguments: List, receiver: IrExpression?, ) { val container = expression.propertyContainer val containerClass = kClassToJavaClass(calculateOwnerKClass(container)) val isPackage = (container is IrClass && container.isFileClass) || container is IrPackageFragment + val contextArgumentsArray = if (contextArguments.isEmpty()) null else + irArrayOf(context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.anyNType), contextArguments) call.arguments.assignFrom( listOfNotNull( - receiver, containerClass, + contextArgumentsArray, receiver, containerClass, irString((expression.symbol.owner as IrDeclarationWithName).name.asString()), computeSignatureString(expression), irInt((if (isPackage) 1 else 0) or (if (expression.isJavaSyntheticPropertyReference) 2 else 0)) @@ -379,7 +385,7 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle return context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset).irBlock { // We do not reuse classes for non-reflective property references because they would not have // a valid enclosing method if the same property is referenced at many points. - val referenceClass = createKPropertySubclass(expression, expression.boundValues) + val referenceClass = createKPropertySubclass(expression) +referenceClass +irCall(referenceClass.constructors.single()).apply { arguments.assignFrom(expression.boundValues) @@ -387,10 +393,7 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle } } - private fun createKPropertySubclass( - expression: IrRichPropertyReference, - boundValues: List - ): IrClass { + private fun createKPropertySubclass(expression: IrRichPropertyReference): IrClass { val superClass = propertyReferenceClassFor(expression).owner val referenceClass = context.irFactory.buildClass { setSourceRange(expression) @@ -404,20 +407,32 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle copyAttributes(expression) } - addConstructor(expression, referenceClass, superClass) + val boundContextArgumentsField = + with(FunctionReferenceLowering) { referenceClass.getBoundContextArgumentsField(this@PropertyReferenceLowering.context) } + + addConstructor(expression, referenceClass, superClass, boundContextArgumentsField) val get = superClass.functions.find { it.name.asString() == "get" } val set = superClass.functions.find { it.name.asString() == "set" } val invoke = superClass.functions.find { it.name.asString() == "invoke" } - fun IrBuilder.getArguments(boundParameters: List, function: IrSimpleFunction): List<() -> IrExpression> { - require(boundParameters.size <= 1) { "Property references can not capture more than one receiver: ${function.dump()}" } - val boundExpressions = boundParameters.map { - { - val field = with(FunctionReferenceLowering) { - referenceClass.getReceiverField(this@PropertyReferenceLowering.context) + fun JvmIrBuilder.getArguments(function: IrSimpleFunction): List<() -> IrExpression> { + val boundExpressions = buildList<() -> IrExpression> { + for (contextIndex in 0 until expression.boundContextArgumentCount) { + add { + irCall(arrayItemGetter).apply { + arguments[0] = irGetField(irGet(function.dispatchReceiverParameter!!), boundContextArgumentsField) + arguments[1] = irInt(contextIndex) + } + } + } + if (expression.hasBoundReceiver) { + add { + val field = with(FunctionReferenceLowering) { + referenceClass.getReceiverField(this@PropertyReferenceLowering.context) + } + irGetField(irGet(function.dispatchReceiverParameter!!), field) } - irGetField(irGet(function.dispatchReceiverParameter!!), field) } } val unboundExpressions = function.nonDispatchParameters.map { { irGet(it) } } @@ -427,7 +442,7 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle expression.getterFunction.let { getter -> referenceClass.addOverride(get!!) { function -> expression.constInitializer?.let { return@addOverride irExprBody(it) } - val arguments = getArguments(boundValues, function) + val arguments = getArguments(function) getter.inlineWithoutTemporaryVariables(function, arguments) } referenceClass.addFakeOverride(invoke!!) @@ -435,7 +450,7 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle expression.setterFunction?.let { setter -> referenceClass.addOverride(set!!) { function -> - val arguments = getArguments(boundValues, function) + val arguments = getArguments(function) setter.inlineWithoutTemporaryVariables(function, arguments) } } @@ -466,22 +481,44 @@ internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrEle }, null) } - private fun addConstructor(expression: IrRichPropertyReference, referenceClass: IrClass, superClass: IrClass) { - val hasBoundReceiver = expression.boundValues.isNotEmpty() - val numOfSuperArgs = (if (hasBoundReceiver) 1 else 0) + 4 - val superConstructor = superClass.constructors.single { it.parameters.size == numOfSuperArgs } + private fun addConstructor( + expression: IrRichPropertyReference, + referenceClass: IrClass, + superClass: IrClass, + boundContextArgumentsField: IrField, + ) { + // The generated subclass stores the bound context arguments in a field itself, so it always delegates to a super constructor + // without the `contextArguments` parameter. + val numOfSuperArgs = (if (expression.hasBoundReceiver) 1 else 0) + 4 + val superConstructor = superClass.constructors.single { + it.parameters.size == numOfSuperArgs && it.parameters.none { parameter -> parameter.name.asString() == "contextArguments" } + } referenceClass.addConstructor { origin = JvmLoweredDeclarationOrigin.GENERATED_MEMBER_IN_CALLABLE_REFERENCE isPrimary = true }.apply { - val receiverParameter = if (hasBoundReceiver) addValueParameter("receiver", context.irBuiltIns.anyNType) else null + val boundContextValuesParams = mutableListOf() + for (contextIndex in 0 until expression.boundContextArgumentCount) { + boundContextValuesParams += addValueParameter("context\$$contextIndex", context.irBuiltIns.anyNType) + } + val receiverParameter = if (expression.hasBoundReceiver) addValueParameter("receiver", context.irBuiltIns.anyNType) else null body = context.createJvmIrBuilder(symbol).run { irBlockBody(startOffset, endOffset) { +irDelegatingConstructorCall(superConstructor).apply { - fillReflectedPropertyArguments(this, expression, receiverParameter?.let(::irGet)) + fillReflectedPropertyArguments(this, expression, contextArguments = emptyList(), receiverParameter?.let(::irGet)) } +IrInstanceInitializerCallImpl(startOffset, endOffset, referenceClass.symbol, context.irBuiltIns.unitType) + if (expression.boundContextArgumentCount > 0) { + +irSetField( + irGet(referenceClass.thisReceiver!!), + boundContextArgumentsField, + this@run.irArrayOf( + context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.anyNType), + boundContextValuesParams.map { irGet(it) }, + ), + ) + } } } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 5bae3a7cb47f2..09d5b6b1fded5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -404,19 +404,29 @@ class JvmSymbols( klass.generateCallableReferenceSuperclassConstructors(withArity = true) } - private fun IrClass.generateCallableReferenceSuperclassConstructors(withArity: Boolean) { - for (hasBoundReceiver in listOf(false, true)) { - addConstructor().apply { - if (withArity) { - addValueParameter("arity", irBuiltIns.intType) - } - if (hasBoundReceiver) { - addValueParameter("receiver", irBuiltIns.anyNType) + private fun IrClass.generateCallableReferenceSuperclassConstructors( + withArity: Boolean, + withBoundContextArguments: Boolean = false, + unboundReceiverCount: Int = 0, + ) { + for (hasBoundContextArguments in if (withBoundContextArguments) listOf(false, true) else listOf(false)) { + for (hasBoundReceiver in listOf(false, true)) { + if (hasBoundContextArguments && unboundReceiverCount + (if (hasBoundReceiver) 1 else 0) > 1) continue + addConstructor().apply { + if (withArity) { + addValueParameter("arity", irBuiltIns.intType) + } + if (hasBoundContextArguments) { + addValueParameter("contextArguments", irBuiltIns.arrayClass.typeWith(irBuiltIns.anyNType)) + } + if (hasBoundReceiver) { + addValueParameter("receiver", irBuiltIns.anyNType) + } + addValueParameter("owner", javaLangClass.starProjectedType) + addValueParameter("name", irBuiltIns.stringType) + addValueParameter("signature", irBuiltIns.stringType) + addValueParameter("flags", irBuiltIns.intType) } - addValueParameter("owner", javaLangClass.starProjectedType) - addValueParameter("name", irBuiltIns.stringType) - addValueParameter("signature", irBuiltIns.stringType) - addValueParameter("flags", irBuiltIns.intType) } } } @@ -513,7 +523,9 @@ class JvmSymbols( classModality = if (impl) Modality.FINAL else Modality.ABSTRACT ) { klass -> if (impl) { - klass.generateCallableReferenceSuperclassConstructors(withArity = false) + klass.generateCallableReferenceSuperclassConstructors( + withArity = false, withBoundContextArguments = true, unboundReceiverCount = parameterCount, + ) klass.superTypes += getPropertyReferenceClass(mutable, parameterCount, false).defaultType } else { diff --git a/compiler/testData/codegen/box/contextParameters/contextualCallableReference.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReference.kt similarity index 96% rename from compiler/testData/codegen/box/contextParameters/contextualCallableReference.kt rename to compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReference.kt index caeebfda2c89f..4ccc6850fd4c1 100644 --- a/compiler/testData/codegen/box/contextParameters/contextualCallableReference.kt +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReference.kt @@ -1,6 +1,4 @@ // LANGUAGE: +ContextParameters +CallableReferencesToContextual -// IGNORE_BACKEND: JVM_IR -// ^KT-86452, KT-87390 // IGNORE_KLIB_BACKEND_ERRORS_WITH_CUSTOM_SECOND_STAGE: Native,Wasm-JS:2.4 // ^^^ KT-87445 is fixed in 2.5.0-Beta1 diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceAdaptations.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceAdaptations.kt new file mode 100644 index 0000000000000..50109fc48ed0a --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceAdaptations.kt @@ -0,0 +1,35 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +var sink = "" + +context(c: String) +fun varargFun(vararg xs: Int): String { + var sum = 0 + for (x in xs) sum += x + return c + sum +} + +context(c: String) +fun withResult(suffix: String): String { + sink = c + suffix + return sink +} + +fun box(): String { + context("ctx") { + // vararg adapter: (Int, Int) -> String over `vararg xs: Int` + val two: (Int, Int) -> String = ::varargFun + if (two(1, 2) != "ctx3") return "FAIL 1: ${two(1, 2)}" + + // vararg adapter with zero unbound arguments: only the bound context argument remains + val zero: () -> String = ::varargFun + if (zero() != "ctx0") return "FAIL 2: ${zero()}" + + // Unit-coercion adapter: the return value is dropped, the context argument is still passed + val u: (String) -> Unit = ::withResult + sink = "" + u("!") + if (sink != "ctx!") return "FAIL 3: $sink" + } + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceBoundContextArguments.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceBoundContextArguments.kt new file mode 100644 index 0000000000000..b679e3235e882 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceBoundContextArguments.kt @@ -0,0 +1,64 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB +// OPT_IN: kotlin.ExperimentalContextParameters +// ISSUE: KT-86452 + +import kotlin.jvm.internal.CallableReference + +context(a: String, b: Int) +fun foo(): String = a + b + +class C(val x: String) { + context(a: String) + fun bar(): String = x + a +} + +context(a: String) +val prop: String get() = a + +context(a: String) +var mutableProp: String + get() = a + set(value) {} + +fun plain(): String = "plain" + +fun box(): String { + // A contextual function reference stores the captured context arguments in the + // `boundContextArguments` field of `kotlin.jvm.internal.CallableReference`, in the + // declaration order of the context parameters. The `receiver` field is not reused + // for them and stays NO_RECEIVER unless a receiver is also bound. + val r: () -> String = context("A", 1) { ::foo } + val rRef = r as CallableReference + val rArgs = rRef.boundContextArguments + ?: return "FAIL: boundContextArguments is null for a contextual function reference" + if (rArgs.size != 2 || rArgs[0] != "A" || rArgs[1] != 1) return "FAIL foo args: ${rArgs.toList()}" + if (rRef.boundReceiver !== CallableReference.NO_RECEIVER) return "FAIL: unexpected bound receiver: ${rRef.boundReceiver}" + + // A bound receiver is stored separately from the bound context arguments. + val c = C("X") + val rb: () -> String = context("A") { c::bar } + val rbRef = rb as CallableReference + val rbArgs = rbRef.boundContextArguments + ?: return "FAIL: boundContextArguments is null for a bound contextual function reference" + if (rbArgs.size != 1 || rbArgs[0] != "A") return "FAIL bar args: ${rbArgs.toList()}" + if (rbRef.boundReceiver !== c) return "FAIL: bound receiver is not the captured instance: ${rbRef.boundReceiver}" + + // Contextual property references: PropertyReference0Impl and MutablePropertyReference0Impl. + val rp = context("A") { ::prop } as CallableReference + val rpArgs = rp.boundContextArguments + ?: return "FAIL: boundContextArguments is null for a contextual property reference" + if (rpArgs.size != 1 || rpArgs[0] != "A") return "FAIL prop args: ${rpArgs.toList()}" + + val rm = context("A") { ::mutableProp } as CallableReference + val rmArgs = rm.boundContextArguments + ?: return "FAIL: boundContextArguments is null for a contextual mutable property reference" + if (rmArgs.size != 1 || rmArgs[0] != "A") return "FAIL mutableProp args: ${rmArgs.toList()}" + + // References to declarations without context parameters leave the field null. + if ((::plain as CallableReference).boundContextArguments != null) + return "FAIL: boundContextArguments is not null for a non-contextual reference" + + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceCompanion.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceCompanion.kt new file mode 100644 index 0000000000000..83f4abb0aaa52 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceCompanion.kt @@ -0,0 +1,23 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +class WithCompanion { + companion object { + context(c: String) + fun greet(arg: String): String = c + arg + + context(c: String) + val decorated: String + get() = "[$c]" + } +} + +fun box(): String { + context("ctx:") { + val g: (String) -> String = WithCompanion.Companion::greet + if (g("arg") != "ctx:arg") return "FAIL 1: ${g("arg")}" + + val d = WithCompanion.Companion::decorated + if (d.get() != "[ctx:]") return "FAIL 2: ${d.get()}" + } + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceEquality.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceEquality.kt new file mode 100644 index 0000000000000..ce28dad696c95 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceEquality.kt @@ -0,0 +1,47 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// IGNORE_BACKEND: WASM_JS, WASM_WASI +// ISSUE: KT-86452 + +context(a: String, b: Int) +fun foo(): String = a + b + +object O { + context(a: String, b: Int) + fun bar(suffix: String = "!"): String = a + b + suffix +} + +fun box(): String { + // --- Plain references (FunctionReferenceImpl) --- + // Two context parameters (of distinct types, to avoid AMBIGUOUS_CONTEXT_ARGUMENT) are bound, + // so both land in `boundContextArguments` and the runtime `receiver` is NO_RECEIVER. + val r1: () -> String = context("A", 1) { ::foo } + val r2: () -> String = context("B", 2) { ::foo } + val r3: () -> String = context("A", 1) { ::foo } + + if (r1() != "A1") return "FAIL invoke r1: ${r1()}" + if (r2() != "B2") return "FAIL invoke r2: ${r2()}" + + // Different captured context arguments => the references must differ. + if (r1 == r2) return "FAIL: plain references capturing different context arguments compare equal" + // Same captured context arguments => the references must be equal, with equal hashCodes. + if (r1 != r3) return "FAIL: plain references capturing equal context arguments compare unequal" + if (r1.hashCode() != r3.hashCode()) return "FAIL: equal plain references have different hashCodes" + + // --- Adapted references (AdaptedFunctionReference; the default `suffix` argument is dropped) --- + // The expected type must be present right at the reference for the default-argument adapter to resolve, + // so the reference is bound to a typed `val` inside the context block (as in contextualCallableReference.kt). + val a1: () -> String = context("A", 1) { val r: () -> String = O::bar; r } + val a2: () -> String = context("B", 2) { val r: () -> String = O::bar; r } + val a3: () -> String = context("A", 1) { val r: () -> String = O::bar; r } + + if (a1() != "A1!") return "FAIL invoke a1: ${a1()}" + if (a2() != "B2!") return "FAIL invoke a2: ${a2()}" + + // Different captured context arguments => the adapted references must differ. + if (a1 == a2) return "FAIL: adapted references capturing different context arguments compare equal" + // Same captured context arguments => the adapted references must be equal, with equal hashCodes. + if (a1 != a3) return "FAIL: adapted references capturing equal context arguments compare unequal" + if (a1.hashCode() != a3.hashCode()) return "FAIL: equal adapted references have different hashCodes" + + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceFakeOverride.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceFakeOverride.kt new file mode 100644 index 0000000000000..8d60341a5882d --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceFakeOverride.kt @@ -0,0 +1,32 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +open class Base { + context(c: String) + open fun f(): String = "base-$c" + + context(c: String) + open val p: String + get() = "basep-$c" +} + +class Derived : Base() { + context(c: String) + override fun f(): String = "derived-$c" +} + +fun box(): String = context("ctx") { + // unbound dispatch receiver typed as Base, dynamic dispatch must reach the override + val viaBase: (Base) -> String = Base::f + if (viaBase(Derived()) != "derived-ctx") return@context "FAIL 1: ${viaBase(Derived())}" + if (viaBase(Base()) != "base-ctx") return@context "FAIL 2: ${viaBase(Base())}" + + // bound receiver of the derived type + val bound: () -> String = Derived()::f + if (bound() != "derived-ctx") return@context "FAIL 3: ${bound()}" + + // property fake override referenced through the subtype + val prop: (Derived) -> String = Derived::p + if (prop(Derived()) != "basep-ctx") return@context "FAIL 4: ${prop(Derived())}" + + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceGeneric.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceGeneric.kt new file mode 100644 index 0000000000000..0a56cd30422b7 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceGeneric.kt @@ -0,0 +1,44 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +context(c: String) +fun describe(x: T): String = c + x + +context(c: C) +fun contextToString(): String = c.toString() + +class Box(val v: T) { + context(c: String) + fun render(): String = c + v +} + +context(c: String) +val T.tagged: String + get() = c + this + +fun box(): String { + context("ctx") { + // generic value parameter, T := Int from the expected type + val d: (Int) -> String = ::describe + if (d(1) != "ctx1") return "FAIL 1: ${d(1)}" + + // member of a generic class, bound dispatch receiver + val bound: () -> String = Box("v")::render + if (bound() != "ctxv") return "FAIL 2: ${bound()}" + + // member of a generic class, unbound dispatch receiver + val unbound: (Box) -> String = Box::render + if (unbound(Box(7)) != "ctx7") return "FAIL 3: ${unbound(Box(7))}" + + // generic extension property, bound receiver + val p: () -> String = 42::tagged + if (p() != "ctx42") return "FAIL 4: ${p()}" + } + + context(9) { + // generic *context* parameter, C := Int inferred from the context argument in scope + val cs: () -> String = ::contextToString + if (cs() != "9") return "FAIL 5: ${cs()}" + } + + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceLocalFunction.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceLocalFunction.kt new file mode 100644 index 0000000000000..f91c4182ee0d5 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceLocalFunction.kt @@ -0,0 +1,14 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +fun box(): String { + var captured = "O" + + context(s: String) + fun local(suffix: String) = captured + s + suffix + + return context("_") { + val ref: (String) -> String = ::local + val r = ref("K") + if (r != "O_K") "FAIL: $r" else "OK" + } +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceNullableContextArg.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceNullableContextArg.kt new file mode 100644 index 0000000000000..a3b6fe34a1b25 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceNullableContextArg.kt @@ -0,0 +1,20 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// IGNORE_BACKEND: WASM_JS, WASM_WASI + +context(c: String?) +fun orDefault(): String = c ?: "default" + +fun box(): String { + val n1 = context String>(null) { ::orDefault } + val n2 = context String>("x") { ::orDefault } + val n3 = context String>(null) { ::orDefault } + + if (n1() != "default") return "FAIL 1: ${n1()}" + if (n2() != "x") return "FAIL 2: ${n2()}" + + if (n1 == n2) return "FAIL 3: references capturing null and non-null context arguments compare equal" + if (n1 != n3) return "FAIL 4: references capturing null context arguments compare unequal" + if (n1.hashCode() != n3.hashCode()) return "FAIL 5: equal references have different hashCodes" + + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferencePropertyEquality.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferencePropertyEquality.kt new file mode 100644 index 0000000000000..b34060e5e803f --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferencePropertyEquality.kt @@ -0,0 +1,40 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// IGNORE_BACKEND: WASM_JS, WASM_WASI + +context(c: String, n: Int) +val topProp: String + get() = c + n + +class A(val tag: String) { + context(c: String) + val member: String + get() = tag + c +} + +fun box(): String { + // --- Top-level property, two bound context arguments, no receiver --- + val p1 = context("A", 1) { ::topProp } + val p2 = context("B", 2) { ::topProp } + val p3 = context("A", 1) { ::topProp } + + if (p1.get() != "A1") return "FAIL invoke p1: ${p1.get()}" + if (p2.get() != "B2") return "FAIL invoke p2: ${p2.get()}" + + if (p1 == p2) return "FAIL: property references capturing different context arguments compare equal" + if (p1 != p3) return "FAIL: property references capturing equal context arguments compare unequal" + if (p1.hashCode() != p3.hashCode()) return "FAIL: equal property references have different hashCodes" + + // --- Class member property: same bound dispatch receiver, different bound context arguments --- + val a = A("t") + val m1 = context("X") { a::member } + val m2 = context("Y") { a::member } + val m3 = context("X") { a::member } + + if (m1.get() != "tX") return "FAIL invoke m1: ${m1.get()}" + + if (m1 == m2) return "FAIL: member property references capturing different context arguments compare equal" + if (m1 != m3) return "FAIL: member property references capturing equal context arguments compare unequal" + if (m1.hashCode() != m3.hashCode()) return "FAIL: equal member property references have different hashCodes" + + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceShapes.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceShapes.kt new file mode 100644 index 0000000000000..44f010de6babb --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceShapes.kt @@ -0,0 +1,79 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +class Cls { + context(c: String) + fun member(): String = "$c-member" +} + +context(c: String) +fun String.ext(): String = "$c-ext-$this" + +context(c: String) +fun single(): String = "$c-single" + +object Obj { + context(c: String) + fun stat(): String = "$c-stat" +} + +context(c: String) +val topProp: String get() = "$c-top" + +class PropCls { + context(c: String) + val memberProp: String get() = "$c-memberProp" +} + +context(c: String) +val Int.extProp: String get() = "$c-extProp-$this" + +var storage: String = "" + +context(c: String, b: Boolean) +var twoCtxProp: String + get() = storage + set(value) { storage = "$c-$b-$value" } + +fun box(): String { + context("ctx", true) { + // --- function: regular-class member --- + val mBound: () -> String = Cls()::member + if (mBound() != "ctx-member") return "FAIL mBound: ${mBound()}" + + val mUnbound: (Cls) -> String = Cls::member + if (mUnbound(Cls()) != "ctx-member") return "FAIL mUnbound: ${mUnbound(Cls())}" + + // --- function: extension (single bound context argument; the extension receiver is bound/unbound) --- + val eBound: () -> String = "R"::ext + if (eBound() != "ctx-ext-R") return "FAIL eBound: ${eBound()}" + + val eUnbound: (String) -> String = String::ext + if (eUnbound("R") != "ctx-ext-R") return "FAIL eUnbound: ${eUnbound("R")}" + + // --- function: single bound context argument, no receiver --- + val s: () -> String = ::single + if (s() != "ctx-single") return "FAIL single: ${s()}" + + // --- function: object member (the @JvmStatic variant is in jvmStaticObjectContextualFunctionRef.kt) --- + val st: () -> String = Obj::stat + if (st() != "ctx-stat") return "FAIL stat: ${st()}" + + // --- property: top-level (get) --- + val tp: () -> String = ::topProp + if (tp() != "ctx-top") return "FAIL topProp: ${tp()}" + + // --- property: regular-class member (get) --- + val mp: () -> String = PropCls()::memberProp + if (mp() != "ctx-memberProp") return "FAIL memberProp: ${mp()}" + + // --- property: extension (get) --- + val ep: () -> String = 42::extProp + if (ep() != "ctx-extProp-42") return "FAIL extProp: ${ep()}" + + // --- property: two context parameters (get + set) --- + val tcp = ::twoCtxProp + tcp.set("V") + if (tcp.get() != "ctx-true-V") return "FAIL twoCtxProp: ${tcp.get()}" + } + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceSuspend.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceSuspend.kt new file mode 100644 index 0000000000000..91bf871a0f5ea --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceSuspend.kt @@ -0,0 +1,42 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB +// WITH_COROUTINES + +import helpers.* +import kotlin.coroutines.* + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +context(c: String) +suspend fun suspendFun(suffix: String): String = c + suffix + +context(c: String) +fun plainFun(suffix: String): String = c + suffix + +var result = "FAIL: not run" + +fun box(): String { + context("O") { + // reference to a contextual *suspend* function + val s: suspend (String) -> String = ::suspendFun + // suspend-conversion adapter over a contextual non-suspend function + val converted: suspend (String) -> String = ::plainFun + + builder { + val r1 = s("K") + if (r1 != "OK") { + result = "FAIL 1: $r1" + return@builder + } + val r2 = converted("K") + if (r2 != "OK") { + result = "FAIL 2: $r2" + return@builder + } + result = "OK" + } + } + return result +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceThroughInline.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceThroughInline.kt new file mode 100644 index 0000000000000..c929b4d10d339 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceThroughInline.kt @@ -0,0 +1,21 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual + +context(c: String) +fun target(suffix: String): String = c + suffix + +inline fun callDirect(f: (String) -> String): String = f("K") + +inline fun capture(crossinline f: (String) -> String): () -> String = { f("K") } + +inline fun callNoinline(noinline f: (String) -> String): String = f("K") + +fun box(): String = context("O") { + if (callDirect(::target) != "OK") return@context "FAIL 1: ${callDirect(::target)}" + + val deferred = capture(::target) + if (deferred() != "OK") return@context "FAIL 2: ${deferred()}" + + if (callNoinline(::target) != "OK") return@context "FAIL 3: ${callNoinline(::target)}" + + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceValueClassContextArg.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceValueClassContextArg.kt new file mode 100644 index 0000000000000..7d6122c3d7e64 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/contextualCallableReferenceValueClassContextArg.kt @@ -0,0 +1,33 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// TARGET_BACKEND: JVM +// WITH_STDLIB + +@JvmInline +value class Z(val value: String) + +context(z: Z) +fun render(suffix: String): String = z.value + suffix + +context(z: Z) +val decorated: String + get() = "[" + z.value + "]" + +fun box(): String { + context(Z("O")) { + val f: (String) -> String = ::render + if (f("K") != "OK") return "FAIL 1: ${f("K")}" + + val p = ::decorated + if (p.get() != "[O]") return "FAIL 2: ${p.get()}" + } + + val r1 = context(Z("A")) { val r: (String) -> String = ::render; r } + val r2 = context(Z("B")) { val r: (String) -> String = ::render; r } + val r3 = context(Z("A")) { val r: (String) -> String = ::render; r } + + if (r1 == r2) return "FAIL 3: references capturing different value-class context arguments compare equal" + if (r1 != r3) return "FAIL 4: references capturing equal value-class context arguments compare unequal" + if (r1.hashCode() != r3.hashCode()) return "FAIL 5: equal references have different hashCodes" + + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefBoundExtensionReceiver.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefBoundExtensionReceiver.kt new file mode 100644 index 0000000000000..0524766a4f6f5 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefBoundExtensionReceiver.kt @@ -0,0 +1,33 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var storage = "" +var receiverEvaluations = 0 + +context(c1: Int, c2: String) +var String.extProp: String + get() = storage + this + c2 + c1 + set(value) { + storage = value + this + } + +fun makeReceiver(): String { + receiverEvaluations++ + return "r" +} + +fun box(): String = context(1, "K") { + class B { + // Both the context arguments and the *extension* receiver are bound + // (the other delegation tests only bind dispatch receivers). + var y by makeReceiver()::extProp + } + val b = B() + if (receiverEvaluations != 1) return@context "FAIL 0: $receiverEvaluations" + b.y = "O" + if (storage != "Or") return@context "FAIL 1: $storage" + if (b.y != "OrrK1") return@context "FAIL 2: ${b.y}" + // The bound extension receiver must be computed once and stored, not reevaluated per accessor call. + if (receiverEvaluations != 1) return@context "FAIL 3: $receiverEvaluations" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefBoundReceiver.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefBoundReceiver.kt new file mode 100644 index 0000000000000..c68e7f8a8e95f --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefBoundReceiver.kt @@ -0,0 +1,33 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var sink = "" +var receiverEvaluations = 0 + +class A(val tag: String) { + context(c1: Int, c2: String) + var prop: String + get() = tag + c2 + c1 + set(value) { + sink = tag + value + c1 + } +} + +fun makeA(): A { + receiverEvaluations++ + return A("a") +} + +fun box(): String = context(1, "K") { + class B { + var y by makeA()::prop + } + val b = B() + if (receiverEvaluations != 1) return@context "FAIL 0: $receiverEvaluations" + b.y = "O" + if (sink != "aO1") return@context "FAIL 1: $sink" + if (b.y != "aK1") return@context "FAIL 2: ${b.y}" + // The non-trivial bound receiver must be computed once and stored, not reevaluated per accessor call. + if (receiverEvaluations != 1) return@context "FAIL 3: $receiverEvaluations" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefLocalProperty.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefLocalProperty.kt new file mode 100644 index 0000000000000..f0656e6ad2280 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefLocalProperty.kt @@ -0,0 +1,25 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var storage = "" + +context(c1: Int, c2: String) +var contextualizedProp: String + get() = storage + c2 + c1 + set(value) { + storage = value + } + +context(c1: Int, c2: String) +val readOnlyProp: String + get() = c2 + c1 + +fun box(): String = context(1, "K") { + val r by ::readOnlyProp + var y by ::contextualizedProp + y = "O" + if (storage != "O") return@context "FAIL 1: $storage" + if (y != "OK1") return@context "FAIL 2: $y" + if (r != "K1") return@context "FAIL 3: $r" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefPlatformTypeContextArg.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefPlatformTypeContextArg.kt new file mode 100644 index 0000000000000..1b8ca7969b1c4 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefPlatformTypeContextArg.kt @@ -0,0 +1,31 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +// FILE: J.java +public class J { + public static String s() { + return "K"; + } +} + +// FILE: test.kt +var storage = "" + +context(c1: Int, c2: String) +var prop: String + get() = storage + c2 + c1 + set(value) { + storage = value + } + +fun box(): String = context(1, J.s()) { + class B { + var y by ::prop + } + val b = B() + b.y = "O" + if (storage != "O") return@context "FAIL 1: $storage" + if (b.y != "OK1") return@context "FAIL 2: ${b.y}" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefSmartCastContextArg.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefSmartCastContextArg.kt new file mode 100644 index 0000000000000..7d12c14516e47 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefSmartCastContextArg.kt @@ -0,0 +1,28 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var storage = "" + +context(c1: Int, c2: String) +var prop: String + get() = storage + c2 + c1 + set(value) { + storage = value + } + +context(c: Any) +fun test(): String { + if (c !is String) return "FAIL 0: $c" + return context(1) { + class B { + var y by ::prop + } + val b = B() + b.y = "O" + if (storage != "O") return@context "FAIL 1: $storage" + if (b.y != "OK1") return@context "FAIL 2: ${b.y}" + "OK" + } +} + +fun box(): String = context("K") { test() } diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefThisAsContextArg.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefThisAsContextArg.kt new file mode 100644 index 0000000000000..2a5bb8ed1890e --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefThisAsContextArg.kt @@ -0,0 +1,30 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var storage = "" + +context(c: Ctx) +var prop: String + get() = storage + c.tag + set(value) { + storage = value + c.tag + } + +class Ctx(val tag: String) { + // The enclosing class instance is the bound context argument of the reference, so the accessors of `y` + // must remap `this` captured in the delegate initializer to their own dispatch receiver. + var y by ::prop +} + +fun box(): String { + val a = Ctx("A") + val b = Ctx("B") + a.y = "O" + if (storage != "OA") return "FAIL 1: $storage" + if (a.y != "OAA") return "FAIL 2: ${a.y}" + if (b.y != "OAB") return "FAIL 3: ${b.y}" + b.y = "X" + if (storage != "XB") return "FAIL 4: $storage" + if (b.y != "XBB") return "FAIL 5: ${b.y}" + return "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefUnboundReceiver.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefUnboundReceiver.kt new file mode 100644 index 0000000000000..9a8f6da666da7 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefUnboundReceiver.kt @@ -0,0 +1,17 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +fun box(): String = context(1, "K") { + class A(val tag: String) { + context(c1: Int, c2: String) + val prop: String + get() = tag + c2 + c1 + + // The context arguments are bound, while the receiver of `prop` stays unbound and is provided + // by the delegated property's own receiver through `KProperty1.getValue`. + val y by A::prop + } + val result = A("a").y + if (result != "aK1") return@context "FAIL: $result" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefUnboundReceiverVar.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefUnboundReceiverVar.kt new file mode 100644 index 0000000000000..fd81293420bd1 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefUnboundReceiverVar.kt @@ -0,0 +1,25 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var storage = "" + +fun box(): String = context(1, "K") { + class A(val tag: String) { + context(c1: Int, c2: String) + var prop: String + get() = storage + tag + c2 + c1 + set(value) { + storage = value + tag + } + + // The context arguments are bound, while the receiver of `prop` stays unbound and is provided + // by the delegated property's own receiver — through `KMutableProperty1.setValue` for writes, + // unlike the read-only delegationToContextualRefUnboundReceiver.kt. + var y by A::prop + } + val a = A("a") + a.y = "O" + if (storage != "Oa") return@context "FAIL 1: $storage" + if (a.y != "OaaK1") return@context "FAIL 2: ${a.y}" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefVar.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefVar.kt new file mode 100644 index 0000000000000..f59825e36b15c --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefVar.kt @@ -0,0 +1,22 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +var storage = "" + +context(c1: Int, c2: String) +var contextualizedProp: String + get() = storage + c2 + c1 + set(value) { + storage = value + } + +fun box(): String = context(1, "K") { + class B { + var y by ::contextualizedProp + } + val b = B() + b.y = "O" + if (storage != "O") return@context "FAIL 1: $storage" + if (b.y != "OK1") return@context "FAIL 2: ${b.y}" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefWithMultibleBoundValues.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefWithMultibleBoundValues.kt new file mode 100644 index 0000000000000..9110bb80163c2 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/delegationToContextualRefWithMultibleBoundValues.kt @@ -0,0 +1,14 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// WITH_STDLIB + +context(c1: Int, c2: String) +val contextualizedProp: String get() = c2 + +fun box(): String = with("OK") { + with(1) { + class B { + val y by ::contextualizedProp + } + B().y + } +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/jvmStaticObjectContextualFunctionRef.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/jvmStaticObjectContextualFunctionRef.kt new file mode 100644 index 0000000000000..4521506024b84 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/jvmStaticObjectContextualFunctionRef.kt @@ -0,0 +1,27 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +object Obj { + @JvmStatic + context(c: String) + fun greet(arg: String): String = c + arg +} + +class WithCompanion { + companion object { + @JvmStatic + context(c: String) + fun greet(arg: String): String = c + arg + } +} + +fun box(): String = context("ctx:") { + val o: (String) -> String = Obj::greet + if (o("arg") != "ctx:arg") return@context "FAIL 1: ${o("arg")}" + + val c: (String) -> String = WithCompanion.Companion::greet + if (c("arg") != "ctx:arg") return@context "FAIL 2: ${c("arg")}" + + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/callableReferences/jvmStaticObjectContextualPropertyRef.kt b/compiler/testData/codegen/box/contextParameters/callableReferences/jvmStaticObjectContextualPropertyRef.kt new file mode 100644 index 0000000000000..163c2fcecca99 --- /dev/null +++ b/compiler/testData/codegen/box/contextParameters/callableReferences/jvmStaticObjectContextualPropertyRef.kt @@ -0,0 +1,33 @@ +// LANGUAGE: +ContextParameters +CallableReferencesToContextual +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +var sideEffects = "" + +object O { + var storage = "" + + @JvmStatic + context(c: Int) + var prop: String + get() = storage + c + set(value) { + storage = value + } +} + +fun makeO(): O { + sideEffects += "makeO;" + return O +} + +fun box(): String = context(1) { + // The reference binds both a context argument and a receiver with side effects; + // the @JvmStatic-in-object rewrite must keep the former and normalize the latter. + val ref = makeO()::prop + if (sideEffects != "makeO;") return@context "FAIL 0: $sideEffects" + ref.set("O") + if (O.storage != "O") return@context "FAIL 1: ${O.storage}" + if (ref.get() != "O1") return@context "FAIL 2: ${ref.get()}" + "OK" +} diff --git a/compiler/testData/codegen/box/contextParameters/contextualCallableReferenceReturnType.kt b/compiler/testData/codegen/box/contextParameters/contextualCallableReferenceReturnType.kt index f4c6eb5ea8b08..0d79f07ed3b3b 100644 --- a/compiler/testData/codegen/box/contextParameters/contextualCallableReferenceReturnType.kt +++ b/compiler/testData/codegen/box/contextParameters/contextualCallableReferenceReturnType.kt @@ -1,8 +1,6 @@ // LANGUAGE: +ContextParameters +CallableReferencesToContextual // DONT_TARGET_EXACT_BACKEND: JS_IR, JS_IR_ES6, WASM_JS, WASM_WASI // WITH_REFLECT -// IGNORE_BACKEND: JVM_IR -// ^KT-86452, KT-87390 // IGNORE_KLIB_BACKEND_ERRORS_WITH_CUSTOM_SECOND_STAGE: Native,Wasm-JS:2.4 // ^^^ KT-87445 is fixed in 2.5.0-Beta1 diff --git a/compiler/testData/ir/irText/expressions/callableReferences/contextual/generic.kt b/compiler/testData/ir/irText/expressions/callableReferences/contextual/generic.kt index 1df0a063a85bf..9d2faeb28e408 100644 --- a/compiler/testData/ir/irText/expressions/callableReferences/contextual/generic.kt +++ b/compiler/testData/ir/irText/expressions/callableReferences/contextual/generic.kt @@ -1,6 +1,4 @@ // LANGUAGE: +ContextParameters +CallableReferencesToContextual -// IGNORE_BACKEND: JVM_IR -// ^KT-86452 import kotlin.reflect.* context(t: A) diff --git a/compiler/testData/ir/irText/expressions/callableReferences/contextual/noParameters.kt b/compiler/testData/ir/irText/expressions/callableReferences/contextual/noParameters.kt index 126198a4300c9..cae846c19f0ab 100644 --- a/compiler/testData/ir/irText/expressions/callableReferences/contextual/noParameters.kt +++ b/compiler/testData/ir/irText/expressions/callableReferences/contextual/noParameters.kt @@ -1,6 +1,4 @@ // LANGUAGE: +ContextParameters +CallableReferencesToContextual -// IGNORE_BACKEND: JVM_IR -// ^KT-86452 context(_: String) fun foo() {} context(_: String, b: Boolean) fun foo2() {} diff --git a/compiler/testData/ir/irText/expressions/callableReferences/contextual/properties.kt b/compiler/testData/ir/irText/expressions/callableReferences/contextual/properties.kt index 4c139c33389c5..4e6f33dda125a 100644 --- a/compiler/testData/ir/irText/expressions/callableReferences/contextual/properties.kt +++ b/compiler/testData/ir/irText/expressions/callableReferences/contextual/properties.kt @@ -1,6 +1,4 @@ // LANGUAGE: +ContextParameters +CallableReferencesToContextual -// IGNORE_BACKEND: JVM_IR -// ^KT-86452 context(_: String) val foo get() = 1 context(_: String, b: Boolean) var foo2 get() = 1