lang: Update the Build signature to return a type

This returns the type with the arg names we'll actually use. This is
helpful so we can pass values to the right places. We have named edges
so you can actually see what's going on.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
This commit is contained in:
James Shubin
2023-08-08 21:59:42 -04:00
parent 31c7144fff
commit c06c391461
12 changed files with 283 additions and 153 deletions

View File

@@ -6938,9 +6938,12 @@ func (obj *ExprFunc) SetType(typ *types.Type) error {
if obj.Function != nil { if obj.Function != nil {
polyFn, ok := obj.function.(interfaces.PolyFunc) // is it statically polymorphic? polyFn, ok := obj.function.(interfaces.PolyFunc) // is it statically polymorphic?
if ok { 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") return errwrap.Wrapf(err, "could not build expr func")
} }
// Cmp doesn't compare arg names.
typ = newTyp // check it's compatible down below...
} }
} }

View File

@@ -41,6 +41,8 @@ func init() {
Register(ContainsFuncName, func() interfaces.Func { return &ContainsFunc{} }) // must register the func and name 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. // ContainsFunc returns true if a value is found in a list. Otherwise false.
type ContainsFunc struct { type ContainsFunc struct {
Type *types.Type // this is the type of value stored in our list 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 // 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 // used. This function is idempotent, as long as the arg isn't changed between
// runs. // 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... // typ is the KindFunc signature we're trying to build...
if typ.Kind != types.KindFunc { 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 { 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 { 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 { if typ.Map == nil {
return fmt.Errorf("invalid input type") return nil, fmt.Errorf("invalid input type")
} }
tNeedle, exists := typ.Map[typ.Ord[0]] tNeedle, exists := typ.Map[typ.Ord[0]]
if !exists || tNeedle == nil { 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]] tHaystack, exists := typ.Map[typ.Ord[1]]
if !exists || tHaystack == nil { 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 { 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 { 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 { 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 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. // 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 { func (obj *ContainsFunc) Info() *interfaces.Info {
var sig *types.Type var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively if obj.Type != nil { // don't panic if called speculatively
s := obj.Type.String() sig = obj.sig() // helper
sig = types.NewType(fmt.Sprintf("func(%s %s, %s []%s) bool", containsArgNameNeedle, s, containsArgNameHaystack, s))
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: true, 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. // Init runs some startup code for this function.
func (obj *ContainsFunc) Init(init *interfaces.Init) error { func (obj *ContainsFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init

View File

@@ -53,6 +53,8 @@ func init() {
funcs.ModuleRegister(ModuleName, PrintfFuncName, func() interfaces.Func { return &PrintfFunc{} }) 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 // 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 // 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 // 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 // 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 // function can appear to be static. That type is used to build our function
// statically. // statically.
func (obj *PrintfFunc) Build(typ *types.Type) error { func (obj *PrintfFunc) Build(typ *types.Type) (*types.Type, error) {
if typ.Kind != types.KindFunc { 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 { 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 { 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 { 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 { if typ.Map == nil {
return fmt.Errorf("invalid input type") return nil, fmt.Errorf("invalid input type")
} }
t0, exists := typ.Map[typ.Ord[0]] t0, exists := typ.Map[typ.Ord[0]]
if !exists || t0 == nil { 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 { 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! //newTyp := typ.Copy()
return nil 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 // Validate makes sure we've built our struct properly. It is usually unused for

View File

@@ -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 // 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 // used. This function is idempotent, as long as the arg isn't changed between
// runs. // 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... // typ is the KindFunc signature we're trying to build...
if typ.Kind != types.KindFunc { 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 { 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 { 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]] tInputs, exists := typ.Map[typ.Ord[0]]
if !exists || tInputs == nil { 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]] tFunction, exists := typ.Map[typ.Ord[1]]
if !exists || tFunction == nil { if !exists || tFunction == nil {
return fmt.Errorf("second argument was missing") return nil, fmt.Errorf("second argument was missing")
} }
if tInputs.Kind != types.KindList { 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 { 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 { 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 { 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 { 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 { 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]] tArg, exists := tFunction.Map[tFunction.Ord[0]]
if !exists || tArg == nil { 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 { 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 { 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 { 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.Type = tInputs.Val // or tArg
obj.RType = tFunction.Out // or typ.Out.Val 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. // 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 // Info returns some static info about itself. Build must be called before this
// will return correct data. // will return correct data.
func (obj *MapFunc) Info() *interfaces.Info { 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? // TODO: what do we put if this is unknown?
tIi := types.TypeVariant tIi := types.TypeVariant
if obj.Type != nil { 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())) 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) s := fmt.Sprintf("func(%s %s, %s %s) %s", argNameInputs, tI, argNameFunction, tF, tO)
typ := types.NewType(s) // yay! return 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(),
}
} }
// Init runs some startup code for this function. // Init runs some startup code for this function.

View File

@@ -55,6 +55,8 @@ func init() {
funcs.Register(TemplateFuncName, func() interfaces.Func { return &TemplateFunc{} }) 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 // 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 // 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 // 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 // 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. // is the special undetermined value that is used before type is known.
Type *types.Type // type of vars 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. built bool // was this function built yet?
NoVars bool
init *interfaces.Init init *interfaces.Init
last types.Value // last value received to use for diff 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, // 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 // which is the dynamic part which can change. That type is used to build our
// function statically. // 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 { 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 { 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 { 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 { 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 { if typ.Map == nil {
return fmt.Errorf("invalid input type") return nil, fmt.Errorf("invalid input type")
} }
t0, exists := typ.Map[typ.Ord[0]] t0, exists := typ.Map[typ.Ord[0]]
if !exists || t0 == nil { 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 { 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) if len(typ.Ord) == 1 { // no args being passed in (boring template)
obj.NoVars = true obj.built = true
return nil return obj.sig(), nil
} }
t1, exists := typ.Map[typ.Ord[1]] t1, exists := typ.Map[typ.Ord[1]]
if !exists || t1 == nil { 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! 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 // Validate makes sure we've built our struct properly. It is usually unused for
// normal functions that users can use directly. // normal functions that users can use directly.
func (obj *TemplateFunc) Validate() error { func (obj *TemplateFunc) Validate() error {
if obj.Type == nil && !obj.NoVars { // build must be run first if !obj.built {
return fmt.Errorf("type is still unspecified") return fmt.Errorf("function wasn't built yet")
} }
return nil return nil
} }
@@ -347,13 +349,8 @@ func (obj *TemplateFunc) Validate() error {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *TemplateFunc) Info() *interfaces.Info { func (obj *TemplateFunc) Info() *interfaces.Info {
var sig *types.Type var sig *types.Type
if obj.NoVars { if obj.built {
str := fmt.Sprintf("func(%s str) str", templateArgNameTemplate) sig = obj.sig() // helper
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)
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: true, 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. // Init runs some startup code for this function.
func (obj *TemplateFunc) Init(init *interfaces.Init) error { func (obj *TemplateFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init

View File

@@ -65,6 +65,8 @@ func init() {
funcs.ModuleRegister(ModuleName, ScheduleFuncName, func() interfaces.Func { return &ScheduleFunc{} }) 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 // ScheduleFunc is special function which determines where code should run in
// the cluster. // the cluster.
type ScheduleFunc struct { 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 // 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 // used. This function is idempotent, as long as the arg isn't changed between
// runs. // 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... // typ is the KindFunc signature we're trying to build...
if typ.Kind != types.KindFunc { 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 { 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 { 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 { 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 { 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]] tNamespace, exists := typ.Map[typ.Ord[0]]
if !exists || tNamespace == nil { 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 { if len(typ.Ord) == 1 {
obj.Type = nil obj.Type = nil
obj.built = true 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]] tOpts, exists := typ.Map[typ.Ord[1]]
if !exists || tOpts == nil { if !exists || tOpts == nil {
return fmt.Errorf("second argument was missing") return nil, fmt.Errorf("second argument was missing")
} }
if tOpts.Kind != types.KindStruct { 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() validOpts := obj.validOpts()
@@ -445,11 +447,11 @@ func (obj *ScheduleFunc) Build(typ *types.Type) error {
t := tOpts.Map[name] t := tOpts.Map[name]
value, exists := validOpts[name] value, exists := validOpts[name]
if !exists { 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 { 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 it exists, check the type
if err := t.Cmp(value); err != nil { 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.Type = tOpts // type of opts struct, even an empty: `struct{}`
obj.built = true obj.built = true
return nil return obj.sig(), nil
} }
// Validate tells us if the input struct takes a valid form. // 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 { func (obj *ScheduleFunc) Info() *interfaces.Info {
// It's important that you don't return a non-nil sig if this is called // 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. // before you're built. Type unification may call it opportunistically.
var typ *types.Type var sig *types.Type
if obj.built { if obj.built {
typ = types.NewType(fmt.Sprintf("func(%s str) []str", scheduleArgNameNamespace)) // simplest form sig = obj.sig() // helper
if obj.Type != nil {
typ = types.NewType(fmt.Sprintf("func(%s str, %s %s) []str", scheduleArgNameNamespace, scheduleArgNameOpts, obj.Type.String()))
}
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
// output is list of hostnames chosen // output is list of hostnames chosen
Sig: typ, // func kind Sig: sig, // func kind
Err: obj.Validate(), 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. // Init runs some startup code for this function.
func (obj *ScheduleFunc) Init(init *interfaces.Init) error { func (obj *ScheduleFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init

View File

@@ -40,6 +40,8 @@ func init() {
Register(HistoryFuncName, func() interfaces.Func { return &HistoryFunc{} }) // must register the func and name 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 // 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. // 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 // 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 // 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 // function can appear to be static. That type is used to build our function
// statically. // statically.
func (obj *HistoryFunc) Build(typ *types.Type) error { func (obj *HistoryFunc) Build(typ *types.Type) (*types.Type, error) {
if typ.Kind != types.KindFunc { 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 { 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 { 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 { if typ.Map == nil {
return fmt.Errorf("invalid input type") return nil, fmt.Errorf("invalid input type")
} }
t1, exists := typ.Map[typ.Ord[1]] t1, exists := typ.Map[typ.Ord[1]]
if !exists || t1 == nil { 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 { 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]] t0, exists := typ.Map[typ.Ord[0]]
if !exists || t0 == nil { 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! 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 // 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 { func (obj *HistoryFunc) Info() *interfaces.Info {
var sig *types.Type var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively if obj.Type != nil { // don't panic if called speculatively
s := obj.Type.String() sig = obj.sig() // helper
sig = types.NewType(fmt.Sprintf("func(%s %s, %s int) %s", historyArgNameValue, s, historyArgNameIndex, s))
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false 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. // Init runs some startup code for this function.
func (obj *HistoryFunc) Init(init *interfaces.Init) error { func (obj *HistoryFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init

View File

@@ -42,6 +42,8 @@ func init() {
Register(MapLookupFuncName, func() interfaces.Func { return &MapLookupFunc{} }) // must register the func and name 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. // MapLookupFunc is a key map lookup function.
type MapLookupFunc struct { type MapLookupFunc struct {
Type *types.Type // Kind == Map, that is used as the map we lookup 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 // 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 // used. This function is idempotent, as long as the arg isn't changed between
// runs. // 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... // typ is the KindFunc signature we're trying to build...
if typ.Kind != types.KindFunc { 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 { 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 { 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 { if typ.Map == nil {
return fmt.Errorf("invalid input type") return nil, fmt.Errorf("invalid input type")
} }
tMap, exists := typ.Map[typ.Ord[0]] tMap, exists := typ.Map[typ.Ord[0]]
if !exists || tMap == nil { 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]] tKey, exists := typ.Map[typ.Ord[1]]
if !exists || tKey == nil { 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]] tDef, exists := typ.Map[typ.Ord[2]]
if !exists || tDef == nil { 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 { 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 { 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 { 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 obj.Type = tMap // map type
return nil return obj.sig(), nil
} }
// Validate tells us if the input struct takes a valid form. // 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 // Info returns some static info about itself. Build must be called before this
// will return correct data. // will return correct data.
func (obj *MapLookupFunc) Info() *interfaces.Info { func (obj *MapLookupFunc) Info() *interfaces.Info {
var typ *types.Type var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively if obj.Type != nil { // don't panic if called speculatively
// TODO: can obj.Type.Key or obj.Type.Val be nil (a partial) ? // TODO: can obj.Type.Key or obj.Type.Val be nil (a partial) ?
k := obj.Type.Key.String() sig = obj.sig() // helper
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))
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: false,
Sig: typ, // func kind Sig: sig, // func kind
Err: obj.Validate(), 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. // Init runs some startup code for this function.
func (obj *MapLookupFunc) Init(init *interfaces.Init) error { func (obj *MapLookupFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init

View File

@@ -336,6 +336,8 @@ func init() {
Register(OperatorFuncName, func() interfaces.Func { return &OperatorFunc{} }) // must register the func and name 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. // OperatorFuncs maps an operator to a list of callable function values.
var OperatorFuncs = make(map[string][]*types.FuncValue) // must initialize 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 // 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 // used. This function is idempotent, as long as the arg isn't changed between
// runs. // 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... // typ is the KindFunc signature we're trying to build...
if len(typ.Ord) < 1 { 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 { 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 // Change arg names to be what we expect...
return nil 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. // Validate tells us if the input struct takes a valid form.

View File

@@ -128,6 +128,8 @@ func consistentArgs(fns []*types.FuncValue) ([]string, error) {
return seq, nil return seq, nil
} }
var _ interfaces.PolyFunc = &WrappedFunc{} // ensure it meets this expectation
// WrappedFunc is a scaffolding function struct which fulfills the boiler-plate // WrappedFunc is a scaffolding function struct which fulfills the boiler-plate
// for the function API, but that can run a very simple, static, pure, // for the function API, but that can run a very simple, static, pure,
// polymorphic function. // 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, // 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 // and must be run before Info() and any of the other Func interface methods are
// used. // 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... // typ is the KindFunc signature we're trying to build...
index, err := langutil.FnMatch(typ, obj.Fns) index, err := langutil.FnMatch(typ, obj.Fns)
if err != nil { 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 // buildFunction builds our concrete static function, from the potentially
// abstract, possibly variant containing list of functions. // abstract, possibly variant containing list of functions.
func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) { func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) *types.Type {
obj.fn = obj.Fns[ix].Copy().(*types.FuncValue) cp := obj.Fns[ix].Copy()
obj.fn.T = typ.Copy() // overwrites any contained "variant" type 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 // Validate makes sure we've built our struct properly. It is usually unused for

View File

@@ -41,6 +41,8 @@ func init() {
Register(StructLookupFuncName, func() interfaces.Func { return &StructLookupFunc{} }) // must register the func and name 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. // StructLookupFunc is a struct field lookup function.
type StructLookupFunc struct { type StructLookupFunc struct {
Type *types.Type // Kind == Struct, that is used as the struct we lookup 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 // 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 // used. This function is idempotent, as long as the arg isn't changed between
// runs. // 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... // typ is the KindFunc signature we're trying to build...
if typ.Kind != types.KindFunc { 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 { 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 { 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 { if typ.Map == nil {
return fmt.Errorf("invalid input type") return nil, fmt.Errorf("invalid input type")
} }
tStruct, exists := typ.Map[typ.Ord[0]] tStruct, exists := typ.Map[typ.Ord[0]]
if !exists || tStruct == nil { 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]] tField, exists := typ.Map[typ.Ord[1]]
if !exists || tField == nil { 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 { 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 // 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. // struct.
obj.Type = tStruct // struct type obj.Type = tStruct // struct type
obj.Out = typ.Out // type of return value 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. // 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 var sig *types.Type
if obj.Type != nil { // don't panic if called speculatively if obj.Type != nil { // don't panic if called speculatively
// TODO: can obj.Out be nil (a partial) ? // 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{ return &interfaces.Info{
Pure: true, 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. // Init runs some startup code for this function.
func (obj *StructLookupFunc) Init(init *interfaces.Init) error { func (obj *StructLookupFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init

View File

@@ -118,11 +118,19 @@ type PolyFunc interface {
// another way: the Expr input is the ExprFunc, not the ExprCall. // another way: the Expr input is the ExprFunc, not the ExprCall.
Unify(Expr) ([]Invariant, error) Unify(Expr) ([]Invariant, error)
// Build takes the known type signature for this function and finalizes // Build takes the known or unified type signature for this function and
// this structure so that it is now determined, and ready to function as // finalizes this structure so that it is now determined, and ready to
// a normal function would. (The normal methods in the Func interface // function as a normal function would. (The normal methods in the Func
// are all that should be needed or used after this point.) // interface are all that should be needed or used after this point.)
Build(*types.Type) error // then, you can get argNames from Info() // 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. // OldPolyFunc is an interface for functions which are statically polymorphic.
@@ -150,11 +158,19 @@ type OldPolyFunc interface {
// want to convert easily. // want to convert easily.
Polymorphisms(*types.Type, []types.Value) ([]*types.Type, error) Polymorphisms(*types.Type, []types.Value) ([]*types.Type, error)
// Build takes the known type signature for this function and finalizes // Build takes the known or unified type signature for this function and
// this structure so that it is now determined, and ready to function as // finalizes this structure so that it is now determined, and ready to
// a normal function would. (The normal methods in the Func interface // function as a normal function would. (The normal methods in the Func
// are all that should be needed or used after this point.) // interface are all that should be needed or used after this point.)
Build(*types.Type) error // then, you can get argNames from Info() // 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 // NamedArgsFunc is a function that uses non-standard function arg names. If you