cue: define Fill on Value, LookupDef and bug fixes
Change-Id: I957956fb9bcfc531ad19baaa294c66ebf91daae9
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4945
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/instance.go b/cue/instance.go
index 6852a71..37ca0dc 100644
--- a/cue/instance.go
+++ b/cue/instance.go
@@ -278,6 +278,7 @@
// if the path did not exist. The Err method reports if any error occurred
// during evaluation. The empty path returns the top-level configuration struct,
// regardless of whether an emit value was specified.
+// Use LookupDef for definitions or LookupField for any kind of field.
func (inst *Instance) Lookup(path ...string) Value {
idx := inst.index
ctx := idx.newContext()
@@ -292,6 +293,15 @@
return v
}
+// LookupDef reports the definition with the given name within struct v. The
+// Exists method of the returned value will report false if the definition did
+// not exist. The Err method reports if any error occurred during evaluation.
+func (inst *Instance) LookupDef(path string) Value {
+ ctx := inst.index.newContext()
+ v := newValueRoot(ctx, inst.rootValue.evalPartial(ctx))
+ return v.LookupDef(path)
+}
+
// LookupField reports a Field at a path starting from v, or an error if the
// path is not. The empty path returns v itself.
//
@@ -313,6 +323,7 @@
if f.IsHidden || (i == 0 || f.IsDefinition) && !goast.IsExported(f.Name) {
return f, errNotFound
}
+ v = f.Value
}
return f, err
}
diff --git a/cue/types.go b/cue/types.go
index 3b60faf..ed269a7 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1293,14 +1293,16 @@
return Iterator{ctx: ctx, val: v, iter: n, len: len(n.arcs)}, nil
}
-// Lookup reports the value at a path starting from v.
-// The empty path returns v itself.
+// Lookup reports the value at a path starting from v. The empty path returns v
+// itself. Use LookupDef for definitions or LookupField for any kind of field.
//
// The Exists() method can be used to verify if the returned value existed.
// Lookup cannot be used to look up hidden or optional fields or definitions.
func (v Value) Lookup(path ...string) Value {
ctx := v.ctx()
for _, k := range path {
+ // TODO(eval) TODO(error): always search in full data and change error
+ // message if a field is found but is of the incorrect type.
obj, err := v.structValData(ctx)
if err != nil {
// TODO: return a Value at the same location and a new error?
@@ -1311,25 +1313,69 @@
return v
}
+// LookupDef reports the definition with the given name within struct v. The
+// Exists method of the returned value will report false if the definition did
+// not exist. The Err method reports if any error occurred during evaluation.
+func (v Value) LookupDef(name string) Value {
+ ctx := v.ctx()
+ o, err := v.structValFull(ctx)
+ if err != nil {
+ return newErrValue(v, err)
+ }
+
+ f := v.ctx().strLabel(name)
+ for i, a := range o.arcs {
+ if a.feature == f {
+ if !a.definition || a.optional {
+ break
+ }
+ return newChildValue(&o, i)
+ }
+ }
+ return newErrValue(v, ctx.mkErr(v.path.v,
+ "defintion %q not found", name))
+}
+
var errNotFound = errors.Newf(token.NoPos, "field not found")
// LookupField reports information about a field of v.
-func (v Value) LookupField(path string) (FieldInfo, error) {
+func (v Value) LookupField(name string) (FieldInfo, error) {
s, err := v.Struct()
if err != nil {
// TODO: return a Value at the same location and a new error?
return FieldInfo{}, err
}
- f, err := s.FieldByName(path)
+ f, err := s.FieldByName(name)
if err != nil {
return f, err
}
- if f.IsHidden || f.IsDefinition && !goast.IsExported(path) {
+ if f.IsHidden || f.IsDefinition && !goast.IsExported(name) {
return f, errNotFound
}
return f, err
}
+// Fill creates a new value by unifying v with the value of x at the given path.
+//
+// Any reference in v referring to the value at the given path will resolve
+// to x in the newly created value. The resulting value is not validated.
+func (v Value) Fill(x interface{}, path ...string) Value {
+ if v.path == nil {
+ return v
+ }
+ ctx := v.ctx()
+ root := v.path.val()
+ for i := len(path) - 1; i >= 0; i-- {
+ x = map[string]interface{}{path[i]: x}
+ }
+ value := convert(ctx, root, true, x)
+ a := v.path.arc
+ a.v = mkBin(ctx, v.Pos(), opUnify, root, value)
+ a.cache = a.v.evalPartial(ctx)
+ // TODO: validate recursively?
+ return Value{v.idx, &valueData{v.path.parent, v.path.index, a}}
+}
+
// Template returns a function that represents the template definition for a
// struct in a configuration file. It returns nil if v is not a struct kind or
// if there is no template associated with the struct.
@@ -1337,6 +1383,8 @@
// The returned function returns the value that would be unified with field
// given its name.
func (v Value) Template() func(label string) Value {
+ // TODO: rename to optional.
+
ctx := v.ctx()
x, ok := v.path.cache.(*structLit)
if !ok || x.optionals.isEmpty() {
diff --git a/cue/types_test.go b/cue/types_test.go
index 0f6b2d7..075f468 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -752,6 +752,68 @@
}
}
+func compile(t *testing.T, r *Runtime, s string) *Instance {
+ t.Helper()
+ inst, err := r.Compile("", s)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return inst
+}
+
+func goValue(v Value) interface{} {
+ var x interface{}
+ err := v.Decode(&x)
+ if err != nil {
+ return err
+ }
+ return x
+}
+
+func TestFill(t *testing.T) {
+ testCases := []struct {
+ in string
+ x interface{}
+ path string // comma-separated path
+ out string
+ }{{
+ in: `
+ foo: int
+ bar: foo
+ `,
+ x: 3,
+ path: "foo",
+ out: `
+ foo: 3
+ bar: 3
+ `,
+ }, {
+ in: `
+ string
+ `,
+ x: "foo",
+ path: "",
+ out: `
+ "foo"
+ `,
+ }}
+
+ for _, tc := range testCases {
+ var path []string
+ if tc.path != "" {
+ path = strings.Split(tc.path, ",")
+ }
+
+ r := &Runtime{}
+ v := compile(t, r, tc.in).Value().Fill(tc.x, path...)
+ w := compile(t, r, tc.out).Value()
+
+ if !reflect.DeepEqual(goValue(v), goValue(w)) {
+ t.Errorf("\ngot: %s\nwant: %s", v, w)
+ }
+ }
+}
+
func TestDefaults(t *testing.T) {
testCases := []struct {
value string