[K/N] Fix generics in FunctionReferenceLowering for more edge cases

There were still some cases where the generated function reference class
would contain references to generic parameters that were not in scope.
One example of that was `Array<*>::get`. Before this fix the generated
function reference class would still contain a reference to the
type parameter declared in the `Array` class.

Also, the logic of generifying the generated function reference class
was incorrect. For example, for function `fun <A, B> foo()` if we had
a reference `::foo<T, T>` (where `T` comes from the outer scope),
the class would still contain two generic parameter, despite that only
one generic parameter from the outer scope was referenced.
This is also fixed here.
This commit is contained in:
Sergej Jaskiewicz
2023-01-24 21:14:56 +01:00
committed by Space Team
parent 73e420d69d
commit 61fc3e99b1
3 changed files with 163 additions and 21 deletions
@@ -211,7 +211,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
/**
* 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.
* [functionReference].
*/
private val allTypeParametersAndArguments: List<Pair<IrTypeParameterSymbol, IrType>> =
referencedFunction.typeParameters.map { typeParam ->
@@ -224,16 +224,11 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
private val allTypeParametersAndArgumentsMap: Map<IrTypeParameterSymbol, IrType> = allTypeParametersAndArguments.toMap()
/**
* The type arguments of [functionReference] that are not concrete types,
* The distinct 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<Pair<IrTypeParameterSymbol, IrType>> =
allTypeParametersAndArguments.filter { it.second.isTypeParameter() }
private val typeParametersFromEnclosingScope: List<IrTypeParameter> = allTypeParametersAndArguments
.mapNotNull { (_, typeArgument) -> (typeArgument.classifierOrNull as? IrTypeParameterSymbol)?.owner }.distinct()
private val functionReferenceClass = irFactory.buildClass {
startOffset = this@FunctionReferenceBuilder.startOffset
@@ -245,7 +240,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
parent = this@FunctionReferenceBuilder.parent
// The function reference class only needs to be generic over type parameters coming from an enclosing scope.
copyTypeParameters(typeParametersAndArgumentsFromEnclosingScope.map { it.first.owner })
copyTypeParameters(typeParametersFromEnclosingScope)
createParameterDeclarations()
// copy the generated name for IrClass, partially solves KT-47194
@@ -253,20 +248,38 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
}
/**
* 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].
* Remaps [typeParametersFromEnclosingScope] to type parameters of [functionReferenceClass].
*/
private val typeParameterRemapper = IrTypeParameterRemapper(
typeParametersAndArgumentsFromEnclosingScope.map { it.first.owner }.zip(functionReferenceClass.typeParameters).toMap()
typeParametersFromEnclosingScope.zip(functionReferenceClass.typeParameters).toMap()
)
private val functionParameterTypes = unboundFunctionParameters.map { typeParameterRemapper.remapType(it.type) }
private val functionReturnType = typeParameterRemapper.remapType(referencedFunction.returnType)
private val functionParameterAndReturnTypes = (functionReference.type as IrSimpleType).arguments.map {
when (it) {
is IrTypeProjection -> typeParameterRemapper.remapType(it.type)
is IrStarProjection -> context.irBuiltIns.anyNType
}
}
private val functionParameterTypes = functionParameterAndReturnTypes.dropLast(1)
private val functionReturnType = functionParameterAndReturnTypes.last()
private val functionReferenceThis = functionReferenceClass.thisReceiver!!
/**
* Replaces [typeParameter] of [referencedFunction] with the corresponding type parameter of [functionReferenceClass]
* if such correspondence takes place. Otherwise, just returns [typeParameter] as [IrType].
*/
private fun substituteTypeParameterOfReferencedFunction(typeParameter: IrTypeParameter): IrType {
if (typeParameter.parent != referencedFunction) {
compilationException(
"The type parameter ${typeParameter.render()} is not defined in the referenced function ${referencedFunction.render()}",
functionReference
)
}
return typeParameterRemapper.remapType(allTypeParametersAndArguments[typeParameter.index].second)
}
/**
* Substitutes a bound value parameter's [type] with a new type according to the following rules:
*
@@ -278,7 +291,8 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
* - Otherwise, just return [type] itself.
*/
private fun substituteBoundValueParameterType(type: IrType): IrType =
typeParameterRemapper.remapType(type).substitute(allTypeParametersAndArgumentsMap)
((type.classifierOrNull as? IrTypeParameterSymbol)?.owner?.let(this::substituteTypeParameterOfReferencedFunction) ?: type)
.substitute(allTypeParametersAndArgumentsMap)
private val argumentToPropertiesMap = boundFunctionParameters.associateWith {
functionReferenceClass.addField {
@@ -321,7 +335,6 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
transformedSuperMethod = samSuperClass.functions.single { it.owner.modality == Modality.ABSTRACT }.owner
} else {
val numberOfParameters = unboundFunctionParameters.size
val functionParameterAndReturnTypes = functionParameterTypes + functionReturnType
if (isKSuspendFunction) {
val suspendFunctionClass = symbols.kSuspendFunctionN(numberOfParameters).owner
superTypes += suspendFunctionClass.typeWith(functionParameterAndReturnTypes)
@@ -434,7 +447,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
val clazz = buildClass()
val constructor = buildConstructor()
val arguments = functionReference.getArgumentsWithIr()
val typeArguments = typeParametersAndArgumentsFromEnclosingScope.map { it.second }
val typeArguments = typeParametersFromEnclosingScope.map { it.defaultType }
val expression = if (arguments.isEmpty()) {
irBuilder.irConstantObject(clazz, emptyMap(), typeArguments)
} else {
@@ -538,7 +551,7 @@ internal class FunctionReferenceLowering(val generationState: NativeGenerationSt
assert(unboundIndex == valueParameters.size) { "Not all arguments of <invoke> are used" }
referencedFunction.typeParameters.forEach { typeParam ->
putTypeArgument(typeParam.index, substituteBoundValueParameterType(typeParam.defaultType))
putTypeArgument(typeParam.index, substituteTypeParameterOfReferencedFunction(typeParam))
}
}
)
@@ -6597,6 +6597,11 @@ fileCheckTest("filecheck_bce") {
annotatedSource = project.file('filecheck/bce.kt')
}
fileCheckTest("filecheck_generic_function_references") {
enabled = project.globalTestArgs.contains('-opt')
annotatedSource = project.file('filecheck/generic_function_references.kt')
}
fileCheckTest("filecheck_adopted_function_reference") {
annotatedSource = project.file('filecheck/adopted_function_reference.kt')
}
@@ -0,0 +1,124 @@
/*
* Copyright 2010-2023 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.
*/
import kotlin.reflect.KFunction2
fun <StringifyTP> stringify(collection: StringifyTP, size: (StringifyTP) -> Int, get: StringifyTP.(Int) -> Any?): String {
var res = "["
for (i in 0 until size(collection)) {
if (i > 0) res += ", "
res += collection.get(i).toString()
}
res += "]"
return res
}
interface I
// CHECK-LABEL: define %struct.ObjHeader* @"kfun:#stringifyArray(kotlin.Array<0:0>){0\C2\A7<I>}kotlin.String"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader** {{%[0-9]+}})
fun <StringifyArrayTP : I> stringifyArray(array: Array<StringifyArrayTP>) =
// CHECK: call %struct.ObjHeader* @"kfun:#stringify(0:0;kotlin.Function1<0:0,kotlin.Int>;kotlin.Function2<0:0,kotlin.Int,kotlin.Any?>){0\C2\A7<kotlin.Any?>}kotlin.String"
stringify(
array,
{ it.size }, // $stringifyArray$lambda$0$FUNCTION_REFERENCE$0
Array<*>::get // $get$FUNCTION_REFERENCE$1
)
// CHECK-LABEL: define %struct.ObjHeader* @"kfun:#stringifyIntArray(kotlin.Array<kotlin.Int>){}kotlin.String"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader** {{%[0-9]+}})
fun stringifyIntArray(array: Array<Int>) =
// CHECK: call %struct.ObjHeader* @"kfun:#stringify(0:0;kotlin.Function1<0:0,kotlin.Int>;kotlin.Function2<0:0,kotlin.Int,kotlin.Any?>){0\C2\A7<kotlin.Any?>}kotlin.String"
stringify(
array,
{ it.size }, // $stringifyIntArray$lambda$1$FUNCTION_REFERENCE$2
Array<Int>::get // $get$FUNCTION_REFERENCE$3
)
class N(val v: Int) : I {
override fun toString() = v.toString()
}
@Suppress("UNUSED_PARAMETER")
fun <BazTP0, BazTP1> foo(p1: BazTP0, p2: BazTP1) {}
fun <QuxTP> bar() {
val ref: KFunction2<QuxTP, QuxTP, Unit> = ::foo // $foo$FUNCTION_REFERENCE$4
println(ref)
}
fun main() {
println(stringifyArray(arrayOf(N(2), N(14))))
println(stringifyIntArray(arrayOf(1, 2, 3)))
bar<Int>()
bar<String>()
val ref: KFunction2<Int, Int, Unit> = ::foo // $foo$FUNCTION_REFERENCE$5
println(ref)
}
// CHECK-LABEL: define internal i32 @"kfun:$stringifyArray$lambda$0$FUNCTION_REFERENCE$0.invoke#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal void @"kfun:$stringifyArray$lambda$0$FUNCTION_REFERENCE$0.<init>#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$stringifyArray$lambda$0$FUNCTION_REFERENCE$0.$<bridge-BNNN>invoke(kotlin.Array<1:0>){}kotlin.Int#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* [[array:%[0-9]+]], %struct.ObjHeader** {{%[0-9]+}})
// CHECK: call i32 @"kfun:$stringifyArray$lambda$0$FUNCTION_REFERENCE$0.invoke#internal"(%struct.ObjHeader* [[this]], %struct.ObjHeader* [[array]])
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$get$FUNCTION_REFERENCE$1.invoke#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* [[array:%[0-9]+]], i32 [[index:%[0-9]+]], %struct.ObjHeader** [[ret:%[0-9]+]])
// CHECK: call %struct.ObjHeader* @Kotlin_Array_get(%struct.ObjHeader* [[array]], i32 [[index]], %struct.ObjHeader** [[ret]])
// CHECK-LABEL: define internal void @"kfun:$get$FUNCTION_REFERENCE$1.<init>#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$get$FUNCTION_REFERENCE$1.$<bridge-NNNNB>invoke(kotlin.Array<*>;kotlin.Int){}kotlin.Any?#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* [[array:%[0-9]+]], %struct.ObjHeader* [[boxedIndex:%[0-9]+]], %struct.ObjHeader** [[ret:%[0-9]+]])
// CHECK: call %struct.ObjHeader* @"kfun:$get$FUNCTION_REFERENCE$1.invoke#internal"(%struct.ObjHeader* [[this]], %struct.ObjHeader* [[array]], i32 {{%[0-9]+}}, %struct.ObjHeader** [[ret]])
// CHECK-LABEL: define internal i32 @"kfun:$stringifyIntArray$lambda$1$FUNCTION_REFERENCE$2.invoke#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal void @"kfun:$stringifyIntArray$lambda$1$FUNCTION_REFERENCE$2.<init>#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$stringifyIntArray$lambda$1$FUNCTION_REFERENCE$2.$<bridge-BNNN>invoke(kotlin.Array<kotlin.Int>){}kotlin.Int#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* [[array:%[0-9]+]], %struct.ObjHeader** {{%[0-9]+}})
// CHECK: call i32 @"kfun:$stringifyIntArray$lambda$1$FUNCTION_REFERENCE$2.invoke#internal"(%struct.ObjHeader* [[this]], %struct.ObjHeader* [[array]])
// CHECK-LABEL: define internal i32 @"kfun:$get$FUNCTION_REFERENCE$3.invoke#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* {{%[0-9]+}}, i32 {{%[0-9]+}})
// CHECK-LABEL: define internal void @"kfun:$get$FUNCTION_REFERENCE$3.<init>#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$get$FUNCTION_REFERENCE$3.$<bridge-BNNNB>invoke(kotlin.Array<kotlin.Int>;kotlin.Int){}kotlin.Int#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* [[array:%[0-9]+]], %struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader** {{%[0-9]+}})
// CHECK: call i32 @"kfun:$get$FUNCTION_REFERENCE$3.invoke#internal"(%struct.ObjHeader* [[this]], %struct.ObjHeader* [[array]], i32 {{%[0-9]+}})
// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$4.invoke#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* [[p1:%[0-9]+]], %struct.ObjHeader* [[p2:%[0-9]+]])
// CHECK: call void @"kfun:#foo(0:0;0:1){0\C2\A7<kotlin.Any?>;1\C2\A7<kotlin.Any?>}"(%struct.ObjHeader* [[p1]], %struct.ObjHeader* [[p2]])
// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$4.<init>#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$foo$FUNCTION_REFERENCE$4.$<bridge-UNNNN>invoke(1:0;1:0){}#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* [[p1:%[0-9]+]], %struct.ObjHeader* [[p2:%[0-9]+]], %struct.ObjHeader** {{%[0-9]+}})
// CHECK: call void @"kfun:$foo$FUNCTION_REFERENCE$4.invoke#internal"(%struct.ObjHeader* [[this]], %struct.ObjHeader* [[p1]], %struct.ObjHeader* [[p2]])
// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$5.invoke#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}}, i32 {{%[0-9]+}}, i32 {{%[0-9]+}})
// CHECK: call void @"kfun:#foo(0:0;0:1){0\C2\A7<kotlin.Any?>;1\C2\A7<kotlin.Any?>}"(%struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$5.<init>#internal"
// CHECK-SAME: (%struct.ObjHeader* {{%[0-9]+}})
// CHECK-LABEL: define internal %struct.ObjHeader* @"kfun:$foo$FUNCTION_REFERENCE$5.$<bridge-UNNBB>invoke(kotlin.Int;kotlin.Int){}#internal"
// CHECK-SAME: (%struct.ObjHeader* [[this:%[0-9]+]], %struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader* {{%[0-9]+}}, %struct.ObjHeader** {{%[0-9]+}})
// CHECK: call void @"kfun:$foo$FUNCTION_REFERENCE$5.invoke#internal"(%struct.ObjHeader* [[this]], i32 {{%[0-9]+}}, i32 {{%[0-9]+}})