Skip to content

Commit 1c4543c

Browse files
committed
Fix codegen for kotlin extension functions
1 parent 09072ba commit 1c4543c

File tree

6 files changed

+39
-4
lines changed

6 files changed

+39
-4
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ import kotlin.reflect.KCallable
2424
import kotlin.reflect.KClass
2525
import kotlin.reflect.KFunction
2626
import kotlin.reflect.KProperty
27+
import kotlin.reflect.full.extensionReceiverParameter
2728
import kotlin.reflect.full.instanceParameter
2829
import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader
2930
import kotlin.reflect.jvm.javaConstructor
3031
import kotlin.reflect.jvm.javaField
3132
import kotlin.reflect.jvm.javaGetter
3233
import kotlin.reflect.jvm.javaMethod
34+
import kotlin.reflect.jvm.kotlinFunction
3335

3436
// ClassId utils
3537

@@ -439,6 +441,9 @@ val MethodId.method: Method
439441
?: error("Can't find method $signature in ${declaringClass.name}")
440442
}
441443

444+
val MethodId.extensionReceiverParameterIndex: Int?
445+
get() = this.method.kotlinFunction?.extensionReceiverParameter?.index
446+
442447
// TODO: maybe cache it somehow in the future
443448
val ConstructorId.constructor: Constructor<*>
444449
get() {

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,13 +475,13 @@ object Junit5 : TestFramework(id = "JUnit5", displayName = "JUnit 5") {
475475
)
476476

477477
val timeunitClassId = BuiltinClassId(
478-
name = "TimeUnit",
478+
name = "java.util.concurrent.TimeUnit",
479479
canonicalName = "java.util.concurrent.TimeUnit",
480480
simpleName = "TimeUnit"
481481
)
482482

483483
val durationClassId = BuiltinClassId(
484-
name = "Duration",
484+
name = "java.time.Duration",
485485
canonicalName = "java.time.Duration",
486486
simpleName = "Duration"
487487
)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import org.utbot.framework.plugin.api.MethodId
4949
import org.utbot.framework.plugin.api.UtExplicitlyThrownException
5050
import org.utbot.framework.plugin.api.util.isStatic
5151
import org.utbot.framework.plugin.api.util.exceptions
52+
import org.utbot.framework.plugin.api.util.extensionReceiverParameterIndex
53+
import org.utbot.framework.plugin.api.util.humanReadableName
5254
import org.utbot.framework.plugin.api.util.id
5355
import org.utbot.framework.plugin.api.util.isArray
5456
import org.utbot.framework.plugin.api.util.isSubtypeOf
@@ -110,7 +112,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
110112
override operator fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall {
111113
val resolvedArgs = args.resolve()
112114
val methodCall = if (method.canBeCalledWith(caller, resolvedArgs)) {
113-
CgMethodCall(caller, method, resolvedArgs.guardedForDirectCallOf(method))
115+
CgMethodCall(caller, method, resolvedArgs.guardedForDirectCallOf(method)).takeCallerFromArgumentsIfNeeded()
114116
} else {
115117
method.callWithReflection(caller, resolvedArgs)
116118
}
@@ -194,6 +196,29 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
194196
else -> false
195197
}
196198

199+
/**
200+
* For Kotlin extension functions, real caller is one of the arguments in JVM method (and declaration class is omitted),
201+
* thus we should move it from arguments to caller
202+
*
203+
* For example, if we have `Int.f(a: Int)` declared in `Main.kt`, the JVM method signature will be `MainKt.f(Int, Int)`
204+
* and in Kotlin we should render this not like `MainKt.f(a, b)` but like `a.f(b)`
205+
*/
206+
private fun CgMethodCall.takeCallerFromArgumentsIfNeeded(): CgMethodCall {
207+
if (codegenLanguage == CodegenLanguage.KOTLIN) {
208+
// TODO: reflection calls for util and some of mockito methods produce exceptions => currently runCatching is needed
209+
// (but their reflection may be supported, alternatively maybe get rid of reflection somehow here)
210+
runCatching {
211+
executableId.extensionReceiverParameterIndex?.let { receiverIndex ->
212+
require(caller == null) { "${executableId.humanReadableName} is an extension function but it already has a non-static caller provided" }
213+
val args = arguments.toMutableList()
214+
return CgMethodCall(args.removeAt(receiverIndex), executableId, args, typeParameters)
215+
}
216+
}
217+
}
218+
219+
return this
220+
}
221+
197222
private infix fun CgExpression.canBeArgOf(type: ClassId): Boolean {
198223
// TODO: SAT-1210 support generics so that we wouldn't need to check specific cases such as this one
199224
if (this is CgExecutableCall && (executableId == any || executableId == anyOfClass)) {

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,10 @@ internal abstract class CgAbstractRenderer(
711711
if (caller != null) {
712712
// 'this' can be omitted, otherwise render caller
713713
if (caller !is CgThisInstance) {
714+
// TODO: we need parentheses for calls like (-1).inv(), do something smarter here
715+
if (caller !is CgVariable) print("(")
714716
caller.accept(this)
717+
if (caller !is CgVariable) print(")")
715718
renderAccess(caller)
716719
}
717720
} else {

utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SignatureUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import kotlin.reflect.KFunction
44
import kotlin.reflect.KParameter
55
import kotlin.reflect.jvm.javaType
66

7+
// Note that rules for obtaining signature here should correlate with PsiMethod.signature()
78
fun KFunction<*>.signature() =
8-
Signature(this.name, this.parameters.filter { it.kind == KParameter.Kind.VALUE }.map { it.type.javaType.typeName })
9+
Signature(this.name, this.parameters.filter { it.kind != KParameter.Kind.INSTANCE }.map { it.type.javaType.typeName })
910

1011
data class Signature(val name: String, val parameterTypes: List<String?>) {
1112

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SignaturesHelper.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.utbot.framework.plugin.api.Signature
77
fun MemberInfo.signature(): Signature =
88
(this.member as PsiMethod).signature()
99

10+
// Note that rules for obtaining signature here should correlate with KFunction<*>.signature()
1011
private fun PsiMethod.signature() =
1112
Signature(this.name, this.parameterList.parameters.map {
1213
it.type.canonicalText

0 commit comments

Comments
 (0)