Skip to content

Commit b901732

Browse files
authored
Add executable generic parameter type resolving (#2584)
* Add executable generic parameter type resolver * Add tests for `resolveParameterTypes` and improve its implementation
1 parent bc1edcd commit b901732

File tree

5 files changed

+341
-49
lines changed

5 files changed

+341
-49
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.utbot.fuzzer
2+
3+
import com.google.common.reflect.TypeResolver
4+
import com.google.common.reflect.TypeToken
5+
import mu.KotlinLogging
6+
import org.utbot.framework.plugin.api.ConstructorId
7+
import org.utbot.framework.plugin.api.ExecutableId
8+
import org.utbot.framework.plugin.api.MethodId
9+
import org.utbot.framework.plugin.api.util.constructor
10+
import org.utbot.framework.plugin.api.util.executable
11+
import org.utbot.framework.plugin.api.util.isArray
12+
import org.utbot.framework.plugin.api.util.jClass
13+
import org.utbot.framework.plugin.api.util.method
14+
import java.lang.reflect.GenericArrayType
15+
import java.lang.reflect.ParameterizedType
16+
import java.lang.reflect.Type
17+
import java.util.Optional
18+
19+
private val logger = KotlinLogging.logger {}
20+
private val loggedUnresolvedExecutables = mutableSetOf<ExecutableId>()
21+
22+
val Type.typeToken: TypeToken<*> get() = TypeToken.of(this)
23+
inline fun <reified T> typeTokenOf(): TypeToken<T> = object : TypeToken<T>() {}
24+
inline fun <reified T> jTypeOf(): Type = typeTokenOf<T>().type
25+
26+
val FuzzedType.jType: Type get() = toType(cache = mutableMapOf())
27+
28+
private fun FuzzedType.toType(cache: MutableMap<FuzzedType, Type>): Type = cache.getOrPut(this) {
29+
when {
30+
generics.isEmpty() -> classId.jClass
31+
classId.isArray && generics.size == 1 -> GenericArrayType { generics.single().toType(cache) }
32+
else -> object : ParameterizedType {
33+
override fun getActualTypeArguments(): Array<Type> =
34+
generics.map { it.toType(cache) }.toTypedArray()
35+
36+
override fun getRawType(): Type =
37+
classId.jClass
38+
39+
override fun getOwnerType(): Type? = null
40+
}
41+
}
42+
}
43+
44+
/**
45+
* Returns fully parameterized type, e.g. for `Map` class
46+
* `Map<K, V>` type is returned, where `K` and `V` are type variables.
47+
*/
48+
fun Class<*>.toTypeParametrizedByTypeVariables(): Type =
49+
if (typeParameters.isEmpty()) this
50+
else object : ParameterizedType {
51+
override fun getActualTypeArguments(): Array<Type> =
52+
typeParameters.toList().toTypedArray()
53+
54+
override fun getRawType(): Type =
55+
this@toTypeParametrizedByTypeVariables
56+
57+
override fun getOwnerType(): Type? =
58+
declaringClass?.toTypeParametrizedByTypeVariables()
59+
}
60+
61+
/**
62+
* Returns types of arguments that should be passed to [executableId] for it to return [neededType] and `null` iff
63+
* [executableId] can't return [neededType] (e.g. if it returns `List<String>` while `List<Integer>` is needed).
64+
*
65+
* For example, if [executableId] is [Optional.of] and [neededType] is `Optional<String>`,
66+
* then one element list containing `String` type is returned.
67+
*/
68+
fun resolveParameterTypes(
69+
executableId: ExecutableId,
70+
neededType: Type
71+
): List<Type>? {
72+
return try {
73+
val actualType = when (executableId) {
74+
is MethodId -> executableId.method.genericReturnType
75+
is ConstructorId -> executableId.constructor.declaringClass.toTypeParametrizedByTypeVariables()
76+
}
77+
78+
val neededClass = neededType.typeToken.rawType
79+
val actualClass = actualType.typeToken.rawType
80+
81+
if (!neededClass.isAssignableFrom(actualClass))
82+
return null
83+
84+
@Suppress("UNCHECKED_CAST")
85+
val actualSuperType = actualType.typeToken.getSupertype(neededClass as Class<in Any>).type
86+
val typeResolver = try {
87+
TypeResolver().where(actualSuperType, neededType)
88+
} catch (e: Exception) {
89+
// TypeResolver.where() throws an exception when unification of actual & needed types fails
90+
// e.g. when unifying Optional<Integer> and Optional<String>
91+
return null
92+
}
93+
94+
// in some cases when bounded wildcards are involved TypeResolver.where() doesn't throw even though types are
95+
// incompatible (e.g. when needed type is `List<? super Integer>` while actual super type is `List<String>`)
96+
if (!typeResolver.resolveType(actualSuperType).typeToken.isSubtypeOf(neededType))
97+
return null
98+
99+
executableId.executable.genericParameterTypes.map {
100+
typeResolver.resolveType(it)
101+
}
102+
} catch (e: Exception) {
103+
if (loggedUnresolvedExecutables.add(executableId))
104+
logger.error(e) { "Failed to resolve types for $executableId, using unresolved generic type" }
105+
106+
executableId.executable.genericParameterTypes.toList()
107+
}
108+
}

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import org.utbot.fuzzing.Routine
1818
import org.utbot.fuzzing.Scope
1919
import org.utbot.fuzzing.ScopeProperty
2020
import org.utbot.fuzzing.Seed
21-
import org.utbot.fuzzing.spring.utils.jType
22-
import org.utbot.fuzzing.spring.utils.toTypeParametrizedByTypeVariables
23-
import org.utbot.fuzzing.spring.utils.typeToken
21+
import org.utbot.fuzzer.jType
22+
import org.utbot.fuzzer.toTypeParametrizedByTypeVariables
23+
import org.utbot.fuzzer.typeToken
2424
import org.utbot.fuzzing.toFuzzerType
2525

2626
val methodsToMockProperty = ScopeProperty<Set<MethodId>>(

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/utils/TypeUtils.kt

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.utbot.fuzzing.samples;
2+
3+
import java.util.*;
4+
5+
public class StringList extends ArrayList<String> {
6+
public static StringList create() {
7+
return new StringList();
8+
}
9+
10+
public static List<String> createAndUpcast() {
11+
return new StringList();
12+
}
13+
14+
public static List<StringList> createListOfLists() {
15+
return new ArrayList<>();
16+
}
17+
18+
public static List<List<String>> createListOfUpcastedLists() {
19+
return new ArrayList<>();
20+
}
21+
22+
public static List<? extends List<? extends String>> createReadOnlyListOfReadOnlyLists() {
23+
return new ArrayList<>();
24+
}
25+
26+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
27+
public static <T> List<? extends List<T>> createListOfParametrizedLists(Optional<? extends T> elm) {
28+
return Collections.singletonList(Collections.singletonList(elm.orElse(null)));
29+
}
30+
}

0 commit comments

Comments
 (0)