diff --git a/lang/structs.go b/lang/structs.go index c15fa59e..8d76c0f2 100644 --- a/lang/structs.go +++ b/lang/structs.go @@ -2981,6 +2981,108 @@ func (obj *StmtComment) Output() (*interfaces.Output, error) { return interfaces.EmptyOutput(), nil } +// ExprAny is a placeholder expression that is used for type unification hacks. +type ExprAny struct { + typ *types.Type +} + +// Apply is a general purpose iterator method that operates on any AST node. It +// is not used as the primary AST traversal function because it is less readable +// and easy to reason about than manually implementing traversal for each node. +// Nevertheless, it is a useful facility for operations that might only apply to +// a select number of node types, since they won't need extra noop iterators... +func (obj *ExprAny) Apply(fn func(interfaces.Node) error) error { return fn(obj) } + +// String returns a short representation of this expression. +func (obj *ExprAny) String() string { return "any" } + +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprAny) Init(*interfaces.Data) error { return nil } + +// Interpolate returns a new node (aka a copy) once it has been expanded. This +// generally increases the size of the AST when it is used. It calls Interpolate +// on any child elements and builds the new node with those new node contents. +// Here it simply returns itself, as no interpolation is possible. +func (obj *ExprAny) Interpolate() (interfaces.Expr, error) { + return &ExprAny{ + typ: obj.typ, + }, nil +} + +// SetScope does nothing for this struct, because it has no child nodes, and it +// does not need to know about the parent scope. +func (obj *ExprAny) SetScope(*interfaces.Scope) error { return nil } + +// SetType is used to set the type of this expression once it is known. This +// usually happens during type unification, but it can also happen during +// parsing if a type is specified explicitly. Since types are static and don't +// change on expressions, if you attempt to set a different type than what has +// previously been set (when not initially known) this will error. +func (obj *ExprAny) SetType(typ *types.Type) error { + if obj.typ != nil { + return obj.typ.Cmp(typ) // if not set, ensure it doesn't change + } + obj.typ = typ // set + return nil +} + +// Type returns the type of this expression. +func (obj *ExprAny) Type() (*types.Type, error) { + if obj.typ == nil { + return nil, interfaces.ErrTypeCurrentlyUnknown + } + return obj.typ, nil +} + +// Unify returns the list of invariants that this node produces. It recursively +// calls Unify on any children elements that exist in the AST, and returns the +// collection to the caller. +func (obj *ExprAny) Unify() ([]interfaces.Invariant, error) { + invariants := []interfaces.Invariant{ + &unification.AnyInvariant{ // it has to be something, anything! + Expr: obj, + }, + } + return invariants, nil +} + +// Graph returns the reactive function graph which is expressed by this node. It +// includes any vertices produced by this node, and the appropriate edges to any +// vertices that are produced by its children. Nodes which fulfill the Expr +// interface directly produce vertices (and possible children) where as nodes +// that fulfill the Stmt interface do not produces vertices, where as their +// children might. This returns a graph with a single vertex (itself) in it, and +// the edges from all of the child graphs to this. +func (obj *ExprAny) Graph() (*pgraph.Graph, error) { + graph, err := pgraph.NewGraph("any") + if err != nil { + return nil, errwrap.Wrapf(err, "could not create graph") + } + graph.AddVertex(obj) + return graph, nil +} + +// Func returns the reactive stream of values that this expression produces. +func (obj *ExprAny) Func() (interfaces.Func, error) { + return nil, fmt.Errorf("programming error") // this should not be called +} + +// SetValue here is a no-op, because algorithmically when this is called from +// the func engine, the child elements (the list elements) will have had this +// done to them first, and as such when we try and retrieve the set value from +// this expression by calling `Value`, it will build it from scratch! +func (obj *ExprAny) SetValue(value types.Value) error { + return fmt.Errorf("programming error") // this should not be called +} + +// Value returns the value of this expression in our type system. This will +// usually only be valid once the engine has run and values have been produced. +// This might get called speculatively (early) during unification to learn more. +func (obj *ExprAny) Value() (types.Value, error) { + return nil, fmt.Errorf("programming error") // this should not be called +} + // ExprBool is a representation of a boolean. type ExprBool struct { V bool @@ -3553,6 +3655,35 @@ func (obj *ExprList) Unify() ([]interfaces.Invariant, error) { invariants = append(invariants, invariant) } + // make sure this empty list gets an element type somehow + if len(obj.Elements) == 0 { + invariant := &unification.AnyInvariant{ + Expr: obj, + } + invariants = append(invariants, invariant) + + // build a placeholder expr to represent a contained element... + exprAny := &ExprAny{} + invars, err := exprAny.Unify() + if err != nil { + return nil, err + } + invariants = append(invariants, invars...) + + // FIXME: instead of using `ExprAny`, we could actually teach + // our unification engine to ensure that our expr kind is list, + // eg: + //&unification.EqualityKindInvariant{ + // Expr1: obj, + // Kind: types.KindList, + //} + invar := &unification.EqualityWrapListInvariant{ + Expr1: obj, + Expr2Val: exprAny, // hack + } + invariants = append(invariants, invar) + } + return invariants, nil } @@ -3874,6 +4005,41 @@ func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) { invariants = append(invariants, invariant) } + // make sure this empty map gets a type for its key/value somehow + if len(obj.KVs) == 0 { + invariant := &unification.AnyInvariant{ + Expr: obj, + } + invariants = append(invariants, invariant) + + // build a placeholder expr to represent a contained key... + exprAnyKey, exprAnyVal := &ExprAny{}, &ExprAny{} + invarsKey, err := exprAnyKey.Unify() + if err != nil { + return nil, err + } + invariants = append(invariants, invarsKey...) + invarsVal, err := exprAnyVal.Unify() + if err != nil { + return nil, err + } + invariants = append(invariants, invarsVal...) + + // FIXME: instead of using `ExprAny`, we could actually teach + // our unification engine to ensure that our expr kind is list, + // eg: + //&unification.EqualityKindInvariant{ + // Expr1: obj, + // Kind: types.KindMap, + //} + invar := &unification.EqualityWrapMapInvariant{ + Expr1: obj, + Expr2Key: exprAnyKey, // hack + Expr2Val: exprAnyVal, // hack + } + invariants = append(invariants, invar) + } + return invariants, nil }