FIR: extend suspend conversion to subtype of functional type
This commit is contained in:
committed by
Denis Zharkov
parent
17176c00ae
commit
b10466f6a2
+55
-29
@@ -14,12 +14,14 @@ import org.jetbrains.kotlin.fir.declarations.*
|
|||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
|
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
|
||||||
import org.jetbrains.kotlin.fir.render
|
import org.jetbrains.kotlin.fir.render
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.createFunctionalType
|
||||||
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
|
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
|
||||||
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
|
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
|
||||||
import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType
|
import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType
|
||||||
import org.jetbrains.kotlin.fir.resolve.providers.getClassDeclaredCallableSymbols
|
import org.jetbrains.kotlin.fir.resolve.providers.getClassDeclaredCallableSymbols
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
|
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||||
@@ -70,7 +72,7 @@ internal class AdapterGenerator(
|
|||||||
function: IrFunction
|
function: IrFunction
|
||||||
): Boolean =
|
): Boolean =
|
||||||
needSuspendConversion(type, function) || needCoercionToUnit(type, function) ||
|
needSuspendConversion(type, function) || needCoercionToUnit(type, function) ||
|
||||||
needVarargSpread(callableReferenceAccess, type, function)
|
needVarargSpread(callableReferenceAccess, type, function)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For example,
|
* For example,
|
||||||
@@ -136,7 +138,6 @@ internal class AdapterGenerator(
|
|||||||
return coneType.toIrType() as IrSimpleType
|
return coneType.toIrType() as IrSimpleType
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor to share some logic with suspend conversion on arguments
|
|
||||||
internal fun generateAdaptedCallableReference(
|
internal fun generateAdaptedCallableReference(
|
||||||
callableReferenceAccess: FirCallableReferenceAccess,
|
callableReferenceAccess: FirCallableReferenceAccess,
|
||||||
explicitReceiverExpression: IrExpression?,
|
explicitReceiverExpression: IrExpression?,
|
||||||
@@ -398,32 +399,35 @@ internal class AdapterGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor to share some logic with suspend conversion for callable reference
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Instead of functions, a class with an overridden invoke can be used too:
|
||||||
|
* class Foo {
|
||||||
|
* override fun invoke() = ...
|
||||||
|
* }
|
||||||
|
* fun useSite(...) = { ... consumer(Foo()) ... }
|
||||||
|
*/
|
||||||
internal fun IrExpression.applySuspendConversionIfNeeded(
|
internal fun IrExpression.applySuspendConversionIfNeeded(
|
||||||
argument: FirExpression,
|
argument: FirExpression,
|
||||||
parameter: FirValueParameter?
|
parameter: FirValueParameter?
|
||||||
): IrExpression {
|
): IrExpression {
|
||||||
|
// TODO: should refer to LanguageVersionSettings.SuspendConversion
|
||||||
if (this is IrBlock && origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) {
|
if (this is IrBlock && origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
if (parameter == null || !needSuspendConversion(argument, parameter)) {
|
if (parameter?.returnTypeRef?.coneType?.isSuspendFunctionType(session) != true) {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
val suspendConvertedType = parameter.returnTypeRef.toIrType() as IrSimpleType
|
val suspendConvertedType = parameter.returnTypeRef.toIrType() as IrSimpleType
|
||||||
val returnType = suspendConvertedType.arguments.last().typeOrNull!!
|
val returnType = suspendConvertedType.arguments.last().typeOrNull!!
|
||||||
val invokeSymbol =
|
val invokeSymbol = findInvokeSymbol(suspendConvertedType, argument) ?: return this
|
||||||
(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 ->
|
return argument.convertWithOffsets { startOffset, endOffset ->
|
||||||
val irAdapterFunction = createAdapterFunctionForArgument(startOffset, endOffset, suspendConvertedType)
|
val irAdapterFunction = createAdapterFunctionForArgument(startOffset, endOffset, suspendConvertedType)
|
||||||
// TODO: Should be able to reuse `this` if that is an immutable IrGetValue
|
// TODO: Should be able to reuse `this` if that is an immutable IrGetValue
|
||||||
@@ -448,19 +452,41 @@ internal class AdapterGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun findInvokeSymbol(type: IrSimpleType, argument: FirExpression): IrSimpleFunctionSymbol? {
|
||||||
* For example,
|
val argumentType = argument.typeRef.coneType
|
||||||
* fun consumer(f: suspend () -> Unit) = ...
|
// Either the argument type is a non-suspend functional type
|
||||||
* fun nonSuspendFunction = { ... }
|
if (argumentType.isBuiltinFunctionalType(session) && !argumentType.isSuspendFunctionType(session)) {
|
||||||
* fun useSite(...) = { ... consumer(nonSuspendFunction) ... }
|
return (argument.typeRef.coneType as? ConeClassLikeType)?.lookupTag?.classId
|
||||||
*
|
?.let { classId ->
|
||||||
* At the use site, instead of the argument, we can put the suspend lambda as an adapter.
|
session.firSymbolProvider
|
||||||
*/
|
.getClassDeclaredCallableSymbols(classId, Name.identifier("invoke"))
|
||||||
private fun needSuspendConversion(argument: FirExpression, parameter: FirValueParameter): Boolean =
|
.filterIsInstance<FirFunctionSymbol<*>>()
|
||||||
// TODO: should refer to LanguageVersionSettings.SuspendConversion
|
.find { firFunctionSymbol ->
|
||||||
parameter.returnTypeRef.coneType.isSuspendFunctionType(session) &&
|
firFunctionSymbol.fir.valueParameters.size == type.arguments.size - 1
|
||||||
argument.typeRef.coneType.isBuiltinFunctionalType(session) &&
|
}?.let { firFunctionSymbol ->
|
||||||
!argument.typeRef.coneType.isSuspendFunctionType(session)
|
declarationStorage.getIrFunctionSymbol(firFunctionSymbol) as? IrSimpleFunctionSymbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or is a class-like type whose corresponding class is a user-defined and has an overridden `invoke`.
|
||||||
|
if (argumentType is ConeClassLikeType) {
|
||||||
|
val klass =
|
||||||
|
(session.firSymbolProvider.getClassLikeSymbolByFqName(argumentType.lookupTag.classId) as? FirRegularClassSymbol)?.fir
|
||||||
|
?: return null
|
||||||
|
if (klass.origin != FirDeclarationOrigin.Source) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return klass.declarations
|
||||||
|
.filterIsInstance<FirSimpleFunction>()
|
||||||
|
.singleOrNull { it.name == Name.identifier("invoke") && it.valueParameters.size == type.arguments.size - 1 }
|
||||||
|
?.let { invokeFunction ->
|
||||||
|
declarationStorage.getIrFunctionSymbol(invokeFunction.symbol) as? IrSimpleFunctionSymbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun createAdapterFunctionForArgument(
|
private fun createAdapterFunctionForArgument(
|
||||||
startOffset: Int,
|
startOffset: Int,
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
package org.jetbrains.kotlin.fir.resolve.calls
|
package org.jetbrains.kotlin.fir.resolve.calls
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
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.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||||
import org.jetbrains.kotlin.fir.resolve.createFunctionalType
|
import org.jetbrains.kotlin.fir.resolve.createFunctionalType
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isLocalClassOrAnonymousObject
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
|
||||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
import org.jetbrains.kotlin.fir.resolve.inference.*
|
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.firUnsafe
|
||||||
@@ -19,8 +19,11 @@ import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
|
|||||||
import org.jetbrains.kotlin.fir.resolve.transformers.ensureResolvedTypeDeclaration
|
import org.jetbrains.kotlin.fir.resolve.transformers.ensureResolvedTypeDeclaration
|
||||||
import org.jetbrains.kotlin.fir.returnExpressions
|
import org.jetbrains.kotlin.fir.returnExpressions
|
||||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
|
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||||
import org.jetbrains.kotlin.fir.typeContext
|
import org.jetbrains.kotlin.fir.typeContext
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
|
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
|
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
|
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
|
||||||
@@ -224,20 +227,12 @@ fun Candidate.resolvePlainArgumentType(
|
|||||||
capturedType
|
capturedType
|
||||||
|
|
||||||
// If the argument is of functional type and the expected type is a suspend function type, we need to do "suspend conversion."
|
// 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
|
if (expectedType != null) {
|
||||||
if (expectedType?.isSuspendFunctionType(session) == true &&
|
argumentTypeWithSuspendConversion(session, expectedType, argumentTypeForApplicabilityCheck)?.let {
|
||||||
argumentTypeForApplicabilityCheck.isBuiltinFunctionalType(session) &&
|
argumentTypeForApplicabilityCheck = it
|
||||||
!argumentTypeForApplicabilityCheck.isSuspendFunctionType(session)
|
substitutor.substituteOrSelf(argumentTypeForApplicabilityCheck)
|
||||||
) {
|
usesSuspendConversion = true
|
||||||
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)
|
|
||||||
usesSuspendConversion = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkApplicabilityForArgumentType(
|
checkApplicabilityForArgumentType(
|
||||||
@@ -245,6 +240,50 @@ fun Candidate.resolvePlainArgumentType(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun argumentTypeWithSuspendConversion(
|
||||||
|
session: FirSession, expectedType: ConeKotlinType, argumentType: ConeKotlinType
|
||||||
|
): ConeKotlinType? {
|
||||||
|
// TODO: should refer to LanguageVersionSettings.SuspendConversion
|
||||||
|
// Expect the expected type to be a suspend functional type.
|
||||||
|
if (!expectedType.isSuspendFunctionType(session)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either the argument type is a non-suspend functional type
|
||||||
|
if (argumentType.isBuiltinFunctionalType(session) && !argumentType.isSuspendFunctionType(session)) {
|
||||||
|
val typeParameters = argumentType.typeArguments.map { it as ConeKotlinType }
|
||||||
|
return createFunctionalType(
|
||||||
|
typeParameters.dropLast(1), null, typeParameters.last(),
|
||||||
|
isSuspend = true,
|
||||||
|
isKFunctionType = argumentType.isKFunctionType(session)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or is a class-like type whose corresponding class is a user-defined and has an overridden `invoke`.
|
||||||
|
if (argumentType is ConeClassLikeType) {
|
||||||
|
val klass =
|
||||||
|
(session.firSymbolProvider.getClassLikeSymbolByFqName(argumentType.lookupTag.classId) as? FirRegularClassSymbol)?.fir
|
||||||
|
?: return null
|
||||||
|
if (klass.origin != FirDeclarationOrigin.Source) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return klass.declarations
|
||||||
|
.filterIsInstance<FirSimpleFunction>()
|
||||||
|
.singleOrNull { it.name == Name.identifier("invoke") && it.valueParameters.size == expectedType.typeArguments.size - 1 }
|
||||||
|
?.let { invokeFunction ->
|
||||||
|
createFunctionalType(
|
||||||
|
invokeFunction.valueParameters.map { it.returnTypeRef.coneType },
|
||||||
|
null,
|
||||||
|
invokeFunction.returnTypeRef.coneType,
|
||||||
|
isSuspend = true,
|
||||||
|
isKFunctionType = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun Candidate.prepareCapturedType(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType {
|
fun Candidate.prepareCapturedType(argumentType: ConeKotlinType, context: ResolutionContext): ConeKotlinType {
|
||||||
return captureTypeFromExpressionOrNull(argumentType, context) ?: argumentType
|
return captureTypeFromExpressionOrNull(argumentType, context) ?: argumentType
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -3,7 +3,6 @@
|
|||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// IGNORE_BACKEND: JVM, NATIVE, JS, JS_IR
|
// IGNORE_BACKEND: JVM, NATIVE, JS, JS_IR
|
||||||
// IGNORE_BACKEND: JS_IR_ES6
|
// IGNORE_BACKEND: JS_IR_ES6
|
||||||
// IGNORE_BACKEND_FIR: JVM_IR
|
|
||||||
// IGNORE_LIGHT_ANALYSIS
|
// IGNORE_LIGHT_ANALYSIS
|
||||||
|
|
||||||
import helpers.*
|
import helpers.*
|
||||||
|
|||||||
Reference in New Issue
Block a user