diff --git a/engine/graph/sendrecv.go b/engine/graph/sendrecv.go index 56e2c735..17870ff2 100644 --- a/engine/graph/sendrecv.go +++ b/engine/graph/sendrecv.go @@ -93,6 +93,13 @@ func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) { value2 := obj2.FieldByName(key2) kind2 := value2.Kind() + // For situations where we send a variant to the resource! + // TODO: should this be a for loop to un-nest multiple times? + if kind1 == reflect.Interface { + value1 = value1.Elem() // un-nest one interface + kind1 = value1.Kind() + } + if obj.Debug { obj.Logf("Send(%s) has %v: %v", type1, kind1, value1) obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2) diff --git a/engine/util/util.go b/engine/util/util.go index 1e8a71ac..00c7d7ac 100644 --- a/engine/util/util.go +++ b/engine/util/util.go @@ -192,6 +192,12 @@ func StructFieldCompat(st1 interface{}, key1 string, st2 interface{}, key2 strin return fmt.Errorf("can't interface the recv") } + // If we're sending _from_ an interface... + if kind1 == reflect.Interface { + // TODO: Can we do more checks instead of only returning early? + return nil + } + if kind1 != kind2 { return fmt.Errorf("field kind mismatch between %s and %s", kind1, kind2) } diff --git a/lang/ast/structs.go b/lang/ast/structs.go index fe545599..051a9f17 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -746,7 +746,13 @@ func (obj *StmtRes) resource(table map[interfaces.Func]types.Value, resName stri if err != nil { return nil, errwrap.Wrapf(err, "resource field `%s` has no compatible type", x.Field) } - if err := t.Cmp(typ); err != nil { + if t == nil { + // possible programming error + return nil, fmt.Errorf("resource field `%s` of nil type cannot match type `%+v`", x.Field, typ) + } + + // Let the variants pass through... + if err := t.Cmp(typ); err != nil && t.Kind != types.KindVariant { return nil, errwrap.Wrapf(err, "resource field `%s` of type `%+v`, cannot take type `%+v`", x.Field, t, typ) } @@ -1320,6 +1326,29 @@ func (obj *StmtResField) Unify(kind string) ([]interfaces.Invariant, error) { if !exists { return nil, fmt.Errorf("field `%s` does not exist in `%s`", obj.Field, kind) } + if typ == nil { + // possible programming error + return nil, fmt.Errorf("type for field `%s` in `%s` is nil", obj.Field, kind) + } + if typ.Kind == types.KindVariant { // special path, res field has interface{} + if typ.Var == nil { + invar := &interfaces.AnyInvariant{ + Expr: obj.Value, + } + invariants = append(invariants, invar) + return invariants, nil + } + + // in case it is present (nil is okay too) + invar := &interfaces.EqualsInvariant{ + Expr: obj.Value, + Type: typ.Var, // in case it is present (nil is okay too) + } + invariants = append(invariants, invar) + return invariants, nil + } + + // regular scenario invar := &interfaces.EqualsInvariant{ Expr: obj.Value, Type: typ, diff --git a/lang/interpret_test.go b/lang/interpret_test.go index 26189bda..c83ba4c3 100644 --- a/lang/interpret_test.go +++ b/lang/interpret_test.go @@ -942,6 +942,11 @@ func TestAstFunc2(t *testing.T) { t.Errorf("test #%d: unification passed, expected fail", index) return } + // XXX: Should we do a kind of SetType on resources here + // to tell the ones with variant fields what their + // concrete field types are? They should only be dynamic + // in implementation and before unification, and static + // once we've unified the specific resource. // build the function graph graph, err := iast.Graph() diff --git a/lang/lang.go b/lang/lang.go index c9468c11..beda0cd9 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -202,6 +202,10 @@ func (obj *Lang) Init() error { if err := unifier.Unify(); err != nil { return errwrap.Wrapf(err, "could not unify types") } + // XXX: Should we do a kind of SetType on resources here to tell the + // ones with variant fields what their concrete field types are? They + // should only be dynamic in implementation and before unification, and + // static once we've unified the specific resource. obj.Logf("building function graph...") // we assume that for some given code, the list of funcs doesn't change diff --git a/lang/types/type.go b/lang/types/type.go index c6860509..1526ed46 100644 --- a/lang/types/type.go +++ b/lang/types/type.go @@ -98,6 +98,7 @@ func ResTypeOf(t reflect.Type) (*Type, error) { StructTagOpt(StructTag), StrictStructTagOpt(true), SkipBadStructFieldsOpt(true), + AllowInterfaceTypeOpt(true), } return ConfigurableTypeOf(t, opts...) } diff --git a/lang/types/value.go b/lang/types/value.go index 54a72f36..4dd99824 100644 --- a/lang/types/value.go +++ b/lang/types/value.go @@ -283,6 +283,15 @@ func Into(v Value, rv reflect.Value) error { if typ == nil { return fmt.Errorf("cannot Into() %+v of type %s into a nil type", v, v.Type()) } + // This is used when we are setting a resource field which has type of + // interface{} instead of a string, bool, list, etc... + if isInterface := typ.Kind() == reflect.Interface; isInterface { + //x := reflect.ValueOf(v) // no! + // use the value with type interface{}, not types.Value + x := reflect.ValueOf(v.Value()) + rv.Set(x) + return nil + } switch v := v.(type) { case *BoolValue: