diff --git a/lang/funcs/maplookup_polyfunc.go b/lang/funcs/maplookup_polyfunc.go index ab1b9eac..519dc74f 100644 --- a/lang/funcs/maplookup_polyfunc.go +++ b/lang/funcs/maplookup_polyfunc.go @@ -61,6 +61,304 @@ func (obj *MapLookupPolyFunc) ArgGen(index int) (string, error) { 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 // this static polymorphic function. It relies on type and value hints to limit // the number of returned possibilities. diff --git a/lang/interpret_test/TestAstFunc2/maplookup0.output b/lang/interpret_test/TestAstFunc2/maplookup0.output new file mode 100644 index 00000000..78d7568f --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/maplookup0.output @@ -0,0 +1,4 @@ +Vertex: test[hello1] +Vertex: test[hello3] +Vertex: test[world2] +Vertex: test[world4] diff --git a/lang/interpret_test/TestAstFunc2/maplookup0/main.mcl b/lang/interpret_test/TestAstFunc2/maplookup0/main.mcl new file mode 100644 index 00000000..a644e654 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/maplookup0/main.mcl @@ -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") {}