cue: interpret nil as _ or top
depending on the context.
Do not interpret as a disjuntion of both.
Fixes #220.
Change-Id: If935316e23d12825eda3fa75fea8f95a04d09da3
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4860
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/builtin.go b/cue/builtin.go
index 3fdf53c..a7479d7 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -310,7 +310,7 @@
case *valueError:
return v.err
}
- return convert(ctx, x, false, call.ret)
+ return convert(ctx, x, true, call.ret)
}
// callCtxt is passed to builtin implementations.
diff --git a/cue/builtinutil.go b/cue/builtinutil.go
index d3c471e..dedf174 100644
--- a/cue/builtinutil.go
+++ b/cue/builtinutil.go
@@ -51,7 +51,7 @@
for i := len(path) - 1; i >= 0; i-- {
x = map[string]interface{}{path[i]: x}
}
- value := convert(ctx, root, true, x)
+ value := convert(ctx, root, false, x)
eval := binOp(ctx, baseValue{}, opUnify, root, value)
return newValueRoot(ctx, eval)
}
diff --git a/cue/go.go b/cue/go.go
index 22f45fb..ae21031 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -38,8 +38,8 @@
// optimized.
func init() {
- internal.FromGoValue = func(runtime, x interface{}, allowDefault bool) interface{} {
- return convertValue(runtime.(*Runtime), x, allowDefault)
+ internal.FromGoValue = func(runtime, x interface{}, nilIsTop bool) interface{} {
+ return convertValue(runtime.(*Runtime), x, nilIsTop)
}
internal.FromGoType = func(runtime, x interface{}) interface{} {
@@ -47,9 +47,9 @@
}
}
-func convertValue(r *Runtime, x interface{}, allowDefault bool) Value {
+func convertValue(r *Runtime, x interface{}, nilIsTop bool) Value {
ctx := r.index().newContext()
- v := convert(ctx, baseValue{}, allowDefault, x)
+ v := convert(ctx, baseValue{}, nilIsTop, x)
return newValueRoot(ctx, v)
}
@@ -189,23 +189,30 @@
}
}
-func convert(ctx *context, src source, allowDefault bool, x interface{}) evaluated {
- v := convertRec(ctx, src, allowDefault, x)
+func convert(ctx *context, src source, nilIsTop bool, x interface{}) evaluated {
+ v := convertRec(ctx, src, nilIsTop, x)
if v == nil {
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", v)
}
return v
}
-func convertRec(ctx *context, src source, allowDefault bool, x interface{}) evaluated {
+func isNil(x reflect.Value) bool {
+ switch x.Kind() {
+ // Only check for supported types; ignore func and chan.
+ case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface:
+ return x.IsNil()
+ }
+ return false
+}
+
+func convertRec(ctx *context, src source, nilIsTop bool, x interface{}) evaluated {
switch v := x.(type) {
case nil:
- if !allowDefault {
- return &nullLit{src.base()}
+ if nilIsTop {
+ return &top{src.base()}
}
- // Interpret a nil pointer as an undefined value that is only
- // null by default, but may still be set: *null | _.
- return makeNullable(&top{src.base()}, false).(evaluated)
+ return &nullLit{src.base()}
case ast.Expr:
x := newVisitorCtx(ctx, nil, nil, nil, false)
@@ -302,7 +309,7 @@
case reflect.Value:
if v.CanInterface() {
- return convertRec(ctx, src, allowDefault, v.Interface())
+ return convertRec(ctx, src, nilIsTop, v.Interface())
}
default:
@@ -328,19 +335,16 @@
return toUint(ctx, src, value.Uint())
case reflect.Float32, reflect.Float64:
- return convertRec(ctx, src, allowDefault, value.Float())
+ return convertRec(ctx, src, nilIsTop, value.Float())
case reflect.Ptr:
if value.IsNil() {
- if !allowDefault {
- return &nullLit{src.base()}
+ if nilIsTop {
+ return &top{src.base()}
}
- // Interpret a nil pointer as an undefined value that is only
- // null by default, but may still be set: *null | _.
- elem := goTypeToValue(ctx, false, reflect.TypeOf(v).Elem())
- return makeNullable(elem, false).(evaluated)
+ return &nullLit{src.base()}
}
- return convertRec(ctx, src, allowDefault, value.Elem().Interface())
+ return convertRec(ctx, src, nilIsTop, value.Elem().Interface())
case reflect.Struct:
obj := newStruct(src)
@@ -351,10 +355,13 @@
continue
}
val := value.Field(i)
+ if !nilIsTop && isNil(val) {
+ continue
+ }
if isOmitEmpty(&t) && isZero(val) {
continue
}
- sub := convertRec(ctx, src, allowDefault, val.Interface())
+ sub := convertRec(ctx, src, nilIsTop, val.Interface())
if sub == nil {
// mimic behavior of encoding/json: skip fields of unsupported types
continue
@@ -388,12 +395,12 @@
reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
for _, k := range value.MapKeys() {
- s := fmt.Sprint(k)
- keys = append(keys, s)
- sorted = append(sorted, s)
+ val := value.MapIndex(k)
+ // if isNil(val) {
+ // continue
+ // }
- val := value.MapIndex(k).Interface()
- sub := convertRec(ctx, src, allowDefault, val)
+ sub := convertRec(ctx, src, nilIsTop, val.Interface())
// mimic behavior of encoding/json: report error of
// unsupported type.
if sub == nil {
@@ -403,6 +410,10 @@
return sub
}
+ s := fmt.Sprint(k)
+ keys = append(keys, s)
+ sorted = append(sorted, s)
+
// Set feature later.
obj.arcs = append(obj.arcs, arc{feature: 0, v: sub})
}
@@ -428,8 +439,8 @@
list := &list{baseValue: src.base()}
arcs := []arc{}
for i := 0; i < value.Len(); i++ {
- val := value.Index(i).Interface()
- x := convertRec(ctx, src, allowDefault, val)
+ val := value.Index(i)
+ x := convertRec(ctx, src, nilIsTop, val.Interface())
if x == nil {
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", val)
}
diff --git a/cue/go_test.go b/cue/go_test.go
index ed2db1b..d83d847 100644
--- a/cue/go_test.go
+++ b/cue/go_test.go
@@ -37,7 +37,7 @@
goVal interface{}
want string
}{{
- nil, "(null | _)",
+ nil, "_",
}, {
true, "true",
}, {
@@ -85,8 +85,18 @@
}, {
[]int{1, 2, 3, 4}, "[1,2,3,4]",
}, {
+ struct {
+ A int
+ B *int
+ }{3, nil},
+ "<0>{A: 3}",
+ }, {
[]interface{}{}, "[]",
}, {
+ []interface{}{nil}, "[_]",
+ }, {
+ map[string]interface{}{"a": 1, "x": nil}, "<0>{x: _, a: 1}",
+ }, {
map[string][]int{
"a": []int{1},
"b": []int{3, 4},
@@ -124,7 +134,7 @@
}, {
&struct{ A int }{3}, "<0>{A: 3}",
}, {
- (*struct{ A int })(nil), "(null | <0>{A: (int & >=-9223372036854775808 & int & <=9223372036854775807)})",
+ (*struct{ A int })(nil), "_",
}, {
reflect.ValueOf(3), "3",
}, {
diff --git a/cuego/cuego.go b/cuego/cuego.go
index 63647d9..f2460a3 100644
--- a/cuego/cuego.go
+++ b/cuego/cuego.go
@@ -171,12 +171,12 @@
}
// fromGoValue converts a Go value to CUE
-func fromGoValue(x interface{}, allowDefault bool) (v cue.Value, err error) {
+func fromGoValue(x interface{}, nilIsNull bool) (v cue.Value, err error) {
// TODO: remove the need to have a lock here. We could use a new index (new
// Instance) here as any previously unrecognized field can never match an
// existing one and can only be merged.
mutex.Lock()
- v = internal.FromGoValue(runtime, x, allowDefault).(cue.Value)
+ v = internal.FromGoValue(runtime, x, nilIsNull).(cue.Value)
mutex.Unlock()
if err := v.Err(); err != nil {
return v, err
diff --git a/encoding/gocode/gocodec/codec_test.go b/encoding/gocode/gocodec/codec_test.go
index 25cc140..731747d 100644
--- a/encoding/gocode/gocodec/codec_test.go
+++ b/encoding/gocode/gocodec/codec_test.go
@@ -20,6 +20,7 @@
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/kr/pretty"
"cuelang.org/go/cue"
)
@@ -219,7 +220,7 @@
err = codec.Complete(v, tc.value)
checkErr(t, err, tc.err)
if !reflect.DeepEqual(tc.value, tc.result) {
- t.Errorf("value:\n got: %#v;\nwant: %#v", tc.value, tc.result)
+ t.Error(pretty.Diff(tc.value, tc.result))
}
})
}