cue: add ReferencePath

Change-Id: I7f8024aca1200995368600010d3e024d7bd7c9a3
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9421
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/path.go b/cue/path.go
index 6d7bc10..11a7bca 100644
--- a/cue/path.go
+++ b/cue/path.go
@@ -469,3 +469,36 @@
 func (p pathError) feature(r adt.Runtime) adt.Feature {
 	return adt.InvalidLabel
 }
+
+func valueToSel(v adt.Value) Selector {
+	switch x := adt.Unwrap(v).(type) {
+	case *adt.Num:
+		i, err := x.X.Int64()
+		if err != nil {
+			return Selector{&pathError{errors.Promote(err, "invalid number")}}
+		}
+		return Index(int(i))
+	case *adt.String:
+		return Str(x.Str)
+	default:
+		return Selector{pathError{errors.Newf(token.NoPos, "dynamic selector")}}
+	}
+}
+
+func featureToSel(f adt.Feature, r adt.Runtime) Selector {
+	switch f.Typ() {
+	case adt.StringLabel:
+		return Str(f.StringValue(r))
+	case adt.IntLabel:
+		return Index(f.Index())
+	case adt.DefinitionLabel:
+		return Def(f.IdentString(r))
+	case adt.HiddenLabel, adt.HiddenDefinitionLabel:
+		ident := f.IdentString(r)
+		pkg := f.PkgID(r)
+		return Hid(ident, pkg)
+	}
+	return Selector{pathError{
+		errors.Newf(token.NoPos, "unexpected feature type %v", f.Typ()),
+	}}
+}
diff --git a/cue/types.go b/cue/types.go
index 4287d0c..81af9a3 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1849,18 +1849,46 @@
 // inst.Lookup(path) resolves to the same value, or no path if this value is not
 // a reference. If a reference contains index selection (foo[bar]), it will
 // only return a reference if the index resolves to a concrete value.
+//
+// Deprecated: use ReferencePath
 func (v Value) Reference() (inst *Instance, path []string) {
+	root, p := v.ReferencePath()
+	if !root.Exists() {
+		return nil, nil
+	}
+
+	inst = getImportFromNode(v.idx, root.v)
+	for _, sel := range p.Selectors() {
+		switch x := sel.sel.(type) {
+		case stringSelector:
+			path = append(path, string(x))
+		default:
+			path = append(path, sel.String())
+		}
+	}
+
+	return inst, path
+}
+
+// ReferencePath returns the value and path referred to by this value such that
+// value.LookupPath(path) resolves to the same value, or no path if this value
+// is not a reference.
+func (v Value) ReferencePath() (root Value, p Path) {
 	// TODO: don't include references to hidden fields.
 	if v.v == nil || len(v.v.Conjuncts) != 1 {
-		return nil, nil
+		return Value{}, Path{}
 	}
 	ctx := v.ctx()
 	c := v.v.Conjuncts[0]
 
-	return reference(v.idx, ctx, c.Env, c.Expr())
+	x, path := reference(v.idx, ctx, c.Env, c.Expr())
+	if x == nil {
+		return Value{}, Path{}
+	}
+	return makeValue(v.idx, x), Path{path: path}
 }
 
-func reference(rt *runtime.Runtime, c *adt.OpContext, env *adt.Environment, r adt.Expr) (inst *Instance, path []string) {
+func reference(rt *runtime.Runtime, c *adt.OpContext, env *adt.Environment, r adt.Expr) (inst *adt.Vertex, path []Selector) {
 	ctx := c
 	defer ctx.PopState(ctx.PushState(env, r.Source()))
 
@@ -1875,7 +1903,7 @@
 	case *adt.FieldReference:
 		env := ctx.Env(x.UpCount)
 		inst, path = mkPath(rt, nil, env.Vertex)
-		path = append(path, x.Label.SelectorString(c))
+		path = appendSelector(path, featureToSel(x.Label, rt))
 
 	case *adt.LabelReference:
 		env := ctx.Env(x.UpCount)
@@ -1885,22 +1913,19 @@
 		env := ctx.Env(x.UpCount)
 		inst, path = mkPath(rt, nil, env.Vertex)
 		v, _ := ctx.Evaluate(env, x.Label)
-		str := ctx.StringValue(v)
-		path = append(path, str)
+		path = appendSelector(path, valueToSel(v))
 
 	case *adt.ImportReference:
-		imp := x.ImportPath.StringValue(ctx)
-		inst = getImportFromPath(rt, imp)
+		inst, _ = rt.LoadImport(rt.LabelStr(x.ImportPath))
 
 	case *adt.SelectorExpr:
 		inst, path = reference(rt, c, env, x.X)
-		path = append(path, x.Sel.SelectorString(ctx))
+		path = appendSelector(path, featureToSel(x.Sel, rt))
 
 	case *adt.IndexExpr:
 		inst, path = reference(rt, c, env, x.X)
 		v, _ := ctx.Evaluate(env, x.Index)
-		str := ctx.StringValue(v)
-		path = append(path, str)
+		path = appendSelector(path, valueToSel(v))
 	}
 	if inst == nil {
 		return nil, nil
@@ -1908,23 +1933,15 @@
 	return inst, path
 }
 
-func mkPath(ctx *runtime.Runtime, a []string, v *adt.Vertex) (inst *Instance, path []string) {
+func mkPath(r *runtime.Runtime, a []Selector, v *adt.Vertex) (root *adt.Vertex, path []Selector) {
 	if v.Parent == nil {
-		return getImportFromNode(ctx, v), a
+		return v, a
 	}
-	inst, path = mkPath(ctx, a, v.Parent)
-	path = append(path, v.Label.SelectorString(ctx))
-	return inst, path
+	root, path = mkPath(r, a, v.Parent)
+	path = appendSelector(path, featureToSel(v.Label, r))
+	return root, path
 }
 
-// // References reports all references used to evaluate this value. It does not
-// // report references for sub fields if v is a struct.
-// //
-// // Deprecated: can be implemented in terms of Reference and Expr.
-// func (v Value) References() [][]string {
-// 	panic("deprecated")
-// }
-
 type options struct {
 	concrete          bool // enforce that values are concrete
 	raw               bool // show original values
diff --git a/cue/types_test.go b/cue/types_test.go
index 3315397..d74aafd 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -3104,7 +3104,7 @@
 	}
 }
 
-func TestReference(t *testing.T) {
+func TestReferencePath(t *testing.T) {
 	testCases := []struct {
 		input string
 		want  string
@@ -3120,16 +3120,31 @@
 		want:  "a",
 	}, {
 		input: "v: w: x: a.b.c, a: b: c: 1",
-		want:  "a b c",
+		want:  "a.b.c",
 	}, {
 		input: "v: w: x: w.a.b.c, v: w: a: b: c: 1",
-		want:  "v w a b c",
+		want:  "v.w.a.b.c",
 	}, {
 		input: `v: w: x: w.a.b.c, v: w: a: b: c: 1, #D: 3, opt?: 3, "v\(#D)": 3, X: {a: 3}, X`,
-		want:  "v w a b c",
+		want:  "v.w.a.b.c",
 	}, {
-		input: `v: w: x: w.a[bb]["c"], v: w: a: b: c: 1, bb: "b"`,
-		want:  "v w a b c",
+		input: `
+		v: w: x: w.a[bb]["c"]
+		v: w: a: b: c: 1
+		bb: "b"`,
+		want: "v.w.a.b.c",
+	}, {
+		input: `
+		X="\(y)": 1
+		v: w: x: X // TODO: Move up for crash
+		y: "foo"`,
+		want: "foo",
+	}, {
+		input: `
+		v: w: _
+		v: [X=string]: x: a[X]
+		a: w: 1`,
+		want: "a.w",
 	}, {
 		input: `v: {
 			for t in src {
@@ -3138,7 +3153,7 @@
 			}
 		},
 		src: ["x", "y"]`,
-		want: "v w tx",
+		want: "v.w.tx",
 	}, {
 		input: `
 		v: w: x: a
@@ -3167,8 +3182,25 @@
 			var r Runtime
 			inst, _ := r.Compile("in", tc.input) // getInstance(t, tc.input)
 			v := inst.Lookup("v", "w", "x")
+
+			root, path := v.ReferencePath()
+			if got := path.String(); got != tc.want {
+				t.Errorf("\n got %v;\nwant %v", got, tc.want)
+			}
+
+			if tc.want != "" {
+				want := "1"
+				if tc.alt != "" {
+					want = tc.alt
+				}
+				v := fmt.Sprint(root.LookupPath(path))
+				if v != want {
+					t.Errorf("path resolved to %s; want %s", v, want)
+				}
+			}
+
 			inst, a := v.Reference()
-			if got := strings.Join(a, " "); got != tc.want {
+			if got := strings.Join(a, "."); got != tc.want {
 				t.Errorf("\n got %v;\nwant %v", got, tc.want)
 			}