pgraph, lang: ast: Fix failing tests due to non-deterministic topo sort
This causes inconsistent type unification when running our tests. It's a bad user experience too.
This commit is contained in:
@@ -3829,8 +3829,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
orderingGraphSingleton = false
|
orderingGraphSingleton = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//nodeOrder, err := orderingGraphFiltered.TopologicalSort()
|
// If we don't do this deterministically the type unification errors can
|
||||||
nodeOrder, err := orderingGraph.TopologicalSort()
|
// flip from `type error: Int != Str` to `type error: Str != Int` etc...
|
||||||
|
nodeOrder, err := orderingGraph.DeterministicTopologicalSort() // sorted!
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: print the cycle in a prettier way (with names?)
|
// TODO: print the cycle in a prettier way (with names?)
|
||||||
if obj.data.Debug {
|
if obj.data.Debug {
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ $out2 = $add($val) # hellohello
|
|||||||
|
|
||||||
test [fmt.printf("%s + %s is %s", $val, $val, $out2),] {} # simple concat
|
test [fmt.printf("%s + %s is %s", $val, $val, $out2),] {} # simple concat
|
||||||
-- OUTPUT --
|
-- OUTPUT --
|
||||||
# err: errUnify: unify error with: topLevel(singleton(func(x) { call:_operator(str("+"), var(x), var(x)) })): type error: Str != Int
|
# err: errUnify: unify error with: topLevel(singleton(func(x) { call:_operator(str("+"), var(x), var(x)) })): type error: Int != Str
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ test "test2" {
|
|||||||
anotherstr => $id("hello"),
|
anotherstr => $id("hello"),
|
||||||
}
|
}
|
||||||
-- OUTPUT --
|
-- OUTPUT --
|
||||||
# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: Str != Int
|
# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: Int != Str
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ class use_polymorphically($id) {
|
|||||||
}
|
}
|
||||||
include use_polymorphically(func($x) {$x})
|
include use_polymorphically(func($x) {$x})
|
||||||
-- OUTPUT --
|
-- OUTPUT --
|
||||||
# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: Str != Int
|
# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: Int != Str
|
||||||
|
|||||||
@@ -669,6 +669,65 @@ func (g *Graph) TopologicalSort() ([]Vertex, error) { // kahn's algorithm
|
|||||||
return L, nil
|
return L, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeterministicTopologicalSort returns the sort of graph vertices in a stable
|
||||||
|
// topological sort order. It's slower than the TopologicalSort implementation,
|
||||||
|
// but guarantees that two identical graphs produce the same sort each time.
|
||||||
|
// TODO: add memoization, and cache invalidation to speed this up :)
|
||||||
|
func (g *Graph) DeterministicTopologicalSort() ([]Vertex, error) { // kahn's algorithm
|
||||||
|
var L []Vertex // empty list that will contain the sorted elements
|
||||||
|
var S []Vertex // set of all nodes with no incoming edges
|
||||||
|
remaining := make(map[Vertex]int) // amount of edges remaining
|
||||||
|
|
||||||
|
var vertices []Vertex
|
||||||
|
indegree := g.InDegree()
|
||||||
|
for k := range indegree {
|
||||||
|
vertices = append(vertices, k)
|
||||||
|
}
|
||||||
|
sort.Sort(VertexSlice(vertices)) // add determinism
|
||||||
|
//for v, d := range g.InDegree()
|
||||||
|
for _, v := range vertices { // map[Vertex]int
|
||||||
|
d := indegree[v]
|
||||||
|
if d == 0 {
|
||||||
|
// accumulate set of all nodes with no incoming edges
|
||||||
|
S = append(S, v)
|
||||||
|
} else {
|
||||||
|
// initialize remaining edge count from indegree
|
||||||
|
remaining[v] = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(S) > 0 {
|
||||||
|
last := len(S) - 1 // remove a node v from S
|
||||||
|
v := S[last]
|
||||||
|
S = S[:last]
|
||||||
|
L = append(L, v) // add v to tail of L
|
||||||
|
// This doesn't need to loop in a deterministically sorted order.
|
||||||
|
for n := range g.adjacency[v] { // map[Vertex]Edge
|
||||||
|
// for each node n remaining in the graph, consume from
|
||||||
|
// remaining, so for remaining[n] > 0
|
||||||
|
if remaining[n] > 0 {
|
||||||
|
remaining[n]-- // remove edge from the graph
|
||||||
|
if remaining[n] == 0 { // if n has no other incoming edges
|
||||||
|
S = append(S, n) // insert n into S
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if graph has edges, eg if any value in rem is > 0
|
||||||
|
for c, in := range remaining {
|
||||||
|
if in > 0 {
|
||||||
|
for n := range g.adjacency[c] {
|
||||||
|
if remaining[n] > 0 {
|
||||||
|
return nil, ErrNotAcyclic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return L, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Reachability finds the shortest path in a DAG from a to b, and returns the
|
// Reachability finds the shortest path in a DAG from a to b, and returns the
|
||||||
// slice of vertices that matched this particular path including both a and b.
|
// slice of vertices that matched this particular path including both a and b.
|
||||||
// It returns nil if a or b is nil, and returns empty list if no path is found.
|
// It returns nil if a or b is nil, and returns empty list if no path is found.
|
||||||
|
|||||||
Reference in New Issue
Block a user