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)
}