Added support for "opaque" return types

These are return types that are left alone by the UCL interpretor and cannot be operated on or get fields of.
This commit is contained in:
Leon Mika 2024-06-05 03:59:53 +00:00
parent 505fd29032
commit 88417aba95
3 changed files with 148 additions and 0 deletions

View file

@ -110,6 +110,8 @@ func (b boolObject) Truthy() bool {
func toGoValue(obj object) (interface{}, bool) { func toGoValue(obj object) (interface{}, bool) {
switch v := obj.(type) { switch v := obj.(type) {
case OpaqueObject:
return v.v, true
case nil: case nil:
return nil, true return nil, true
case strObject: case strObject:
@ -149,6 +151,8 @@ func toGoValue(obj object) (interface{}, bool) {
func fromGoValue(v any) (object, error) { func fromGoValue(v any) (object, error) {
switch t := v.(type) { switch t := v.(type) {
case OpaqueObject:
return t, nil
case nil: case nil:
return nil, nil return nil, nil
case string: case string:
@ -476,6 +480,22 @@ func (s structProxyObject) Each(fn func(k string, v object) error) error {
return nil return nil
} }
type OpaqueObject struct {
v any
}
func Opaque(v any) OpaqueObject {
return OpaqueObject{v: v}
}
func (p OpaqueObject) String() string {
return fmt.Sprintf("opaque{%T}", p.v)
}
func (p OpaqueObject) Truthy() bool {
return p.v != nil
}
type errBreak struct { type errBreak struct {
isCont bool isCont bool
ret object ret object

View file

@ -121,6 +121,23 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
} }
switch t := arg.(type) { switch t := arg.(type) {
case OpaqueObject:
if v == nil {
return errors.New("opaque object not bindable to nil")
}
vr := reflect.ValueOf(v)
tr := reflect.ValueOf(t.v)
if vr.Kind() != reflect.Pointer {
return errors.New("expected pointer for an opaque object bind")
}
if !tr.Type().AssignableTo(vr.Elem().Type()) {
return errors.New("opaque object not assignable to passed in value")
}
vr.Elem().Set(tr)
return nil
case proxyObject: case proxyObject:
return bindProxyObject(v, reflect.ValueOf(t.p)) return bindProxyObject(v, reflect.ValueOf(t.p))
case listableProxyObject: case listableProxyObject:
@ -142,6 +159,18 @@ func canBindArg(v interface{}, arg object) bool {
} }
switch t := arg.(type) { switch t := arg.(type) {
case OpaqueObject:
vr := reflect.ValueOf(v)
tr := reflect.ValueOf(t.v)
if vr.Kind() != reflect.Pointer {
return false
}
if !tr.Type().AssignableTo(vr.Elem().Type()) {
return false
}
return true
case proxyObject: case proxyObject:
return canBindProxyObject(v, reflect.ValueOf(t.p)) return canBindProxyObject(v, reflect.ValueOf(t.p))
case listableProxyObject: case listableProxyObject:

View file

@ -183,6 +183,105 @@ func TestInst_SetBuiltin(t *testing.T) {
}) })
} }
}) })
t.Run("opaques returned as is", func(t *testing.T) {
type opaqueThingType struct {
x string
y string
z string
}
opaqueThing := &opaqueThingType{x: "do", y: "not", z: "touch"}
tests := []struct {
descr string
expr string
wantErr bool
}{
{descr: "return as is", expr: `getOpaque`, wantErr: false},
{descr: "carry around ok", expr: `set x (getOpaque) ; $x`, wantErr: false},
{descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) {
outW := bytes.NewBuffer(nil)
inst := ucl.New(ucl.WithOut(outW))
inst.SetBuiltin("getOpaque", func(ctx context.Context, args ucl.CallArgs) (any, error) {
return ucl.Opaque(opaqueThing), nil
})
res, err := inst.Eval(context.Background(), tt.expr)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Same(t, opaqueThing, res)
}
})
}
})
t.Run("operate on opaques", func(t *testing.T) {
type opaqueThingType struct {
x string
y string
z string
}
opaqueThing := &opaqueThingType{x: "do", y: "not", z: "touch"}
tests := []struct {
descr string
expr string
want opaqueThingType
}{
{descr: "return as is", expr: `getOpaque`, want: *opaqueThing},
{descr: "update pointer 1", expr: `set x (getOpaque) ; setProp $x -x "do" -y "touch" -z "this"`, want: opaqueThingType{x: "do", y: "touch", z: "this"}},
{descr: "update pointer 2", expr: `set x (getOpaque) ; setProp $x -x "yes" ; setProp $x -y "this" -z "too"`, want: opaqueThingType{x: "yes", y: "this", z: "too"}},
}
for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) {
outW := bytes.NewBuffer(nil)
inst := ucl.New(ucl.WithOut(outW))
inst.SetBuiltin("getOpaque", func(ctx context.Context, args ucl.CallArgs) (any, error) {
return ucl.Opaque(opaqueThing), nil
})
inst.SetBuiltin("setProp", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var o *opaqueThingType
if err := args.Bind(&o); err != nil {
return nil, err
}
if args.HasSwitch("x") {
var s string
_ = args.BindSwitch("x", &s)
o.x = s
}
if args.HasSwitch("y") {
var s string
_ = args.BindSwitch("y", &s)
o.y = s
}
if args.HasSwitch("z") {
var s string
_ = args.BindSwitch("z", &s)
o.z = s
}
return nil, nil
})
_, err := inst.Eval(context.Background(), tt.expr)
assert.NoError(t, err)
assert.Equal(t, tt.want, *opaqueThing)
})
}
})
} }
func TestCallArgs_Bind(t *testing.T) { func TestCallArgs_Bind(t *testing.T) {