Skip to content

Commit 40e6c88

Browse files
committed
One more case in subtype checker
1 parent 8ee695d commit 40e6c88

File tree

5 files changed

+66
-17
lines changed

5 files changed

+66
-17
lines changed

utbot-python/samples/easy_samples/subtypes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,13 @@ def func_for_R(x: R) -> None:
4343

4444

4545
a: List[int] = []
46+
47+
48+
T = TypeVar('T')
49+
50+
51+
def func_abs(x: SupportsAbs[T]):
52+
return abs(x)
53+
54+
55+
b: int = 10

utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonTypeComparison.kt

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class PythonSubtypeChecker(
129129
if (!left.isPythonType() || !right.isPythonType())
130130
error("Trying to create PythonSubtypeChecker for non-Python types $left, $right")
131131
}
132+
132133
fun rightIsSubtypeOfLeft(): Boolean {
133134
val leftWrapper = PythonTypeWrapperForEqualityCheck(left)
134135
val rightWrapper = PythonTypeWrapperForEqualityCheck(right)
@@ -171,6 +172,7 @@ class PythonSubtypeChecker(
171172
is PythonTupleTypeDescription -> caseOfLeftTupleType(leftMeta)
172173
}
173174
}
175+
174176
private fun caseOfLeftTupleType(leftMeta: PythonTupleTypeDescription): Boolean {
175177
return when (val rightMeta = right.pythonDescription()) {
176178
is PythonAnyTypeDescription -> true
@@ -195,6 +197,7 @@ class PythonSubtypeChecker(
195197
else -> false
196198
}
197199
}
200+
198201
private fun caseOfLeftOverload(leftMeta: PythonOverloadTypeDescription): Boolean {
199202
val leftAsStatefulType = leftMeta.castToCompatibleTypeApi(left)
200203
return leftAsStatefulType.parameters.all {
@@ -208,6 +211,7 @@ class PythonSubtypeChecker(
208211
).rightIsSubtypeOfLeft()
209212
}
210213
}
214+
211215
private fun caseOfLeftCompositeType(leftMeta: PythonConcreteCompositeTypeDescription): Boolean {
212216
if (left.isPythonObjectType())
213217
return true
@@ -221,7 +225,8 @@ class PythonSubtypeChecker(
221225
val (args, param) = it
222226
val (leftArg, rightArg) = args
223227
if (leftArg.pythonDescription() is PythonAnyTypeDescription ||
224-
rightArg.pythonDescription() is PythonAnyTypeDescription)
228+
rightArg.pythonDescription() is PythonAnyTypeDescription
229+
)
225230
return@all true
226231
val typeVarDescription = param.pythonDescription()
227232
if (typeVarDescription !is PythonTypeVarDescription) // shouldn't be possible
@@ -264,17 +269,21 @@ class PythonSubtypeChecker(
264269
else -> false
265270
}
266271
}
272+
267273
private fun caseOfLeftTypeVar(leftMeta: PythonTypeVarDescription): Boolean {
268274
// TODO: more accurate case analysis
275+
if (!typeParameterCorrespondence.any { it.first == left })
276+
return true // treat unbounded TypeVars like Any. TODO: here might occur false-positives
269277
return when (val rightMeta = right.pythonDescription()) {
270278
is PythonAnyTypeDescription -> true
271279
is PythonTypeVarDescription -> caseOfLeftAndRightTypeVar(leftMeta, rightMeta)
272280
else -> false
273281
}
274282
}
283+
275284
private fun caseOfLeftUnion(leftMeta: PythonUnionTypeDescription): Boolean {
276285
val children = leftMeta.getAnnotationParameters(left)
277-
return children.any { childType ->
286+
return children.any { childType ->
278287
PythonSubtypeChecker(
279288
left = childType,
280289
right = right,
@@ -285,12 +294,17 @@ class PythonSubtypeChecker(
285294
).rightIsSubtypeOfLeft()
286295
}
287296
}
288-
private fun caseOfLeftAndRightTypeVar(leftMeta: PythonTypeVarDescription, rightMeta: PythonTypeVarDescription): Boolean {
297+
298+
private fun caseOfLeftAndRightTypeVar(
299+
leftMeta: PythonTypeVarDescription,
300+
rightMeta: PythonTypeVarDescription
301+
): Boolean {
289302
val leftParam = leftMeta.castToCompatibleTypeApi(left)
290303
val rightParam = rightMeta.castToCompatibleTypeApi(right)
291304
// TODO: more accurate case analysis
292305
return typeParameterCorrespondence.contains(Pair(leftParam, rightParam))
293306
}
307+
294308
private fun caseOfLeftProtocol(leftMeta: PythonProtocolDescription): Boolean {
295309
val membersNotToCheck = listOf("__new__", "__init__")
296310
return leftMeta.protocolMemberNames.subtract(membersNotToCheck).all { protocolMemberName ->
@@ -310,6 +324,7 @@ class PythonSubtypeChecker(
310324
).rightIsSubtypeOfLeft()
311325
}
312326
}
327+
313328
private fun caseOfLeftCallable(leftMeta: PythonCallableTypeDescription): Boolean {
314329
val rightCallAttribute = right.getPythonAttributeByName(pythonTypeStorage, "__call__")?.type as? FunctionType
315330
?: return false
@@ -321,12 +336,21 @@ class PythonSubtypeChecker(
321336
val rightBounded = rightCallAttribute.getBoundedParameters()
322337

323338
// TODO: more accurate case analysis
324-
if (leftBounded.size != rightBounded.size)
339+
if (rightBounded.isNotEmpty() && leftBounded.size != rightBounded.size)
325340
return false
326341

327-
val newCorrespondence = typeParameterCorrespondence + (leftBounded zip rightBounded)
342+
var newLeftAsFunctionType = leftAsFunctionType
343+
344+
// TODO: here might occur false-positives
345+
if (rightBounded.isEmpty() && leftBounded.isNotEmpty()) {
346+
val newLeft = DefaultSubstitutionProvider.substitute(left, leftBounded.associateWith { pythonAnyType })
347+
newLeftAsFunctionType = leftMeta.castToCompatibleTypeApi(newLeft)
348+
}
349+
350+
val newCorrespondence = typeParameterCorrespondence +
351+
if (rightBounded.isNotEmpty()) (leftBounded zip rightBounded) else emptyList()
328352

329-
var args = leftAsFunctionType.arguments zip rightCallAttribute.arguments
353+
var args = newLeftAsFunctionType.arguments zip rightCallAttribute.arguments
330354
if (skipFirstArgument)
331355
args = args.drop(1)
332356

@@ -340,7 +364,7 @@ class PythonSubtypeChecker(
340364
recursionDepth + 1
341365
).rightIsSubtypeOfLeft()
342366
} && PythonSubtypeChecker(
343-
left = leftAsFunctionType.returnValue,
367+
left = newLeftAsFunctionType.returnValue,
344368
right = rightCallAttribute.returnValue,
345369
pythonTypeStorage,
346370
newCorrespondence,
@@ -353,14 +377,20 @@ class PythonSubtypeChecker(
353377
correspondence.map { Pair(it.second, it.first) }
354378

355379
private val nextAssumingSubtypePairs: List<Pair<PythonTypeWrapperForEqualityCheck, PythonTypeWrapperForEqualityCheck>>
356-
by lazy {
357-
if (left.pythonDescription() is PythonCompositeTypeDescription
358-
&& right.pythonDescription() is PythonCompositeTypeDescription)
359-
assumingSubtypePairs +
360-
listOf(Pair(PythonTypeWrapperForEqualityCheck(left), PythonTypeWrapperForEqualityCheck(right)))
361-
else
362-
assumingSubtypePairs
363-
}
380+
by lazy {
381+
if (left.pythonDescription() is PythonCompositeTypeDescription
382+
&& right.pythonDescription() is PythonCompositeTypeDescription
383+
)
384+
assumingSubtypePairs +
385+
listOf(
386+
Pair(
387+
PythonTypeWrapperForEqualityCheck(left),
388+
PythonTypeWrapperForEqualityCheck(right)
389+
)
390+
)
391+
else
392+
assumingSubtypePairs
393+
}
364394

365395
companion object {
366396
fun checkIfRightIsSubtypeOfLeft(left: Type, right: Type, pythonTypeStorage: PythonTypeStorage): Boolean =

utbot-python/src/test/kotlin/org/utbot/python/newtyping/PythonSubtypeCheckerTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test
66
import org.junit.jupiter.api.TestInstance
77
import org.utbot.python.newtyping.PythonSubtypeChecker.Companion.checkIfRightIsSubtypeOfLeft
88
import org.utbot.python.newtyping.general.DefaultSubstitutionProvider
9+
import org.utbot.python.newtyping.general.FunctionType
910
import org.utbot.python.newtyping.general.TypeParameter
1011

1112
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -71,4 +72,12 @@ internal class PythonSubtypeCheckerTest {
7172
assertTrue(checkIfRightIsSubtypeOfLeft(frozensetOfObj, frozensetOfInt, pythonTypeStorage))
7273
assertFalse(checkIfRightIsSubtypeOfLeft(frozensetOfInt, frozensetOfObj, pythonTypeStorage))
7374
}
75+
76+
@Test
77+
fun testSimpleFunctionWithVariables() {
78+
val b = storage.definitions["subtypes"]!!["b"]!!.annotation.asUtBotType
79+
val func = storage.definitions["subtypes"]!!["func_abs"]!!.annotation.asUtBotType as FunctionType
80+
81+
assertTrue(checkIfRightIsSubtypeOfLeft(func.arguments[0], b, pythonTypeStorage))
82+
}
7483
}

utbot-python/src/test/resources/annotation_sample.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

utbot-python/src/test/resources/subtypes_sample.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)