|
| 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 | +} |
0 commit comments