lang: funcs: Add Unify method for history function

This could use some tests.
This commit is contained in:
James Shubin
2021-05-19 08:38:50 -04:00
parent 9ca2cda8c7
commit d04c7a6ae4

View File

@@ -22,6 +22,7 @@ import (
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap"
) )
const ( const (
@@ -66,6 +67,179 @@ func (obj *HistoryFunc) ArgGen(index int) (string, error) {
return seq[index], nil return seq[index], nil
} }
// Unify returns the list of invariants that this func produces.
func (obj *HistoryFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
var invariants []interfaces.Invariant
var invar interfaces.Invariant
// func(value T1, index int) T1
valueName, err := obj.ArgGen(0)
if err != nil {
return nil, err
}
indexName, err := obj.ArgGen(1)
if err != nil {
return nil, err
}
dummyValue := &interfaces.ExprAny{} // corresponds to the value type
dummyIndex := &interfaces.ExprAny{} // corresponds to the index type
dummyOut := &interfaces.ExprAny{} // corresponds to the out string
// index arg type of int
invar = &interfaces.EqualsInvariant{
Expr: dummyIndex,
Type: types.TypeInt,
}
invariants = append(invariants, invar)
// index and return are the same type
invar = &interfaces.EqualityInvariant{
Expr1: dummyValue,
Expr2: dummyOut,
}
invariants = append(invariants, invar)
// full function
mapped := make(map[string]interfaces.Expr)
ordered := []string{valueName, indexName}
mapped[valueName] = dummyValue
mapped[indexName] = dummyIndex
invar = &interfaces.EqualityWrapFuncInvariant{
Expr1: expr, // maps directly to us!
Expr2Map: mapped,
Expr2Ord: ordered,
Expr2Out: dummyOut,
}
invariants = append(invariants, invar)
// generator function
fn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
for _, invariant := range fnInvariants {
// search for this special type of invariant
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
if !ok {
continue
}
// did we find the mapping from us to ExprCall ?
if cfavInvar.Func != expr {
continue
}
// cfavInvar.Expr is the ExprCall! (the return pointer)
// cfavInvar.Args are the args that ExprCall uses!
if l := len(cfavInvar.Args); l != 2 {
return nil, fmt.Errorf("unable to build function with %d args", l)
}
var invariants []interfaces.Invariant
var invar interfaces.Invariant
// add the relationship to the returned value
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Expr,
Expr2: dummyOut,
}
invariants = append(invariants, invar)
// second arg must be an int
invar = &interfaces.EqualsInvariant{
Expr: cfavInvar.Args[1],
Type: types.TypeInt,
}
invariants = append(invariants, invar)
// add the relationships to the called args
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Args[0],
Expr2: dummyValue,
}
invariants = append(invariants, invar)
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Args[1],
Expr2: dummyIndex,
}
invariants = append(invariants, invar)
if typ, err := cfavInvar.Args[1].Type(); err == nil { // is it known?
if k := typ.Kind; k != types.KindInt {
return nil, fmt.Errorf("unable to build function with 1st arg of kind: %s", k)
}
}
// We just need to figure out one type to know the full
// type...
var t1 *types.Type // the value type
// validateArg0 checks: value T1
validateArg0 := func(typ *types.Type) error {
if typ == nil { // unknown so far
return nil
}
if err := typ.Cmp(t1); t1 != nil && err != nil {
return errwrap.Wrapf(err, "input type was inconsistent")
}
// learn!
t1 = typ
return nil
}
if typ, err := cfavInvar.Args[0].Type(); err == nil { // is it known?
// this sets t1 and t2 on success if it learned
if err := validateArg0(typ); err != nil {
return nil, errwrap.Wrapf(err, "first struct arg type is inconsistent")
}
}
if typ, exists := solved[cfavInvar.Args[0]]; exists { // alternate way to lookup type
// this sets t1 and t2 on success if it learned
if err := validateArg0(typ); err != nil {
return nil, errwrap.Wrapf(err, "first struct arg type is inconsistent")
}
}
// XXX: if the struct type/value isn't know statically?
if t1 != nil {
invar = &interfaces.EqualsInvariant{
Expr: dummyValue,
Type: t1,
}
invariants = append(invariants, invar)
invar = &interfaces.EqualsInvariant{ // bonus
Expr: dummyOut,
Type: t1,
}
invariants = append(invariants, invar)
}
// TODO: do we return this relationship with ExprCall?
invar = &interfaces.EqualityWrapCallInvariant{
// TODO: should Expr1 and Expr2 be reversed???
Expr1: cfavInvar.Expr,
//Expr2Func: cfavInvar.Func, // same as below
Expr2Func: expr,
}
invariants = append(invariants, invar)
// TODO: are there any other invariants we should build?
return invariants, nil // generator return
}
// We couldn't tell the solver anything it didn't already know!
return nil, fmt.Errorf("couldn't generate new invariants")
}
invar = &interfaces.GeneratorInvariant{
Func: fn,
}
invariants = append(invariants, invar)
return invariants, nil
}
// Polymorphisms returns the possible type signature for this function. In this // Polymorphisms returns the possible type signature for this function. In this
// case, since the number of possible types for the first arg can be infinite, // case, since the number of possible types for the first arg can be infinite,
// it returns the final precise type only if it can be gleamed statically. If // it returns the final precise type only if it can be gleamed statically. If