diff --git a/lang/ast/structs.go b/lang/ast/structs.go index 95871785..3948f927 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -6938,9 +6938,12 @@ func (obj *ExprFunc) SetType(typ *types.Type) error { if obj.Function != nil { polyFn, ok := obj.function.(interfaces.PolyFunc) // is it statically polymorphic? if ok { - if err := polyFn.Build(typ); err != nil { + newTyp, err := polyFn.Build(typ) + if err != nil { return errwrap.Wrapf(err, "could not build expr func") } + // Cmp doesn't compare arg names. + typ = newTyp // check it's compatible down below... } } diff --git a/lang/funcs/contains_func.go b/lang/funcs/contains_func.go index e0b21d39..954357b1 100644 --- a/lang/funcs/contains_func.go +++ b/lang/funcs/contains_func.go @@ -41,6 +41,8 @@ func init() { Register(ContainsFuncName, func() interfaces.Func { return &ContainsFunc{} }) // must register the func and name } +var _ interfaces.PolyFunc = &ContainsFunc{} // ensure it meets this expectation + // ContainsFunc returns true if a value is found in a list. Otherwise false. type ContainsFunc struct { Type *types.Type // this is the type of value stored in our list @@ -291,46 +293,46 @@ func (obj *ContainsFunc) Polymorphisms(partialType *types.Type, partialValues [] // and must be run before Info() and any of the other Func interface methods are // used. This function is idempotent, as long as the arg isn't changed between // runs. -func (obj *ContainsFunc) Build(typ *types.Type) error { +func (obj *ContainsFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 2 { - return fmt.Errorf("the contains function needs exactly two args") + return nil, fmt.Errorf("the contains function needs exactly two args") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } tNeedle, exists := typ.Map[typ.Ord[0]] if !exists || tNeedle == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } tHaystack, exists := typ.Map[typ.Ord[1]] if !exists || tHaystack == nil { - return fmt.Errorf("second arg must be specified") + return nil, fmt.Errorf("second arg must be specified") } if tHaystack.Kind != types.KindList { - return fmt.Errorf("second argument must be of kind list") + return nil, fmt.Errorf("second argument must be of kind list") } if err := tHaystack.Val.Cmp(tNeedle); err != nil { - return errwrap.Wrapf(err, "type of first arg must match type of list elements in second arg") + return nil, errwrap.Wrapf(err, "type of first arg must match type of list elements in second arg") } if err := typ.Out.Cmp(types.TypeBool); err != nil { - return errwrap.Wrapf(err, "return type must be a boolean") + return nil, errwrap.Wrapf(err, "return type must be a boolean") } obj.Type = tNeedle // type of value stored in our list - return nil + return obj.sig(), nil } // Validate tells us if the input struct takes a valid form. @@ -346,8 +348,7 @@ func (obj *ContainsFunc) Validate() error { func (obj *ContainsFunc) Info() *interfaces.Info { var sig *types.Type if obj.Type != nil { // don't panic if called speculatively - s := obj.Type.String() - sig = types.NewType(fmt.Sprintf("func(%s %s, %s []%s) bool", containsArgNameNeedle, s, containsArgNameHaystack, s)) + sig = obj.sig() // helper } return &interfaces.Info{ Pure: true, @@ -357,6 +358,12 @@ func (obj *ContainsFunc) Info() *interfaces.Info { } } +// helper +func (obj *ContainsFunc) sig() *types.Type { + s := obj.Type.String() + return types.NewType(fmt.Sprintf("func(%s %s, %s []%s) bool", containsArgNameNeedle, s, containsArgNameHaystack, s)) +} + // Init runs some startup code for this function. func (obj *ContainsFunc) Init(init *interfaces.Init) error { obj.init = init diff --git a/lang/funcs/core/fmt/printf_func.go b/lang/funcs/core/fmt/printf_func.go index 27965287..2ca27150 100644 --- a/lang/funcs/core/fmt/printf_func.go +++ b/lang/funcs/core/fmt/printf_func.go @@ -53,6 +53,8 @@ func init() { funcs.ModuleRegister(ModuleName, PrintfFuncName, func() interfaces.Func { return &PrintfFunc{} }) } +var _ interfaces.PolyFunc = &PrintfFunc{} // ensure it meets this expectation + // PrintfFunc is a static polymorphic function that compiles a format string and // returns the output as a string. It bases its output on the values passed in // to it. It examines the type of the arguments at compile time and then @@ -437,33 +439,49 @@ func (obj *PrintfFunc) Polymorphisms(partialType *types.Type, partialValues []ty // Build takes the now known function signature and stores it so that this // function can appear to be static. That type is used to build our function // statically. -func (obj *PrintfFunc) Build(typ *types.Type) error { +func (obj *PrintfFunc) Build(typ *types.Type) (*types.Type, error) { if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) < 1 { - return fmt.Errorf("the printf function needs at least one arg") + return nil, fmt.Errorf("the printf function needs at least one arg") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Out.Cmp(types.TypeStr) != nil { - return fmt.Errorf("return type of function must be an str") + return nil, fmt.Errorf("return type of function must be an str") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } t0, exists := typ.Map[typ.Ord[0]] if !exists || t0 == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } if t0.Cmp(types.TypeStr) != nil { - return fmt.Errorf("first arg for printf must be an str") + return nil, fmt.Errorf("first arg for printf must be an str") } - obj.Type = typ // function type is now known! - return nil + //newTyp := typ.Copy() + newTyp := &types.Type{ + Kind: typ.Kind, // copy + Map: make(map[string]*types.Type), // new + Ord: []string{}, // new + Out: typ.Out, // copy + } + for i, x := range typ.Ord { // remap arg names + argName, err := obj.ArgGen(i) + if err != nil { + return nil, err + } + newTyp.Map[argName] = typ.Map[x] + newTyp.Ord = append(newTyp.Ord, argName) + } + + obj.Type = newTyp // function type is now known! + return obj.Type, nil } // Validate makes sure we've built our struct properly. It is usually unused for diff --git a/lang/funcs/core/iter/map_func.go b/lang/funcs/core/iter/map_func.go index c09eb818..28f16f48 100644 --- a/lang/funcs/core/iter/map_func.go +++ b/lang/funcs/core/iter/map_func.go @@ -452,66 +452,67 @@ func (obj *MapFunc) Polymorphisms(partialType *types.Type, partialValues []types // and must be run before Info() and any of the other Func interface methods are // used. This function is idempotent, as long as the arg isn't changed between // runs. -func (obj *MapFunc) Build(typ *types.Type) error { +func (obj *MapFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 2 { - return fmt.Errorf("the map needs exactly two args") + return nil, fmt.Errorf("the map needs exactly two args") } if typ.Map == nil { - return fmt.Errorf("the map is nil") + return nil, fmt.Errorf("the map is nil") } tInputs, exists := typ.Map[typ.Ord[0]] if !exists || tInputs == nil { - return fmt.Errorf("first argument was missing") + return nil, fmt.Errorf("first argument was missing") } tFunction, exists := typ.Map[typ.Ord[1]] if !exists || tFunction == nil { - return fmt.Errorf("second argument was missing") + return nil, fmt.Errorf("second argument was missing") } if tInputs.Kind != types.KindList { - return fmt.Errorf("first argument must be of kind list") + return nil, fmt.Errorf("first argument must be of kind list") } if tFunction.Kind != types.KindFunc { - return fmt.Errorf("second argument must be of kind func") + return nil, fmt.Errorf("second argument must be of kind func") } if typ.Out == nil { - return fmt.Errorf("return type must be specified") + return nil, fmt.Errorf("return type must be specified") } if typ.Out.Kind != types.KindList { - return fmt.Errorf("return argument must be a list") + return nil, fmt.Errorf("return argument must be a list") } if len(tFunction.Ord) != 1 { - return fmt.Errorf("the functions map needs exactly one arg") + return nil, fmt.Errorf("the functions map needs exactly one arg") } if tFunction.Map == nil { - return fmt.Errorf("the functions map is nil") + return nil, fmt.Errorf("the functions map is nil") } tArg, exists := tFunction.Map[tFunction.Ord[0]] if !exists || tArg == nil { - return fmt.Errorf("the functions first argument was missing") + return nil, fmt.Errorf("the functions first argument was missing") } if err := tArg.Cmp(tInputs.Val); err != nil { - return errwrap.Wrapf(err, "the functions arg type must match the input list contents type") + return nil, errwrap.Wrapf(err, "the functions arg type must match the input list contents type") } if tFunction.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if err := tFunction.Out.Cmp(typ.Out.Val); err != nil { - return errwrap.Wrapf(err, "return type of function must match returned list contents type") + return nil, errwrap.Wrapf(err, "return type of function must match returned list contents type") } obj.Type = tInputs.Val // or tArg obj.RType = tFunction.Out // or typ.Out.Val - return nil + + return obj.sig(), nil } // Validate tells us if the input struct takes a valid form. @@ -525,6 +526,18 @@ func (obj *MapFunc) Validate() error { // Info returns some static info about itself. Build must be called before this // will return correct data. func (obj *MapFunc) Info() *interfaces.Info { + sig := obj.sig() // helper + + return &interfaces.Info{ + Pure: false, // TODO: what if the input function isn't pure? + Memo: false, + Sig: sig, + Err: obj.Validate(), + } +} + +// helper +func (obj *MapFunc) sig() *types.Type { // TODO: what do we put if this is unknown? tIi := types.TypeVariant if obj.Type != nil { @@ -542,14 +555,7 @@ func (obj *MapFunc) Info() *interfaces.Info { tF := types.NewType(fmt.Sprintf("func(%s) %s", tIi.String(), tOi.String())) s := fmt.Sprintf("func(%s %s, %s %s) %s", argNameInputs, tI, argNameFunction, tF, tO) - typ := types.NewType(s) // yay! - - return &interfaces.Info{ - Pure: false, // TODO: what if the input function isn't pure? - Memo: false, - Sig: typ, - Err: obj.Validate(), - } + return types.NewType(s) // yay! } // Init runs some startup code for this function. diff --git a/lang/funcs/core/template_func.go b/lang/funcs/core/template_func.go index 75b05bef..5db52482 100644 --- a/lang/funcs/core/template_func.go +++ b/lang/funcs/core/template_func.go @@ -55,6 +55,8 @@ func init() { funcs.Register(TemplateFuncName, func() interfaces.Func { return &TemplateFunc{} }) } +var _ interfaces.PolyFunc = &TemplateFunc{} // ensure it meets this expectation + // TemplateFunc is a static polymorphic function that compiles a template and // returns the output as a string. It bases its output on the values passed in // to it. It examines the type of the second argument (the input data vars) at @@ -66,9 +68,8 @@ type TemplateFunc struct { // Type is the type of the input vars (2nd) arg if one is specified. Nil // is the special undetermined value that is used before type is known. Type *types.Type // type of vars - // NoVars is set to true instead of specifying Type if we have a boring - // template that takes no args. - NoVars bool + + built bool // was this function built yet? init *interfaces.Init last types.Value // last value received to use for diff @@ -296,50 +297,51 @@ func (obj *TemplateFunc) Polymorphisms(partialType *types.Type, partialValues [] // function can appear to be static. It extracts the type of the vars argument, // which is the dynamic part which can change. That type is used to build our // function statically. -func (obj *TemplateFunc) Build(typ *types.Type) error { +func (obj *TemplateFunc) Build(typ *types.Type) (*types.Type, error) { if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 2 && len(typ.Ord) != 1 { - return fmt.Errorf("the template function needs exactly one or two args") + return nil, fmt.Errorf("the template function needs exactly one or two args") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Out.Cmp(types.TypeStr) != nil { - return fmt.Errorf("return type of function must be an str") + return nil, fmt.Errorf("return type of function must be an str") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } t0, exists := typ.Map[typ.Ord[0]] if !exists || t0 == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } if t0.Cmp(types.TypeStr) != nil { - return fmt.Errorf("first arg for template must be an str") + return nil, fmt.Errorf("first arg for template must be an str") } if len(typ.Ord) == 1 { // no args being passed in (boring template) - obj.NoVars = true - return nil + obj.built = true + return obj.sig(), nil } t1, exists := typ.Map[typ.Ord[1]] if !exists || t1 == nil { - return fmt.Errorf("second arg must be specified") + return nil, fmt.Errorf("second arg must be specified") } obj.Type = t1 // extracted vars type is now known! - return nil + obj.built = true + return obj.sig(), nil } // Validate makes sure we've built our struct properly. It is usually unused for // normal functions that users can use directly. func (obj *TemplateFunc) Validate() error { - if obj.Type == nil && !obj.NoVars { // build must be run first - return fmt.Errorf("type is still unspecified") + if !obj.built { + return fmt.Errorf("function wasn't built yet") } return nil } @@ -347,13 +349,8 @@ func (obj *TemplateFunc) Validate() error { // Info returns some static info about itself. func (obj *TemplateFunc) Info() *interfaces.Info { var sig *types.Type - if obj.NoVars { - str := fmt.Sprintf("func(%s str) str", templateArgNameTemplate) - sig = types.NewType(str) - - } else if obj.Type != nil { // don't panic if called speculatively - str := fmt.Sprintf("func(%s str, %s %s) str", templateArgNameTemplate, templateArgNameVars, obj.Type.String()) - sig = types.NewType(str) + if obj.built { + sig = obj.sig() // helper } return &interfaces.Info{ Pure: true, @@ -363,6 +360,17 @@ func (obj *TemplateFunc) Info() *interfaces.Info { } } +// helper +func (obj *TemplateFunc) sig() *types.Type { + if obj.Type != nil { // don't panic if called speculatively + str := fmt.Sprintf("func(%s str, %s %s) str", templateArgNameTemplate, templateArgNameVars, obj.Type.String()) + return types.NewType(str) + } + + str := fmt.Sprintf("func(%s str) str", templateArgNameTemplate) + return types.NewType(str) +} + // Init runs some startup code for this function. func (obj *TemplateFunc) Init(init *interfaces.Init) error { obj.init = init diff --git a/lang/funcs/core/world/schedule_func.go b/lang/funcs/core/world/schedule_func.go index 2476a803..bb32d345 100644 --- a/lang/funcs/core/world/schedule_func.go +++ b/lang/funcs/core/world/schedule_func.go @@ -65,6 +65,8 @@ func init() { funcs.ModuleRegister(ModuleName, ScheduleFuncName, func() interfaces.Func { return &ScheduleFunc{} }) } +var _ interfaces.PolyFunc = &ScheduleFunc{} // ensure it meets this expectation + // ScheduleFunc is special function which determines where code should run in // the cluster. type ScheduleFunc struct { @@ -398,43 +400,43 @@ func (obj *ScheduleFunc) Polymorphisms(partialType *types.Type, partialValues [] // and must be run before Info() and any of the other Func interface methods are // used. This function is idempotent, as long as the arg isn't changed between // runs. -func (obj *ScheduleFunc) Build(typ *types.Type) error { +func (obj *ScheduleFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 1 && len(typ.Ord) != 2 { - return fmt.Errorf("the schedule function needs either one or two args") + return nil, fmt.Errorf("the schedule function needs either one or two args") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } if err := typ.Out.Cmp(types.NewType("[]str")); err != nil { - return errwrap.Wrapf(err, "return type must be a list of strings") + return nil, errwrap.Wrapf(err, "return type must be a list of strings") } tNamespace, exists := typ.Map[typ.Ord[0]] if !exists || tNamespace == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } if len(typ.Ord) == 1 { obj.Type = nil obj.built = true - return nil // done early, 2nd arg is absent! + return obj.sig(), nil // done early, 2nd arg is absent! } tOpts, exists := typ.Map[typ.Ord[1]] if !exists || tOpts == nil { - return fmt.Errorf("second argument was missing") + return nil, fmt.Errorf("second argument was missing") } if tOpts.Kind != types.KindStruct { - return fmt.Errorf("second argument must be of kind struct") + return nil, fmt.Errorf("second argument must be of kind struct") } validOpts := obj.validOpts() @@ -445,11 +447,11 @@ func (obj *ScheduleFunc) Build(typ *types.Type) error { t := tOpts.Map[name] value, exists := validOpts[name] if !exists { - return fmt.Errorf("unexpected opts field: `%s`", name) + return nil, fmt.Errorf("unexpected opts field: `%s`", name) } if err := t.Cmp(value); err != nil { - return errwrap.Wrapf(err, "expected different type for opts field: `%s`", name) + return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name) } } @@ -470,14 +472,14 @@ func (obj *ScheduleFunc) Build(typ *types.Type) error { // if it exists, check the type if err := t.Cmp(value); err != nil { - return errwrap.Wrapf(err, "expected different type for opts field: `%s`", name) + return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name) } } } obj.Type = tOpts // type of opts struct, even an empty: `struct{}` obj.built = true - return nil + return obj.sig(), nil } // Validate tells us if the input struct takes a valid form. @@ -497,22 +499,28 @@ func (obj *ScheduleFunc) Validate() error { func (obj *ScheduleFunc) Info() *interfaces.Info { // It's important that you don't return a non-nil sig if this is called // before you're built. Type unification may call it opportunistically. - var typ *types.Type + var sig *types.Type if obj.built { - typ = types.NewType(fmt.Sprintf("func(%s str) []str", scheduleArgNameNamespace)) // simplest form - if obj.Type != nil { - typ = types.NewType(fmt.Sprintf("func(%s str, %s %s) []str", scheduleArgNameNamespace, scheduleArgNameOpts, obj.Type.String())) - } + sig = obj.sig() // helper } return &interfaces.Info{ Pure: false, // definitely false Memo: false, // output is list of hostnames chosen - Sig: typ, // func kind + Sig: sig, // func kind Err: obj.Validate(), } } +// helper +func (obj *ScheduleFunc) sig() *types.Type { + sig := types.NewType(fmt.Sprintf("func(%s str) []str", scheduleArgNameNamespace)) // simplest form + if obj.Type != nil { + sig = types.NewType(fmt.Sprintf("func(%s str, %s %s) []str", scheduleArgNameNamespace, scheduleArgNameOpts, obj.Type.String())) + } + return sig +} + // Init runs some startup code for this function. func (obj *ScheduleFunc) Init(init *interfaces.Init) error { obj.init = init diff --git a/lang/funcs/history_func.go b/lang/funcs/history_func.go index c5fbb9c8..9614601a 100644 --- a/lang/funcs/history_func.go +++ b/lang/funcs/history_func.go @@ -40,6 +40,8 @@ func init() { Register(HistoryFuncName, func() interfaces.Func { return &HistoryFunc{} }) // must register the func and name } +var _ interfaces.PolyFunc = &HistoryFunc{} // ensure it meets this expectation + // HistoryFunc is special function which returns the Nth oldest value seen. It // must store up incoming values until it gets enough to return the desired one. // A restart of the program, will expunge the stored state. This obviously takes @@ -299,35 +301,35 @@ func (obj *HistoryFunc) Polymorphisms(partialType *types.Type, partialValues []t // Build takes the now known function signature and stores it so that this // function can appear to be static. That type is used to build our function // statically. -func (obj *HistoryFunc) Build(typ *types.Type) error { +func (obj *HistoryFunc) Build(typ *types.Type) (*types.Type, error) { if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 2 { - return fmt.Errorf("the history function needs exactly two args") + return nil, fmt.Errorf("the history function needs exactly two args") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } t1, exists := typ.Map[typ.Ord[1]] if !exists || t1 == nil { - return fmt.Errorf("second arg must be specified") + return nil, fmt.Errorf("second arg must be specified") } if t1.Cmp(types.TypeInt) != nil { - return fmt.Errorf("second arg for history must be an int") + return nil, fmt.Errorf("second arg for history must be an int") } t0, exists := typ.Map[typ.Ord[0]] if !exists || t0 == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } obj.Type = t0 // type of historical value is now known! - return nil + return obj.sig(), nil } // Validate makes sure we've built our struct properly. It is usually unused for @@ -343,8 +345,7 @@ func (obj *HistoryFunc) Validate() error { func (obj *HistoryFunc) Info() *interfaces.Info { var sig *types.Type if obj.Type != nil { // don't panic if called speculatively - s := obj.Type.String() - sig = types.NewType(fmt.Sprintf("func(%s %s, %s int) %s", historyArgNameValue, s, historyArgNameIndex, s)) + sig = obj.sig() // helper } return &interfaces.Info{ Pure: false, // definitely false @@ -354,6 +355,12 @@ func (obj *HistoryFunc) Info() *interfaces.Info { } } +// helper +func (obj *HistoryFunc) sig() *types.Type { + s := obj.Type.String() + return types.NewType(fmt.Sprintf("func(%s %s, %s int) %s", historyArgNameValue, s, historyArgNameIndex, s)) +} + // Init runs some startup code for this function. func (obj *HistoryFunc) Init(init *interfaces.Init) error { obj.init = init diff --git a/lang/funcs/maplookup_func.go b/lang/funcs/maplookup_func.go index 40c1faac..7785d47b 100644 --- a/lang/funcs/maplookup_func.go +++ b/lang/funcs/maplookup_func.go @@ -42,6 +42,8 @@ func init() { Register(MapLookupFuncName, func() interfaces.Func { return &MapLookupFunc{} }) // must register the func and name } +var _ interfaces.PolyFunc = &MapLookupFunc{} // ensure it meets this expectation + // MapLookupFunc is a key map lookup function. type MapLookupFunc struct { Type *types.Type // Kind == Map, that is used as the map we lookup @@ -467,51 +469,51 @@ func (obj *MapLookupFunc) Polymorphisms(partialType *types.Type, partialValues [ // and must be run before Info() and any of the other Func interface methods are // used. This function is idempotent, as long as the arg isn't changed between // runs. -func (obj *MapLookupFunc) Build(typ *types.Type) error { +func (obj *MapLookupFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 3 { - return fmt.Errorf("the maplookup function needs exactly three args") + return nil, fmt.Errorf("the maplookup function needs exactly three args") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } tMap, exists := typ.Map[typ.Ord[0]] if !exists || tMap == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } tKey, exists := typ.Map[typ.Ord[1]] if !exists || tKey == nil { - return fmt.Errorf("second arg must be specified") + return nil, fmt.Errorf("second arg must be specified") } tDef, exists := typ.Map[typ.Ord[2]] if !exists || tDef == nil { - return fmt.Errorf("third arg must be specified") + return nil, fmt.Errorf("third arg must be specified") } if err := tMap.Key.Cmp(tKey); err != nil { - return errwrap.Wrapf(err, "key must match map key type") + return nil, errwrap.Wrapf(err, "key must match map key type") } if err := tMap.Val.Cmp(tDef); err != nil { - return errwrap.Wrapf(err, "default must match map val type") + return nil, errwrap.Wrapf(err, "default must match map val type") } if err := tMap.Val.Cmp(typ.Out); err != nil { - return errwrap.Wrapf(err, "return type must match map val type") + return nil, errwrap.Wrapf(err, "return type must match map val type") } obj.Type = tMap // map type - return nil + return obj.sig(), nil } // Validate tells us if the input struct takes a valid form. @@ -528,21 +530,26 @@ func (obj *MapLookupFunc) Validate() error { // Info returns some static info about itself. Build must be called before this // will return correct data. func (obj *MapLookupFunc) Info() *interfaces.Info { - var typ *types.Type + var sig *types.Type if obj.Type != nil { // don't panic if called speculatively // TODO: can obj.Type.Key or obj.Type.Val be nil (a partial) ? - k := obj.Type.Key.String() - v := obj.Type.Val.String() - typ = types.NewType(fmt.Sprintf("func(%s %s, %s %s, %s %s) %s", mapLookupArgNameMap, obj.Type.String(), mapLookupArgNameKey, k, mapLookupArgNameDef, v, v)) + sig = obj.sig() // helper } return &interfaces.Info{ Pure: true, Memo: false, - Sig: typ, // func kind + Sig: sig, // func kind Err: obj.Validate(), } } +// helper +func (obj *MapLookupFunc) sig() *types.Type { + k := obj.Type.Key.String() + v := obj.Type.Val.String() + return types.NewType(fmt.Sprintf("func(%s %s, %s %s, %s %s) %s", mapLookupArgNameMap, obj.Type.String(), mapLookupArgNameKey, k, mapLookupArgNameDef, v, v)) +} + // Init runs some startup code for this function. func (obj *MapLookupFunc) Init(init *interfaces.Init) error { obj.init = init diff --git a/lang/funcs/operator_func.go b/lang/funcs/operator_func.go index 99b34482..00db6a23 100644 --- a/lang/funcs/operator_func.go +++ b/lang/funcs/operator_func.go @@ -336,6 +336,8 @@ func init() { Register(OperatorFuncName, func() interfaces.Func { return &OperatorFunc{} }) // must register the func and name } +var _ interfaces.PolyFunc = &OperatorFunc{} // ensure it meets this expectation + // OperatorFuncs maps an operator to a list of callable function values. var OperatorFuncs = make(map[string][]*types.FuncValue) // must initialize @@ -791,17 +793,46 @@ func (obj *OperatorFunc) Polymorphisms(partialType *types.Type, partialValues [] // and must be run before Info() and any of the other Func interface methods are // used. This function is idempotent, as long as the arg isn't changed between // runs. -func (obj *OperatorFunc) Build(typ *types.Type) error { +func (obj *OperatorFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if len(typ.Ord) < 1 { - return fmt.Errorf("the operator function needs at least 1 arg") + return nil, fmt.Errorf("the operator function needs at least 1 arg") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") + } + if typ.Kind != types.KindFunc { + return nil, fmt.Errorf("unexpected build kind of: %v", typ.Kind) } - obj.Type = typ // func type - return nil + // Change arg names to be what we expect... + if _, exists := typ.Map[typ.Ord[0]]; !exists { + return nil, fmt.Errorf("invalid build type") + } + + //newTyp := typ.Copy() + newTyp := &types.Type{ + Kind: typ.Kind, // copy + Map: make(map[string]*types.Type), // new + Ord: []string{}, // new + Out: typ.Out, // copy + } + for i, x := range typ.Ord { // remap arg names + //argName := util.NumToAlpha(i - 1) + //if i == 0 { + // argName = operatorArgName + //} + argName, err := obj.ArgGen(i) + if err != nil { + return nil, err + } + + newTyp.Map[argName] = typ.Map[x] + newTyp.Ord = append(newTyp.Ord, argName) + } + + obj.Type = newTyp // func type + return obj.Type, nil } // Validate tells us if the input struct takes a valid form. diff --git a/lang/funcs/simplepoly/simplepoly.go b/lang/funcs/simplepoly/simplepoly.go index 2f9fba79..26102cd1 100644 --- a/lang/funcs/simplepoly/simplepoly.go +++ b/lang/funcs/simplepoly/simplepoly.go @@ -128,6 +128,8 @@ func consistentArgs(fns []*types.FuncValue) ([]string, error) { return seq, nil } +var _ interfaces.PolyFunc = &WrappedFunc{} // ensure it meets this expectation + // WrappedFunc is a scaffolding function struct which fulfills the boiler-plate // for the function API, but that can run a very simple, static, pure, // polymorphic function. @@ -478,23 +480,32 @@ func (obj *WrappedFunc) Polymorphisms(partialType *types.Type, partialValues []t // specific statically typed version. It is usually run after Unify completes, // and must be run before Info() and any of the other Func interface methods are // used. -func (obj *WrappedFunc) Build(typ *types.Type) error { +func (obj *WrappedFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... index, err := langutil.FnMatch(typ, obj.Fns) if err != nil { - return err + return nil, err } - obj.buildFunction(typ, index) // found match at this index + newTyp := obj.buildFunction(typ, index) // found match at this index - return nil + return newTyp, nil } // buildFunction builds our concrete static function, from the potentially // abstract, possibly variant containing list of functions. -func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) { - obj.fn = obj.Fns[ix].Copy().(*types.FuncValue) - obj.fn.T = typ.Copy() // overwrites any contained "variant" type +func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) *types.Type { + cp := obj.Fns[ix].Copy() + fn, ok := cp.(*types.FuncValue) + if !ok { + panic("unexpected type") + } + obj.fn = fn + if obj.fn.T == nil { // XXX: should this even ever happen? What about argnames here? + obj.fn.T = typ.Copy() // overwrites any contained "variant" type + } + + return obj.fn.T } // Validate makes sure we've built our struct properly. It is usually unused for diff --git a/lang/funcs/structlookup_func.go b/lang/funcs/structlookup_func.go index 2b3ae3e6..283ed4cb 100644 --- a/lang/funcs/structlookup_func.go +++ b/lang/funcs/structlookup_func.go @@ -41,6 +41,8 @@ func init() { Register(StructLookupFuncName, func() interfaces.Func { return &StructLookupFunc{} }) // must register the func and name } +var _ interfaces.PolyFunc = &StructLookupFunc{} // ensure it meets this expectation + // StructLookupFunc is a struct field lookup function. type StructLookupFunc struct { Type *types.Type // Kind == Struct, that is used as the struct we lookup @@ -394,33 +396,33 @@ func (obj *StructLookupFunc) Polymorphisms(partialType *types.Type, partialValue // and must be run before Info() and any of the other Func interface methods are // used. This function is idempotent, as long as the arg isn't changed between // runs. -func (obj *StructLookupFunc) Build(typ *types.Type) error { +func (obj *StructLookupFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if typ.Kind != types.KindFunc { - return fmt.Errorf("input type must be of kind func") + return nil, fmt.Errorf("input type must be of kind func") } if len(typ.Ord) != 2 { - return fmt.Errorf("the structlookup function needs exactly two args") + return nil, fmt.Errorf("the structlookup function needs exactly two args") } if typ.Out == nil { - return fmt.Errorf("return type of function must be specified") + return nil, fmt.Errorf("return type of function must be specified") } if typ.Map == nil { - return fmt.Errorf("invalid input type") + return nil, fmt.Errorf("invalid input type") } tStruct, exists := typ.Map[typ.Ord[0]] if !exists || tStruct == nil { - return fmt.Errorf("first arg must be specified") + return nil, fmt.Errorf("first arg must be specified") } tField, exists := typ.Map[typ.Ord[1]] if !exists || tField == nil { - return fmt.Errorf("second arg must be specified") + return nil, fmt.Errorf("second arg must be specified") } if err := tField.Cmp(types.TypeStr); err != nil { - return errwrap.Wrapf(err, "field must be an str") + return nil, errwrap.Wrapf(err, "field must be an str") } // NOTE: We actually don't know which field this is, only its type! we @@ -429,7 +431,8 @@ func (obj *StructLookupFunc) Build(typ *types.Type) error { // struct. obj.Type = tStruct // struct type obj.Out = typ.Out // type of return value - return nil + + return obj.sig(), nil } // Validate tells us if the input struct takes a valid form. @@ -458,7 +461,7 @@ func (obj *StructLookupFunc) Info() *interfaces.Info { var sig *types.Type if obj.Type != nil { // don't panic if called speculatively // TODO: can obj.Out be nil (a partial) ? - sig = types.NewType(fmt.Sprintf("func(%s %s, %s str) %s", structLookupArgNameStruct, obj.Type.String(), structLookupArgNameField, obj.Out.String())) + sig = obj.sig() // helper } return &interfaces.Info{ Pure: true, @@ -468,6 +471,11 @@ func (obj *StructLookupFunc) Info() *interfaces.Info { } } +// helper +func (obj *StructLookupFunc) sig() *types.Type { + return types.NewType(fmt.Sprintf("func(%s %s, %s str) %s", structLookupArgNameStruct, obj.Type.String(), structLookupArgNameField, obj.Out.String())) +} + // Init runs some startup code for this function. func (obj *StructLookupFunc) Init(init *interfaces.Init) error { obj.init = init diff --git a/lang/interfaces/func.go b/lang/interfaces/func.go index 6f381ae5..80a2d4b9 100644 --- a/lang/interfaces/func.go +++ b/lang/interfaces/func.go @@ -118,11 +118,19 @@ type PolyFunc interface { // another way: the Expr input is the ExprFunc, not the ExprCall. Unify(Expr) ([]Invariant, error) - // Build takes the known type signature for this function and finalizes - // this structure so that it is now determined, and ready to function as - // a normal function would. (The normal methods in the Func interface - // are all that should be needed or used after this point.) - Build(*types.Type) error // then, you can get argNames from Info() + // Build takes the known or unified type signature for this function and + // finalizes this structure so that it is now determined, and ready to + // function as a normal function would. (The normal methods in the Func + // interface are all that should be needed or used after this point.) + // Of note, the names of the specific input args shouldn't matter as + // long as they are unique. Their position doesn't matter. This is so + // that unification can use "arg0", "arg1", "argN"... if they can't be + // determined statically. Build can transform them into it's desired + // form, and must return the type (with the correct arg names) that it + // will use. These are used when constructing the function graphs. This + // means that when this is called from SetType, it can set the correct + // type arg names, and this will also match what's in function Info(). + Build(*types.Type) (*types.Type, error) } // OldPolyFunc is an interface for functions which are statically polymorphic. @@ -150,11 +158,19 @@ type OldPolyFunc interface { // want to convert easily. Polymorphisms(*types.Type, []types.Value) ([]*types.Type, error) - // Build takes the known type signature for this function and finalizes - // this structure so that it is now determined, and ready to function as - // a normal function would. (The normal methods in the Func interface - // are all that should be needed or used after this point.) - Build(*types.Type) error // then, you can get argNames from Info() + // Build takes the known or unified type signature for this function and + // finalizes this structure so that it is now determined, and ready to + // function as a normal function would. (The normal methods in the Func + // interface are all that should be needed or used after this point.) + // Of note, the names of the specific input args shouldn't matter as + // long as they are unique. Their position doesn't matter. This is so + // that unification can use "arg0", "arg1", "argN"... if they can't be + // determined statically. Build can transform them into it's desired + // form, and must return the type (with the correct arg names) that it + // will use. These are used when constructing the function graphs. This + // means that when this is called from SetType, it can set the correct + // type arg names, and this will also match what's in function Info(). + Build(*types.Type) (*types.Type, error) } // NamedArgsFunc is a function that uses non-standard function arg names. If you