diff --git a/lang/interfaces/unification.go b/lang/interfaces/unification.go index ab2b6c7a..ed9861f8 100644 --- a/lang/interfaces/unification.go +++ b/lang/interfaces/unification.go @@ -19,8 +19,10 @@ package interfaces import ( "fmt" + "strings" "github.com/purpleidea/mgmt/lang/types" + "github.com/purpleidea/mgmt/util/errwrap" ) // Invariant represents a constraint that is described by the Expr's and Stmt's, @@ -46,3 +48,800 @@ type Invariant interface { // preferred over eliminating a tricky, but possible one. Possible(partials []Invariant) error } + +// EqualsInvariant is an invariant that symbolizes that the expression has a +// known type. +// TODO: is there a better name than EqualsInvariant +type EqualsInvariant struct { + Expr Expr + Type *types.Type +} + +// String returns a representation of this invariant. +func (obj *EqualsInvariant) String() string { + return fmt.Sprintf("%p == %s", obj.Expr, obj.Type) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualsInvariant) ExprList() []Expr { + return []Expr{obj.Expr} +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualsInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + typ, exists := solved[obj.Expr] + if !exists { + return false, nil + } + if err := typ.Cmp(obj.Type); err != nil { + return false, err + } + return true, nil +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +func (obj *EqualsInvariant) Possible(partials []Invariant) error { + // TODO: we could pass in a solver here + //set := []Invariant{} + //set = append(set, obj) + //set = append(set, partials...) + //_, err := SimpleInvariantSolver(set, ...) + //if err != nil { + // // being ambiguous doesn't guarantee that we're possible + // if err == ErrAmbiguous { + // return nil // might be possible, might not be... + // } + // return err + //} + + // FIXME: This is not right because we want to know if the whole thing + // works together, and as a result, the above solver is better, however, + // the goal is to eliminate easy impossible solutions, so allow this! + // XXX: Double check this is logical. + solved := map[Expr]*types.Type{ + obj.Expr: obj.Type, + } + for _, invar := range partials { // check each one + _, err := invar.Matches(solved) + if err != nil { // inconsistent, so it's not possible + return errwrap.Wrapf(err, "not possible") + } + } + + return nil +} + +// EqualityInvariant is an invariant that symbolizes that the two expressions +// must have equivalent types. +// TODO: is there a better name than EqualityInvariant +type EqualityInvariant struct { + Expr1 Expr + Expr2 Expr +} + +// String returns a representation of this invariant. +func (obj *EqualityInvariant) String() string { + return fmt.Sprintf("%p == %p", obj.Expr1, obj.Expr2) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityInvariant) ExprList() []Expr { + return []Expr{obj.Expr1, obj.Expr2} +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + t1, exists1 := solved[obj.Expr1] + t2, exists2 := solved[obj.Expr2] + if !exists1 || !exists2 { + return false, nil // not matched yet + } + if err := t1.Cmp(t2); err != nil { + return false, err + } + + return true, nil // matched! +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +func (obj *EqualityInvariant) Possible(partials []Invariant) error { + // The idea here is that we look for the expression pointers in the list + // of partial invariants. It's only impossible if we (1) find both of + // them, and (2) that they relate to each other. The second part is + // harder. + var one, two bool + exprs := []Invariant{} + for _, x := range partials { + for _, y := range x.ExprList() { // []Expr + if y == obj.Expr1 { + one = true + exprs = append(exprs, x) + } + if y == obj.Expr2 { + two = true + exprs = append(exprs, x) + } + } + } + + if !one || !two { + return nil // we're unconnected to anything, this is possible! + } + + // we only need to check the connections in this case... + // let's keep this simple, and less perfect for now... + var typ *types.Type + for _, x := range exprs { + eq, ok := x.(*EqualsInvariant) + if !ok { + // XXX: add support for other kinds in the future... + continue + } + + if typ != nil { + if err := typ.Cmp(eq.Type); err != nil { + // we found proof it's not possible + return errwrap.Wrapf(err, "not possible") + } + } + + typ = eq.Type // store for next type + } + + return nil +} + +// EqualityInvariantList is an invariant that symbolizes that all the +// expressions listed must have equivalent types. +type EqualityInvariantList struct { + Exprs []Expr +} + +// String returns a representation of this invariant. +func (obj *EqualityInvariantList) String() string { + var a []string + for _, x := range obj.Exprs { + a = append(a, fmt.Sprintf("%p", x)) + } + return fmt.Sprintf("[%s]", strings.Join(a, ", ")) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityInvariantList) ExprList() []Expr { + return obj.Exprs +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityInvariantList) Matches(solved map[Expr]*types.Type) (bool, error) { + found := true // assume true + var typ *types.Type + for _, x := range obj.Exprs { + t, exists := solved[x] + if !exists { + found = false + continue + } + if typ == nil { // set the first time + typ = t + } + if err := typ.Cmp(t); err != nil { + return false, err + } + } + return found, nil +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +func (obj *EqualityInvariantList) Possible(partials []Invariant) error { + // The idea here is that we look for the expression pointers in the list + // of partial invariants. It's only impossible if we (1) find two or + // more, and (2) that any of them relate to each other. The second part + // is harder. + inList := func(needle Expr, haystack []Expr) bool { + for _, x := range haystack { + if x == needle { + return true + } + } + return false + } + + exprs := []Invariant{} + for _, x := range partials { + for _, y := range x.ExprList() { // []Expr + if inList(y, obj.Exprs) { + exprs = append(exprs, x) + } + } + } + + if len(exprs) <= 1 { + return nil // we're unconnected to anything, this is possible! + } + + // we only need to check the connections in this case... + // let's keep this simple, and less perfect for now... + var typ *types.Type + for _, x := range exprs { + eq, ok := x.(*EqualsInvariant) + if !ok { + // XXX: add support for other kinds in the future... + continue + } + + if typ != nil { + if err := typ.Cmp(eq.Type); err != nil { + // we found proof it's not possible + return errwrap.Wrapf(err, "not possible") + } + } + + typ = eq.Type // store for next type + } + + return nil +} + +// EqualityWrapListInvariant expresses that a list in Expr1 must have elements +// that have the same type as the expression in Expr2Val. +type EqualityWrapListInvariant struct { + Expr1 Expr + Expr2Val Expr +} + +// String returns a representation of this invariant. +func (obj *EqualityWrapListInvariant) String() string { + return fmt.Sprintf("%p == [%p]", obj.Expr1, obj.Expr2Val) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityWrapListInvariant) ExprList() []Expr { + return []Expr{obj.Expr1, obj.Expr2Val} +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityWrapListInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + t1, exists1 := solved[obj.Expr1] // list type + t2, exists2 := solved[obj.Expr2Val] + if !exists1 || !exists2 { + return false, nil // not matched yet + } + if t1.Kind != types.KindList { + return false, fmt.Errorf("expected list kind") + } + if err := t1.Val.Cmp(t2); err != nil { + return false, err // inconsistent! + } + return true, nil // matched! +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *EqualityWrapListInvariant) Possible(partials []Invariant) error { + // XXX: not implemented + return nil // safer to return nil than error +} + +// EqualityWrapMapInvariant expresses that a map in Expr1 must have keys that +// match the type of the expression in Expr2Key and values that match the type +// of the expression in Expr2Val. +type EqualityWrapMapInvariant struct { + Expr1 Expr + Expr2Key Expr + Expr2Val Expr +} + +// String returns a representation of this invariant. +func (obj *EqualityWrapMapInvariant) String() string { + return fmt.Sprintf("%p == {%p: %p}", obj.Expr1, obj.Expr2Key, obj.Expr2Val) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityWrapMapInvariant) ExprList() []Expr { + return []Expr{obj.Expr1, obj.Expr2Key, obj.Expr2Val} +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityWrapMapInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + t1, exists1 := solved[obj.Expr1] // map type + t2, exists2 := solved[obj.Expr2Key] + t3, exists3 := solved[obj.Expr2Val] + if !exists1 || !exists2 || !exists3 { + return false, nil // not matched yet + } + if t1.Kind != types.KindMap { + return false, fmt.Errorf("expected map kind") + } + if err := t1.Key.Cmp(t2); err != nil { + return false, err // inconsistent! + } + if err := t1.Val.Cmp(t3); err != nil { + return false, err // inconsistent! + } + return true, nil // matched! +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *EqualityWrapMapInvariant) Possible(partials []Invariant) error { + // XXX: not implemented + return nil // safer to return nil than error +} + +// EqualityWrapStructInvariant expresses that a struct in Expr1 must have fields +// that match the type of the expressions listed in Expr2Map. +type EqualityWrapStructInvariant struct { + Expr1 Expr + Expr2Map map[string]Expr + Expr2Ord []string +} + +// String returns a representation of this invariant. +func (obj *EqualityWrapStructInvariant) String() string { + var s = make([]string, len(obj.Expr2Ord)) + for i, k := range obj.Expr2Ord { + t, ok := obj.Expr2Map[k] + if !ok { + panic("malformed struct order") + } + if t == nil { + panic("malformed struct field") + } + s[i] = fmt.Sprintf("%s %p", k, t) + } + return fmt.Sprintf("%p == struct{%s}", obj.Expr1, strings.Join(s, "; ")) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityWrapStructInvariant) ExprList() []Expr { + exprs := []Expr{obj.Expr1} + for _, x := range obj.Expr2Map { + exprs = append(exprs, x) + } + return exprs +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityWrapStructInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + t1, exists1 := solved[obj.Expr1] // struct type + if !exists1 { + return false, nil // not matched yet + } + if t1.Kind != types.KindStruct { + return false, fmt.Errorf("expected struct kind") + } + + found := true // assume true + for _, key := range obj.Expr2Ord { + _, exists := t1.Map[key] + if !exists { + return false, fmt.Errorf("missing invariant struct key of: `%s`", key) + } + e, exists := obj.Expr2Map[key] + if !exists { + return false, fmt.Errorf("missing matched struct key of: `%s`", key) + } + t, exists := solved[e] + if !exists { + found = false + continue + } + if err := t1.Map[key].Cmp(t); err != nil { + return false, err // inconsistent! + } + } + + return found, nil // matched! +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *EqualityWrapStructInvariant) Possible(partials []Invariant) error { + // XXX: not implemented + return nil // safer to return nil than error +} + +// EqualityWrapFuncInvariant expresses that a func in Expr1 must have args that +// match the type of the expressions listed in Expr2Map and a return value that +// matches the type of the expression in Expr2Out. +// TODO: should this be named EqualityWrapCallInvariant or not? +type EqualityWrapFuncInvariant struct { + Expr1 Expr + Expr2Map map[string]Expr + Expr2Ord []string + Expr2Out Expr +} + +// String returns a representation of this invariant. +func (obj *EqualityWrapFuncInvariant) String() string { + var s = make([]string, len(obj.Expr2Ord)) + for i, k := range obj.Expr2Ord { + t, ok := obj.Expr2Map[k] + if !ok { + panic("malformed func order") + } + if t == nil { + panic("malformed func field") + } + s[i] = fmt.Sprintf("%s %p", k, t) + } + return fmt.Sprintf("%p == func(%s) %p", obj.Expr1, strings.Join(s, "; "), obj.Expr2Out) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityWrapFuncInvariant) ExprList() []Expr { + exprs := []Expr{obj.Expr1} + for _, x := range obj.Expr2Map { + exprs = append(exprs, x) + } + exprs = append(exprs, obj.Expr2Out) + return exprs +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityWrapFuncInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + t1, exists1 := solved[obj.Expr1] // func type + if !exists1 { + return false, nil // not matched yet + } + if t1.Kind != types.KindFunc { + return false, fmt.Errorf("expected func kind") + } + + found := true // assume true + for _, key := range obj.Expr2Ord { + _, exists := t1.Map[key] + if !exists { + return false, fmt.Errorf("missing invariant struct key of: `%s`", key) + } + e, exists := obj.Expr2Map[key] + if !exists { + return false, fmt.Errorf("missing matched struct key of: `%s`", key) + } + t, exists := solved[e] + if !exists { + found = false + continue + } + if err := t1.Map[key].Cmp(t); err != nil { + return false, err // inconsistent! + } + } + + t, exists := solved[obj.Expr2Out] + if !exists { + return false, nil + } + if err := t1.Out.Cmp(t); err != nil { + return false, err // inconsistent! + } + + return found, nil // matched! +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *EqualityWrapFuncInvariant) Possible(partials []Invariant) error { + // XXX: not implemented + return nil // safer to return nil than error +} + +// EqualityWrapCallInvariant expresses that a call result that happened in Expr1 +// must match the type of the function result listed in Expr2. In this case, +// Expr2 will be a function expression, and the returned expression should match +// with the Expr1 expression, when comparing types. +// TODO: should this be named EqualityWrapFuncInvariant or not? +// TODO: should Expr1 and Expr2 be reversed??? +type EqualityWrapCallInvariant struct { + Expr1 Expr + Expr2Func Expr +} + +// String returns a representation of this invariant. +func (obj *EqualityWrapCallInvariant) String() string { + return fmt.Sprintf("%p == call(%p)", obj.Expr1, obj.Expr2Func) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *EqualityWrapCallInvariant) ExprList() []Expr { + return []Expr{obj.Expr1, obj.Expr2Func} +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *EqualityWrapCallInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + t1, exists1 := solved[obj.Expr1] // call type + t2, exists2 := solved[obj.Expr2Func] + if !exists1 || !exists2 { + return false, nil // not matched yet + } + //if t1.Kind != types.KindFunc { + // return false, fmt.Errorf("expected func kind") + //} + + if t2.Kind != types.KindFunc { + return false, fmt.Errorf("expected func kind") + } + if err := t1.Cmp(t2.Out); err != nil { + return false, err // inconsistent! + } + return true, nil // matched! +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *EqualityWrapCallInvariant) Possible(partials []Invariant) error { + // XXX: not implemented + return nil // safer to return nil than error +} + +// ConjunctionInvariant represents a list of invariants which must all be true +// together. In other words, it's a grouping construct for a set of invariants. +type ConjunctionInvariant struct { + Invariants []Invariant +} + +// String returns a representation of this invariant. +func (obj *ConjunctionInvariant) String() string { + var a []string + for _, x := range obj.Invariants { + s := x.String() + a = append(a, s) + } + return fmt.Sprintf("[%s]", strings.Join(a, ", ")) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *ConjunctionInvariant) ExprList() []Expr { + exprs := []Expr{} + for _, x := range obj.Invariants { + exprs = append(exprs, x.ExprList()...) + } + return exprs +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *ConjunctionInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + found := true // assume true + for _, invar := range obj.Invariants { + match, err := invar.Matches(solved) + if err != nil { + return false, nil + } + if !match { + found = false + } + } + return found, nil +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *ConjunctionInvariant) Possible(partials []Invariant) error { + for _, invar := range obj.Invariants { + if err := invar.Possible(partials); err != nil { + // we found proof it's not possible + return errwrap.Wrapf(err, "not possible") + } + } + // XXX: unfortunately we didn't look for them all together with a solver + return nil +} + +// ExclusiveInvariant represents a list of invariants where one and *only* one +// should hold true. To combine multiple invariants in one of the list elements, +// you can group multiple invariants together using a ConjunctionInvariant. Do +// note that the solver might not verify that only one of the invariants in the +// list holds true, as it might choose to be lazy and pick the first solution +// found. +type ExclusiveInvariant struct { + Invariants []Invariant +} + +// String returns a representation of this invariant. +func (obj *ExclusiveInvariant) String() string { + var a []string + for _, x := range obj.Invariants { + s := x.String() + a = append(a, s) + } + return fmt.Sprintf("[%s]", strings.Join(a, ", ")) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *ExclusiveInvariant) ExprList() []Expr { + // XXX: We should do this if we assume that exclusives don't have some + // sort of transient expr to satisfy that doesn't disappear depending on + // which choice in the exclusive is chosen... + //exprs := []Expr{} + //for _, x := range obj.Invariants { + // exprs = append(exprs, x.ExprList()...) + //} + //return exprs + // XXX: But if we ever specify an expr in this exclusive that isn't + // referenced anywhere else, then we'd need to use the above so that our + // type unification algorithm knows not to stop too early. + return []Expr{} // XXX: Do we want to the set instead? +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. Because this partial invariant requires only +// one to be true, it will mask children errors, since it's normal for only one +// to be consistent. +func (obj *ExclusiveInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + found := false + reterr := fmt.Errorf("all exclusives errored") + var errs error + for _, invar := range obj.Invariants { + match, err := invar.Matches(solved) + if err != nil { + errs = errwrap.Append(errs, err) + continue + } + if !match { + // at least one was false, so we're not done here yet... + // we don't want to error yet, since we can't know there + // won't be a conflict once we get more data about this! + reterr = nil // clear the error + continue + } + if found { // we already found one + return false, fmt.Errorf("more than one exclusive solution") + } + found = true + } + + if found { // we got exactly one valid solution + return true, nil + } + + return false, errwrap.Wrapf(reterr, errwrap.String(errs)) +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation is currently not implemented! +func (obj *ExclusiveInvariant) Possible(partials []Invariant) error { + var errs error + for _, invar := range obj.Invariants { + err := invar.Possible(partials) + if err == nil { + // we found proof it's possible + return nil + } + errs = errwrap.Append(errs, err) + } + + return errwrap.Wrapf(errs, "not possible") +} + +// Simplify attempts to reduce the exclusive invariant to eliminate any +// possibilities based on the list of known partials at this time. Hopefully, +// this will weed out some of the function polymorphism possibilities so that we +// can solve the problem without recursive, combinatorial permutation, which is +// very, very slow. +func (obj *ExclusiveInvariant) Simplify(partials []Invariant) ([]Invariant, error) { + if len(obj.Invariants) == 0 { // unexpected case + return []Invariant{}, nil // we don't need anything! + } + + possible := []Invariant{} + var reasons error + for _, invar := range obj.Invariants { // []Invariant + if err := invar.Possible(partials); err != nil { + reasons = errwrap.Append(reasons, err) + continue + } + possible = append(possible, invar) + } + + if len(possible) == 0 { // nothing was possible + return nil, errwrap.Wrapf(reasons, "no possible simplifications") + } + if len(possible) == 1 { // we flattened out the exclusive! + return possible, nil + } + + if len(possible) == len(obj.Invariants) { // nothing changed + return nil, fmt.Errorf("no possible simplifications, we're unchanged") + } + + invar := &ExclusiveInvariant{ + Invariants: possible, // hopefully a smaller exclusive! + } + return []Invariant{invar}, nil +} + +// AnyInvariant is an invariant that symbolizes that the expression can be any +// type. It is sometimes used to ensure that an expr actually gets a solution +// type so that it is not left unreferenced, and as a result, unsolved. +// TODO: is there a better name than AnyInvariant +type AnyInvariant struct { + Expr Expr +} + +// String returns a representation of this invariant. +func (obj *AnyInvariant) String() string { + return fmt.Sprintf("%p == *", obj.Expr) +} + +// ExprList returns the list of valid expressions in this invariant. +func (obj *AnyInvariant) ExprList() []Expr { + return []Expr{obj.Expr} +} + +// Matches returns whether an invariant matches the existing solution. If it is +// inconsistent, then it errors. +func (obj *AnyInvariant) Matches(solved map[Expr]*types.Type) (bool, error) { + _, exists := solved[obj.Expr] // we only care that it is found. + return exists, nil +} + +// Possible returns an error if it is certain that it is NOT possible to get a +// solution with this invariant and the set of partials. In certain cases, it +// might not be able to determine that it's not possible, while simultaneously +// not being able to guarantee a possible solution either. In this situation, it +// should return nil, since this is used as a filtering mechanism, and the nil +// result of possible is preferred over eliminating a tricky, but possible one. +// This particular implementation always returns nil. +func (obj *AnyInvariant) Possible([]Invariant) error { + // keep it simple, even though we don't technically check the inputs... + return nil +} diff --git a/lang/structs.go b/lang/structs.go index 37367542..0955a695 100644 --- a/lang/structs.go +++ b/lang/structs.go @@ -32,7 +32,6 @@ import ( "github.com/purpleidea/mgmt/lang/funcs/structs" "github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/types" - "github.com/purpleidea/mgmt/lang/unification" langutil "github.com/purpleidea/mgmt/lang/util" "github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/util" @@ -481,19 +480,19 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) { // name must be a string or a list ors := []interfaces.Invariant{} - invarStr := &unification.EqualsInvariant{ + invarStr := &interfaces.EqualsInvariant{ Expr: obj.Name, Type: types.TypeStr, } ors = append(ors, invarStr) - invarListStr := &unification.EqualsInvariant{ + invarListStr := &interfaces.EqualsInvariant{ Expr: obj.Name, Type: types.NewType("[]str"), } ors = append(ors, invarListStr) - invar := &unification.ExclusiveInvariant{ + invar := &interfaces.ExclusiveInvariant{ Invariants: ors, // one and only one of these should be true } invariants = append(invariants, invar) @@ -1177,7 +1176,7 @@ func (obj *StmtResField) Unify(kind string) ([]interfaces.Invariant, error) { invariants = append(invariants, condition...) // the condition must ultimately be a boolean - conditionInvar := &unification.EqualsInvariant{ + conditionInvar := &interfaces.EqualsInvariant{ Expr: obj.Condition, Type: types.TypeBool, } @@ -1203,7 +1202,7 @@ func (obj *StmtResField) Unify(kind string) ([]interfaces.Invariant, error) { if !exists { return nil, fmt.Errorf("field `%s` does not exist in `%s`", obj.Field, kind) } - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj.Value, Type: typ, } @@ -1435,7 +1434,7 @@ func (obj *StmtResEdge) Unify(kind string) ([]interfaces.Invariant, error) { invariants = append(invariants, condition...) // the condition must ultimately be a boolean - conditionInvar := &unification.EqualsInvariant{ + conditionInvar := &interfaces.EqualsInvariant{ Expr: obj.Condition, Type: types.TypeBool, } @@ -1690,7 +1689,7 @@ func (obj *StmtResMeta) Unify(kind string) ([]interfaces.Invariant, error) { invariants = append(invariants, condition...) // the condition must ultimately be a boolean - conditionInvar := &unification.EqualsInvariant{ + conditionInvar := &interfaces.EqualsInvariant{ Expr: obj.Condition, Type: types.TypeBool, } @@ -1700,7 +1699,7 @@ func (obj *StmtResMeta) Unify(kind string) ([]interfaces.Invariant, error) { // add additional invariants based on what's in obj.Property !!! var invar interfaces.Invariant static := func(typ *types.Type) interfaces.Invariant { - return &unification.EqualsInvariant{ + return &interfaces.EqualsInvariant{ Expr: obj.MetaExpr, Type: typ, } @@ -1744,7 +1743,7 @@ func (obj *StmtResMeta) Unify(kind string) ([]interfaces.Invariant, error) { //invarStruct := static(types.NewType("struct{edges str}")) //ors = append(ors, invarStruct) - invar = &unification.ExclusiveInvariant{ + invar = &interfaces.ExclusiveInvariant{ Invariants: ors, // one and only one of these should be true } @@ -1768,7 +1767,7 @@ func (obj *StmtResMeta) Unify(kind string) ([]interfaces.Invariant, error) { // TODO: decide what fields we might want here //invarStruct := static(wrap(types.NewType("struct{edges str}"))) //ors = append(ors, invarStruct) - invar = &unification.ExclusiveInvariant{ + invar = &interfaces.ExclusiveInvariant{ Invariants: ors, // one and only one of these should be true } @@ -2250,19 +2249,19 @@ func (obj *StmtEdgeHalf) Unify() ([]interfaces.Invariant, error) { // name must be a string or a list ors := []interfaces.Invariant{} - invarStr := &unification.EqualsInvariant{ + invarStr := &interfaces.EqualsInvariant{ Expr: obj.Name, Type: types.TypeStr, } ors = append(ors, invarStr) - invarListStr := &unification.EqualsInvariant{ + invarListStr := &interfaces.EqualsInvariant{ Expr: obj.Name, Type: types.NewType("[]str"), } ors = append(ors, invarListStr) - invar := &unification.ExclusiveInvariant{ + invar := &interfaces.ExclusiveInvariant{ Invariants: ors, // one and only one of these should be true } invariants = append(invariants, invar) @@ -2540,7 +2539,7 @@ func (obj *StmtIf) Unify() ([]interfaces.Invariant, error) { invariants = append(invariants, condition...) // the condition must ultimately be a boolean - conditionInvar := &unification.EqualsInvariant{ + conditionInvar := &interfaces.EqualsInvariant{ Expr: obj.Condition, Type: types.TypeBool, } @@ -4371,7 +4370,7 @@ func (obj *StmtInclude) Unify() ([]interfaces.Invariant, error) { // TODO: are additional invariants required? // add invariants between the args and the class if typ := obj.class.Args[i].Type; typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj.Args[i], Type: typ, // type of arg } @@ -4667,7 +4666,7 @@ func (obj *ExprAny) Type() (*types.Type, error) { // collection to the caller. func (obj *ExprAny) Unify() ([]interfaces.Invariant, error) { invariants := []interfaces.Invariant{ - &unification.AnyInvariant{ // it has to be something, anything! + &interfaces.AnyInvariant{ // it has to be something, anything! Expr: obj, }, } @@ -4785,7 +4784,7 @@ func (obj *ExprBool) Type() (*types.Type, error) { return types.TypeBool, nil } // collection to the caller. func (obj *ExprBool) Unify() ([]interfaces.Invariant, error) { invariants := []interfaces.Invariant{ - &unification.EqualsInvariant{ + &interfaces.EqualsInvariant{ Expr: obj, Type: types.TypeBool, }, @@ -4956,7 +4955,7 @@ func (obj *ExprStr) Type() (*types.Type, error) { return types.TypeStr, nil } // collection to the caller. func (obj *ExprStr) Unify() ([]interfaces.Invariant, error) { invariants := []interfaces.Invariant{ - &unification.EqualsInvariant{ + &interfaces.EqualsInvariant{ Expr: obj, // unique id for this expression (a pointer) Type: types.TypeStr, }, @@ -5082,7 +5081,7 @@ func (obj *ExprInt) Type() (*types.Type, error) { return types.TypeInt, nil } // collection to the caller. func (obj *ExprInt) Unify() ([]interfaces.Invariant, error) { invariants := []interfaces.Invariant{ - &unification.EqualsInvariant{ + &interfaces.EqualsInvariant{ Expr: obj, Type: types.TypeInt, }, @@ -5210,7 +5209,7 @@ func (obj *ExprFloat) Type() (*types.Type, error) { return types.TypeFloat, nil // collection to the caller. func (obj *ExprFloat) Unify() ([]interfaces.Invariant, error) { invariants := []interfaces.Invariant{ - &unification.EqualsInvariant{ + &interfaces.EqualsInvariant{ Expr: obj, Type: types.TypeFloat, }, @@ -5461,7 +5460,7 @@ func (obj *ExprList) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -5479,7 +5478,7 @@ func (obj *ExprList) Unify() ([]interfaces.Invariant, error) { // each element must be equal to each other if len(obj.Elements) > 1 { - invariant := &unification.EqualityInvariantList{ + invariant := &interfaces.EqualityInvariantList{ Exprs: obj.Elements, } invariants = append(invariants, invariant) @@ -5487,7 +5486,7 @@ func (obj *ExprList) Unify() ([]interfaces.Invariant, error) { // we should be type list of (type of element) if len(obj.Elements) > 0 { - invariant := &unification.EqualityWrapListInvariant{ + invariant := &interfaces.EqualityWrapListInvariant{ Expr1: obj, // unique id for this expression (a pointer) Expr2Val: obj.Elements[0], } @@ -5496,7 +5495,7 @@ func (obj *ExprList) Unify() ([]interfaces.Invariant, error) { // make sure this empty list gets an element type somehow if len(obj.Elements) == 0 { - invariant := &unification.AnyInvariant{ + invariant := &interfaces.AnyInvariant{ Expr: obj, } invariants = append(invariants, invariant) @@ -5512,11 +5511,11 @@ func (obj *ExprList) Unify() ([]interfaces.Invariant, error) { // FIXME: instead of using `ExprAny`, we could actually teach // our unification engine to ensure that our expr kind is list, // eg: - //&unification.EqualityKindInvariant{ + //&interfaces.EqualityKindInvariant{ // Expr1: obj, // Kind: types.KindList, //} - invar := &unification.EqualityWrapListInvariant{ + invar := &interfaces.EqualityWrapListInvariant{ Expr1: obj, Expr2Val: exprAny, // hack } @@ -5913,7 +5912,7 @@ func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -5943,12 +5942,12 @@ func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) { valExprs = append(valExprs, obj.KVs[i].Val) } - keyInvariant := &unification.EqualityInvariantList{ + keyInvariant := &interfaces.EqualityInvariantList{ Exprs: keyExprs, } invariants = append(invariants, keyInvariant) - valInvariant := &unification.EqualityInvariantList{ + valInvariant := &interfaces.EqualityInvariantList{ Exprs: valExprs, } invariants = append(invariants, valInvariant) @@ -5956,7 +5955,7 @@ func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) { // we should be type map of (type of element) if len(obj.KVs) > 0 { - invariant := &unification.EqualityWrapMapInvariant{ + invariant := &interfaces.EqualityWrapMapInvariant{ Expr1: obj, // unique id for this expression (a pointer) Expr2Key: obj.KVs[0].Key, Expr2Val: obj.KVs[0].Val, @@ -5966,7 +5965,7 @@ func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) { // make sure this empty map gets a type for its key/value somehow if len(obj.KVs) == 0 { - invariant := &unification.AnyInvariant{ + invariant := &interfaces.AnyInvariant{ Expr: obj, } invariants = append(invariants, invariant) @@ -5987,11 +5986,11 @@ func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) { // FIXME: instead of using `ExprAny`, we could actually teach // our unification engine to ensure that our expr kind is list, // eg: - //&unification.EqualityKindInvariant{ + //&interfaces.EqualityKindInvariant{ // Expr1: obj, // Kind: types.KindMap, //} - invar := &unification.EqualityWrapMapInvariant{ + invar := &interfaces.EqualityWrapMapInvariant{ Expr1: obj, Expr2Key: exprAnyKey, // hack Expr2Val: exprAnyVal, // hack @@ -6381,7 +6380,7 @@ func (obj *ExprStruct) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -6404,7 +6403,7 @@ func (obj *ExprStruct) Unify() ([]interfaces.Invariant, error) { mapped[x.Name] = x.Value ordered = append(ordered, x.Name) } - invariant := &unification.EqualityWrapStructInvariant{ + invariant := &interfaces.EqualityWrapStructInvariant{ Expr1: obj, // unique id for this expression (a pointer) Expr2Map: mapped, Expr2Ord: ordered, @@ -7013,7 +7012,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -7023,7 +7022,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // if we know the type statically... // TODO: is this redundant, or do we need something similar elsewhere? if typ, err := obj.Type(); err == nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: typ, } @@ -7056,7 +7055,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // if the arg's type is known statically... if typ := obj.Args[i].Type; typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: arg, Type: typ, } @@ -7071,7 +7070,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { return nil, errwrap.Wrapf(err, "can't get body scope") } if bodyScope != nil { // TODO: can this be nil? - invar := &unification.EqualityInvariant{ + invar := &interfaces.EqualityInvariant{ Expr1: arg, Expr2: bodyScope.Variables[name], } @@ -7093,7 +7092,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // // // if the arg's type is known statically... // if typ := arg.Type; typ != nil { - // invar := &unification.EqualsInvariant{ + // invar := &interfaces.EqualsInvariant{ // Expr: expr, // Type: typ, // } @@ -7107,7 +7106,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // // return nil, errwrap.Wrapf(err, "can't get body scope") // //} // //// The scoped variable should match the arg. - // //invar := &unification.EqualityInvariant{ + // //invar := &interfaces.EqualityInvariant{ // // Expr1: expr, // // Expr2: bodyScope.Variables[name], // ??? // //} @@ -7116,7 +7115,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { } // XXX: is this the right kind of invariant??? - invariant := &unification.EqualityWrapFuncInvariant{ + invariant := &interfaces.EqualityWrapFuncInvariant{ Expr1: obj, Expr2Map: mapped, Expr2Ord: ordered, @@ -7127,7 +7126,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // return type must be equal to the body expression if obj.Body != nil && obj.Return != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj.Body, Type: obj.Return, } @@ -7141,7 +7140,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { //if !ok { // sig := fn.Info().Sig // if sig != nil && !sig.HasVariant() { - // invar := &unification.EqualsInvariant{ + // invar := &interfaces.EqualsInvariant{ // Expr: obj, // Type: sig, // } @@ -7174,7 +7173,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // TODO: Previously, we just skipped all of these invariants! If // we get examples that don't work well, just abandon this part. if !typ.HasVariant() { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: typ, } @@ -7183,7 +7182,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // Add at *most* only one any invariant in an exclusive // set, otherwise two or more possibilities will have // equivalent answers. - anyInvar := &unification.AnyInvariant{ + anyInvar := &interfaces.AnyInvariant{ Expr: obj, } ors = append(ors, anyInvar) @@ -7192,7 +7191,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { } // end results loop if len(ors) > 0 { - var invar interfaces.Invariant = &unification.ExclusiveInvariant{ + var invar interfaces.Invariant = &interfaces.ExclusiveInvariant{ Invariants: ors, // one and only one of these should be true } if len(ors) == 1 { @@ -7783,7 +7782,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -7791,7 +7790,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { } //if obj.typ != nil { // XXX: i think this is probably incorrect... - // invar := &unification.EqualsInvariant{ + // invar := &interfaces.EqualsInvariant{ // Expr: obj.expr, // Type: obj.typ, // } @@ -7815,13 +7814,13 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { } invariants = append(invariants, invars...) - anyInvar := &unification.AnyInvariant{ // TODO: maybe this isn't needed? + anyInvar := &interfaces.AnyInvariant{ // TODO: maybe this isn't needed? Expr: obj.expr, } invariants = append(invariants, anyInvar) // our type should equal the return type of the called function - invar := &unification.EqualityWrapCallInvariant{ + invar := &interfaces.EqualityWrapCallInvariant{ // TODO: should Expr1 and Expr2 be reversed??? Expr1: obj, // return type expression from calling the function Expr2Func: obj.expr, @@ -7837,7 +7836,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { // if we know the return type, it should match our type if fn.Body != nil && fn.Return != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, // return type from calling the function Type: fn.Return, // specified return type } @@ -7857,7 +7856,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { if x.Type == nil { // unknown type continue } - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj.Args[i], Type: x.Type, } @@ -7872,7 +7871,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { if !exists || x.Type == nil { continue } - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: expr, Type: x.Type, } @@ -7888,7 +7887,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { } // determine the type of the function itself - invariant := &unification.EqualityWrapFuncInvariant{ + invariant := &interfaces.EqualityWrapFuncInvariant{ Expr1: fn, // unique id for this expression (a pointer) Expr2Map: mapped, Expr2Ord: ordered, @@ -7897,7 +7896,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { invariants = append(invariants, invariant) //if fn.Return != nil { - // invariant := &unification.EqualityWrapFuncInvariant{ + // invariant := &interfaces.EqualityWrapFuncInvariant{ // Expr1: fn, // unique id for this expression (a pointer) // Expr2Map: mapped, // Expr2Ord: ordered, @@ -7909,14 +7908,14 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { // TODO: Do we need to add an EqualityWrapCallInvariant here? // the return type of this call expr, should match the body type - invar := &unification.EqualityInvariant{ + invar := &interfaces.EqualityInvariant{ Expr1: obj, Expr2: fn.Body, } invariants = append(invariants, invar) //if fn.Return != nil { - // invar := &unification.EqualityInvariant{ + // invar := &interfaces.EqualityInvariant{ // Expr1: obj, // Expr2: fn.Return, XXX: ??? // } @@ -8036,7 +8035,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { } if typ.Kind == types.KindVariant { // XXX: ¯\_(ツ)_/¯ // XXX: maybe needed to avoid an oversimplified exclusive! - anyInvar := &unification.AnyInvariant{ + anyInvar := &interfaces.AnyInvariant{ Expr: fn, // TODO: fn or obj ? } ors = append(ors, anyInvar) @@ -8054,13 +8053,13 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { for i, x := range typ.Ord { if typ.Map[x].HasVariant() { // XXX: ¯\_(ツ)_/¯ // TODO: maybe this isn't needed? - invar := &unification.AnyInvariant{ + invar := &interfaces.AnyInvariant{ Expr: obj.Args[i], } invars = append(invars, invar) continue } - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj.Args[i], Type: typ.Map[x], // type of arg } @@ -8070,12 +8069,12 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { // this expression should equal the output type of the function if typ.Out.HasVariant() { // XXX: ¯\_(ツ)_/¯ // TODO: maybe this isn't needed? - invar := &unification.AnyInvariant{ + invar := &interfaces.AnyInvariant{ Expr: obj, } invars = append(invars, invar) } else { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: typ.Out, } @@ -8093,14 +8092,14 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { } if !typ.HasVariant() { // XXX: ¯\_(ツ)_/¯ - funcInvariant := &unification.EqualsInvariant{ + funcInvariant := &interfaces.EqualsInvariant{ Expr: fn, Type: typ, } invars = append(invars, funcInvariant) } else { // XXX: maybe needed to avoid an oversimplified exclusive! - anyInvar := &unification.AnyInvariant{ + anyInvar := &interfaces.AnyInvariant{ Expr: fn, // TODO: fn or obj ? } invars = append(invars, anyInvar) @@ -8110,7 +8109,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { // is the return type which is produced, where as the entire // function itself has its own type which includes the types of // the input arguments... - invar := &unification.EqualityWrapFuncInvariant{ + invar := &interfaces.EqualityWrapFuncInvariant{ Expr1: fn, Expr2Map: mapped, Expr2Ord: ordered, @@ -8119,7 +8118,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { invars = append(invars, invar) // all of these need to be true together - and := &unification.ConjunctionInvariant{ + and := &interfaces.ConjunctionInvariant{ Invariants: invars, } @@ -8131,7 +8130,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { // return nil, fmt.Errorf("can't find any valid signatures that match func `%s`", obj.Name) //} if len(ors) > 0 { - var invar interfaces.Invariant = &unification.ExclusiveInvariant{ + var invar interfaces.Invariant = &interfaces.ExclusiveInvariant{ Invariants: ors, // one and only one of these should be true } if len(ors) == 1 { @@ -8464,7 +8463,7 @@ func (obj *ExprVar) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -8481,7 +8480,7 @@ func (obj *ExprVar) Unify() ([]interfaces.Invariant, error) { // this expression's type must be the type of what the var is bound to! // TODO: does this always cause an identical duplicate invariant? - invar := &unification.EqualityInvariant{ + invar := &interfaces.EqualityInvariant{ Expr1: obj, Expr2: expr, } @@ -8868,7 +8867,7 @@ func (obj *ExprIf) Unify() ([]interfaces.Invariant, error) { // if this was set explicitly by the parser if obj.typ != nil { - invar := &unification.EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: obj, Type: obj.typ, } @@ -8883,7 +8882,7 @@ func (obj *ExprIf) Unify() ([]interfaces.Invariant, error) { invariants = append(invariants, condition...) // the condition must ultimately be a boolean - conditionInvar := &unification.EqualsInvariant{ + conditionInvar := &interfaces.EqualsInvariant{ Expr: obj.Condition, Type: types.TypeBool, } @@ -8903,19 +8902,19 @@ func (obj *ExprIf) Unify() ([]interfaces.Invariant, error) { invariants = append(invariants, elseBranch...) // the two branches must be equally typed - branchesInvar := &unification.EqualityInvariant{ + branchesInvar := &interfaces.EqualityInvariant{ Expr1: obj.ThenBranch, Expr2: obj.ElseBranch, } invariants = append(invariants, branchesInvar) // the two branches must match the type of the whole expression - thenInvar := &unification.EqualityInvariant{ + thenInvar := &interfaces.EqualityInvariant{ Expr1: obj, Expr2: obj.ThenBranch, } invariants = append(invariants, thenInvar) - elseInvar := &unification.EqualityInvariant{ + elseInvar := &interfaces.EqualityInvariant{ Expr1: obj, Expr2: obj.ElseBranch, } diff --git a/lang/unification/simplesolver.go b/lang/unification/simplesolver.go index 28a12080..5354c967 100644 --- a/lang/unification/simplesolver.go +++ b/lang/unification/simplesolver.go @@ -61,19 +61,19 @@ func SimpleInvariantSolverLogger(logf func(format string, v ...interface{})) fun // It is intended to be very simple, even if it's computationally inefficient. func SimpleInvariantSolver(invariants []interfaces.Invariant, expected []interfaces.Expr, logf func(format string, v ...interface{})) (*InvariantSolution, error) { debug := false // XXX: add to interface - process := func(invariants []interfaces.Invariant) ([]interfaces.Invariant, []*ExclusiveInvariant, error) { + process := func(invariants []interfaces.Invariant) ([]interfaces.Invariant, []*interfaces.ExclusiveInvariant, error) { equalities := []interfaces.Invariant{} - exclusives := []*ExclusiveInvariant{} + exclusives := []*interfaces.ExclusiveInvariant{} for _, x := range invariants { switch invariant := x.(type) { - case *EqualsInvariant: + case *interfaces.EqualsInvariant: equalities = append(equalities, invariant) - case *EqualityInvariant: + case *interfaces.EqualityInvariant: equalities = append(equalities, invariant) - case *EqualityInvariantList: + case *interfaces.EqualityInvariantList: // de-construct this list variant into a series // of equality variants so that our solver can // be implemented more simply... @@ -81,41 +81,41 @@ func SimpleInvariantSolver(invariants []interfaces.Invariant, expected []interfa return nil, nil, fmt.Errorf("list invariant needs at least two elements") } for i := 0; i < len(invariant.Exprs)-1; i++ { - invar := &EqualityInvariant{ + invar := &interfaces.EqualityInvariant{ Expr1: invariant.Exprs[i], Expr2: invariant.Exprs[i+1], } equalities = append(equalities, invar) } - case *EqualityWrapListInvariant: + case *interfaces.EqualityWrapListInvariant: equalities = append(equalities, invariant) - case *EqualityWrapMapInvariant: + case *interfaces.EqualityWrapMapInvariant: equalities = append(equalities, invariant) - case *EqualityWrapStructInvariant: + case *interfaces.EqualityWrapStructInvariant: equalities = append(equalities, invariant) - case *EqualityWrapFuncInvariant: + case *interfaces.EqualityWrapFuncInvariant: equalities = append(equalities, invariant) - case *EqualityWrapCallInvariant: + case *interfaces.EqualityWrapCallInvariant: equalities = append(equalities, invariant) // contains a list of invariants which this represents - case *ConjunctionInvariant: + case *interfaces.ConjunctionInvariant: for _, invar := range invariant.Invariants { equalities = append(equalities, invar) } - case *ExclusiveInvariant: + case *interfaces.ExclusiveInvariant: // these are special, note the different list if len(invariant.Invariants) > 0 { exclusives = append(exclusives, invariant) } - case *AnyInvariant: + case *interfaces.AnyInvariant: equalities = append(equalities, invariant) default: @@ -171,7 +171,7 @@ Loop: // method on the Invariant type to simplify this code? switch eq := x.(type) { // trivials - case *EqualsInvariant: + case *interfaces.EqualsInvariant: typ, exists := solved[eq.Expr] if !exists { solved[eq.Expr] = eq.Type // yay, we learned something! @@ -190,7 +190,7 @@ Loop: continue // partials - case *EqualityWrapListInvariant: + case *interfaces.EqualityWrapListInvariant: if _, exists := listPartials[eq.Expr1]; !exists { listPartials[eq.Expr1] = make(map[interfaces.Expr]*types.Type) } @@ -248,7 +248,7 @@ Loop: continue } - case *EqualityWrapMapInvariant: + case *interfaces.EqualityWrapMapInvariant: if _, exists := mapPartials[eq.Expr1]; !exists { mapPartials[eq.Expr1] = make(map[interfaces.Expr]*types.Type) } @@ -319,7 +319,7 @@ Loop: continue } - case *EqualityWrapStructInvariant: + case *interfaces.EqualityWrapStructInvariant: if _, exists := structPartials[eq.Expr1]; !exists { structPartials[eq.Expr1] = make(map[interfaces.Expr]*types.Type) } @@ -393,7 +393,7 @@ Loop: continue } - case *EqualityWrapFuncInvariant: + case *interfaces.EqualityWrapFuncInvariant: if _, exists := funcPartials[eq.Expr1]; !exists { funcPartials[eq.Expr1] = make(map[interfaces.Expr]*types.Type) } @@ -494,7 +494,7 @@ Loop: continue } - case *EqualityWrapCallInvariant: + case *interfaces.EqualityWrapCallInvariant: // the logic is slightly different here, because // we can only go from the func type to the call // type as we can't do the reverse determination @@ -531,7 +531,7 @@ Loop: } // regular matching - case *EqualityInvariant: + case *interfaces.EqualityInvariant: typ1, exists1 := solved[eq.Expr1] typ2, exists2 := solved[eq.Expr2] @@ -565,7 +565,7 @@ Loop: panic("reached unexpected code") // wtf matching - case *AnyInvariant: + case *interfaces.AnyInvariant: // this basically ensures that the expr gets solved if _, exists := solved[eq.Expr]; exists { used = append(used, i) // mark equality as used up @@ -643,7 +643,7 @@ Loop: if len(exclusives) > 0 { // FIXME: can we do this loop in a deterministic, sorted way? for expr, typ := range solved { - invar := &EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: expr, Type: typ, } @@ -675,7 +675,7 @@ Loop: for i, invar := range exclusives { // The partialSolutions don't contain any other // exclusives... We look at each individually. - s, err := invar.simplify(partialSolutions) // XXX: pass in the solver? + s, err := invar.Simplify(partialSolutions) // XXX: pass in the solver? if err != nil { logf("exclusive simplification failed: %+v", invar) continue @@ -754,10 +754,10 @@ Loop: } // end giant for loop // build final solution - solutions := []*EqualsInvariant{} + solutions := []*interfaces.EqualsInvariant{} // FIXME: can we do this loop in a deterministic, sorted way? for expr, typ := range solved { - invar := &EqualsInvariant{ + invar := &interfaces.EqualsInvariant{ Expr: expr, Type: typ, } diff --git a/lang/unification/unification.go b/lang/unification/unification.go index cb36b347..cec3e402 100644 --- a/lang/unification/unification.go +++ b/lang/unification/unification.go @@ -23,8 +23,6 @@ import ( "strings" "github.com/purpleidea/mgmt/lang/interfaces" - "github.com/purpleidea/mgmt/lang/types" - "github.com/purpleidea/mgmt/util/errwrap" ) // Unifier holds all the data that the Unify function will need for it to run. @@ -145,766 +143,6 @@ func (obj *Unifier) Unify() error { return nil } -// EqualsInvariant is an invariant that symbolizes that the expression has a -// known type. -// TODO: is there a better name than EqualsInvariant -type EqualsInvariant struct { - Expr interfaces.Expr - Type *types.Type -} - -// String returns a representation of this invariant. -func (obj *EqualsInvariant) String() string { - return fmt.Sprintf("%p == %s", obj.Expr, obj.Type) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualsInvariant) ExprList() []interfaces.Expr { - return []interfaces.Expr{obj.Expr} -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualsInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - typ, exists := solved[obj.Expr] - if !exists { - return false, nil - } - if err := typ.Cmp(obj.Type); err != nil { - return false, err - } - return true, nil -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -func (obj *EqualsInvariant) Possible(partials []interfaces.Invariant) error { - // TODO: we could pass in a solver here - //set := []interfaces.Invariant{} - //set = append(set, obj) - //set = append(set, partials...) - //_, err := SimpleInvariantSolver(set, ...) - //if err != nil { - // // being ambiguous doesn't guarantee that we're possible - // if err == ErrAmbiguous { - // return nil // might be possible, might not be... - // } - // return err - //} - - // FIXME: This is not right because we want to know if the whole thing - // works together, and as a result, the above solver is better, however, - // the goal is to eliminate easy impossible solutions, so allow this! - // XXX: Double check this is logical. - solved := map[interfaces.Expr]*types.Type{ - obj.Expr: obj.Type, - } - for _, invar := range partials { // check each one - _, err := invar.Matches(solved) - if err != nil { // inconsistent, so it's not possible - return errwrap.Wrapf(err, "not possible") - } - } - - return nil -} - -// EqualityInvariant is an invariant that symbolizes that the two expressions -// must have equivalent types. -// TODO: is there a better name than EqualityInvariant -type EqualityInvariant struct { - Expr1 interfaces.Expr - Expr2 interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *EqualityInvariant) String() string { - return fmt.Sprintf("%p == %p", obj.Expr1, obj.Expr2) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityInvariant) ExprList() []interfaces.Expr { - return []interfaces.Expr{obj.Expr1, obj.Expr2} -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - t1, exists1 := solved[obj.Expr1] - t2, exists2 := solved[obj.Expr2] - if !exists1 || !exists2 { - return false, nil // not matched yet - } - if err := t1.Cmp(t2); err != nil { - return false, err - } - - return true, nil // matched! -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -func (obj *EqualityInvariant) Possible(partials []interfaces.Invariant) error { - // The idea here is that we look for the expression pointers in the list - // of partial invariants. It's only impossible if we (1) find both of - // them, and (2) that they relate to each other. The second part is - // harder. - var one, two bool - exprs := []interfaces.Invariant{} - for _, x := range partials { - for _, y := range x.ExprList() { // []interfaces.Expr - if y == obj.Expr1 { - one = true - exprs = append(exprs, x) - } - if y == obj.Expr2 { - two = true - exprs = append(exprs, x) - } - } - } - - if !one || !two { - return nil // we're unconnected to anything, this is possible! - } - - // we only need to check the connections in this case... - // let's keep this simple, and less perfect for now... - var typ *types.Type - for _, x := range exprs { - eq, ok := x.(*EqualsInvariant) - if !ok { - // XXX: add support for other kinds in the future... - continue - } - - if typ != nil { - if err := typ.Cmp(eq.Type); err != nil { - // we found proof it's not possible - return errwrap.Wrapf(err, "not possible") - } - } - - typ = eq.Type // store for next type - } - - return nil -} - -// EqualityInvariantList is an invariant that symbolizes that all the -// expressions listed must have equivalent types. -type EqualityInvariantList struct { - Exprs []interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *EqualityInvariantList) String() string { - var a []string - for _, x := range obj.Exprs { - a = append(a, fmt.Sprintf("%p", x)) - } - return fmt.Sprintf("[%s]", strings.Join(a, ", ")) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityInvariantList) ExprList() []interfaces.Expr { - return obj.Exprs -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityInvariantList) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - found := true // assume true - var typ *types.Type - for _, x := range obj.Exprs { - t, exists := solved[x] - if !exists { - found = false - continue - } - if typ == nil { // set the first time - typ = t - } - if err := typ.Cmp(t); err != nil { - return false, err - } - } - return found, nil -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -func (obj *EqualityInvariantList) Possible(partials []interfaces.Invariant) error { - // The idea here is that we look for the expression pointers in the list - // of partial invariants. It's only impossible if we (1) find two or - // more, and (2) that any of them relate to each other. The second part - // is harder. - inList := func(needle interfaces.Expr, haystack []interfaces.Expr) bool { - for _, x := range haystack { - if x == needle { - return true - } - } - return false - } - - exprs := []interfaces.Invariant{} - for _, x := range partials { - for _, y := range x.ExprList() { // []interfaces.Expr - if inList(y, obj.Exprs) { - exprs = append(exprs, x) - } - } - } - - if len(exprs) <= 1 { - return nil // we're unconnected to anything, this is possible! - } - - // we only need to check the connections in this case... - // let's keep this simple, and less perfect for now... - var typ *types.Type - for _, x := range exprs { - eq, ok := x.(*EqualsInvariant) - if !ok { - // XXX: add support for other kinds in the future... - continue - } - - if typ != nil { - if err := typ.Cmp(eq.Type); err != nil { - // we found proof it's not possible - return errwrap.Wrapf(err, "not possible") - } - } - - typ = eq.Type // store for next type - } - - return nil -} - -// EqualityWrapListInvariant expresses that a list in Expr1 must have elements -// that have the same type as the expression in Expr2Val. -type EqualityWrapListInvariant struct { - Expr1 interfaces.Expr - Expr2Val interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *EqualityWrapListInvariant) String() string { - return fmt.Sprintf("%p == [%p]", obj.Expr1, obj.Expr2Val) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityWrapListInvariant) ExprList() []interfaces.Expr { - return []interfaces.Expr{obj.Expr1, obj.Expr2Val} -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityWrapListInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - t1, exists1 := solved[obj.Expr1] // list type - t2, exists2 := solved[obj.Expr2Val] - if !exists1 || !exists2 { - return false, nil // not matched yet - } - if t1.Kind != types.KindList { - return false, fmt.Errorf("expected list kind") - } - if err := t1.Val.Cmp(t2); err != nil { - return false, err // inconsistent! - } - return true, nil // matched! -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *EqualityWrapListInvariant) Possible(partials []interfaces.Invariant) error { - // XXX: not implemented - return nil // safer to return nil than error -} - -// EqualityWrapMapInvariant expresses that a map in Expr1 must have keys that -// match the type of the expression in Expr2Key and values that match the type -// of the expression in Expr2Val. -type EqualityWrapMapInvariant struct { - Expr1 interfaces.Expr - Expr2Key interfaces.Expr - Expr2Val interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *EqualityWrapMapInvariant) String() string { - return fmt.Sprintf("%p == {%p: %p}", obj.Expr1, obj.Expr2Key, obj.Expr2Val) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityWrapMapInvariant) ExprList() []interfaces.Expr { - return []interfaces.Expr{obj.Expr1, obj.Expr2Key, obj.Expr2Val} -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityWrapMapInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - t1, exists1 := solved[obj.Expr1] // map type - t2, exists2 := solved[obj.Expr2Key] - t3, exists3 := solved[obj.Expr2Val] - if !exists1 || !exists2 || !exists3 { - return false, nil // not matched yet - } - if t1.Kind != types.KindMap { - return false, fmt.Errorf("expected map kind") - } - if err := t1.Key.Cmp(t2); err != nil { - return false, err // inconsistent! - } - if err := t1.Val.Cmp(t3); err != nil { - return false, err // inconsistent! - } - return true, nil // matched! -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *EqualityWrapMapInvariant) Possible(partials []interfaces.Invariant) error { - // XXX: not implemented - return nil // safer to return nil than error -} - -// EqualityWrapStructInvariant expresses that a struct in Expr1 must have fields -// that match the type of the expressions listed in Expr2Map. -type EqualityWrapStructInvariant struct { - Expr1 interfaces.Expr - Expr2Map map[string]interfaces.Expr - Expr2Ord []string -} - -// String returns a representation of this invariant. -func (obj *EqualityWrapStructInvariant) String() string { - var s = make([]string, len(obj.Expr2Ord)) - for i, k := range obj.Expr2Ord { - t, ok := obj.Expr2Map[k] - if !ok { - panic("malformed struct order") - } - if t == nil { - panic("malformed struct field") - } - s[i] = fmt.Sprintf("%s %p", k, t) - } - return fmt.Sprintf("%p == struct{%s}", obj.Expr1, strings.Join(s, "; ")) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityWrapStructInvariant) ExprList() []interfaces.Expr { - exprs := []interfaces.Expr{obj.Expr1} - for _, x := range obj.Expr2Map { - exprs = append(exprs, x) - } - return exprs -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityWrapStructInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - t1, exists1 := solved[obj.Expr1] // struct type - if !exists1 { - return false, nil // not matched yet - } - if t1.Kind != types.KindStruct { - return false, fmt.Errorf("expected struct kind") - } - - found := true // assume true - for _, key := range obj.Expr2Ord { - _, exists := t1.Map[key] - if !exists { - return false, fmt.Errorf("missing invariant struct key of: `%s`", key) - } - e, exists := obj.Expr2Map[key] - if !exists { - return false, fmt.Errorf("missing matched struct key of: `%s`", key) - } - t, exists := solved[e] - if !exists { - found = false - continue - } - if err := t1.Map[key].Cmp(t); err != nil { - return false, err // inconsistent! - } - } - - return found, nil // matched! -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *EqualityWrapStructInvariant) Possible(partials []interfaces.Invariant) error { - // XXX: not implemented - return nil // safer to return nil than error -} - -// EqualityWrapFuncInvariant expresses that a func in Expr1 must have args that -// match the type of the expressions listed in Expr2Map and a return value that -// matches the type of the expression in Expr2Out. -// TODO: should this be named EqualityWrapCallInvariant or not? -type EqualityWrapFuncInvariant struct { - Expr1 interfaces.Expr - Expr2Map map[string]interfaces.Expr - Expr2Ord []string - Expr2Out interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *EqualityWrapFuncInvariant) String() string { - var s = make([]string, len(obj.Expr2Ord)) - for i, k := range obj.Expr2Ord { - t, ok := obj.Expr2Map[k] - if !ok { - panic("malformed func order") - } - if t == nil { - panic("malformed func field") - } - s[i] = fmt.Sprintf("%s %p", k, t) - } - return fmt.Sprintf("%p == func(%s) %p", obj.Expr1, strings.Join(s, "; "), obj.Expr2Out) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityWrapFuncInvariant) ExprList() []interfaces.Expr { - exprs := []interfaces.Expr{obj.Expr1} - for _, x := range obj.Expr2Map { - exprs = append(exprs, x) - } - exprs = append(exprs, obj.Expr2Out) - return exprs -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityWrapFuncInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - t1, exists1 := solved[obj.Expr1] // func type - if !exists1 { - return false, nil // not matched yet - } - if t1.Kind != types.KindFunc { - return false, fmt.Errorf("expected func kind") - } - - found := true // assume true - for _, key := range obj.Expr2Ord { - _, exists := t1.Map[key] - if !exists { - return false, fmt.Errorf("missing invariant struct key of: `%s`", key) - } - e, exists := obj.Expr2Map[key] - if !exists { - return false, fmt.Errorf("missing matched struct key of: `%s`", key) - } - t, exists := solved[e] - if !exists { - found = false - continue - } - if err := t1.Map[key].Cmp(t); err != nil { - return false, err // inconsistent! - } - } - - t, exists := solved[obj.Expr2Out] - if !exists { - return false, nil - } - if err := t1.Out.Cmp(t); err != nil { - return false, err // inconsistent! - } - - return found, nil // matched! -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *EqualityWrapFuncInvariant) Possible(partials []interfaces.Invariant) error { - // XXX: not implemented - return nil // safer to return nil than error -} - -// EqualityWrapCallInvariant expresses that a call result that happened in Expr1 -// must match the type of the function result listed in Expr2. In this case, -// Expr2 will be a function expression, and the returned expression should match -// with the Expr1 expression, when comparing types. -// TODO: should this be named EqualityWrapFuncInvariant or not? -// TODO: should Expr1 and Expr2 be reversed??? -type EqualityWrapCallInvariant struct { - Expr1 interfaces.Expr - Expr2Func interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *EqualityWrapCallInvariant) String() string { - return fmt.Sprintf("%p == call(%p)", obj.Expr1, obj.Expr2Func) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *EqualityWrapCallInvariant) ExprList() []interfaces.Expr { - return []interfaces.Expr{obj.Expr1, obj.Expr2Func} -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *EqualityWrapCallInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - t1, exists1 := solved[obj.Expr1] // call type - t2, exists2 := solved[obj.Expr2Func] - if !exists1 || !exists2 { - return false, nil // not matched yet - } - //if t1.Kind != types.KindFunc { - // return false, fmt.Errorf("expected func kind") - //} - - if t2.Kind != types.KindFunc { - return false, fmt.Errorf("expected func kind") - } - if err := t1.Cmp(t2.Out); err != nil { - return false, err // inconsistent! - } - return true, nil // matched! -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *EqualityWrapCallInvariant) Possible(partials []interfaces.Invariant) error { - // XXX: not implemented - return nil // safer to return nil than error -} - -// ConjunctionInvariant represents a list of invariants which must all be true -// together. In other words, it's a grouping construct for a set of invariants. -type ConjunctionInvariant struct { - Invariants []interfaces.Invariant -} - -// String returns a representation of this invariant. -func (obj *ConjunctionInvariant) String() string { - var a []string - for _, x := range obj.Invariants { - s := x.String() - a = append(a, s) - } - return fmt.Sprintf("[%s]", strings.Join(a, ", ")) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *ConjunctionInvariant) ExprList() []interfaces.Expr { - exprs := []interfaces.Expr{} - for _, x := range obj.Invariants { - exprs = append(exprs, x.ExprList()...) - } - return exprs -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *ConjunctionInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - found := true // assume true - for _, invar := range obj.Invariants { - match, err := invar.Matches(solved) - if err != nil { - return false, nil - } - if !match { - found = false - } - } - return found, nil -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *ConjunctionInvariant) Possible(partials []interfaces.Invariant) error { - for _, invar := range obj.Invariants { - if err := invar.Possible(partials); err != nil { - // we found proof it's not possible - return errwrap.Wrapf(err, "not possible") - } - } - // XXX: unfortunately we didn't look for them all together with a solver - return nil -} - -// ExclusiveInvariant represents a list of invariants where one and *only* one -// should hold true. To combine multiple invariants in one of the list elements, -// you can group multiple invariants together using a ConjunctionInvariant. Do -// note that the solver might not verify that only one of the invariants in the -// list holds true, as it might choose to be lazy and pick the first solution -// found. -type ExclusiveInvariant struct { - Invariants []interfaces.Invariant -} - -// String returns a representation of this invariant. -func (obj *ExclusiveInvariant) String() string { - var a []string - for _, x := range obj.Invariants { - s := x.String() - a = append(a, s) - } - return fmt.Sprintf("[%s]", strings.Join(a, ", ")) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *ExclusiveInvariant) ExprList() []interfaces.Expr { - // XXX: We should do this if we assume that exclusives don't have some - // sort of transient expr to satisfy that doesn't disappear depending on - // which choice in the exclusive is chosen... - //exprs := []interfaces.Expr{} - //for _, x := range obj.Invariants { - // exprs = append(exprs, x.ExprList()...) - //} - //return exprs - // XXX: But if we ever specify an expr in this exclusive that isn't - // referenced anywhere else, then we'd need to use the above so that our - // type unification algorithm knows not to stop too early. - return []interfaces.Expr{} // XXX: Do we want to the set instead? -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. Because this partial invariant requires only -// one to be true, it will mask children errors, since it's normal for only one -// to be consistent. -func (obj *ExclusiveInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - found := false - reterr := fmt.Errorf("all exclusives errored") - var errs error - for _, invar := range obj.Invariants { - match, err := invar.Matches(solved) - if err != nil { - errs = errwrap.Append(errs, err) - continue - } - if !match { - // at least one was false, so we're not done here yet... - // we don't want to error yet, since we can't know there - // won't be a conflict once we get more data about this! - reterr = nil // clear the error - continue - } - if found { // we already found one - return false, fmt.Errorf("more than one exclusive solution") - } - found = true - } - - if found { // we got exactly one valid solution - return true, nil - } - - return false, errwrap.Wrapf(reterr, errwrap.String(errs)) -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation is currently not implemented! -func (obj *ExclusiveInvariant) Possible(partials []interfaces.Invariant) error { - var errs error - for _, invar := range obj.Invariants { - err := invar.Possible(partials) - if err == nil { - // we found proof it's possible - return nil - } - errs = errwrap.Append(errs, err) - } - - return errwrap.Wrapf(errs, "not possible") -} - -// simplify attempts to reduce the exclusive invariant to eliminate any -// possibilities based on the list of known partials at this time. Hopefully, -// this will weed out some of the function polymorphism possibilities so that we -// can solve the problem without recursive, combinatorial permutation, which is -// very, very slow. -func (obj *ExclusiveInvariant) simplify(partials []interfaces.Invariant) ([]interfaces.Invariant, error) { - if len(obj.Invariants) == 0 { // unexpected case - return []interfaces.Invariant{}, nil // we don't need anything! - } - - possible := []interfaces.Invariant{} - var reasons error - for _, invar := range obj.Invariants { // []interfaces.Invariant - if err := invar.Possible(partials); err != nil { - reasons = errwrap.Append(reasons, err) - continue - } - possible = append(possible, invar) - } - - if len(possible) == 0 { // nothing was possible - return nil, errwrap.Wrapf(reasons, "no possible simplifications") - } - if len(possible) == 1 { // we flattened out the exclusive! - return possible, nil - } - - if len(possible) == len(obj.Invariants) { // nothing changed - return nil, fmt.Errorf("no possible simplifications, we're unchanged") - } - - invar := &ExclusiveInvariant{ - Invariants: possible, // hopefully a smaller exclusive! - } - return []interfaces.Invariant{invar}, nil -} - // exclusivesProduct returns a list of different products produced from the // combinatorial product of the list of exclusives. Each ExclusiveInvariant must // contain between one and more Invariants. This takes every combination of @@ -912,7 +150,7 @@ func (obj *ExclusiveInvariant) simplify(partials []interfaces.Invariant) ([]inte // In other words, if you have three exclusives, with invariants named (A1, B1), // (A2), and (A3, B3, C3) you'll get: (A1, A2, A3), (A1, A2, B3), (A1, A2, C3), // (B1, A2, A3), (B1, A2, B3), (B1, A2, C3) as results for this function call. -func exclusivesProduct(exclusives []*ExclusiveInvariant) [][]interfaces.Invariant { +func exclusivesProduct(exclusives []*interfaces.ExclusiveInvariant) [][]interfaces.Invariant { if len(exclusives) == 0 { return nil } @@ -944,47 +182,10 @@ func exclusivesProduct(exclusives []*ExclusiveInvariant) [][]interfaces.Invarian return results } -// AnyInvariant is an invariant that symbolizes that the expression can be any -// type. It is sometimes used to ensure that an expr actually gets a solution -// type so that it is not left unreferenced, and as a result, unsolved. -// TODO: is there a better name than AnyInvariant -type AnyInvariant struct { - Expr interfaces.Expr -} - -// String returns a representation of this invariant. -func (obj *AnyInvariant) String() string { - return fmt.Sprintf("%p == *", obj.Expr) -} - -// ExprList returns the list of valid expressions in this invariant. -func (obj *AnyInvariant) ExprList() []interfaces.Expr { - return []interfaces.Expr{obj.Expr} -} - -// Matches returns whether an invariant matches the existing solution. If it is -// inconsistent, then it errors. -func (obj *AnyInvariant) Matches(solved map[interfaces.Expr]*types.Type) (bool, error) { - _, exists := solved[obj.Expr] // we only care that it is found. - return exists, nil -} - -// Possible returns an error if it is certain that it is NOT possible to get a -// solution with this invariant and the set of partials. In certain cases, it -// might not be able to determine that it's not possible, while simultaneously -// not being able to guarantee a possible solution either. In this situation, it -// should return nil, since this is used as a filtering mechanism, and the nil -// result of possible is preferred over eliminating a tricky, but possible one. -// This particular implementation always returns nil. -func (obj *AnyInvariant) Possible([]interfaces.Invariant) error { - // keep it simple, even though we don't technically check the inputs... - return nil -} - // InvariantSolution lists a trivial set of EqualsInvariant mappings so that you // can populate your AST with SetType calls in a simple loop. type InvariantSolution struct { - Solutions []*EqualsInvariant // list of trivial solutions for each node + Solutions []*interfaces.EqualsInvariant // list of trivial solutions for each node } // ExprList returns the list of valid expressions. This struct is not part of