Skip to content

Commit 05030ec

Browse files
committed
Support captured values of lambdas
1 parent 9f1463a commit 05030ec

File tree

6 files changed

+76
-11
lines changed

6 files changed

+76
-11
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ class UtLambdaModel(
548548
val samType: ClassId,
549549
val declaringClass: ClassId,
550550
val lambdaName: String,
551+
val capturedValues: MutableList<UtModel> = mutableListOf(),
551552
) : UtReferenceModel(id, samType)
552553

553554
/**
@@ -1062,12 +1063,12 @@ open class MethodId(
10621063
get() = Modifier.isPrivate(method.modifiers)
10631064
}
10641065

1065-
class ConstructorId(
1066+
open class ConstructorId(
10661067
override val classId: ClassId,
10671068
override val parameters: List<ClassId>
10681069
) : ExecutableId() {
1069-
override val name: String = "<init>"
1070-
override val returnType: ClassId = voidClassId
1070+
final override val name: String = "<init>"
1071+
final override val returnType: ClassId = voidClassId
10711072

10721073
override val isPublic: Boolean
10731074
get() = Modifier.isPublic(constructor.modifiers)
@@ -1091,6 +1092,15 @@ class BuiltinMethodId(
10911092
override val isPrivate: Boolean = false
10921093
) : MethodId(classId, name, returnType, parameters)
10931094

1095+
class BuiltinConstructorId(
1096+
classId: ClassId,
1097+
parameters: List<ClassId>,
1098+
// by default, we assume that the builtin constructor is public
1099+
override val isPublic: Boolean = true,
1100+
override val isProtected: Boolean = false,
1101+
override val isPrivate: Boolean = false
1102+
) : ConstructorId(classId, parameters)
1103+
10941104
open class TypeParameters(val parameters: List<ClassId> = emptyList())
10951105

10961106
class WildcardTypeParameter : TypeParameters(emptyList())

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
@@ -1,6 +1,7 @@
11
package org.utbot.framework.plugin.api.util
22

33
import org.utbot.framework.plugin.api.BuiltinClassId
4+
import org.utbot.framework.plugin.api.BuiltinConstructorId
45
import org.utbot.framework.plugin.api.BuiltinMethodId
56
import org.utbot.framework.plugin.api.ClassId
67
import org.utbot.framework.plugin.api.ConstructorId
@@ -499,6 +500,10 @@ fun builtinMethodId(classId: BuiltinClassId, name: String, returnType: ClassId,
499500
return BuiltinMethodId(classId, name, returnType, arguments.toList())
500501
}
501502

503+
fun builtinConstructorId(classId: BuiltinClassId, vararg arguments: ClassId): BuiltinConstructorId {
504+
return BuiltinConstructorId(classId, arguments.toList())
505+
}
506+
502507
fun builtinStaticMethodId(classId: ClassId, name: String, returnType: ClassId, vararg arguments: ClassId): BuiltinMethodId {
503508
return BuiltinMethodId(classId, name, returnType, arguments.toList(), isStatic = true)
504509
}

utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,9 @@ class Resolver(
515515
// In case sootClass represents a lambda, we will not be able to load its class here, so some workaround is required.
516516

517517
if (sootClass.isLambda) {
518-
return constructLambda(concreteAddr, sootClass)
518+
return constructLambda(concreteAddr, sootClass).also { lambda ->
519+
lambda.capturedValues += collectFieldModels(addr, actualType).values
520+
}
519521
}
520522

521523
val clazz = classLoader.loadClass(sootClass.name)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import org.utbot.framework.codegen.model.constructor.util.arrayTypeOf
55
import org.utbot.framework.codegen.model.constructor.util.utilMethodId
66
import org.utbot.framework.codegen.model.tree.CgClassId
77
import org.utbot.framework.plugin.api.BuiltinClassId
8+
import org.utbot.framework.plugin.api.BuiltinConstructorId
89
import org.utbot.framework.plugin.api.ClassId
910
import org.utbot.framework.plugin.api.MethodId
1011
import org.utbot.framework.plugin.api.util.booleanClassId
12+
import org.utbot.framework.plugin.api.util.builtinConstructorId
1113
import org.utbot.framework.plugin.api.util.classClassId
1214
import org.utbot.framework.plugin.api.util.id
1315
import org.utbot.framework.plugin.api.util.intClassId
@@ -229,3 +231,6 @@ val ClassId.capturedArgumentClassId: BuiltinClassId
229231
canonicalName = "${this.name}.CapturedArgument",
230232
simpleName = "CapturedArgument"
231233
)
234+
235+
val ClassId.capturedArgumentConstructorId: BuiltinConstructorId
236+
get() = builtinConstructorId(capturedArgumentClassId, classClassId, objectClassId)

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import org.utbot.framework.codegen.model.util.at
5353
import org.utbot.framework.codegen.model.util.isAccessibleFrom
5454
import org.utbot.framework.codegen.model.util.nullLiteral
5555
import org.utbot.framework.codegen.model.util.resolve
56+
import org.utbot.framework.plugin.api.BuiltinConstructorId
5657
import org.utbot.framework.plugin.api.BuiltinMethodId
5758
import org.utbot.framework.plugin.api.ClassId
5859
import org.utbot.framework.plugin.api.ConstructorId
@@ -153,11 +154,30 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
153154

154155
private fun newConstructorCall(constructorId: ConstructorId) {
155156
importIfNeeded(constructorId.classId)
157+
158+
// Builtin constructors do not have jClass, so [constructorId.exceptions] will crash on it,
159+
// so we need to collect required exceptions manually from source codes (see BuiltinConstructorId.findExceptionTypes()).
160+
161+
if (constructorId is BuiltinConstructorId) {
162+
constructorId.findExceptionTypes().forEach { addExceptionIfNeeded(it) }
163+
return
164+
}
165+
156166
for (exception in constructorId.exceptions) {
157167
addExceptionIfNeeded(exception)
158168
}
159169
}
160170

171+
private fun BuiltinConstructorId.findExceptionTypes(): Set<ClassId> {
172+
// At the moment we do not have builtin ids for constructors that throw exceptions,
173+
// so we have this trivial when-expression. But if we ever add ids for such constructors,
174+
// then we **must** specify their exceptions here, so that we take them into account when generating code.
175+
@Suppress("UNUSED_EXPRESSION")
176+
return when (this) {
177+
else -> emptySet()
178+
}
179+
}
180+
161181
//WARN: if you make changes in the following sets of exceptions,
162182
//don't forget to change them in hardcoded [UtilMethods] as well
163183
private fun BuiltinMethodId.findExceptionTypes(): Set<ClassId> {

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.codegen.model.constructor.tree
22

3+
import org.utbot.framework.codegen.model.constructor.builtin.capturedArgumentConstructorId
34
import org.utbot.framework.codegen.model.constructor.builtin.forName
45
import org.utbot.framework.codegen.model.constructor.builtin.setArrayElement
56
import org.utbot.framework.codegen.model.constructor.context.CgContext
@@ -19,6 +20,7 @@ import org.utbot.framework.codegen.model.tree.CgExpression
1920
import org.utbot.framework.codegen.model.tree.CgFieldAccess
2021
import org.utbot.framework.codegen.model.tree.CgGetJavaClass
2122
import org.utbot.framework.codegen.model.tree.CgLiteral
23+
import org.utbot.framework.codegen.model.tree.CgMethodCall
2224
import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess
2325
import org.utbot.framework.codegen.model.tree.CgValue
2426
import org.utbot.framework.codegen.model.tree.CgVariable
@@ -127,20 +129,41 @@ internal class CgVariableConstructor(val context: CgContext) :
127129
// synthetic lambda methods should not have overloads, so we always expect there to be only one method with the given name
128130
?: error("More than one method with name ${model.lambdaName} found in class: ${lambdaDeclaringClass.canonicalName}")
129131

130-
// TODO: Support captured variables. At the moment only lambdas without them are supported.
132+
val capturedValues = model.capturedValues
131133
return newVar(model.samType, baseName) {
132134
if (lambdaMethodId.isStatic) {
133-
testClassThisInstance[buildStaticLambda](
134-
getClassOf(model.samType),
135-
getClassOf(model.declaringClass),
136-
model.lambdaName
137-
)
135+
constructStaticLambda(model, capturedValues)
138136
} else {
139-
TODO("non-static lambdas")
137+
constructLambda(model, capturedValues)
140138
}
141139
}
142140
}
143141

142+
private fun constructStaticLambda(model: UtLambdaModel, capturedValues: List<UtModel>): CgMethodCall {
143+
val capturedArguments = capturedValues.map { outerMostTestClass.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) }
144+
return testClassThisInstance[buildStaticLambda](
145+
getClassOf(model.samType),
146+
getClassOf(model.declaringClass),
147+
model.lambdaName,
148+
*capturedArguments.toTypedArray()
149+
)
150+
}
151+
152+
private fun constructLambda(model: UtLambdaModel, capturedValues: List<UtModel>): CgMethodCall {
153+
require(capturedValues.isNotEmpty()) { "Non-static lambda must capture `this` instance, so there must be at least one captured value" }
154+
val capturedThisInstance = getOrCreateVariable(capturedValues.first())
155+
val capturedArguments = capturedValues
156+
.subList(1, capturedValues.size)
157+
.map { outerMostTestClass.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) }
158+
return testClassThisInstance[buildLambda](
159+
getClassOf(model.samType),
160+
getClassOf(model.declaringClass),
161+
model.lambdaName,
162+
capturedThisInstance,
163+
*capturedArguments.toTypedArray()
164+
)
165+
}
166+
144167
private fun constructComposite(model: UtCompositeModel, baseName: String): CgVariable {
145168
val obj = if (model.isMock) {
146169
mockFrameworkManager.createMockFor(model, baseName)

0 commit comments

Comments
 (0)