From 310e26dda9e38093f49a2369990f2799f7a10761 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Wed, 19 May 2021 04:00:26 -0400 Subject: [PATCH] lang: Switch over to the new PolyFunc interface This isn't perfect yet, but we're trying to do this incrementally, and merge whatever we can as early as possible. During this work, I realized that the Simplify method of the exclusive could probably be improved, and possibly receive a better signature. This work will have to happen later. --- lang/interfaces/func.go | 32 +++++----- .../TestAstFunc2/printfinterpolate0.output | 2 +- lang/structs.go | 61 +++++++++++-------- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/lang/interfaces/func.go b/lang/interfaces/func.go index 9102d296..92e39483 100644 --- a/lang/interfaces/func.go +++ b/lang/interfaces/func.go @@ -66,23 +66,21 @@ type Func interface { Close() error } -// UnifiedPolyFunc is an interface for functions which are statically -// polymorphic. In other words, they are functions which before compile time are -// polymorphic, but after a successful compilation have a fixed static -// signature. This makes implementing what would appear to be generic or -// polymorphic instead something that is actually static and that still has the -// language safety properties. Our engine requires that by the end of -// compilation, everything is static. This is needed so that values can flow -// safely along the DAG that represents their execution. If the types could -// change, then we wouldn't be able to safely pass values around. +// PolyFunc is an interface for functions which are statically polymorphic. In +// other words, they are functions which before compile time are polymorphic, +// but after a successful compilation have a fixed static signature. This makes +// implementing what would appear to be generic or polymorphic instead something +// that is actually static and that still has the language safety properties. +// Our engine requires that by the end of compilation, everything is static. +// This is needed so that values can flow safely along the DAG that represents +// their execution. If the types could change, then we wouldn't be able to +// safely pass values around. // -// XXX: This interface is similar to PolyFunc, except that it uses a Unify +// NOTE: This interface is similar to OldPolyFunc, except that it uses a Unify // method that works differently than the original Polymorphisms method. This // allows us to build invariants that are used directly by the type unification -// solver. If this new approach is more successful, then we will rename the -// UnifiedPolyFunc to PolyFunc. This interface is subject to change because this -// is currently not properly tested. -type UnifiedPolyFunc interface { // XXX: name this "PolyFunc" and remove or wrap the old interface? +// solver. +type PolyFunc interface { Func // implement everything in Func but add the additional requirements // Unify returns the list of invariants that this func produces. It is a @@ -109,12 +107,12 @@ type UnifiedPolyFunc interface { // XXX: name this "PolyFunc" and remove or wrap Build(*types.Type) error // then, you can get argNames from Info() } -// PolyFunc is an interface for functions which are statically polymorphic. In -// other words, they are functions which before compile time are polymorphic, +// OldPolyFunc is an interface for functions which are statically polymorphic. +// In other words, they are functions which before compile time are polymorphic, // but after a successful compilation have a fixed static signature. This makes // implementing what would appear to be generic or polymorphic instead something // that is actually static and that still has the language safety properties. -type PolyFunc interface { +type OldPolyFunc interface { Func // implement everything in Func but add the additional requirements // Polymorphisms returns a list of possible function type signatures. It diff --git a/lang/interpret_test/TestAstFunc2/printfinterpolate0.output b/lang/interpret_test/TestAstFunc2/printfinterpolate0.output index 1812a5e7..c040f8ca 100644 --- a/lang/interpret_test/TestAstFunc2/printfinterpolate0.output +++ b/lang/interpret_test/TestAstFunc2/printfinterpolate0.output @@ -1 +1 @@ -# err: errUnify: polymorphic signatures for func `fmt.printf` could not be found: could not determine type from format string +# err: errUnify: only recursive solutions left diff --git a/lang/structs.go b/lang/structs.go index f1af6d50..bb3b488e 100644 --- a/lang/structs.go +++ b/lang/structs.go @@ -7088,37 +7088,36 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) { // TODO: should we try and add invariants for obj.Args? + // We don't want to Unify against the *original* ExprFunc pointer, since + // we want the copy of it which is what ExprCall points to. We don't + // call this Unify() method from anywhere except from when it's within + // an ExprCall. We don't call it from StmtFunc which is just a container + // for it. This is basically used as a helper function! By the time this + // is called, we've already made an obj.function which is the copied, + // instantiated version of obj.Function that we are going to use. if obj.Function != nil { - // XXX: can we add anything here, perhaps this? - fn := obj.Function() - _, ok1 := fn.(interfaces.PolyFunc) // is it statically polymorphic? - unifiedPolyFn, ok2 := fn.(interfaces.UnifiedPolyFunc) // is it statically polymorphic? - if !ok1 && !ok2 { - sig := fn.Info().Sig - if sig != nil && !sig.HasVariant() { - invar := &interfaces.EqualsInvariant{ - Expr: obj, - Type: sig, - } - invariants = append(invariants, invar) - } - - } else if ok2 { // XXX: try the new interface for now + fn := obj.function // instantiated copy of obj.Function + polyFn, ok := fn.(interfaces.PolyFunc) // is it statically polymorphic? + if ok { // We just run the Unify() method of the ExprFunc if it // happens to have one. Get the list of Invariants, and // return them directly. - invars, err := unifiedPolyFn.Unify(obj) + invars, err := polyFn.Unify(obj) if err != nil { return nil, err } invariants = append(invariants, invars...) + } - } else { - // XXX: ignore this part for now... plan on deprecating it - //results, err := polyFn.Polymorphisms(nil, nil) // TODO: is this okay? - //if err == nil { - // // TODO: build an exclusive here... - //} + // It's okay to attempt to get a static signature too, if it's + // nil or has a variant (polymorphic funcs) then it's ignored. + sig := fn.Info().Sig + if sig != nil && !sig.HasVariant() { + invar := &interfaces.EqualsInvariant{ + Expr: obj, + Type: sig, + } + invariants = append(invariants, invar) } } @@ -7974,20 +7973,30 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) { var polyFn interfaces.PolyFunc var ok bool - // do we have a special case like the operator or template function? if fn.Function != nil { polyFn, ok = fn.function.(interfaces.PolyFunc) // is it statically polymorphic? } if fn.Function != nil && ok { - var err error - results, err = polyFn.Polymorphisms(partialType, partialValues) + // We just run the Unify() method of the ExprFunc if it happens + // to have one. Get the list of Invariants, and return them + // directly. We want to run this unification inside of ExprCall + // and not in ExprFunc, because it's only in ExprCall that we + // have the instantiated copy of the ExprFunc that we actually + // build and unify against and which has the correct pointer + // now, where as the ExprFunc pointer isn't what we unify with! + invars, err := polyFn.Unify(obj.expr) if err != nil { - return nil, errwrap.Wrapf(err, "polymorphic signatures for func `%s` could not be found", obj.Name) + return nil, errwrap.Wrapf(err, "polymorphic unification for func `%s` could not be done", obj.Name) } + invariants = append(invariants, invars...) } else if fn.Function != nil && !ok { sig := fn.function.Info().Sig + if sig == nil { + // this can happen if it's incorrectly implemented + return nil, errwrap.Wrapf(err, "unification for func `%s` returned nil signature", obj.Name) + } results = []*types.Type{sig} // only one (non-polymorphic) }