cue: add Dereference and fix value paths

Ensure Value's path reflects the actual values when
using the various functions.

Also introduces Dereference to follow a reference
to a newly rooted Value.

Change-Id: Ie555322a81a82d662bf7ef4a9b6ed072c62b0ade
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5404
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt b/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
index 101c0d9..71d9d02 100644
--- a/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
@@ -3,7 +3,7 @@
 cmp stderr cmd_baddisplay.out
 
 -- cmd_baddisplay.out --
-text: conflicting values 42 and string (mismatched types int and string):
+command.baddisplay.display.text: conflicting values 42 and string (mismatched types int and string):
     ./task_tool.cue:6:9
     tool/cli:4:9
 -- task.cue --
diff --git a/cue/op.go b/cue/op.go
index f465692..a1d7812 100644
--- a/cue/op.go
+++ b/cue/op.go
@@ -64,7 +64,10 @@
 )
 
 var opToOp = map[op]Op{
-	opUnify:          AndOp,
+	opUnify: AndOp,
+	// TODO(eval): opUnifyUnchecked is not the same as opUnify and should have its own
+	// category, if needed. More likely opUnifyUnchecked, should be
+	// represented as a separate embedding method.
 	opUnifyUnchecked: AndOp,
 	opDisjunction:    OrOp,
 	opLand:           BooleanAndOp,
diff --git a/cue/types.go b/cue/types.go
index 7481905..9b71831 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -648,6 +648,82 @@
 	return Value{obj.ctx.index, &valueData{obj.path, uint32(i), a}}
 }
 
+// Dereference reports to the value v refers to if v is a reference or v itself
+// otherwise.
+func Dereference(v Value) Value {
+	if v.path == nil {
+		return v
+	}
+
+	ctx := v.ctx()
+	a, n := appendPath(ctx, make([]label, 0, 3), v.path.v)
+
+	if n == nil {
+		return v
+
+	}
+
+	p := locateNode(v.path, n)
+
+	if p == nil {
+
+		imp := ctx.getImportFromNode(n.node)
+		if imp == nil {
+			// TODO(eval): embedded structs are currently represented at the
+			// same level as the enclosing struct. This means that the parent
+			// of an embedded struct skips the struct in which it is embedded.
+			// Treat embedded structs as "anonymous" fields.
+			// See TestPathCorrection.
+			return v
+		}
+		p = &valueData{arc: arc{v: imp.rootValue, cache: imp.rootStruct}}
+	}
+
+	cached := p.cache
+	if cached != nil {
+		cached = p.v.evalPartial(ctx)
+	}
+	s := cached.(*structLit)
+	for _, f := range a {
+		a := s.lookup(ctx, f)
+		p = &valueData{parent: p, arc: a} // index
+		s, _ = a.cache.(*structLit)
+	}
+
+	v = Value{v.idx, p}
+	return v
+}
+
+func appendPath(ctx *context, a []label, v value) (path []label, n *nodeRef) {
+	switch x := v.(type) {
+	case *selectorExpr:
+		a, n = appendPath(ctx, a, x.x)
+		if n == nil {
+			return nil, nil
+		}
+
+		a = append(a, x.feature)
+
+	case *indexExpr:
+		e := x.index.evalPartial(ctx)
+		s, ok := e.(*stringLit)
+		if !ok {
+			return nil, nil
+		}
+
+		a, n = appendPath(ctx, a, x.x)
+		if n == nil {
+			return nil, nil
+		}
+
+		a = append(a, ctx.label(s.str, false))
+
+	case *nodeRef:
+		n = x
+	}
+	return a, n
+}
+
 func remakeValue(base Value, v value) Value {
 	p := base.path
 	if n, ok := v.(*nodeRef); ok {
@@ -1100,7 +1176,7 @@
 // Elem returns the value of undefined element types of lists and structs.
 func (v Value) Elem() (Value, bool) {
 	ctx := v.ctx()
-	switch x := v.path.v.(type) {
+	switch x := v.path.cache.(type) {
 	case *structLit:
 		t, _ := x.optionals.constraint(ctx, nil)
 		if t == nil {
@@ -1491,11 +1567,11 @@
 		return nil
 	}
 
-	return func(label string) (v Value) {
+	return func(label string) Value {
 		arg := &stringLit{x.baseValue, label, nil}
 
-		if v, _ := x.optionals.constraint(ctx, arg); v != nil {
-			return newValueRoot(ctx, v)
+		if val, _ := x.optionals.constraint(ctx, arg); val != nil {
+			return remakeValue(v, val)
 		}
 		return v
 	}
@@ -1563,7 +1639,7 @@
 	b := w.path.v
 	src := binSrc(token.NoPos, opUnify, a, b)
 	val := mkBin(ctx, src.Pos(), opUnify, a, b)
-	u := newValueRoot(ctx, val)
+	u := remakeValue(v, val)
 	if err := u.Validate(); err != nil {
 		u = newValueRoot(ctx, ctx.mkErr(src, err))
 	}
@@ -1588,7 +1664,14 @@
 		fmt.Fprint(state, "<nil>")
 		return
 	}
-	_, _ = io.WriteString(state, ctx.str(v.path.cache))
+	switch {
+	case state.Flag('#'):
+		_, _ = io.WriteString(state, ctx.str(v.path.v))
+	case state.Flag('+'):
+		_, _ = io.WriteString(state, debugStr(ctx, v.path.v))
+	default:
+		_, _ = io.WriteString(state, ctx.str(v.path.cache))
+	}
 }
 
 func (v Value) instance() *Instance {
@@ -1652,9 +1735,18 @@
 
 	case *nodeRef:
 		// the parent must exist.
-		up = locateNode(up, x)
 		var v value
-		v, a = mkFromRoot(c, up, d+2)
+		if p := locateNode(up, x); p != nil {
+			v, a = mkFromRoot(c, p, d+2)
+		} else {
+			// Either this references another parent, or it is an embedding.
+			imp = c.getImportFromNode(x.node)
+			if imp != nil {
+				break
+			}
+			// This must be an embedding, go one up.
+			v, a = mkFromRoot(c, up.parent, d+2)
+		}
 		if v == nil {
 			v = x.node
 		}
diff --git a/cue/types_test.go b/cue/types_test.go
index a4c7b0b..0160f4f 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -2267,25 +2267,194 @@
 }
 
 func TestPathCorrection(t *testing.T) {
-	var r Runtime
-	inst, err := r.Compile("in", `
-	a: b: {
-		c: d: b
-	}
-	`)
-	if err != nil {
-		t.Fatal(err)
-	}
-	_, a := inst.Lookup("a", "b", "c", "d").Expr()
-	v := a[0].Lookup("b", "c", "d")
-	gotInst, ref := v.Reference()
-	if gotInst != inst {
-		t.Error("reference not in original instance")
-	}
-	gotPath := strings.Join(ref, ".")
-	wantPath := "a.b"
-	if gotPath != wantPath {
-		t.Errorf("got path %s; want %s", gotPath, wantPath)
+	testCases := []struct {
+		input  string
+		lookup func(i *Instance) Value
+		want   string
+		skip   bool
+	}{{
+		input: `
+		a: b: {
+			c: d: b
+		}
+		`,
+		lookup: func(i *Instance) Value {
+			_, a := i.Lookup("a", "b", "c", "d").Expr()
+			return a[0].Lookup("b", "c", "d")
+		},
+		want: "a.b",
+	}, {
+		input: `
+		a: {
+			c: 3
+			{x: c}
+		}
+		`,
+		lookup: func(i *Instance) Value {
+			_, a := i.Lookup("a").Expr()
+			return a[1].Lookup("x")
+		},
+		want: "a.c",
+	}, {
+		input: `
+		a: b: [...T]
+		a: b: [...T]
+		T: 1
+		`,
+		lookup: func(i *Instance) Value {
+			v, _ := i.Lookup("a", "b").Elem()
+			_, a := v.Expr()
+			return a[0]
+		},
+		want: "T",
+	}, {
+		input: `
+			a :: {
+				T :: {b: 3}
+				close({}) | close({c: T}) | close({d: string})
+			}
+			`,
+		lookup: func(i *Instance) Value {
+			f, _ := i.LookupField("a")
+			_, a := f.Value.Expr() // &
+			_, a = a[1].Expr()     // |
+			return a[1].Lookup("c")
+		},
+		want: "a.T",
+	}, {
+		input: `
+		package foo
+
+		Struct :: {
+			T :: int
+
+			{b?: T}
+		}`,
+		want: "Struct.T",
+		lookup: func(inst *Instance) Value {
+			// Locate Struct
+			i, _ := inst.Value().Fields(Definitions(true))
+			if !i.Next() {
+				t.Fatal("no fields")
+			}
+			// Locate b
+			i, _ = i.Value().Fields(Definitions(true), Optional(true))
+			if !(i.Next() && i.Next()) {
+				t.Fatal("no fields")
+			}
+			v := i.Value()
+			return v
+		},
+	}, {
+		input: `
+		package foo
+
+		A :: B :: T
+
+		T :: {
+			a: S.U
+			S :: U:: {}
+		}
+		`,
+		want: "T.S.U",
+		lookup: func(inst *Instance) Value {
+			f, _ := inst.Value().LookupField("A")
+			f, _ = f.Value.LookupField("B")
+			v := f.Value
+			v = Dereference(v)
+			v = v.Lookup("a")
+			return v
+		},
+	}, {
+		input: `
+		package foo
+
+		A :: B :: T
+
+		T :: {
+			a: [...S]
+			S :: {}
+		}
+		`,
+		want: "T.S",
+		lookup: func(inst *Instance) Value {
+			f, _ := inst.Value().LookupField("A")
+			f, _ = f.Value.LookupField("B")
+			v := f.Value
+			v = Dereference(v)
+			v, _ = v.Lookup("a").Elem()
+			return v
+		},
+	}, {
+		input: `
+		A :: {
+			b: T
+		}
+
+		T :: {
+			a: S
+			S :: {}
+		}
+		`,
+		want: "T.S",
+		lookup: func(inst *Instance) Value {
+			f, _ := inst.Value().LookupField("A")
+			v := f.Value.Lookup("b")
+			v = Dereference(v)
+			v = v.Lookup("a")
+			return v
+		},
+	}, {
+		// TODO(eval): embedded structs are currently represented at the same
+		// level as the enclosing struct. This means that the parent of an
+		// embedded struct skips the struct in which it is embedded. Treat
+		// embedded structs as "anonymous" fields.
+		// This could perhaps be made fixed with dereferencing as well.
+		skip: true,
+		input: `
+		Tracing :: {
+			T :: { address?: string }
+			S :: { ip?: string }
+
+			close({}) | close({
+				t: T
+			}) | close({
+				s: S
+			})
+		}
+		X :: {}
+		X // Disconnect top-level struct from the one visible by close.
+		`,
+		want: "",
+		lookup: func(inst *Instance) Value {
+			f, _ := inst.Value().LookupField("Tracing")
+			v := f.Value.Eval()
+			_, args := v.Expr()
+			v = args[1].Lookup("t")
+			v = Dereference(v)
+			return v
+		},
+	}}
+	for _, tc := range testCases {
+		if tc.skip {
+			continue
+		}
+		t.Run("", func(t *testing.T) {
+			var r Runtime
+			inst, err := r.Compile("in", tc.input)
+			if err != nil {
+				t.Fatal(err)
+			}
+			v := tc.lookup(inst)
+			gotInst, ref := v.Reference()
+			if gotInst != inst {
+				t.Error("reference not in original instance")
+			}
+			gotPath := strings.Join(ref, ".")
+			if gotPath != tc.want {
+				t.Errorf("got path %s; want %s", gotPath, tc.want)
+			}
+		})
 	}
 }
 
diff --git a/internal/filetypes/filetypes_test.go b/internal/filetypes/filetypes_test.go
index 4a38607..29bc9fc 100644
--- a/internal/filetypes/filetypes_test.go
+++ b/internal/filetypes/filetypes_test.go
@@ -42,7 +42,7 @@
 	}{{
 		name: "must specify encoding",
 		in:   build.File{},
-		out:  `encoding: non-concrete value string`,
+		out:  `FileInfo.encoding: non-concrete value string`,
 	}, {
 		// Default without any
 		name: "cue",