diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AdapterGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AdapterGenerator.kt new file mode 100644 index 00000000000..18a3cda582a --- /dev/null +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/AdapterGenerator.kt @@ -0,0 +1,529 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.backend.generators + +import org.jetbrains.kotlin.builtins.functions.FunctionClassKind +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.fir.backend.* +import org.jetbrains.kotlin.fir.backend.convertWithOffsets +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.fir.resolve.firSymbolProvider +import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType +import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType +import org.jetbrains.kotlin.fir.resolve.providers.getClassDeclaredCallableSymbols +import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl +import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.descriptors.WrappedSimpleFunctionDescriptor +import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor +import org.jetbrains.kotlin.ir.expressions.IrBlock +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.isUnit +import org.jetbrains.kotlin.ir.types.typeOrNull +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.utils.SmartList + +/** + * A generator that converts callable references or arguments that needs an adapter in between. This covers: + * 1) Suspend conversion where a reference to or qualified access of non-suspend functional type is passed as an argument whose expected + * type is a suspend functional type; + * 2) coercion-to-unit where a reference to a function whose return type isn't Unit is passed as an argument whose expected return type is + * Unit; + * 3) vararg spread where a reference to a function with vararg parameter is passed as an argument whose use of that vararg parameter + * requires spreading. + */ +internal class AdapterGenerator( + private val components: Fir2IrComponents, + private val conversionScope: Fir2IrConversionScope +) : Fir2IrComponents by components { + + private fun FirTypeRef.toIrType(): IrType = with(typeConverter) { toIrType() } + + private fun ConeKotlinType.toIrType(): IrType = with(typeConverter) { toIrType() } + + internal fun needToGenerateAdaptedCallableReference( + callableReferenceAccess: FirCallableReferenceAccess, + type: IrSimpleType, + function: IrFunction + ): Boolean = + needSuspendConversion(type, function) || needCoercionToUnit(type, function) || + needVarargSpread(callableReferenceAccess, type, function) + + /** + * For example, + * fun referenceConsumer(f: suspend () -> Unit) = ... + * fun nonSuspendFunction(...) = ... + * fun useSite(...) = { ... referenceConsumer(::nonSuspendFunction) ... } + * + * At the use site, instead of referenced, we can put the suspend lambda as an adapter. + */ + private fun needSuspendConversion(type: IrSimpleType, function: IrFunction): Boolean = + // TODO: should refer to LanguageVersionSettings.SuspendConversion + type.isKSuspendFunction() && !function.isSuspend + + /** + * For example, + * fun referenceConsumer(f: () -> Unit) = f() + * fun referenced(...): Any { ... } + * fun useSite(...) = { ... referenceConsumer(::referenced) ... } + * + * At the use site, instead of referenced, we can put the adapter: { ... -> referenced(...) } + */ + private fun needCoercionToUnit(type: IrSimpleType, function: IrFunction): Boolean { + val expectedReturnType = type.arguments.last().typeOrNull + return expectedReturnType?.isUnit() == true && !function.returnType.isUnit() + } + + /** + * For example, + * fun referenceConsumer(f: (Char, Char) -> String): String = ... // e.g., f(char1, char2) + * fun referenced(vararg xs: Char) = ... + * fun useSite(...) = { ... referenceConsumer(::referenced) ... } + * + * At the use site, instead of referenced, we can put the adapter: { a, b -> referenced(a, b) } + */ + private fun needVarargSpread( + callableReferenceAccess: FirCallableReferenceAccess, + type: IrSimpleType, + function: IrFunction + ): Boolean { + // Unbound callable reference 'A::foo' + val shift = if (callableReferenceAccess.explicitReceiver is FirResolvedQualifier) 1 else 0 + val typeArguments = type.arguments + // Drop the return type from type arguments + val expectedParameterSize = typeArguments.size - 1 - shift + if (expectedParameterSize < function.valueParameters.size) { + return false + } + var hasSpreadCase = false + function.valueParameters.forEachIndexed { index, irValueParameter -> + if (irValueParameter.isVararg && typeArguments[shift + index] == irValueParameter.varargElementType) { + hasSpreadCase = true + } + } + return hasSpreadCase + } + + internal fun ConeKotlinType.kFunctionTypeToFunctionType(): IrSimpleType { + val kind = + if (isSuspendFunctionType(session)) FunctionClassKind.SuspendFunction + else FunctionClassKind.Function + val functionalTypeId = ClassId(kind.packageFqName, kind.numberedClassName(typeArguments.size - 1)) + val coneType = ConeClassLikeTypeImpl(ConeClassLikeLookupTagImpl(functionalTypeId), typeArguments, isNullable = false) + return coneType.toIrType() as IrSimpleType + } + + // TODO: refactor to share some logic with suspend conversion on arguments + internal fun generateAdaptedCallableReference( + callableReferenceAccess: FirCallableReferenceAccess, + explicitReceiverExpression: IrExpression?, + adapteeSymbol: IrFunctionSymbol, + type: IrSimpleType + ): IrExpression { + val firAdaptee = callableReferenceAccess.toResolvedCallableReference()?.resolvedSymbol?.fir as? FirSimpleFunction + val adaptee = adapteeSymbol.owner + val expectedReturnType = type.arguments.last().typeOrNull + return callableReferenceAccess.convertWithOffsets { startOffset, endOffset -> + val boundDispatchReceiver = callableReferenceAccess.findBoundReceiver(explicitReceiverExpression, isDispatch = true) + val boundExtensionReceiver = callableReferenceAccess.findBoundReceiver(explicitReceiverExpression, isDispatch = false) + + val irAdapterFunction = createAdapterFunctionForCallableReference( + callableReferenceAccess, startOffset, endOffset, firAdaptee!!, adaptee, type, boundDispatchReceiver, boundExtensionReceiver + ) + val irCall = createAdapteeCallForCallableReference( + callableReferenceAccess, adapteeSymbol, irAdapterFunction, boundDispatchReceiver, boundExtensionReceiver + ) + irAdapterFunction.body = irFactory.createBlockBody(startOffset, endOffset) { + if (expectedReturnType?.isUnit() == true) { + statements.add(irCall) + } else { + statements.add(IrReturnImpl(startOffset, endOffset, irBuiltIns.nothingType, irAdapterFunction.symbol, irCall)) + } + } + + val boundReceiver = boundDispatchReceiver ?: boundExtensionReceiver + if (boundReceiver == null) { + IrFunctionExpressionImpl(startOffset, endOffset, type, irAdapterFunction, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) + } else { + val irAdapterRef = IrFunctionReferenceImpl( + startOffset, endOffset, type, irAdapterFunction.symbol, irAdapterFunction.typeParameters.size, + irAdapterFunction.valueParameters.size, null, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE + ) + val statements = SmartList() + if (boundReceiver.isSafeToUseWithoutCopying()) { + irAdapterRef.extensionReceiver = boundReceiver + } else { + val (irVariable, irVariableSymbol) = createTemporaryVariable(boundReceiver.deepCopyWithSymbols(), conversionScope) + irAdapterRef.extensionReceiver = IrGetValueImpl(startOffset, endOffset, irVariableSymbol) + statements.add(irVariable) + } + statements.add(irAdapterFunction) + statements.add(irAdapterRef) + + IrBlockImpl(startOffset, endOffset, type, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE, statements) + } + } + } + + private fun FirQualifiedAccess.findBoundReceiver(explicitReceiverExpression: IrExpression?, isDispatch: Boolean): IrExpression? { + val firReceiver = if (isDispatch) dispatchReceiver else extensionReceiver + if (firReceiver is FirNoReceiverExpression) { + return null + } + with(callGenerator) { + return findIrReceiver(explicitReceiverExpression, isDispatch) + } + } + + private fun createAdapterFunctionForCallableReference( + callableReferenceAccess: FirCallableReferenceAccess, + startOffset: Int, + endOffset: Int, + firAdaptee: FirSimpleFunction, + adaptee: IrFunction, + type: IrSimpleType, + boundDispatchReceiver: IrExpression?, + boundExtensionReceiver: IrExpression? + ): IrSimpleFunction { + val returnType = type.arguments.last().typeOrNull!! + val parameterTypes = type.arguments.dropLast(1).map { it.typeOrNull!! } + val adapterFunctionDescriptor = WrappedSimpleFunctionDescriptor() + return symbolTable.declareSimpleFunction(adapterFunctionDescriptor) { irAdapterSymbol -> + irFactory.createFunction( + startOffset, endOffset, + IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE, + irAdapterSymbol, + adaptee.name, + DescriptorVisibilities.LOCAL, + Modality.FINAL, + returnType, + isInline = firAdaptee.isInline, + isExternal = firAdaptee.isExternal, + isTailrec = firAdaptee.isTailRec, + isSuspend = firAdaptee.isSuspend || type.isSuspendFunction(), + isOperator = firAdaptee.isOperator, + isInfix = firAdaptee.isInfix, + isExpect = firAdaptee.isExpect, + isFakeOverride = false + ).also { irAdapterFunction -> + adapterFunctionDescriptor.bind(irAdapterFunction) + irAdapterFunction.metadata = FirMetadataSource.Function(firAdaptee) + + symbolTable.enterScope(irAdapterFunction) + irAdapterFunction.dispatchReceiverParameter = null + val boundReceiver = boundDispatchReceiver ?: boundExtensionReceiver + when { + boundReceiver == null -> + irAdapterFunction.extensionReceiverParameter = null + boundDispatchReceiver != null && boundExtensionReceiver != null -> + error("Bound callable references can't have both receivers: ${callableReferenceAccess.render()}") + else -> + irAdapterFunction.extensionReceiverParameter = + createAdapterParameter( + irAdapterFunction, + Name.identifier("receiver"), + index = -1, + boundReceiver.type, + IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE + ) + } + irAdapterFunction.valueParameters += parameterTypes.mapIndexed { index, parameterType -> + createAdapterParameter( + irAdapterFunction, + Name.identifier("p$index"), + index, + parameterType, + IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE + ) + } + symbolTable.leaveScope(irAdapterFunction) + + irAdapterFunction.parent = conversionScope.parent()!! + } + } + } + + private fun createAdapterParameter( + adapterFunction: IrFunction, + name: Name, + index: Int, + type: IrType, + origin: IrDeclarationOrigin + ): IrValueParameter { + val startOffset = adapterFunction.startOffset + val endOffset = adapterFunction.endOffset + val descriptor = WrappedValueParameterDescriptor() + return symbolTable.declareValueParameter( + startOffset, endOffset, origin, descriptor, type + ) { irAdapterParameterSymbol -> + irFactory.createValueParameter( + startOffset, endOffset, + origin, + irAdapterParameterSymbol, + name, + index, + type, + varargElementType = null, + isCrossinline = false, + isNoinline = false + ).also { irAdapterValueParameter -> + descriptor.bind(irAdapterValueParameter) + irAdapterValueParameter.parent = adapterFunction + } + } + } + + private fun IrValueDeclaration.toIrGetValue(startOffset: Int, endOffset: Int): IrGetValue = + IrGetValueImpl(startOffset, endOffset, this.type, this.symbol) + + private fun createAdapteeCallForCallableReference( + callableReferenceAccess: FirCallableReferenceAccess, + adapteeSymbol: IrFunctionSymbol, + adapterFunction: IrFunction, + boundDispatchReceiver: IrExpression?, + boundExtensionReceiver: IrExpression? + ): IrExpression { + val adapteeFunction = adapteeSymbol.owner + val startOffset = adapteeFunction.startOffset + val endOffset = adapteeFunction.endOffset + val type = adapteeFunction.returnType + val irCall = when (adapteeSymbol) { + is IrConstructorSymbol -> + IrConstructorCallImpl.fromSymbolOwner(startOffset, endOffset, type, adapteeSymbol) + is IrSimpleFunctionSymbol -> + IrCallImpl( + startOffset, + endOffset, + type, + adapteeSymbol, + callableReferenceAccess.typeArguments.size, + adapteeFunction.valueParameters.size, + origin = null, + superQualifierSymbol = null + ) + else -> + error("unknown callee kind: ${adapteeFunction.render()}") + } + + var adapterParameterIndex = 0 + if (boundDispatchReceiver != null || boundExtensionReceiver != null) { + val receiverValue = IrGetValueImpl( + startOffset, endOffset, adapterFunction.extensionReceiverParameter!!.symbol, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE + ) + when { + boundDispatchReceiver != null -> irCall.dispatchReceiver = receiverValue + boundExtensionReceiver != null -> irCall.extensionReceiver = receiverValue + } + } else if (callableReferenceAccess.explicitReceiver is FirResolvedQualifier) { + // Unbound callable reference 'A::foo' + val adaptedReceiverParameter = adapterFunction.valueParameters[0] + val adaptedReceiverValue = IrGetValueImpl( + startOffset, endOffset, adaptedReceiverParameter.type, adaptedReceiverParameter.symbol + ) + if (adapteeFunction.extensionReceiverParameter != null) { + irCall.extensionReceiver = adaptedReceiverValue + adapterParameterIndex++ + } else { + irCall.dispatchReceiver = adaptedReceiverValue + } + } + + adapteeFunction.valueParameters.mapIndexed { index, valueParameter -> + when { + valueParameter.isVararg -> { + if (adapterFunction.valueParameters.size <= index) { + irCall.putValueArgument(index, null) + } else { + val adaptedValueArgument = + IrVarargImpl(startOffset, endOffset, valueParameter.type, valueParameter.varargElementType!!) + var neitherArrayNorSpread = false + while (adapterParameterIndex < adapterFunction.valueParameters.size) { + val irValueArgument = + adapterFunction.valueParameters[adapterParameterIndex].toIrGetValue(startOffset, endOffset) + if (irValueArgument.type == valueParameter.type) { + adaptedValueArgument.addElement(IrSpreadElementImpl(startOffset, endOffset, irValueArgument)) + adapterParameterIndex++ + break + } else if (irValueArgument.type == valueParameter.varargElementType) { + adaptedValueArgument.addElement(irValueArgument) + adapterParameterIndex++ + } else { + neitherArrayNorSpread = true + break + } + } + if (neitherArrayNorSpread) { + irCall.putValueArgument(index, null) + } else { + irCall.putValueArgument(index, adaptedValueArgument) + } + } + } + valueParameter.hasDefaultValue() -> { + irCall.putValueArgument(index, null) + } + else -> { + irCall.putValueArgument( + index, adapterFunction.valueParameters[adapterParameterIndex++].toIrGetValue(startOffset, endOffset) + ) + } + } + } + + with(callGenerator) { + return irCall.applyTypeArguments(callableReferenceAccess) + } + } + + // TODO: refactor to share some logic with suspend conversion for callable reference + internal fun IrExpression.applySuspendConversionIfNeeded( + argument: FirExpression, + parameter: FirValueParameter? + ): IrExpression { + if (this is IrBlock && origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) { + return this + } + if (parameter == null || !needSuspendConversion(argument, parameter)) { + return this + } + + val suspendConvertedType = parameter.returnTypeRef.toIrType() as IrSimpleType + val returnType = suspendConvertedType.arguments.last().typeOrNull!! + val invokeSymbol = + (argument.typeRef.coneType as? ConeClassLikeType)?.lookupTag?.classId + ?.let { classId -> + session.firSymbolProvider + .getClassDeclaredCallableSymbols(classId, Name.identifier("invoke")) + .filterIsInstance>() + .find { firFunctionSymbol -> + firFunctionSymbol.fir.valueParameters.size == suspendConvertedType.arguments.size - 1 + }?.let { firFunctionSymbol -> + declarationStorage.getIrFunctionSymbol(firFunctionSymbol) as? IrSimpleFunctionSymbol + } + } ?: return this + return argument.convertWithOffsets { startOffset, endOffset -> + val irAdapterFunction = createAdapterFunctionForArgument(startOffset, endOffset, suspendConvertedType) + // TODO: Should be able to reuse `this` if that is an immutable IrGetValue + val irArgumentValue = createTemporaryVariable(this, conversionScope).first + val irCall = createAdapteeCallForArgument(startOffset, endOffset, irAdapterFunction, invokeSymbol, irArgumentValue) + irAdapterFunction.body = irFactory.createBlockBody(startOffset, endOffset) { + if (returnType.isUnit()) { + statements.add(irCall) + } else { + statements.add(IrReturnImpl(startOffset, endOffset, irBuiltIns.nothingType, irAdapterFunction.symbol, irCall)) + } + } + + val statements = SmartList() + statements.add(irArgumentValue) + statements.add( + IrFunctionExpressionImpl( + startOffset, endOffset, suspendConvertedType, irAdapterFunction, IrStatementOrigin.SUSPEND_CONVERSION + ) + ) + IrBlockImpl(startOffset, endOffset, suspendConvertedType, IrStatementOrigin.SUSPEND_CONVERSION, statements) + } + } + + /** + * For example, + * fun consumer(f: suspend () -> Unit) = ... + * fun nonSuspendFunction = { ... } + * fun useSite(...) = { ... consumer(nonSuspendFunction) ... } + * + * At the use site, instead of the argument, we can put the suspend lambda as an adapter. + */ + private fun needSuspendConversion(argument: FirExpression, parameter: FirValueParameter): Boolean = + // TODO: should refer to LanguageVersionSettings.SuspendConversion + parameter.returnTypeRef.coneType.isSuspendFunctionType(session) && + argument.typeRef.coneType.isBuiltinFunctionalType(session) && + !argument.typeRef.coneType.isSuspendFunctionType(session) + + private fun createAdapterFunctionForArgument( + startOffset: Int, + endOffset: Int, + type: IrSimpleType + ): IrSimpleFunction { + val returnType = type.arguments.last().typeOrNull!! + val parameterTypes = type.arguments.dropLast(1).map { it.typeOrNull!! } + val adapterFunctionDescriptor = WrappedSimpleFunctionDescriptor() + return symbolTable.declareSimpleFunction(adapterFunctionDescriptor) { irAdapterSymbol -> + irFactory.createFunction( + startOffset, endOffset, + IrDeclarationOrigin.ADAPTER_FOR_SUSPEND_CONVERSION, + irAdapterSymbol, + // TODO: need a better way to avoid name clash + Name.identifier("suspendConversion"), + DescriptorVisibilities.LOCAL, + Modality.FINAL, + returnType, + isInline = false, + isExternal = false, + isTailrec = false, + isSuspend = true, + isOperator = false, + isInfix = false, + isExpect = false, + isFakeOverride = false + ).also { irAdapterFunction -> + adapterFunctionDescriptor.bind(irAdapterFunction) + symbolTable.enterScope(irAdapterFunction) + irAdapterFunction.valueParameters += parameterTypes.mapIndexed { index, parameterType -> + createAdapterParameter( + irAdapterFunction, + Name.identifier("p$index"), + index, + parameterType, + IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_SUSPEND_CONVERSION + ) + } + symbolTable.leaveScope(irAdapterFunction) + irAdapterFunction.parent = conversionScope.parent()!! + } + } + } + + private fun createAdapteeCallForArgument( + startOffset: Int, + endOffset: Int, + adapterFunction: IrFunction, + invokeSymbol: IrSimpleFunctionSymbol, + irCapturedValue: IrValueDeclaration + ): IrExpression { + val irCall = IrCallImpl( + startOffset, endOffset, + adapterFunction.returnType, + invokeSymbol, + typeArgumentsCount = 0, + valueArgumentsCount = adapterFunction.valueParameters.size + ) + irCall.dispatchReceiver = irCapturedValue.toIrGetValue(startOffset, endOffset) + for (irAdapterParameter in adapterFunction.valueParameters) { + irCall.putValueArgument(irAdapterParameter.index, irAdapterParameter.toIrGetValue(startOffset, endOffset)) + } + return irCall + } +} diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt index 64af973a1dc..6f8870734d0 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/CallAndReferenceGenerator.kt @@ -5,8 +5,6 @@ package org.jetbrains.kotlin.fir.backend.generators -import org.jetbrains.kotlin.builtins.functions.FunctionClassKind -import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.fir.backend.* import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.* @@ -19,29 +17,18 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.fir.resolve.calls.isFunctional import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType -import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.* -import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl -import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.descriptors.WrappedSimpleFunctionDescriptor -import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtPropertyDelegate import org.jetbrains.kotlin.psi2ir.generators.hasNoSideEffects -import org.jetbrains.kotlin.utils.SmartList -import org.jetbrains.kotlin.descriptors.DescriptorVisibilities -import org.jetbrains.kotlin.fir.resolve.firSymbolProvider -import org.jetbrains.kotlin.fir.resolve.providers.getClassDeclaredCallableSymbols class CallAndReferenceGenerator( private val components: Fir2IrComponents, @@ -49,6 +36,8 @@ class CallAndReferenceGenerator( private val conversionScope: Fir2IrConversionScope ) : Fir2IrComponents by components { + private val adapterGenerator = AdapterGenerator(components, conversionScope) + private fun FirTypeRef.toIrType(): IrType = with(typeConverter) { toIrType() } private fun ConeKotlinType.toIrType(): IrType = with(typeConverter) { toIrType() } @@ -106,13 +95,11 @@ class CallAndReferenceGenerator( } type as IrSimpleType val function = symbol.owner - // TODO: should refer to LanguageVersionSettings.SuspendConversion - if (requiresVarargSpread(callableReferenceAccess, type, function) || - requiresCoercionToUnit(type, function) || - requiresSuspendConversion(type, function) - ) { - val adaptedType = callableReferenceAccess.typeRef.coneType.kFunctionTypeToFunctionType() - generateAdaptedCallableReference(callableReferenceAccess, explicitReceiverExpression, symbol, adaptedType) + if (adapterGenerator.needToGenerateAdaptedCallableReference(callableReferenceAccess, type, function)) { + with(adapterGenerator) { + val adaptedType = callableReferenceAccess.typeRef.coneType.kFunctionTypeToFunctionType() + generateAdaptedCallableReference(callableReferenceAccess, explicitReceiverExpression, symbol, adaptedType) + } } else { IrFunctionReferenceImpl( startOffset, endOffset, type, symbol, @@ -131,319 +118,6 @@ class CallAndReferenceGenerator( }.applyTypeArguments(callableReferenceAccess).applyReceivers(callableReferenceAccess, explicitReceiverExpression) } - /* - * For example, - * fun referenceConsumer(f: (Char, Char) -> String): String = ... // e.g., f(char1, char2) - * fun referenced(vararg xs: Char) = ... - * fun useSite(...) = { ... referenceConsumer(::referenced) ... } - * - * At the use site, instead of referenced, we can put the adapter: { a, b -> referenced(a, b) } - */ - private fun requiresVarargSpread( - callableReferenceAccess: FirCallableReferenceAccess, - type: IrSimpleType, - function: IrFunction - ): Boolean { - // Unbound callable reference 'A::foo' - val shift = if (callableReferenceAccess.explicitReceiver is FirResolvedQualifier) 1 else 0 - val typeArguments = type.arguments - // Drop the return type from type arguments - val expectedParameterSize = typeArguments.size - 1 - shift - if (expectedParameterSize < function.valueParameters.size) { - return false - } - var hasSpreadCase = false - function.valueParameters.forEachIndexed { index, irValueParameter -> - if (irValueParameter.isVararg && typeArguments[shift + index] == irValueParameter.varargElementType) { - hasSpreadCase = true - } - } - return hasSpreadCase - } - - /* - * For example, - * fun referenceConsumer(f: () -> Unit) = f() - * fun referenced(...): Any { ... } - * fun useSite(...) = { ... referenceConsumer(::referenced) ... } - * - * At the use site, instead of referenced, we can put the adapter: { ... -> referenced(...) } - */ - private fun requiresCoercionToUnit(type: IrSimpleType, function: IrFunction): Boolean { - val expectedReturnType = type.arguments.last().typeOrNull - return expectedReturnType?.isUnit() == true && !function.returnType.isUnit() - } - - /* - * For example, - * fun referenceConsumer(f: suspend () -> Unit) = ... - * fun nonSuspendFunction(...) = ... - * fun useSite(...) = { ... referenceConsumer(::nonSuspendFunction) ... } - * - * At the use site, instead of referenced, we can put the suspend lambda as an adapter. - */ - private fun requiresSuspendConversion(type: IrSimpleType, function: IrFunction): Boolean = - type.isKSuspendFunction() && !function.isSuspend - - private fun ConeKotlinType.kFunctionTypeToFunctionType(): IrSimpleType { - val kind = - if (isSuspendFunctionType(session)) FunctionClassKind.SuspendFunction - else FunctionClassKind.Function - val functionalTypeId = ClassId(kind.packageFqName, kind.numberedClassName(typeArguments.size - 1)) - val coneType = ConeClassLikeTypeImpl(ConeClassLikeLookupTagImpl(functionalTypeId), typeArguments, isNullable = false) - return coneType.toIrType() as IrSimpleType - } - - // TODO: refactor to share some logic with suspend conversion on arguments (maybe introduce AdapterGenerator?) - private fun generateAdaptedCallableReference( - callableReferenceAccess: FirCallableReferenceAccess, - explicitReceiverExpression: IrExpression?, - adapteeSymbol: IrFunctionSymbol, - type: IrSimpleType - ): IrExpression { - val firAdaptee = callableReferenceAccess.toResolvedCallableReference()?.resolvedSymbol?.fir as? FirSimpleFunction - val adaptee = adapteeSymbol.owner - val expectedReturnType = type.arguments.last().typeOrNull - return callableReferenceAccess.convertWithOffsets { startOffset, endOffset -> - val boundDispatchReceiver = callableReferenceAccess.findBoundReceiver(explicitReceiverExpression, isDispatch = true) - val boundExtensionReceiver = callableReferenceAccess.findBoundReceiver(explicitReceiverExpression, isDispatch = false) - - val irAdapterFunction = createAdapterFunctionForCallableReference( - callableReferenceAccess, startOffset, endOffset, firAdaptee!!, adaptee, type, boundDispatchReceiver, boundExtensionReceiver - ) - val irCall = createAdapteeCallForCallableReference( - callableReferenceAccess, adapteeSymbol, irAdapterFunction, boundDispatchReceiver, boundExtensionReceiver - ) - irAdapterFunction.body = irFactory.createBlockBody(startOffset, endOffset) { - if (expectedReturnType?.isUnit() == true) { - statements.add(irCall) - } else { - statements.add(IrReturnImpl(startOffset, endOffset, irBuiltIns.nothingType, irAdapterFunction.symbol, irCall)) - } - } - - val boundReceiver = boundDispatchReceiver ?: boundExtensionReceiver - if (boundReceiver == null) { - IrFunctionExpressionImpl(startOffset, endOffset, type, irAdapterFunction, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) - } else { - val irAdapterRef = IrFunctionReferenceImpl( - startOffset, endOffset, type, irAdapterFunction.symbol, irAdapterFunction.typeParameters.size, - irAdapterFunction.valueParameters.size, null, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE - ) - val statements = SmartList() - if (boundReceiver.isSafeToUseWithoutCopying()) { - irAdapterRef.extensionReceiver = boundReceiver - } else { - val (irVariable, irVariableSymbol) = createTemporaryVariable(boundReceiver.deepCopyWithSymbols(), conversionScope) - irAdapterRef.extensionReceiver = IrGetValueImpl(startOffset, endOffset, irVariableSymbol) - statements.add(irVariable) - } - statements.add(irAdapterFunction) - statements.add(irAdapterRef) - - IrBlockImpl(startOffset, endOffset, type, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE, statements) - } - } - } - - private fun createAdapterFunctionForCallableReference( - callableReferenceAccess: FirCallableReferenceAccess, - startOffset: Int, - endOffset: Int, - firAdaptee: FirSimpleFunction, - adaptee: IrFunction, - type: IrSimpleType, - boundDispatchReceiver: IrExpression?, - boundExtensionReceiver: IrExpression? - ): IrSimpleFunction { - val returnType = type.arguments.last().typeOrNull!! - val parameterTypes = type.arguments.dropLast(1).map { it.typeOrNull!! } - val adapterFunctionDescriptor = WrappedSimpleFunctionDescriptor() - return symbolTable.declareSimpleFunction(adapterFunctionDescriptor) { irAdapterSymbol -> - irFactory.createFunction( - startOffset, endOffset, - IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE, - irAdapterSymbol, - adaptee.name, - DescriptorVisibilities.LOCAL, - Modality.FINAL, - returnType, - isInline = firAdaptee.isInline, - isExternal = firAdaptee.isExternal, - isTailrec = firAdaptee.isTailRec, - isSuspend = firAdaptee.isSuspend || type.isSuspendFunction(), - isOperator = firAdaptee.isOperator, - isInfix = firAdaptee.isInfix, - isExpect = firAdaptee.isExpect, - isFakeOverride = false - ).also { irAdapterFunction -> - adapterFunctionDescriptor.bind(irAdapterFunction) - irAdapterFunction.metadata = FirMetadataSource.Function(firAdaptee) - - symbolTable.enterScope(irAdapterFunction) - irAdapterFunction.dispatchReceiverParameter = null - val boundReceiver = boundDispatchReceiver ?: boundExtensionReceiver - when { - boundReceiver == null -> - irAdapterFunction.extensionReceiverParameter = null - boundDispatchReceiver != null && boundExtensionReceiver != null -> - error("Bound callable references can't have both receivers: ${callableReferenceAccess.render()}") - else -> - irAdapterFunction.extensionReceiverParameter = - createAdapterParameter( - irAdapterFunction, - Name.identifier("receiver"), - index = -1, - boundReceiver.type, - IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE - ) - } - irAdapterFunction.valueParameters += parameterTypes.mapIndexed { index, parameterType -> - createAdapterParameter( - irAdapterFunction, - Name.identifier("p$index"), - index, - parameterType, - IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE - ) - } - symbolTable.leaveScope(irAdapterFunction) - - irAdapterFunction.parent = conversionScope.parent()!! - } - } - } - - private fun createAdapterParameter( - adapterFunction: IrFunction, - name: Name, - index: Int, - type: IrType, - origin: IrDeclarationOrigin - ): IrValueParameter { - val startOffset = adapterFunction.startOffset - val endOffset = adapterFunction.endOffset - val descriptor = WrappedValueParameterDescriptor() - return symbolTable.declareValueParameter( - startOffset, endOffset, origin, descriptor, type - ) { irAdapterParameterSymbol -> - irFactory.createValueParameter( - startOffset, endOffset, - origin, - irAdapterParameterSymbol, - name, - index, - type, - varargElementType = null, - isCrossinline = false, - isNoinline = false - ).also { irAdapterValueParameter -> - descriptor.bind(irAdapterValueParameter) - irAdapterValueParameter.parent = adapterFunction - } - } - } - - private fun IrValueDeclaration.toIrGetValue(startOffset: Int, endOffset: Int): IrGetValue = - IrGetValueImpl(startOffset, endOffset, this.type, this.symbol) - - private fun createAdapteeCallForCallableReference( - callableReferenceAccess: FirCallableReferenceAccess, - adapteeSymbol: IrFunctionSymbol, - adapterFunction: IrFunction, - boundDispatchReceiver: IrExpression?, - boundExtensionReceiver: IrExpression? - ): IrExpression { - val adapteeFunction = adapteeSymbol.owner - val startOffset = adapteeFunction.startOffset - val endOffset = adapteeFunction.endOffset - val type = adapteeFunction.returnType - val irCall = when (adapteeSymbol) { - is IrConstructorSymbol -> - IrConstructorCallImpl.fromSymbolOwner(startOffset, endOffset, type, adapteeSymbol) - is IrSimpleFunctionSymbol -> - IrCallImpl( - startOffset, - endOffset, - type, - adapteeSymbol, - callableReferenceAccess.typeArguments.size, - adapteeFunction.valueParameters.size, - origin = null, - superQualifierSymbol = null - ) - else -> - error("unknown callee kind: ${adapteeFunction.render()}") - } - - var adapterParameterIndex = 0 - if (boundDispatchReceiver != null || boundExtensionReceiver != null) { - val receiverValue = IrGetValueImpl( - startOffset, endOffset, adapterFunction.extensionReceiverParameter!!.symbol, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE - ) - when { - boundDispatchReceiver != null -> irCall.dispatchReceiver = receiverValue - boundExtensionReceiver != null -> irCall.extensionReceiver = receiverValue - } - } else if (callableReferenceAccess.explicitReceiver is FirResolvedQualifier) { - // Unbound callable reference 'A::foo' - val adaptedReceiverParameter = adapterFunction.valueParameters[0] - val adaptedReceiverValue = IrGetValueImpl( - startOffset, endOffset, adaptedReceiverParameter.type, adaptedReceiverParameter.symbol - ) - if (adapteeFunction.extensionReceiverParameter != null) { - irCall.extensionReceiver = adaptedReceiverValue - adapterParameterIndex++ - } else { - irCall.dispatchReceiver = adaptedReceiverValue - } - } - - adapteeFunction.valueParameters.mapIndexed { index, valueParameter -> - when { - valueParameter.isVararg -> { - if (adapterFunction.valueParameters.size <= index) { - irCall.putValueArgument(index, null) - } else { - val adaptedValueArgument = - IrVarargImpl(startOffset, endOffset, valueParameter.type, valueParameter.varargElementType!!) - var neitherArrayNorSpread = false - while (adapterParameterIndex < adapterFunction.valueParameters.size) { - val irValueArgument = - adapterFunction.valueParameters[adapterParameterIndex].toIrGetValue(startOffset, endOffset) - if (irValueArgument.type == valueParameter.type) { - adaptedValueArgument.addElement(IrSpreadElementImpl(startOffset, endOffset, irValueArgument)) - adapterParameterIndex++ - break - } else if (irValueArgument.type == valueParameter.varargElementType) { - adaptedValueArgument.addElement(irValueArgument) - adapterParameterIndex++ - } else { - neitherArrayNorSpread = true - break - } - } - if (neitherArrayNorSpread) { - irCall.putValueArgument(index, null) - } else { - irCall.putValueArgument(index, adaptedValueArgument) - } - } - } - valueParameter.hasDefaultValue() -> { - irCall.putValueArgument(index, null) - } - else -> { - irCall.putValueArgument( - index, adapterFunction.valueParameters[adapterParameterIndex++].toIrGetValue(startOffset, endOffset) - ) - } - } - } - - return irCall.applyTypeArguments(callableReferenceAccess) - } - private fun FirQualifiedAccess.tryConvertToSamConstructorCall(type: IrType): IrTypeOperatorCall? { val calleeReference = calleeReference as? FirResolvedNamedReference ?: return null val fir = calleeReference.resolvedSymbol.fir @@ -730,10 +404,12 @@ class CallAndReferenceGenerator( for ((index, argument) in call.arguments.withIndex()) { val valueParameter = valueParameters?.get(index) val argumentExpression = - visitor.convertToIrExpression(argument) - .applySamConversionIfNeeded(argument, valueParameter) - .applySuspendConversionIfNeeded(argument, valueParameter) - .applyAssigningArrayElementsToVarargInNamedForm(argument, valueParameter) + with(adapterGenerator) { + visitor.convertToIrExpression(argument) + .applySamConversionIfNeeded(argument, valueParameter) + .applySuspendConversionIfNeeded(argument, valueParameter) + .applyAssigningArrayElementsToVarargInNamedForm(argument, valueParameter) + } putValueArgument(index, argumentExpression) } } @@ -772,10 +448,12 @@ class CallAndReferenceGenerator( for ((argument, parameter) in argumentMapping) { val parameterIndex = valueParameters.indexOf(parameter) val irArgument = - visitor.convertToIrExpression(argument) - .applySamConversionIfNeeded(argument, parameter) - .applySuspendConversionIfNeeded(argument, parameter) - .applyAssigningArrayElementsToVarargInNamedForm(argument, parameter) + with(adapterGenerator) { + visitor.convertToIrExpression(argument) + .applySamConversionIfNeeded(argument, parameter) + .applySuspendConversionIfNeeded(argument, parameter) + .applyAssigningArrayElementsToVarargInNamedForm(argument, parameter) + } if (irArgument.hasNoSideEffects()) { putValueArgument(parameterIndex, irArgument) } else { @@ -791,10 +469,12 @@ class CallAndReferenceGenerator( } else { for ((argument, parameter) in argumentMapping) { val argumentExpression = - visitor.convertToIrExpression(argument, annotationMode) - .applySamConversionIfNeeded(argument, parameter) - .applySuspendConversionIfNeeded(argument, parameter) - .applyAssigningArrayElementsToVarargInNamedForm(argument, parameter) + with(adapterGenerator) { + visitor.convertToIrExpression(argument, annotationMode) + .applySamConversionIfNeeded(argument, parameter) + .applySuspendConversionIfNeeded(argument, parameter) + .applyAssigningArrayElementsToVarargInNamedForm(argument, parameter) + } putValueArgument(valueParameters.indexOf(parameter), argumentExpression) } if (annotationMode) { @@ -848,127 +528,6 @@ class CallAndReferenceGenerator( return argument.isFunctional(session) } - // TODO: refactor to share some logic with suspend conversion for callable reference (maybe introduce AdapterGenerator?) - private fun IrExpression.applySuspendConversionIfNeeded( - argument: FirExpression, - parameter: FirValueParameter? - ): IrExpression { - if (this is IrBlock && origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) { - return this - } - if (parameter == null || !needSuspendConversion(argument, parameter)) { - return this - } - - val suspendConvertedType = parameter.returnTypeRef.toIrType() as IrSimpleType - val returnType = suspendConvertedType.arguments.last().typeOrNull!! - val invokeSymbol = - (argument.typeRef.coneType as? ConeClassLikeType)?.lookupTag?.classId - ?.let { classId -> - session.firSymbolProvider - .getClassDeclaredCallableSymbols(classId, Name.identifier("invoke")) - .filterIsInstance>() - .find { firFunctionSymbol -> - firFunctionSymbol.fir.valueParameters.size == suspendConvertedType.arguments.size - 1 - }?.let { firFunctionSymbol -> - declarationStorage.getIrFunctionSymbol(firFunctionSymbol) as? IrSimpleFunctionSymbol - } - } ?: return this - return argument.convertWithOffsets { startOffset, endOffset -> - val irAdapterFunction = createAdapterFunctionForArgument(startOffset, endOffset, suspendConvertedType) - // TODO: Should be able to reuse `this` if that is an immutable IrGetValue - val irArgumentValue = createTemporaryVariable(this, conversionScope).first - val irCall = createAdapteeCallForArgument(startOffset, endOffset, irAdapterFunction, invokeSymbol, irArgumentValue) - irAdapterFunction.body = irFactory.createBlockBody(startOffset, endOffset) { - if (returnType.isUnit()) { - statements.add(irCall) - } else { - statements.add(IrReturnImpl(startOffset, endOffset, irBuiltIns.nothingType, irAdapterFunction.symbol, irCall)) - } - } - - val statements = SmartList() - statements.add(irArgumentValue) - statements.add( - IrFunctionExpressionImpl( - startOffset, endOffset, suspendConvertedType, irAdapterFunction, IrStatementOrigin.SUSPEND_CONVERSION - ) - ) - IrBlockImpl(startOffset, endOffset, suspendConvertedType, IrStatementOrigin.SUSPEND_CONVERSION, statements) - } - } - - private fun needSuspendConversion(argument: FirExpression, parameter: FirValueParameter): Boolean = - // TODO: should refer to LanguageVersionSettings.SuspendConversion - parameter.returnTypeRef.coneType.isSuspendFunctionType(session) && - argument.typeRef.coneType.isBuiltinFunctionalType(session) && - !argument.typeRef.coneType.isSuspendFunctionType(session) - - private fun createAdapterFunctionForArgument( - startOffset: Int, - endOffset: Int, - type: IrSimpleType - ): IrSimpleFunction { - val returnType = type.arguments.last().typeOrNull!! - val parameterTypes = type.arguments.dropLast(1).map { it.typeOrNull!! } - val adapterFunctionDescriptor = WrappedSimpleFunctionDescriptor() - return symbolTable.declareSimpleFunction(adapterFunctionDescriptor) { irAdapterSymbol -> - irFactory.createFunction( - startOffset, endOffset, - IrDeclarationOrigin.ADAPTER_FOR_SUSPEND_CONVERSION, - irAdapterSymbol, - // TODO: need a better way to avoid name clash - Name.identifier("suspendConversion"), - DescriptorVisibilities.LOCAL, - Modality.FINAL, - returnType, - isInline = false, - isExternal = false, - isTailrec = false, - isSuspend = true, - isOperator = false, - isInfix = false, - isExpect = false, - isFakeOverride = false - ).also { irAdapterFunction -> - adapterFunctionDescriptor.bind(irAdapterFunction) - symbolTable.enterScope(irAdapterFunction) - irAdapterFunction.valueParameters += parameterTypes.mapIndexed { index, parameterType -> - createAdapterParameter( - irAdapterFunction, - Name.identifier("p$index"), - index, - parameterType, - IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_SUSPEND_CONVERSION - ) - } - symbolTable.leaveScope(irAdapterFunction) - irAdapterFunction.parent = conversionScope.parent()!! - } - } - } - - private fun createAdapteeCallForArgument( - startOffset: Int, - endOffset: Int, - adapterFunction: IrFunction, - invokeSymbol: IrSimpleFunctionSymbol, - irCapturedValue: IrValueDeclaration - ): IrExpression { - val irCall = IrCallImpl( - startOffset, endOffset, - adapterFunction.returnType, - invokeSymbol, - typeArgumentsCount = 0, - valueArgumentsCount = adapterFunction.valueParameters.size - ) - irCall.dispatchReceiver = irCapturedValue.toIrGetValue(startOffset, endOffset) - for (irAdapterParameter in adapterFunction.valueParameters) { - irCall.putValueArgument(irAdapterParameter.index, irAdapterParameter.toIrGetValue(startOffset, endOffset)) - } - return irCall - } - private fun IrExpression.applyAssigningArrayElementsToVarargInNamedForm( argument: FirExpression, parameter: FirValueParameter? @@ -993,7 +552,7 @@ class CallAndReferenceGenerator( return this } - private fun IrExpression.applyTypeArguments(access: FirQualifiedAccess): IrExpression { + internal fun IrExpression.applyTypeArguments(access: FirQualifiedAccess): IrExpression { return when (this) { is IrMemberAccessExpression<*> -> { val argumentsCount = access.typeArguments.size @@ -1030,7 +589,7 @@ class CallAndReferenceGenerator( private fun FirQualifiedAccess.findIrExtensionReceiver(explicitReceiverExpression: IrExpression?): IrExpression? = findIrReceiver(explicitReceiverExpression, isDispatch = false) - private fun FirQualifiedAccess.findIrReceiver(explicitReceiverExpression: IrExpression?, isDispatch: Boolean): IrExpression? { + internal fun FirQualifiedAccess.findIrReceiver(explicitReceiverExpression: IrExpression?, isDispatch: Boolean): IrExpression? { val firReceiver = if (isDispatch) dispatchReceiver else extensionReceiver if (firReceiver == explicitReceiver) { return explicitReceiverExpression @@ -1047,14 +606,6 @@ class CallAndReferenceGenerator( } } - private fun FirQualifiedAccess.findBoundReceiver(explicitReceiverExpression: IrExpression?, isDispatch: Boolean): IrExpression? { - val firReceiver = if (isDispatch) dispatchReceiver else extensionReceiver - if (firReceiver is FirNoReceiverExpression) { - return null - } - return findIrReceiver(explicitReceiverExpression, isDispatch) - } - private fun IrExpression.applyReceivers(qualifiedAccess: FirQualifiedAccess, explicitReceiverExpression: IrExpression?): IrExpression { return when (this) { is IrMemberAccessExpression<*> -> { @@ -1100,4 +651,3 @@ class CallAndReferenceGenerator( ) } } -