diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt index 5b6b3ae246e..887c535af11 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt @@ -124,24 +124,33 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) private val useOptimizedSuperClass = context.state.generateOptimizedCallableReferenceSuperClasses - private val adaptedReferenceOriginalTarget = if (callee.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) { - // The body of a callable reference adapter contains either only a call, or an IMPLICIT_COERCION_TO_UNIT type operator - // applied to a call. That call's target is the original function which we need to get owner/name/signature. - val call = when (val statement = callee.body!!.statements.single()) { - is IrTypeOperatorCall -> { - assert(statement.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT) { - "Unexpected type operator in ADAPTER_FOR_CALLABLE_REFERENCE: ${callee.render()}" + private val adaptedReferenceOriginalTarget: IrFunction? + private val adapteeCall: IrFunctionAccessExpression? + + init { + if (callee.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) { + // The body of a callable reference adapter contains either only a call, or an IMPLICIT_COERCION_TO_UNIT type operator + // applied to a call. That call's target is the original function which we need to get owner/name/signature. + val call = when (val statement = callee.body!!.statements.single()) { + is IrTypeOperatorCall -> { + assert(statement.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT) { + "Unexpected type operator in ADAPTER_FOR_CALLABLE_REFERENCE: ${callee.render()}" + } + statement.argument } - statement.argument + is IrReturn -> statement.value + else -> statement } - is IrReturn -> statement.value - else -> statement + if (call !is IrFunctionAccessExpression) { + throw UnsupportedOperationException("Unknown structure of ADAPTER_FOR_CALLABLE_REFERENCE: ${callee.render()}") + } + adapteeCall = call + adaptedReferenceOriginalTarget = call.symbol.owner + } else { + adapteeCall = null + adaptedReferenceOriginalTarget = null } - if (call !is IrFunctionAccessExpression) { - throw UnsupportedOperationException("Unknown structure of ADAPTER_FOR_CALLABLE_REFERENCE: ${callee.render()}") - } - call.symbol.owner - } else null + } private val superType = samSuperType ?: when { @@ -269,7 +278,7 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) putValueArgument(index++, generateSignature(callableReferenceTarget.symbol)) putValueArgument( index, - irInt(if (callableReferenceTarget.parent.let { it is IrClass && it.isFileClass }) 1 else 0) + irInt(getFunctionReferenceFlags(callableReferenceTarget)) ) } } @@ -279,6 +288,36 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) } } + private fun getFunctionReferenceFlags(callableReferenceTarget: IrFunction): Int { + val isTopLevelBit = if (callableReferenceTarget.parent.let { it is IrClass && it.isFileClass }) 1 else 0 + val adaptedCallableReferenceFlags = getAdaptedCallableReferenceFlags() + return isTopLevelBit + (adaptedCallableReferenceFlags shl 1) + } + + private fun getAdaptedCallableReferenceFlags(): Int { + if (adaptedReferenceOriginalTarget == null) return 0 + + val isVarargMappedToElementBit = if (hasVarargMappedToElement()) 1 else 0 + val isSuspendConvertedBit = if (!adaptedReferenceOriginalTarget.isSuspend && callee.isSuspend) 1 else 0 + val isCoercedToUnitBit = if (!adaptedReferenceOriginalTarget.returnType.isUnit() && callee.returnType.isUnit()) 1 else 0 + + return isVarargMappedToElementBit + + (isSuspendConvertedBit shl 1) + + (isCoercedToUnitBit shl 2) + } + + private fun hasVarargMappedToElement(): Boolean { + if (adapteeCall == null) return false + for (i in 0 until adapteeCall.valueArgumentsCount) { + val arg = adapteeCall.getValueArgument(i) ?: continue + if (arg !is IrVararg) continue + for (varargElement in arg.elements) { + if (varargElement is IrGetValue) return true + } + } + return false + } + private fun createInvokeMethod(receiverVar: IrValueDeclaration?): IrSimpleFunction = functionReferenceClass.addFunction { setSourceRange(if (isLambda) callee else irFunctionReference) diff --git a/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithDefaults.kt b/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithDefaults.kt index daf91f9feda..49445f62b28 100644 --- a/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithDefaults.kt +++ b/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithDefaults.kt @@ -16,6 +16,7 @@ fun target(x: Int, y: String = "", z: String = ""): Int = x fun captureNoDefaults(fn: (Int, String, String) -> Unit): Any = fn fun captureOneDefault(fn: (Int, String) -> Unit): Any = fn fun captureAllDefaults(fn: (Int) -> Unit): Any = fn +fun captureOneDefaultWithoutCoercionToUnit(fn: (Int, String) -> Int): Any = fn fun box(): String { checkEqual(captureNoDefaults(::target), captureNoDefaults(::target)) @@ -26,6 +27,8 @@ fun box(): String { checkNotEqual(captureNoDefaults(::target), captureOneDefault(::target)) checkNotEqual(captureNoDefaults(::target), captureAllDefaults(::target)) + checkNotEqual(captureOneDefault(::target), captureOneDefaultWithoutCoercionToUnit(::target)) + return "OK" } diff --git a/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithVararg.kt b/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithVararg.kt index 6231cde162d..aac3d93f0d4 100644 --- a/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithVararg.kt +++ b/compiler/testData/codegen/box/callableReference/equality/coercionToUnitWithVararg.kt @@ -16,6 +16,9 @@ fun target(x: Int, vararg ys: String): Int = x + ys.size fun captureVararg1(fn: (Int, String) -> Unit): Any = fn fun captureVararg0(fn: (Int) -> Unit): Any = fn +fun captureNoVararg(fn: (Int, Array) -> Int): Any = fn +fun captureNoVarargCoerced(fn: (Int, Array) -> Unit): Any = fn + fun box(): String { checkEqual(captureVararg1(::target), captureVararg1(::target)) checkEqual(captureVararg0(::target), captureVararg0(::target)) @@ -23,6 +26,8 @@ fun box(): String { checkNotEqual(captureVararg1(::target), captureVararg0(::target)) + checkNotEqual(captureNoVararg(::target), captureNoVarargCoerced(::target)) + return "OK" } diff --git a/compiler/testData/codegen/box/callableReference/equality/suspendConversion.kt b/compiler/testData/codegen/box/callableReference/equality/suspendConversion.kt index 8a862fccb95..f6aed0e40b5 100644 --- a/compiler/testData/codegen/box/callableReference/equality/suspendConversion.kt +++ b/compiler/testData/codegen/box/callableReference/equality/suspendConversion.kt @@ -1,6 +1,7 @@ // !LANGUAGE: +SuspendConversion // IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND: JVM_IR, JS_IR +// ^ TODO fix suspend coercion for bound function references in JVM_IR // FILE: suspendCovnersion.kt fun checkNotEqual(x: Any, y: Any) {