engine, lang: Allow resources with a field of type interface
This lets us add a resource that has an implementation with a field whose type is determined at compile time. This let's us write more flexible resources. What's missing is additional type checking so that we guarantee that a specific resource doesn't change types during run-time.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -98,6 +98,7 @@ func ResTypeOf(t reflect.Type) (*Type, error) {
|
||||
StructTagOpt(StructTag),
|
||||
StrictStructTagOpt(true),
|
||||
SkipBadStructFieldsOpt(true),
|
||||
AllowInterfaceTypeOpt(true),
|
||||
}
|
||||
return ConfigurableTypeOf(t, opts...)
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user