lang: unification, interfaces: Don't pass over generators
We were skipping over being fully consistent with all of the generator invariants when running the solver. This allowed us to miss some of the conditions that a generator might impose. Usually this caused us to be "solved" when in fact we had an invalid program.
This commit is contained in:
@@ -671,6 +671,11 @@ type GeneratorInvariant struct {
|
|||||||
// from the list. If we error, it's because we don't have any new
|
// from the list. If we error, it's because we don't have any new
|
||||||
// information to provide at this time...
|
// information to provide at this time...
|
||||||
Func func(invariants []Invariant, solved map[Expr]*types.Type) ([]Invariant, error)
|
Func func(invariants []Invariant, solved map[Expr]*types.Type) ([]Invariant, error)
|
||||||
|
|
||||||
|
// Inactive specifies that we tried to run this, but it didn't help us
|
||||||
|
// progress forwards. It can be reset if needed. It should only be set
|
||||||
|
// or read by the solver itself.
|
||||||
|
Inactive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a representation of this invariant.
|
// String returns a representation of this invariant.
|
||||||
|
|||||||
10
lang/interpret_test/TestAstFunc2/printfempty.txtar
Normal file
10
lang/interpret_test/TestAstFunc2/printfempty.txtar
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
# This should not unify, we need at least one arg.
|
||||||
|
# NOTE: We have historically needed to turn on the recursive solver to find that
|
||||||
|
# this was a bug!
|
||||||
|
test fmt.printf() {}
|
||||||
|
|
||||||
|
-- OUTPUT --
|
||||||
|
# err: errUnify: 2 unconsumed generators
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
test fmt.printf("%d%d", 42) {} # should not pass, missing second int
|
test fmt.printf("%d%d", 42) {} # should not pass, missing second int
|
||||||
-- OUTPUT --
|
-- OUTPUT --
|
||||||
# err: errUnify: only recursive solutions left
|
# err: errUnify: 2 unconsumed generators
|
||||||
|
|||||||
@@ -349,12 +349,39 @@ func SimpleInvariantSolver(invariants []interfaces.Invariant, expected []interfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
countGenerators := func() (int, int) {
|
||||||
|
active := 0
|
||||||
|
total := 0
|
||||||
|
for _, x := range equalities {
|
||||||
|
gen, ok := x.(*interfaces.GeneratorInvariant)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total++ // total count
|
||||||
|
if gen.Inactive {
|
||||||
|
continue // skip inactive
|
||||||
|
}
|
||||||
|
active++ // active
|
||||||
|
}
|
||||||
|
return total, active
|
||||||
|
}
|
||||||
|
activeGenerators := func() int {
|
||||||
|
_, active := countGenerators()
|
||||||
|
return active
|
||||||
|
}
|
||||||
|
|
||||||
logf("%s: starting loop with %d equalities", Name, len(equalities))
|
logf("%s: starting loop with %d equalities", Name, len(equalities))
|
||||||
// run until we're solved, stop consuming equalities, or type clash
|
// run until we're solved, stop consuming equalities, or type clash
|
||||||
Loop:
|
Loop:
|
||||||
for {
|
for {
|
||||||
|
// Once we're done solving everything else except the generators
|
||||||
|
// then we can exit, but we want to make sure the generators had
|
||||||
|
// a chance to "speak up" and make sure they were part of Unify.
|
||||||
|
// Every generator gets to run once, and if that does not change
|
||||||
|
// the result, then we mark it as inactive.
|
||||||
|
|
||||||
logf("%s: iterate...", Name)
|
logf("%s: iterate...", Name)
|
||||||
if len(equalities) == 0 && len(exclusives) == 0 {
|
if len(equalities) == 0 && len(exclusives) == 0 && activeGenerators() == 0 {
|
||||||
break // we're done, nothing left
|
break // we're done, nothing left
|
||||||
}
|
}
|
||||||
used := []int{}
|
used := []int{}
|
||||||
@@ -988,6 +1015,13 @@ Loop:
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip if the inactive flag has been set, as it
|
||||||
|
// won't produce any new (novel) inequalities we
|
||||||
|
// can use to progress to a result at this time.
|
||||||
|
if eq.Inactive {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If this returns nil, we add the invariants
|
// If this returns nil, we add the invariants
|
||||||
// it returned and we remove it from the list.
|
// it returned and we remove it from the list.
|
||||||
// If we error, it's because we don't have any
|
// If we error, it's because we don't have any
|
||||||
@@ -995,6 +1029,8 @@ Loop:
|
|||||||
// XXX: should we pass in `invariants` instead?
|
// XXX: should we pass in `invariants` instead?
|
||||||
gi, err := eq.Func(equalities, solved)
|
gi, err := eq.Func(equalities, solved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// set the inactive flag of this generator
|
||||||
|
eq.Inactive = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,6 +1044,15 @@ Loop:
|
|||||||
|
|
||||||
used = append(used, eqi) // mark equality as used up
|
used = append(used, eqi) // mark equality as used up
|
||||||
logf("%s: solved `generator` equality", Name)
|
logf("%s: solved `generator` equality", Name)
|
||||||
|
// reset all other generator equality "inactive" flags
|
||||||
|
for _, x := range equalities {
|
||||||
|
gen, ok := x.(*interfaces.GeneratorInvariant)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gen.Inactive = false
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
// wtf matching
|
// wtf matching
|
||||||
@@ -1033,7 +1078,7 @@ Loop:
|
|||||||
return nil, fmt.Errorf("unknown invariant type: %T", eqx)
|
return nil, fmt.Errorf("unknown invariant type: %T", eqx)
|
||||||
}
|
}
|
||||||
} // end inner for loop
|
} // end inner for loop
|
||||||
if len(used) == 0 {
|
if len(used) == 0 && activeGenerators() == 0 {
|
||||||
// Looks like we're now ambiguous, but if we have any
|
// Looks like we're now ambiguous, but if we have any
|
||||||
// exclusives, recurse into each possibility to see if
|
// exclusives, recurse into each possibility to see if
|
||||||
// one of them can help solve this! first one wins. Add
|
// one of them can help solve this! first one wins. Add
|
||||||
@@ -1066,6 +1111,16 @@ Loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
total, active := countGenerators()
|
||||||
|
// we still have work to do for consistency
|
||||||
|
if active > 0 {
|
||||||
|
continue Loop
|
||||||
|
}
|
||||||
|
|
||||||
|
if total > 0 {
|
||||||
|
return nil, fmt.Errorf("%d unconsumed generators", total)
|
||||||
|
}
|
||||||
|
|
||||||
// check for consistency against remaining invariants
|
// check for consistency against remaining invariants
|
||||||
logf("%s: checking for consistency against %d exclusives...", Name, len(exclusives))
|
logf("%s: checking for consistency against %d exclusives...", Name, len(exclusives))
|
||||||
done := []int{}
|
done := []int{}
|
||||||
|
|||||||
Reference in New Issue
Block a user