lang: funcs: Add Unify method for maplookup function

This also adds a few tests.
This commit is contained in:
James Shubin
2021-05-19 08:02:11 -04:00
parent fbd93ecf0d
commit 97baad4cb1
3 changed files with 313 additions and 0 deletions

View File

@@ -61,6 +61,304 @@ func (obj *MapLookupPolyFunc) 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 *MapLookupPolyFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
var invariants []interfaces.Invariant
var invar interfaces.Invariant
// func(map T1, key T2, default T3) T3
// (map: T2 => T3)
mapName, err := obj.ArgGen(0)
if err != nil {
return nil, err
}
keyName, err := obj.ArgGen(1)
if err != nil {
return nil, err
}
defaultName, err := obj.ArgGen(2)
if err != nil {
return nil, err
}
dummyMap := &interfaces.ExprAny{} // corresponds to the map type
dummyKey := &interfaces.ExprAny{} // corresponds to the key type
dummyDefault := &interfaces.ExprAny{} // corresponds to the default type
dummyOut := &interfaces.ExprAny{} // corresponds to the out string
// default type and out are the same
invar = &interfaces.EqualityInvariant{
Expr1: dummyDefault,
Expr2: dummyOut,
}
invariants = append(invariants, invar)
// relationship between T1, T2 and T3
invar = &interfaces.EqualityWrapMapInvariant{
Expr1: dummyMap,
Expr2Key: dummyKey,
Expr2Val: dummyDefault,
}
invariants = append(invariants, invar)
// full function
mapped := make(map[string]interfaces.Expr)
ordered := []string{mapName, keyName, defaultName}
mapped[mapName] = dummyMap
mapped[keyName] = dummyKey
mapped[defaultName] = dummyDefault
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 != 3 {
return nil, fmt.Errorf("unable to build function with %d args", l)
}
// add the relationship to the returned value
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Expr,
Expr2: dummyOut,
}
invariants = append(invariants, invar)
// add the relationships to the called args
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Args[0],
Expr2: dummyMap,
}
invariants = append(invariants, invar)
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Args[1],
Expr2: dummyKey,
}
invariants = append(invariants, invar)
invar = &interfaces.EqualityInvariant{
Expr1: cfavInvar.Args[2],
Expr2: dummyDefault,
}
invariants = append(invariants, invar)
var invariants []interfaces.Invariant
var invar interfaces.Invariant
// If we figure out all of these three types, we'll
// know the full type...
var t1 *types.Type // map type
var t2 *types.Type // map key type
var t3 *types.Type // map val type
// validateArg0 checks: map T1
validateArg0 := func(typ *types.Type) error {
if typ == nil { // unknown so far
return nil
}
// we happen to have a map!
if k := typ.Kind; k != types.KindMap {
return fmt.Errorf("unable to build function with 0th arg of kind: %s", k)
}
if typ.Key == nil || typ.Val == nil {
// programming error
return fmt.Errorf("map is missing type")
}
if err := typ.Cmp(t1); t1 != nil && err != nil {
return errwrap.Wrapf(err, "input type was inconsistent")
}
if err := typ.Key.Cmp(t2); t2 != nil && err != nil {
return errwrap.Wrapf(err, "input key type was inconsistent")
}
if err := typ.Val.Cmp(t3); t3 != nil && err != nil {
return errwrap.Wrapf(err, "input val type was inconsistent")
}
// learn!
t1 = typ
t2 = typ.Key
t3 = typ.Val
return nil
}
// validateArg1 checks: map key T2
validateArg1 := func(typ *types.Type) error {
if typ == nil { // unknown so far
return nil
}
if err := typ.Cmp(t2); t2 != nil && err != nil {
return errwrap.Wrapf(err, "input key type was inconsistent")
}
if t1 != nil {
if err := typ.Cmp(t1.Key); err != nil {
return errwrap.Wrapf(err, "input key type was inconsistent")
}
}
if t3 != nil {
t := &types.Type{ // build t1
Kind: types.KindMap,
Key: typ, // t2
Val: t3,
}
if err := t.Cmp(t1); t1 != nil && err != nil {
return errwrap.Wrapf(err, "input type was inconsistent")
}
t1 = t // learn!
}
// learn!
t2 = typ
return nil
}
// validateArg2 checks: map val T3
validateArg2 := func(typ *types.Type) error {
if typ == nil { // unknown so far
return nil
}
if err := typ.Cmp(t3); t3 != nil && err != nil {
return errwrap.Wrapf(err, "input val type was inconsistent")
}
if t1 != nil {
if err := typ.Cmp(t1.Val); err != nil {
return errwrap.Wrapf(err, "input val type was inconsistent")
}
}
if t2 != nil {
t := &types.Type{ // build t1
Kind: types.KindMap,
Key: t2,
Val: typ, // t3
}
if err := t.Cmp(t1); t1 != nil && err != nil {
return errwrap.Wrapf(err, "input type was inconsistent")
}
t1 = t // learn!
}
// learn!
t3 = typ
return nil
}
if typ, err := cfavInvar.Args[0].Type(); err == nil { // is it known?
// this sets t1 and t2 and t3 on success if it learned
if err := validateArg0(typ); err != nil {
return nil, errwrap.Wrapf(err, "first map arg type is inconsistent")
}
}
if typ, exists := solved[cfavInvar.Args[0]]; exists { // alternate way to lookup type
// this sets t1 and t2 and t3 on success if it learned
if err := validateArg0(typ); err != nil {
return nil, errwrap.Wrapf(err, "first map arg type is inconsistent")
}
}
if typ, err := cfavInvar.Args[1].Type(); err == nil { // is it known?
// this sets t2 (and sometimes t1) on success if it learned
if err := validateArg1(typ); err != nil {
return nil, errwrap.Wrapf(err, "second key arg type is inconsistent")
}
}
if typ, exists := solved[cfavInvar.Args[1]]; exists { // alternate way to lookup type
// this sets t2 (and sometimes t1) on success if it learned
if err := validateArg1(typ); err != nil {
return nil, errwrap.Wrapf(err, "second key arg type is inconsistent")
}
}
if typ, err := cfavInvar.Args[2].Type(); err == nil { // is it known?
// this sets t3 (and sometimes t1) on success if it learned
if err := validateArg2(typ); err != nil {
return nil, errwrap.Wrapf(err, "third default arg type is inconsistent")
}
}
if typ, exists := solved[cfavInvar.Args[2]]; exists { // alternate way to lookup type
// this sets t3 (and sometimes t1) on success if it learned
if err := validateArg2(typ); err != nil {
return nil, errwrap.Wrapf(err, "third default arg type is inconsistent")
}
}
// XXX: if the types aren't know statically?
if t1 != nil {
invar := &interfaces.EqualsInvariant{
Expr: dummyMap,
Type: t1,
}
invariants = append(invariants, invar)
}
if t2 != nil {
invar := &interfaces.EqualsInvariant{
Expr: dummyKey,
Type: t2,
}
invariants = append(invariants, invar)
}
if t3 != nil {
invar := &interfaces.EqualsInvariant{
Expr: dummyDefault,
Type: t3,
}
invariants = append(invariants, invar)
}
// XXX: if t{1..3} are missing, we could also return a
// new generator for later if we learn new information,
// but we'd have to be careful to not do the infinitely
// 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 list of possible function signatures available for // Polymorphisms returns the list of possible function signatures available for
// this static polymorphic function. It relies on type and value hints to limit // this static polymorphic function. It relies on type and value hints to limit
// the number of returned possibilities. // the number of returned possibilities.

View File

@@ -0,0 +1,4 @@
Vertex: test[hello1]
Vertex: test[hello3]
Vertex: test[world2]
Vertex: test[world4]

View File

@@ -0,0 +1,11 @@
$map1 map{int: str} = {42 => "hello1",}
test maplookup($map1, 42, "not found") {}
$map2 map{int: str} = {42 => "hello2",}
test maplookup($map2, 13, "world2") {}
$map3 = {42 => "hello3",}
test maplookup($map3, 42, "not found") {}
$map4 = {42 => "hello4",}
test maplookup($map4, 13, "world4") {}