From 150b8061e9ae1a68e48278bd6d384d41f462a55e Mon Sep 17 00:00:00 2001 From: Sergej Jaskiewicz Date: Mon, 16 Jan 2023 17:54:03 +0100 Subject: [PATCH] [K/N] Fix FunctionReferenceLowering for bound refs to generic functions 4a2a77d9b95a08763fd687b05fc4732ef294ebc8 introduced a regression where for bound references to functions generic over their receiver the generated class was also generic even if the function reference itself only contained concrete types. This is fixed here. --- .../konan/lower/FunctionReferenceLowering.kt | 88 +++++++++++++++---- .../filecheck/kt49847_generic_receiver.kt | 25 ++++-- 2 files changed, 93 insertions(+), 20 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt index d2f6626d23a..f1a2dfe9d1b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt @@ -5,11 +5,8 @@ package org.jetbrains.kotlin.backend.konan.lower -import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.* import org.jetbrains.kotlin.backend.common.lower.createIrBuilder -import org.jetbrains.kotlin.backend.common.pop -import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.konan.NativeGenerationState import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.konan.llvm.computeFullName @@ -22,6 +19,7 @@ import org.jetbrains.kotlin.ir.builders.declarations.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -211,6 +209,32 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt private val functionReferenceTarget = adaptedReferenceOriginalTarget ?: referencedFunction + /** + * The first element of a pair is a type parameter of [referencedFunction], the second element is its argument in + * [functionReference], to be passed upon instantiation of the function reference class. + */ + private val allTypeParametersAndArguments: List> = + referencedFunction.typeParameters.map { typeParam -> + typeParam.symbol to functionReference.getTypeArgument(typeParam.index)!! + } + + /** + * @see allTypeParametersAndArguments + */ + private val allTypeParametersAndArgumentsMap: Map = allTypeParametersAndArguments.toMap() + + /** + * The type arguments of [functionReference] that are not concrete types, + * but are themselves type parameters coming from an enclosing scope. + * + * The first element of a pair is a type parameter of [referencedFunction], the second element is its argument in + * [functionReference]. + * + * [functionReferenceClass] is only generic over these type parameters. + */ + private val typeParametersAndArgumentsFromEnclosingScope: List> = + allTypeParametersAndArguments.filter { it.second.isTypeParameter() } + private val functionReferenceClass = irFactory.buildClass { startOffset = this@FunctionReferenceBuilder.startOffset endOffset = this@FunctionReferenceBuilder.endOffset @@ -219,30 +243,50 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt visibility = DescriptorVisibilities.PRIVATE }.apply { parent = this@FunctionReferenceBuilder.parent - copyTypeParametersFrom(referencedFunction) + + // The function reference class only needs to be generic over type parameters coming from an enclosing scope. + copyTypeParameters(typeParametersAndArgumentsFromEnclosingScope.map { it.first.owner }) createParameterDeclarations() // copy the generated name for IrClass, partially solves KT-47194 generationState.copyLocalClassName(functionReference, this) } - private val typeArguments = functionReferenceClass.typeParameters.map { typeParam -> - typeParam.symbol to functionReference.getTypeArgument(typeParam.index)!! - } + /** + * Remaps type parameters of [referencedFunction] to type parameters of [functionReferenceClass]. + * + * Note: this is not a 1-to-1 mapping. Some type parameters of [referencedFunction] may be bound to a concrete type, in this case + * there will be no corresponding type parameter in [functionReferenceClass]. + */ + private val typeParameterRemapper = IrTypeParameterRemapper( + typeParametersAndArgumentsFromEnclosingScope.map { it.first.owner }.zip(functionReferenceClass.typeParameters).toMap() + ) - private val typeParameterRemapper = IrTypeParameterRemapper(referencedFunction.typeParameters.zip(functionReferenceClass.typeParameters).toMap()) private val functionParameterTypes = unboundFunctionParameters.map { typeParameterRemapper.remapType(it.type) } private val functionReturnType = typeParameterRemapper.remapType(referencedFunction.returnType) private val functionReferenceThis = functionReferenceClass.thisReceiver!! + /** + * Substitutes a bound value parameter's [type] with a new type according to the following rules: + * + * - If [type] is a type parameter from an enclosing scope (i.e. for which we don't know the concrete type), replace it with + * the corresponding [functionReferenceClass]'s type parameter. + * - If this value parameter's type is a type parameter for which we know the concrete type, replace it with + * the concrete type. For example, consider the `5::foo` function reference where `foo` is declared as `fun T.foo()`. Here, + * `T` will be replaced with `Int`. + * - Otherwise, just return [type] itself. + */ + private fun substituteBoundValueParameterType(type: IrType): IrType = + typeParameterRemapper.remapType(type).substitute(allTypeParametersAndArgumentsMap) + private val argumentToPropertiesMap = boundFunctionParameters.associateWith { functionReferenceClass.addField { startOffset = this@FunctionReferenceBuilder.startOffset endOffset = this@FunctionReferenceBuilder.endOffset origin = DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL name = it.name - type = it.type + type = substituteBoundValueParameterType(it.type) isFinal = true } } @@ -253,6 +297,17 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt private val kSuspendFunctionImplConstructorSymbol = kSuspendFunctionImplSymbol.constructors.single() private fun buildClass(): IrClass { + if (unboundFunctionParameters.size != (functionReference.type as IrSimpleType).arguments.size - 1) { + compilationException( + "The number of unbound value parameters of the function reference should match the number of type arguments " + + "of the K[Suspend]FunctionN superclass minus one.\n\n" + + "Unbound function parameters:\n" + + unboundFunctionParameters.joinToString(separator = "\n", transform = IrElement::render) + + "\n\nFunction reference type: " + + functionReference.type.render(), + functionReference + ) + } val superClass = when { isKSuspendFunction -> kSuspendFunctionImplSymbol.typeWith(functionReturnType) isLambda -> irBuiltIns.anyType @@ -351,10 +406,13 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt origin = DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL isPrimary = true }.apply { - val typeArgumentsMap = typeArguments.toMap() valueParameters += boundFunctionParameters.mapIndexed { index, parameter -> - parameter.copyTo(this, DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL, index, - type = parameter.type.substitute(typeArgumentsMap)) + parameter.copyTo( + this, + DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL, + index, + type = substituteBoundValueParameterType(parameter.type) + ) } body = context.createIrBuilder(symbol, startOffset, endOffset).irBlockBody { @@ -376,7 +434,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt val clazz = buildClass() val constructor = buildConstructor() val arguments = functionReference.getArgumentsWithIr() - val typeArguments = typeArguments.map { it.second } + val typeArguments = typeParametersAndArgumentsFromEnclosingScope.map { it.second } val expression = if (arguments.isEmpty()) { irBuilder.irConstantObject(clazz, emptyMap(), typeArguments) } else { @@ -480,7 +538,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt assert(unboundIndex == valueParameters.size) { "Not all arguments of are used" } referencedFunction.typeParameters.forEach { typeParam -> - putTypeArgument(typeParam.index, functionReferenceClass.typeParameters[typeParam.index].defaultType) + putTypeArgument(typeParam.index, substituteBoundValueParameterType(typeParam.defaultType)) } } ) diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt index f830b47f29c..521291e1630 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt @@ -3,11 +3,26 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$0.#internal" -// CHECK-SAME: i32 - fun T.foo() { println(this) } -fun main() { - println(5::foo) +// CHECK-LABEL: define void @"kfun:#bar(0:0){0\C2\A7}" +// CHECK-SAME: (%struct.ObjHeader* [[x:%[0-9]+]]) +fun bar(x: BarTP) { + // CHECK: call void @"kfun:$foo$FUNCTION_REFERENCE$0.#internal"(%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* [[x]]) + println(x::foo) } + +// CHECK-LABEL: define void @"kfun:#main(){}" +fun main() { + // CHECK: call void @"kfun:$foo$FUNCTION_REFERENCE$1.#internal"(%struct.ObjHeader* {{%[0-9]+}}, i32 5) + println(5::foo) + + bar("hello") + bar(42) +} + +// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$0.#internal" +// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* {{%[0-9]+}}) + +// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$1.#internal" +// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, i32 {{%[0-9]+}})