Skip to content

Fixed case with infinite recursion in TypeAlias #1841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions utbot-python/samples/easy_samples/boruvka.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from typing import Any


class Graph:
def __init__(self, num_of_nodes: int) -> None:
"""
Arguments:
num_of_nodes - the number of nodes in the graph
Attributes:
m_num_of_nodes - the number of nodes in the graph.
m_edges - the list of edges.
m_component - the dictionary which stores the index of the component which
a node belongs to.
"""

self.m_num_of_nodes = num_of_nodes
self.m_edges: list[list[int]] = []
self.m_component: dict[int, int] = {}

def add_edge(self, u_node: int, v_node: int, weight: int) -> None:
"""Adds an edge in the format [first, second, edge weight] to graph."""

self.m_edges.append([u_node, v_node, weight])

def find_component(self, u_node: int) -> int:
"""Propagates a new component throughout a given component."""

if self.m_component[u_node] == u_node:
return u_node
return self.find_component(self.m_component[u_node])

def set_component(self, u_node: int) -> None:
"""Finds the component index of a given node"""

if self.m_component[u_node] != u_node:
for k in self.m_component:
self.m_component[k] = self.find_component(k)

def union(self, component_size: list[int], u_node: int, v_node: int) -> None:
"""Union finds the roots of components for two nodes, compares the components
in terms of size, and attaches the smaller one to the larger one to form
single component"""

if component_size[u_node] <= component_size[v_node]:
self.m_component[u_node] = v_node
component_size[v_node] += component_size[u_node]
self.set_component(u_node)

elif component_size[u_node] >= component_size[v_node]:
self.m_component[v_node] = self.find_component(u_node)
component_size[u_node] += component_size[v_node]
self.set_component(v_node)

def boruvka(self) -> None:
"""Performs Borůvka's algorithm to find MST."""

# Initialize additional lists required to algorithm.
component_size = []
mst_weight = 0

minimum_weight_edge: list[Any] = [-1] * self.m_num_of_nodes

# A list of components (initialized to all of the nodes)
for node in range(self.m_num_of_nodes):
self.m_component.update({node: node})
component_size.append(1)

num_of_components = self.m_num_of_nodes

while num_of_components > 1:
for edge in self.m_edges:
u, v, w = edge

u_component = self.m_component[u]
v_component = self.m_component[v]

if u_component != v_component:
"""If the current minimum weight edge of component u doesn't
exist (is -1), or if it's greater than the edge we're
observing right now, we will assign the value of the edge
we're observing to it.

If the current minimum weight edge of component v doesn't
exist (is -1), or if it's greater than the edge we're
observing right now, we will assign the value of the edge
we're observing to it"""

for component in (u_component, v_component):
if (
minimum_weight_edge[component] == -1
or minimum_weight_edge[component][2] > w
):
minimum_weight_edge[component] = [u, v, w]

for edge in minimum_weight_edge:
if isinstance(edge, list):
u, v, w = edge

u_component = self.m_component[u]
v_component = self.m_component[v]

if u_component != v_component:
mst_weight += w
self.union(component_size, u_component, v_component)
print(f"Added edge [{u} - {v}]\nAdded weight: {w}\n")
num_of_components -= 1

minimum_weight_edge = [-1] * self.m_num_of_nodes
print(f"The total weight of the minimal spanning tree is: {mst_weight}")
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ object PythonOverloadTypeDescription : PythonSpecialAnnotation(overloadName) {
}
}

object PythonTypeAliasDescription : PythonSpecialAnnotation(pythonTypeAliasName) {
override fun castToCompatibleTypeApi(type: Type): CompositeType {
return type as? CompositeType ?: error("Got unexpected type for PythonTypeAliasDescription: $type")
}
fun getInterior(type: Type): Type {
val casted = castToCompatibleTypeApi(type)
return casted.members.first()
}
}

object PythonTupleTypeDescription : PythonSpecialAnnotation(pythonTupleName) {
override fun getAnnotationParameters(type: Type): List<Type> = castToCompatibleTypeApi(type).parameters
// TODO: getMemberByName and/or getNamedMembers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ val pythonNoneName = Name(emptyList(), "None")
val pythonTupleName = Name(listOf("typing"), "Tuple")
val pythonCallableName = Name(listOf("typing"), "Callable")
val overloadName = Name(emptyList(), "Overload")
val pythonTypeAliasName = Name(listOf("typing"), "TypeAlias")

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

fun createPythonTypeAlias(initialization: (Type) -> Type): CompositeType =
CompositeTypeCreator.create(0, PythonTypeAliasDescription) { self ->
CompositeTypeCreator.InitializationData(
members = listOf(initialization(self)),
supertypes = emptyList()
)
}

fun createPythonConcreteCompositeType(
name: Name,
numberOfParameters: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,17 @@ class PythonSubtypeChecker(
return false
}

if (rightMeta is PythonTypeAliasDescription)
return PythonSubtypeChecker(
left = left,
right = rightMeta.getInterior(right),
pythonTypeStorage,
typeParameterCorrespondence, assumingSubtypePairs, recursionDepth + 1
).rightIsSubtypeOfLeft()

return when (leftMeta) {
is PythonAnyTypeDescription -> true
is PythonTypeAliasDescription -> caseOfLeftTypeAlias(leftMeta)
is PythonTypeVarDescription -> caseOfLeftTypeVar(leftMeta)
is PythonProtocolDescription -> caseOfLeftProtocol(leftMeta)
is PythonCallableTypeDescription -> caseOfLeftCallable(leftMeta)
Expand All @@ -185,6 +194,17 @@ class PythonSubtypeChecker(
}
}

private fun caseOfLeftTypeAlias(leftMeta: PythonTypeAliasDescription): Boolean {
return PythonSubtypeChecker(
left = leftMeta.getInterior(left),
right = right,
pythonTypeStorage,
typeParameterCorrespondence,
nextAssumingSubtypePairs,
recursionDepth + 1
).rightIsSubtypeOfLeft()
}

private fun caseOfLeftTupleType(leftMeta: PythonTupleTypeDescription): Boolean {
return when (val rightMeta = right.pythonDescription()) {
is PythonAnyTypeDescription -> true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fun propagateConstraint(type: Type, constraint: TypeConstraint, storage: PythonT
is PythonUnionTypeDescription -> emptyMap() // TODO
is PythonTupleTypeDescription -> emptyMap() // TODO
is PythonProtocolDescription -> emptyMap() // TODO
is PythonTypeAliasDescription -> emptyMap() // TODO
is PythonConcreteCompositeTypeDescription -> {
propagateConstraintForCompositeType(type, description, constraint, storage)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import org.utbot.python.newtyping.pythonTypeRepresentation

fun main() {
TypeInferenceProcessor(
"python3",
directoriesForSysPath = setOf("/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/samples"),
"/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/samples/type_inference.py",
moduleOfSourceFile = "type_inference",
"type_inference"
"python3.9",
directoriesForSysPath = setOf("/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples"),
"/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/easy_samples/boruvka.py",
moduleOfSourceFile = "easy_samples.boruvka",
"boruvka",
className = "Graph"
).inferTypes(cancel = { false }).forEach {
println(it.pythonTypeRepresentation())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,17 @@ class OverloadedFunction(
}
}

class TypeAliasNode(val target: MypyAnnotation): MypyAnnotationNode() {
override val children: List<MypyAnnotation>
get() = super.children + target
override fun initializeType(): Type {
return createPythonTypeAlias { self ->
storage.nodeToUtBotType[this] = self
target.asUtBotType
}
}
}

class UnknownAnnotationNode: MypyAnnotationNode() {
override fun initializeType(): Type {
return pythonAnyType
Expand All @@ -221,6 +232,7 @@ enum class AnnotationType {
Union,
Tuple,
NoneType,
TypeAlias,
Unknown
}

Expand All @@ -236,4 +248,5 @@ val annotationAdapter: PolymorphicJsonAdapterFactory<MypyAnnotationNode> =
.withSubtype(PythonUnion::class.java, AnnotationType.Union.name)
.withSubtype(PythonTuple::class.java, AnnotationType.Tuple.name)
.withSubtype(PythonNoneType::class.java, AnnotationType.NoneType.name)
.withSubtype(TypeAliasNode::class.java, AnnotationType.TypeAlias.name)
.withSubtype(UnknownAnnotationNode::class.java, AnnotationType.Unknown.name)
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ class MypyAnnotationStorage(
}
val nodeToUtBotType: MutableMap<MypyAnnotationNode, Type> = mutableMapOf()
fun getUtBotTypeOfNode(node: MypyAnnotationNode): Type {
//println("entering $node")
val mem = nodeToUtBotType[node]
if (mem != null)
if (mem != null) {
//println("exiting $node")
return mem
}
val res = node.initializeType()
nodeToUtBotType[node] = res
//println("exiting $node")
return res
}
init {
Expand Down
2 changes: 1 addition & 1 deletion utbot-python/src/main/resources/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mypy==1.0.0
coverage
utbot-executor==0.2.4
utbot-mypy-runner==0.2.6
utbot-mypy-runner==0.2.8
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import org.utbot.python.newtyping.general.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class MypyStorageKtTest {
lateinit var storage: MypyAnnotationStorage
lateinit var storageBoruvka: MypyAnnotationStorage
@BeforeAll
fun setup() {
val sample = MypyStorageKtTest::class.java.getResource("/annotation_sample.json")!!.readText()
storage = readMypyAnnotationStorage(sample)
val sample1 = MypyStorageKtTest::class.java.getResource("/boruvka.json")!!.readText()
storageBoruvka = readMypyAnnotationStorage(sample1)
}

@Test
Expand Down Expand Up @@ -117,4 +120,12 @@ internal class MypyStorageKtTest {
val attrs = A.getPythonAttributes().map { it.meta.name }
assertTrue(attrs.containsAll(listOf("y", "x", "self_")))
}

@Test
fun testTypeAlias() {
val isinstance = storageBoruvka.types["boruvka"]!!.find { it.startOffset == 3731L }!!.type.asUtBotType
val func = isinstance as FunctionType
val classInfo = func.arguments[1]
assertTrue(classInfo.pythonDescription() is PythonTypeAliasDescription)
}
}
2 changes: 1 addition & 1 deletion utbot-python/src/test/resources/annotation_sample.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions utbot-python/src/test/resources/boruvka.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion utbot-python/src/test/resources/imports_sample.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion utbot-python/src/test/resources/subtypes_sample.json

Large diffs are not rendered by default.