FIR: initial support of suspend conversion on arguments

This commit is contained in:
Jinseong Jeon
2020-09-08 14:59:28 -07:00
committed by Mikhail Glukhikh
parent 49679f3145
commit 6de8ba40c1
19 changed files with 393 additions and 119 deletions
@@ -477,14 +477,21 @@ fun Fir2IrComponents.createSafeCallConstruction(
}
}
fun Fir2IrComponents.createTemporaryVariableForSafeCallConstruction(
fun Fir2IrComponents.createTemporaryVariable(
receiverExpression: IrExpression,
conversionScope: Fir2IrConversionScope
conversionScope: Fir2IrConversionScope,
nameHint: String? = null
): Pair<IrVariable, IrValueSymbol> {
val receiverVariable = declarationStorage.declareTemporaryVariable(receiverExpression, "safe_receiver").apply {
val receiverVariable = declarationStorage.declareTemporaryVariable(receiverExpression, nameHint).apply {
parent = conversionScope.parentFromStack()
}
val variableSymbol = receiverVariable.symbol
return Pair(receiverVariable, variableSymbol)
}
fun Fir2IrComponents.createTemporaryVariableForSafeCallConstruction(
receiverExpression: IrExpression,
conversionScope: Fir2IrConversionScope
): Pair<IrVariable, IrValueSymbol> =
createTemporaryVariable(receiverExpression, conversionScope, "safe_receiver")
@@ -40,6 +40,8 @@ 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,
@@ -192,6 +194,7 @@ class CallAndReferenceGenerator(
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?,
@@ -205,10 +208,10 @@ class CallAndReferenceGenerator(
val boundDispatchReceiver = callableReferenceAccess.findBoundReceiver(explicitReceiverExpression, isDispatch = true)
val boundExtensionReceiver = callableReferenceAccess.findBoundReceiver(explicitReceiverExpression, isDispatch = false)
val irAdapterFunction = createAdapterFunction(
val irAdapterFunction = createAdapterFunctionForCallableReference(
callableReferenceAccess, startOffset, endOffset, firAdaptee!!, adaptee, type, boundDispatchReceiver, boundExtensionReceiver
)
val irCall = createAdapteeCall(
val irCall = createAdapteeCallForCallableReference(
callableReferenceAccess, adapteeSymbol, irAdapterFunction, boundDispatchReceiver, boundExtensionReceiver
)
irAdapterFunction.body = irFactory.createBlockBody(startOffset, endOffset) {
@@ -231,8 +234,7 @@ class CallAndReferenceGenerator(
if (boundReceiver.isSafeToUseWithoutCopying()) {
irAdapterRef.extensionReceiver = boundReceiver
} else {
val (irVariable, irVariableSymbol) =
createTemporaryVariableForSafeCallConstruction(boundReceiver.deepCopyWithSymbols(), conversionScope)
val (irVariable, irVariableSymbol) = createTemporaryVariable(boundReceiver.deepCopyWithSymbols(), conversionScope)
irAdapterRef.extensionReceiver = IrGetValueImpl(startOffset, endOffset, irVariableSymbol)
statements.add(irVariable)
}
@@ -244,7 +246,7 @@ class CallAndReferenceGenerator(
}
}
private fun createAdapterFunction(
private fun createAdapterFunctionForCallableReference(
callableReferenceAccess: FirCallableReferenceAccess,
startOffset: Int,
endOffset: Int,
@@ -288,10 +290,22 @@ class CallAndReferenceGenerator(
error("Bound callable references can't have both receivers: ${callableReferenceAccess.render()}")
else ->
irAdapterFunction.extensionReceiverParameter =
createAdapterParameter(irAdapterFunction, Name.identifier("receiver"), -1, boundReceiver.type)
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)
createAdapterParameter(
irAdapterFunction,
Name.identifier("p$index"),
index,
parameterType,
IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE
)
}
symbolTable.leaveScope(irAdapterFunction)
@@ -304,17 +318,18 @@ class CallAndReferenceGenerator(
adapterFunction: IrFunction,
name: Name,
index: Int,
type: IrType
type: IrType,
origin: IrDeclarationOrigin
): IrValueParameter {
val startOffset = adapterFunction.startOffset
val endOffset = adapterFunction.endOffset
val descriptor = WrappedValueParameterDescriptor()
return symbolTable.declareValueParameter(
startOffset, endOffset, IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE, descriptor, type
startOffset, endOffset, origin, descriptor, type
) { irAdapterParameterSymbol ->
irFactory.createValueParameter(
startOffset, endOffset,
IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE,
origin,
irAdapterParameterSymbol,
name,
index,
@@ -329,7 +344,10 @@ class CallAndReferenceGenerator(
}
}
private fun createAdapteeCall(
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,
@@ -381,9 +399,6 @@ class CallAndReferenceGenerator(
}
}
fun IrValueParameter.toGetValue(): IrGetValue =
IrGetValueImpl(startOffset, endOffset, this.type, this.symbol)
adapteeFunction.valueParameters.mapIndexed { index, valueParameter ->
when {
valueParameter.isVararg -> {
@@ -394,7 +409,8 @@ class CallAndReferenceGenerator(
IrVarargImpl(startOffset, endOffset, valueParameter.type, valueParameter.varargElementType!!)
var neitherArrayNorSpread = false
while (adapterParameterIndex < adapterFunction.valueParameters.size) {
val irValueArgument = adapterFunction.valueParameters[adapterParameterIndex].toGetValue()
val irValueArgument =
adapterFunction.valueParameters[adapterParameterIndex].toIrGetValue(startOffset, endOffset)
if (irValueArgument.type == valueParameter.type) {
adaptedValueArgument.addElement(IrSpreadElementImpl(startOffset, endOffset, irValueArgument))
adapterParameterIndex++
@@ -418,7 +434,9 @@ class CallAndReferenceGenerator(
irCall.putValueArgument(index, null)
}
else -> {
irCall.putValueArgument(index, adapterFunction.valueParameters[adapterParameterIndex++].toGetValue())
irCall.putValueArgument(
index, adapterFunction.valueParameters[adapterParameterIndex++].toIrGetValue(startOffset, endOffset)
)
}
}
}
@@ -714,6 +732,7 @@ class CallAndReferenceGenerator(
val argumentExpression =
visitor.convertToIrExpression(argument)
.applySamConversionIfNeeded(argument, valueParameter)
.applySuspendConversionIfNeeded(argument, valueParameter)
.applyAssigningArrayElementsToVarargInNamedForm(argument, valueParameter)
putValueArgument(index, argumentExpression)
}
@@ -755,6 +774,7 @@ class CallAndReferenceGenerator(
val irArgument =
visitor.convertToIrExpression(argument)
.applySamConversionIfNeeded(argument, parameter)
.applySuspendConversionIfNeeded(argument, parameter)
.applyAssigningArrayElementsToVarargInNamedForm(argument, parameter)
if (irArgument.hasNoSideEffects()) {
putValueArgument(parameterIndex, irArgument)
@@ -773,6 +793,7 @@ class CallAndReferenceGenerator(
val argumentExpression =
visitor.convertToIrExpression(argument, annotationMode)
.applySamConversionIfNeeded(argument, parameter)
.applySuspendConversionIfNeeded(argument, parameter)
.applyAssigningArrayElementsToVarargInNamedForm(argument, parameter)
putValueArgument(valueParameters.indexOf(parameter), argumentExpression)
}
@@ -827,6 +848,127 @@ 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<FirFunctionSymbol<*>>()
.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<IrStatement>()
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?
@@ -11,10 +11,9 @@ import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.createFunctionalType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
import org.jetbrains.kotlin.fir.resolve.inference.preprocessCallableReference
import org.jetbrains.kotlin.fir.resolve.inference.preprocessLambdaArgument
import org.jetbrains.kotlin.fir.resolve.inference.*
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.firUnsafe
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
import org.jetbrains.kotlin.fir.resolve.transformers.ensureResolvedTypeDeclaration
@@ -233,12 +232,29 @@ fun Candidate.resolvePlainArgumentType(
val session = context.session
val capturedType = prepareCapturedType(argumentType, context)
val argumentTypeForApplicabilityCheck =
var argumentTypeForApplicabilityCheck =
if (useNullableArgumentType)
capturedType.withNullability(ConeNullability.NULLABLE, session.typeContext)
else
capturedType
// If the argument is of functional type and the expected type is a suspend function type, we need to do "suspend conversion."
// TODO: should refer to LanguageVersionSettings.SuspendConversion
// TODO: should prefer another candidate without suspend conversion when ambiguous
if (expectedType?.isSuspendFunctionType(session) == true &&
argumentTypeForApplicabilityCheck.isBuiltinFunctionalType(session) &&
!argumentTypeForApplicabilityCheck.isSuspendFunctionType(session)
) {
val typeParameters = argumentTypeForApplicabilityCheck.typeArguments.map { it as ConeKotlinType }
argumentTypeForApplicabilityCheck =
createFunctionalType(
typeParameters.dropLast(1), null, typeParameters.last(),
isSuspend = true,
isKFunctionType = argumentTypeForApplicabilityCheck.isKFunctionType(session)
)
substitutor.substituteOrSelf(argumentTypeForApplicabilityCheck)
}
checkApplicabilityForArgumentType(
csBuilder, argumentTypeForApplicabilityCheck, expectedType, position, isReceiver, isDispatch, sink, context
)
@@ -16,31 +16,35 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
fun ConeKotlinType.isBuiltinFunctionalType(session: FirSession): Boolean {
private fun ConeKotlinType.functionClassKind(session: FirSession): FunctionClassKind? {
contract {
returns(true) implies (this@isBuiltinFunctionalType is ConeClassLikeType)
returns(true) implies (this@functionClassKind is ConeClassLikeType)
}
if (this !is ConeClassLikeType) return false
if (this !is ConeClassLikeType) return null
val classId = fullyExpandedType(session).lookupTag.classId
val kind = FunctionClassKind.byClassNamePrefix(classId.packageFqName, classId.relativeClassName.asString()) ?: return false
return FunctionClassKind.byClassNamePrefix(classId.packageFqName, classId.relativeClassName.asString())
}
fun ConeKotlinType.isBuiltinFunctionalType(session: FirSession): Boolean {
val kind = functionClassKind(session) ?: return false
return kind == FunctionClassKind.Function ||
kind == FunctionClassKind.KFunction ||
kind == FunctionClassKind.SuspendFunction ||
kind == FunctionClassKind.KSuspendFunction
}
@OptIn(ExperimentalContracts::class)
fun ConeKotlinType.isSuspendFunctionType(session: FirSession): Boolean {
contract {
returns(true) implies (this@isSuspendFunctionType is ConeClassLikeType)
}
if (this !is ConeClassLikeType) return false
val classId = this.fullyExpandedType(session).lookupTag.classId
val kind = FunctionClassKind.byClassNamePrefix(classId.packageFqName, classId.relativeClassName.asString()) ?: return false
val kind = functionClassKind(session) ?: return false
return kind == FunctionClassKind.SuspendFunction ||
kind == FunctionClassKind.KSuspendFunction
}
fun ConeKotlinType.isKFunctionType(session: FirSession): Boolean {
val kind = functionClassKind(session) ?: return false
return kind == FunctionClassKind.KFunction ||
kind == FunctionClassKind.KSuspendFunction
}
fun ConeKotlinType.receiverType(expectedTypeRef: FirTypeRef?, session: FirSession): ConeKotlinType? {
if (isBuiltinFunctionalType(session) && expectedTypeRef?.isExtensionFunctionType(session) == true) {
return (this.fullyExpandedType(session).typeArguments.first() as ConeKotlinTypeProjection).type