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