Skip to content

Commit 8daad71

Browse files
tochilinaksofurihafe
authored andcommitted
Fixed case with infinite recursion in TypeAlias (#1841)
* Added TypeAliasType * Added test
1 parent 1b54d75 commit 8daad71

File tree

14 files changed

+189
-10
lines changed

14 files changed

+189
-10
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from typing import Any
2+
3+
4+
class Graph:
5+
def __init__(self, num_of_nodes: int) -> None:
6+
"""
7+
Arguments:
8+
num_of_nodes - the number of nodes in the graph
9+
Attributes:
10+
m_num_of_nodes - the number of nodes in the graph.
11+
m_edges - the list of edges.
12+
m_component - the dictionary which stores the index of the component which
13+
a node belongs to.
14+
"""
15+
16+
self.m_num_of_nodes = num_of_nodes
17+
self.m_edges: list[list[int]] = []
18+
self.m_component: dict[int, int] = {}
19+
20+
def add_edge(self, u_node: int, v_node: int, weight: int) -> None:
21+
"""Adds an edge in the format [first, second, edge weight] to graph."""
22+
23+
self.m_edges.append([u_node, v_node, weight])
24+
25+
def find_component(self, u_node: int) -> int:
26+
"""Propagates a new component throughout a given component."""
27+
28+
if self.m_component[u_node] == u_node:
29+
return u_node
30+
return self.find_component(self.m_component[u_node])
31+
32+
def set_component(self, u_node: int) -> None:
33+
"""Finds the component index of a given node"""
34+
35+
if self.m_component[u_node] != u_node:
36+
for k in self.m_component:
37+
self.m_component[k] = self.find_component(k)
38+
39+
def union(self, component_size: list[int], u_node: int, v_node: int) -> None:
40+
"""Union finds the roots of components for two nodes, compares the components
41+
in terms of size, and attaches the smaller one to the larger one to form
42+
single component"""
43+
44+
if component_size[u_node] <= component_size[v_node]:
45+
self.m_component[u_node] = v_node
46+
component_size[v_node] += component_size[u_node]
47+
self.set_component(u_node)
48+
49+
elif component_size[u_node] >= component_size[v_node]:
50+
self.m_component[v_node] = self.find_component(u_node)
51+
component_size[u_node] += component_size[v_node]
52+
self.set_component(v_node)
53+
54+
def boruvka(self) -> None:
55+
"""Performs Borůvka's algorithm to find MST."""
56+
57+
# Initialize additional lists required to algorithm.
58+
component_size = []
59+
mst_weight = 0
60+
61+
minimum_weight_edge: list[Any] = [-1] * self.m_num_of_nodes
62+
63+
# A list of components (initialized to all of the nodes)
64+
for node in range(self.m_num_of_nodes):
65+
self.m_component.update({node: node})
66+
component_size.append(1)
67+
68+
num_of_components = self.m_num_of_nodes
69+
70+
while num_of_components > 1:
71+
for edge in self.m_edges:
72+
u, v, w = edge
73+
74+
u_component = self.m_component[u]
75+
v_component = self.m_component[v]
76+
77+
if u_component != v_component:
78+
"""If the current minimum weight edge of component u doesn't
79+
exist (is -1), or if it's greater than the edge we're
80+
observing right now, we will assign the value of the edge
81+
we're observing to it.
82+
83+
If the current minimum weight edge of component v doesn't
84+
exist (is -1), or if it's greater than the edge we're
85+
observing right now, we will assign the value of the edge
86+
we're observing to it"""
87+
88+
for component in (u_component, v_component):
89+
if (
90+
minimum_weight_edge[component] == -1
91+
or minimum_weight_edge[component][2] > w
92+
):
93+
minimum_weight_edge[component] = [u, v, w]
94+
95+
for edge in minimum_weight_edge:
96+
if isinstance(edge, list):
97+
u, v, w = edge
98+
99+
u_component = self.m_component[u]
100+
v_component = self.m_component[v]
101+
102+
if u_component != v_component:
103+
mst_weight += w
104+
self.union(component_size, u_component, v_component)
105+
print(f"Added edge [{u} - {v}]\nAdded weight: {w}\n")
106+
num_of_components -= 1
107+
108+
minimum_weight_edge = [-1] * self.m_num_of_nodes
109+
print(f"The total weight of the minimal spanning tree is: {mst_weight}")

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,16 @@ object PythonOverloadTypeDescription : PythonSpecialAnnotation(overloadName) {
237237
}
238238
}
239239

240+
object PythonTypeAliasDescription : PythonSpecialAnnotation(pythonTypeAliasName) {
241+
override fun castToCompatibleTypeApi(type: Type): CompositeType {
242+
return type as? CompositeType ?: error("Got unexpected type for PythonTypeAliasDescription: $type")
243+
}
244+
fun getInterior(type: Type): Type {
245+
val casted = castToCompatibleTypeApi(type)
246+
return casted.members.first()
247+
}
248+
}
249+
240250
object PythonTupleTypeDescription : PythonSpecialAnnotation(pythonTupleName) {
241251
override fun getAnnotationParameters(type: Type): List<Type> = castToCompatibleTypeApi(type).parameters
242252
// TODO: getMemberByName and/or getNamedMembers

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ val pythonNoneName = Name(emptyList(), "None")
4747
val pythonTupleName = Name(listOf("typing"), "Tuple")
4848
val pythonCallableName = Name(listOf("typing"), "Callable")
4949
val overloadName = Name(emptyList(), "Overload")
50+
val pythonTypeAliasName = Name(listOf("typing"), "TypeAlias")
5051

5152
val pythonAnyType = createTypeWithMembers(PythonAnyTypeDescription, emptyList())
5253
val pythonNoneType = createTypeWithMembers(PythonNoneTypeDescription, emptyList())
@@ -60,6 +61,14 @@ fun createOverload(members: List<Type>): Type =
6061
fun createPythonTupleType(members: List<Type>): Type =
6162
createTypeWithMembers(PythonTupleTypeDescription, members)
6263

64+
fun createPythonTypeAlias(initialization: (Type) -> Type): CompositeType =
65+
CompositeTypeCreator.create(0, PythonTypeAliasDescription) { self ->
66+
CompositeTypeCreator.InitializationData(
67+
members = listOf(initialization(self)),
68+
supertypes = emptyList()
69+
)
70+
}
71+
6372
fun createPythonConcreteCompositeType(
6473
name: Name,
6574
numberOfParameters: Int,

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,17 @@ class PythonSubtypeChecker(
172172
return false
173173
}
174174

175+
if (rightMeta is PythonTypeAliasDescription)
176+
return PythonSubtypeChecker(
177+
left = left,
178+
right = rightMeta.getInterior(right),
179+
pythonTypeStorage,
180+
typeParameterCorrespondence, assumingSubtypePairs, recursionDepth + 1
181+
).rightIsSubtypeOfLeft()
182+
175183
return when (leftMeta) {
176184
is PythonAnyTypeDescription -> true
185+
is PythonTypeAliasDescription -> caseOfLeftTypeAlias(leftMeta)
177186
is PythonTypeVarDescription -> caseOfLeftTypeVar(leftMeta)
178187
is PythonProtocolDescription -> caseOfLeftProtocol(leftMeta)
179188
is PythonCallableTypeDescription -> caseOfLeftCallable(leftMeta)
@@ -185,6 +194,17 @@ class PythonSubtypeChecker(
185194
}
186195
}
187196

197+
private fun caseOfLeftTypeAlias(leftMeta: PythonTypeAliasDescription): Boolean {
198+
return PythonSubtypeChecker(
199+
left = leftMeta.getInterior(left),
200+
right = right,
201+
pythonTypeStorage,
202+
typeParameterCorrespondence,
203+
nextAssumingSubtypePairs,
204+
recursionDepth + 1
205+
).rightIsSubtypeOfLeft()
206+
}
207+
188208
private fun caseOfLeftTupleType(leftMeta: PythonTupleTypeDescription): Boolean {
189209
return when (val rightMeta = right.pythonDescription()) {
190210
is PythonAnyTypeDescription -> true

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fun propagateConstraint(type: Type, constraint: TypeConstraint, storage: PythonT
3333
is PythonUnionTypeDescription -> emptyMap() // TODO
3434
is PythonTupleTypeDescription -> emptyMap() // TODO
3535
is PythonProtocolDescription -> emptyMap() // TODO
36+
is PythonTypeAliasDescription -> emptyMap() // TODO
3637
is PythonConcreteCompositeTypeDescription -> {
3738
propagateConstraintForCompositeType(type, description, constraint, storage)
3839
}

utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/test.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import org.utbot.python.newtyping.pythonTypeRepresentation
44

55
fun main() {
66
TypeInferenceProcessor(
7-
"python3",
8-
directoriesForSysPath = setOf("/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/samples"),
9-
"/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/samples/type_inference.py",
10-
moduleOfSourceFile = "type_inference",
11-
"type_inference"
7+
"python3.9",
8+
directoriesForSysPath = setOf("/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples"),
9+
"/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/easy_samples/boruvka.py",
10+
moduleOfSourceFile = "easy_samples.boruvka",
11+
"boruvka",
12+
className = "Graph"
1213
).inferTypes(cancel = { false }).forEach {
1314
println(it.pythonTypeRepresentation())
1415
}

utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,17 @@ class OverloadedFunction(
204204
}
205205
}
206206

207+
class TypeAliasNode(val target: MypyAnnotation): MypyAnnotationNode() {
208+
override val children: List<MypyAnnotation>
209+
get() = super.children + target
210+
override fun initializeType(): Type {
211+
return createPythonTypeAlias { self ->
212+
storage.nodeToUtBotType[this] = self
213+
target.asUtBotType
214+
}
215+
}
216+
}
217+
207218
class UnknownAnnotationNode: MypyAnnotationNode() {
208219
override fun initializeType(): Type {
209220
return pythonAnyType
@@ -221,6 +232,7 @@ enum class AnnotationType {
221232
Union,
222233
Tuple,
223234
NoneType,
235+
TypeAlias,
224236
Unknown
225237
}
226238

@@ -236,4 +248,5 @@ val annotationAdapter: PolymorphicJsonAdapterFactory<MypyAnnotationNode> =
236248
.withSubtype(PythonUnion::class.java, AnnotationType.Union.name)
237249
.withSubtype(PythonTuple::class.java, AnnotationType.Tuple.name)
238250
.withSubtype(PythonNoneType::class.java, AnnotationType.NoneType.name)
251+
.withSubtype(TypeAliasNode::class.java, AnnotationType.TypeAlias.name)
239252
.withSubtype(UnknownAnnotationNode::class.java, AnnotationType.Unknown.name)

utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyStorage.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,15 @@ class MypyAnnotationStorage(
4242
}
4343
val nodeToUtBotType: MutableMap<MypyAnnotationNode, Type> = mutableMapOf()
4444
fun getUtBotTypeOfNode(node: MypyAnnotationNode): Type {
45+
//println("entering $node")
4546
val mem = nodeToUtBotType[node]
46-
if (mem != null)
47+
if (mem != null) {
48+
//println("exiting $node")
4749
return mem
50+
}
4851
val res = node.initializeType()
4952
nodeToUtBotType[node] = res
53+
//println("exiting $node")
5054
return res
5155
}
5256
init {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
mypy==1.0.0
22
coverage
33
utbot-executor==0.2.4
4-
utbot-mypy-runner==0.2.6
4+
utbot-mypy-runner==0.2.8

utbot-python/src/test/kotlin/org/utbot/python/newtyping/mypy/MypyStorageKtTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import org.utbot.python.newtyping.general.*
1010
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
1111
internal class MypyStorageKtTest {
1212
lateinit var storage: MypyAnnotationStorage
13+
lateinit var storageBoruvka: MypyAnnotationStorage
1314
@BeforeAll
1415
fun setup() {
1516
val sample = MypyStorageKtTest::class.java.getResource("/annotation_sample.json")!!.readText()
1617
storage = readMypyAnnotationStorage(sample)
18+
val sample1 = MypyStorageKtTest::class.java.getResource("/boruvka.json")!!.readText()
19+
storageBoruvka = readMypyAnnotationStorage(sample1)
1720
}
1821

1922
@Test
@@ -117,4 +120,12 @@ internal class MypyStorageKtTest {
117120
val attrs = A.getPythonAttributes().map { it.meta.name }
118121
assertTrue(attrs.containsAll(listOf("y", "x", "self_")))
119122
}
123+
124+
@Test
125+
fun testTypeAlias() {
126+
val isinstance = storageBoruvka.types["boruvka"]!!.find { it.startOffset == 3731L }!!.type.asUtBotType
127+
val func = isinstance as FunctionType
128+
val classInfo = func.arguments[1]
129+
assertTrue(classInfo.pythonDescription() is PythonTypeAliasDescription)
130+
}
120131
}

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/boruvka.json

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

utbot-python/src/test/resources/imports_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)