psi2ir: rework representation of bound adapted function references

This fixes the problem in JVM IR backend which didn't pass bound
receiver value of an adapted function reference to the superclass
(kotlin/jvm/internal/AdaptedFunctionReference), which caused equals to
work incorrectly on such references (see changes in box tests).

Previously, bound adapted function reference was represented as
IrFunctionExpression to an adapter function which calls the callee. The
value of the bound receiver in that case could only be found in the body
of that adapter function. This is not very convenient, so this change
makes psi2ir produce a block of the adapter function + reference to it.
The bound receiver value is then found in the reference. This is
basically similar to what ProvisionalFunctionExpressionLowering is doing
for all function expressions. And since this IR structure is already
supported in FunctionReferenceLowering, the problem in the JVM IR is
fixed without any additional modifications.

However, inliners do not support this IR structure yet, see KT-38535 and
KT-38536.
This commit is contained in:
Alexander Udalov
2020-04-22 17:45:11 +02:00
parent 22bf23025a
commit 025d1ca64d
20 changed files with 303 additions and 150 deletions
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.builtins.createFunctionType
import org.jetbrains.kotlin.builtins.isKFunctionType
import org.jetbrains.kotlin.builtins.isKSuspendFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
@@ -31,9 +32,6 @@ 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.IrVariableSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.util.referenceClassifier
import org.jetbrains.kotlin.ir.util.referenceFunction
import org.jetbrains.kotlin.ir.util.withScope
@@ -44,6 +42,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffsetSkippingComments
import org.jetbrains.kotlin.psi2ir.intermediate.CallBuilder
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
@@ -110,7 +109,7 @@ class ReflectionReferencesGenerator(statementGenerator: StatementGenerator) : St
private fun generateAdaptedCallableReference(
ktCallableReference: KtCallableReferenceExpression,
callBuilder: CallBuilder
): IrExpressionBase {
): IrExpression {
val adapteeDescriptor = callBuilder.descriptor
if (adapteeDescriptor !is FunctionDescriptor) {
throw AssertionError("Function descriptor expected in adapted callable reference: $adapteeDescriptor")
@@ -128,9 +127,9 @@ class ReflectionReferencesGenerator(statementGenerator: StatementGenerator) : St
val ktExpectedReturnType = ktFunctionalTypeArguments.last().type
val ktExpectedParameterTypes = ktFunctionalTypeArguments.take(ktFunctionalTypeArguments.size - 1).map { it.type }
val irAdapterFun = createAdapterFun(startOffset, endOffset, adapteeDescriptor, ktExpectedParameterTypes, ktExpectedReturnType)
val adapteeCall = createAdapteeCall(startOffset, endOffset, ktCallableReference, adapteeSymbol, callBuilder, irAdapterFun)
val irCall = adapteeCall.callExpression
val irAdapterFun =
createAdapterFun(startOffset, endOffset, adapteeDescriptor, ktExpectedParameterTypes, ktExpectedReturnType, callBuilder)
val irCall = createAdapteeCall(startOffset, endOffset, adapteeSymbol, callBuilder, irAdapterFun)
irAdapterFun.body = IrBlockBodyImpl(startOffset, endOffset).apply {
if (KotlinBuiltIns.isUnit(ktExpectedReturnType))
@@ -139,96 +138,91 @@ class ReflectionReferencesGenerator(statementGenerator: StatementGenerator) : St
statements.add(IrReturnImpl(startOffset, endOffset, context.irBuiltIns.nothingType, irAdapterFun.symbol, irCall))
}
val irFunExpr = IrFunctionExpressionImpl(
startOffset, endOffset,
irFunctionalType,
irAdapterFun,
IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE
)
return if (adapteeCall.tmpReceivers.isEmpty()) {
irFunExpr
} else {
IrBlockImpl(
startOffset, endOffset, irFunctionalType,
origin = null,
statements = adapteeCall.tmpReceivers + irFunExpr
val resolvedCall = callBuilder.original
return statementGenerator.generateCallReceiver(
ktCallableReference,
resolvedCall.resultingDescriptor,
resolvedCall.dispatchReceiver, resolvedCall.extensionReceiver,
isSafe = false
).call { dispatchReceiverValue, extensionReceiverValue ->
val irAdapterRef = IrFunctionReferenceImpl(
startOffset, endOffset, irFunctionalType, irAdapterFun.symbol, irAdapterFun.typeParameters.size,
irAdapterFun.valueParameters.size, null, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE
)
val irDispatchReceiver = dispatchReceiverValue?.loadIfExists()
val irExtensionReceiver = extensionReceiverValue?.loadIfExists()
check(irDispatchReceiver == null || irExtensionReceiver == null) {
"Bound callable reference cannot have both receivers: $adapteeDescriptor"
}
val receiver = irDispatchReceiver ?: irExtensionReceiver
if (receiver == null) {
IrFunctionExpressionImpl(
startOffset, endOffset, irFunctionalType, irAdapterFun, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE
)
} else {
val statements = SmartList<IrStatement>()
if (receiver.isSafeToUseWithoutCopying()) {
irAdapterRef.extensionReceiver = receiver
} else {
val irVariable = statementGenerator.scope.createTemporaryVariable(receiver, "receiver")
irAdapterRef.extensionReceiver = IrGetValueImpl(startOffset, endOffset, irVariable.symbol)
statements.add(irVariable)
}
statements.add(irAdapterFun)
statements.add(irAdapterRef)
IrBlockImpl(startOffset, endOffset, irFunctionalType, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE, statements)
}
}
}
private class AdapteeCall(
val callExpression: IrExpression,
val tmpReceivers: List<IrVariable>
)
private fun createAdapteeCall(
startOffset: Int,
endOffset: Int,
ktCallableReference: KtCallableReferenceExpression,
adapteeSymbol: IrFunctionSymbol,
callBuilder: CallBuilder,
irAdapterFun: IrSimpleFunction
): AdapteeCall {
): IrExpression {
val resolvedCall = callBuilder.original
val resolvedDescriptor = resolvedCall.resultingDescriptor
val tmpReceivers = SmartList<IrVariable>()
val irType = resolvedDescriptor.returnType!!.toIrType()
val irCall = statementGenerator.generateCallReceiver(
ktCallableReference,
resolvedDescriptor,
resolvedCall.dispatchReceiver, resolvedCall.extensionReceiver,
isSafe = false
).call { dispatchReceiverValue, extensionReceiverValue ->
val irType = resolvedDescriptor.returnType!!.toIrType()
val irCall =
if (resolvedDescriptor is ConstructorDescriptor)
IrConstructorCallImpl.fromSymbolDescriptor(
startOffset, endOffset, irType,
adapteeSymbol as IrConstructorSymbol
)
else
IrCallImpl(
startOffset, endOffset, irType,
adapteeSymbol,
origin = null, superQualifierSymbol = null
)
val irAdapteeCallInner =
if (resolvedDescriptor is ConstructorDescriptor)
IrConstructorCallImpl.fromSymbolDescriptor(
startOffset, endOffset, irType,
adapteeSymbol as IrConstructorSymbol
)
else
IrCallImpl(
startOffset, endOffset, irType,
adapteeSymbol,
origin = null, superQualifierSymbol = null
)
context.callToSubstitutedDescriptorMap[irAdapteeCallInner] = resolvedDescriptor
val irDispatchReceiver = dispatchReceiverValue?.loadIfExists()
val irExtensionReceiver = extensionReceiverValue?.loadIfExists()
if (irDispatchReceiver != null) {
if (irDispatchReceiver.isSafeToUseWithoutCopying()) {
irAdapteeCallInner.dispatchReceiver = irDispatchReceiver
} else {
val irVariable = statementGenerator.scope.createTemporaryVariable(irDispatchReceiver, "this")
irAdapteeCallInner.dispatchReceiver = IrGetValueImpl(startOffset, endOffset, irVariable.symbol)
tmpReceivers.add(irVariable)
}
val hasBoundDispatchReceiver = resolvedCall.dispatchReceiver != null && resolvedCall.dispatchReceiver !is TransientReceiver
val hasBoundExtensionReceiver = resolvedCall.extensionReceiver != null && resolvedCall.extensionReceiver !is TransientReceiver
if (hasBoundDispatchReceiver || hasBoundExtensionReceiver) {
// In case of a bound reference, the receiver (which can only be one) is passed in the extension receiver parameter.
val receiverValue = IrGetValueImpl(
startOffset, endOffset, irAdapterFun.extensionReceiverParameter!!.symbol, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE
)
when {
hasBoundDispatchReceiver -> irCall.dispatchReceiver = receiverValue
hasBoundExtensionReceiver -> irCall.extensionReceiver = receiverValue
}
if (irExtensionReceiver != null) {
if (irExtensionReceiver.isSafeToUseWithoutCopying()) {
irAdapteeCallInner.extensionReceiver = irExtensionReceiver
} else {
val irVariable = statementGenerator.scope.createTemporaryVariable(irExtensionReceiver, "receiver")
irAdapteeCallInner.extensionReceiver = IrGetValueImpl(startOffset, endOffset, irVariable.symbol)
tmpReceivers.add(irVariable)
}
}
irAdapteeCallInner.putTypeArguments(callBuilder.typeArguments) { it.toIrType() }
putAdaptedValueArguments(startOffset, endOffset, irAdapteeCallInner, irAdapterFun, resolvedCall)
irAdapteeCallInner
}
return AdapteeCall(irCall, tmpReceivers)
context.callToSubstitutedDescriptorMap[irCall] = resolvedDescriptor
irCall.putTypeArguments(callBuilder.typeArguments) { it.toIrType() }
putAdaptedValueArguments(startOffset, endOffset, irCall, irAdapterFun, resolvedCall)
return irCall
}
private fun IrExpression.isSafeToUseWithoutCopying() =
@@ -330,7 +324,8 @@ class ReflectionReferencesGenerator(statementGenerator: StatementGenerator) : St
endOffset: Int,
adapteeDescriptor: FunctionDescriptor,
ktExpectedParameterTypes: List<KotlinType>,
ktExpectedReturnType: KotlinType
ktExpectedReturnType: KotlinType,
callBuilder: CallBuilder,
): IrSimpleFunction {
val adapterFunctionDescriptor = WrappedSimpleFunctionDescriptor()
@@ -361,34 +356,52 @@ class ReflectionReferencesGenerator(statementGenerator: StatementGenerator) : St
irAdapterFun.metadata = MetadataSource.Function(adapteeDescriptor)
irAdapterFun.dispatchReceiverParameter = null
irAdapterFun.extensionReceiverParameter = null
val boundReceiver = callBuilder.original.selectBoundReceiver()
if (boundReceiver != null) {
irAdapterFun.extensionReceiverParameter =
createAdapterParameter(startOffset, endOffset, Name.identifier("receiver"), -1, boundReceiver.type)
} else {
irAdapterFun.extensionReceiverParameter = null
}
irAdapterFun.valueParameters += ktExpectedParameterTypes.mapIndexed { index, ktExpectedParameterType ->
val adapterValueParameterDescriptor = WrappedValueParameterDescriptor()
context.symbolTable.declareValueParameter(
startOffset, endOffset,
IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE,
adapterValueParameterDescriptor,
ktExpectedParameterType.toIrType()
) { irAdapterParameterSymbol ->
IrValueParameterImpl(
startOffset, endOffset,
IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE,
irAdapterParameterSymbol,
Name.identifier("p$index"),
index,
ktExpectedParameterType.toIrType(),
varargElementType = null, isCrossinline = false, isNoinline = false
).also { irAdapterValueParameter ->
adapterValueParameterDescriptor.bind(irAdapterValueParameter)
}
}
createAdapterParameter(startOffset, endOffset, Name.identifier("p$index"), index, ktExpectedParameterType)
}
}
}
}
}
private fun ResolvedCall<*>.selectBoundReceiver(): ReceiverValue? {
val dispatchReceiver = dispatchReceiver.takeUnless { it is TransientReceiver }
val extensionReceiver = extensionReceiver.takeUnless { it is TransientReceiver }
return when {
dispatchReceiver == null -> extensionReceiver
extensionReceiver == null -> dispatchReceiver
else -> error("Bound callable references can't have both receivers: $resultingDescriptor")
}
}
private fun createAdapterParameter(startOffset: Int, endOffset: Int, name: Name, index: Int, type: KotlinType): IrValueParameter {
val descriptor = WrappedValueParameterDescriptor()
return context.symbolTable.declareValueParameter(
startOffset, endOffset, IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE, descriptor, type.toIrType()
) { irAdapterParameterSymbol ->
IrValueParameterImpl(
startOffset, endOffset,
IrDeclarationOrigin.ADAPTER_PARAMETER_FOR_CALLABLE_REFERENCE,
irAdapterParameterSymbol,
name,
index,
type.toIrType(),
varargElementType = null, isCrossinline = false, isNoinline = false
).also { irAdapterValueParameter ->
descriptor.bind(irAdapterValueParameter)
}
}
}
fun generateCallableReference(
ktElement: KtElement,
type: KotlinType,