cue: fix struct instance matching of references
- struct changed during evalutation needs to be reflected
in Value.
- nodeRefs that are returned through Expr need to be
reanchored at the correct path.
Change-Id: I73e1ab05c6f16702ab691cebd8f0ca6777dc5fcf
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5380
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/instance.go b/cue/instance.go
index 3abd5fc..5d4127a 100644
--- a/cue/instance.go
+++ b/cue/instance.go
@@ -54,7 +54,7 @@
panic("struct must not be nil")
}
p.index = x
- x.imports[p.rootStruct] = p
+ x.imports[p.rootValue] = p
if p.ImportPath != "" {
x.importsByPath[p.ImportPath] = p
}
@@ -277,12 +277,11 @@
return i
}
-// Lookup reports the value at a path starting from the top level struct (not
-// the emitted value). The Exists method of the returned value will report false
-// 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.
+// Lookup reports the value at a path starting from the top level struct. The
+// Exists method of the returned value will report false 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. Use LookupDef for definitions or LookupField for
+// any kind of field.
func (inst *Instance) Lookup(path ...string) Value {
idx := inst.index
ctx := idx.newContext()
diff --git a/cue/types.go b/cue/types.go
index 0d7667f..b37631f 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -649,12 +649,25 @@
}
func remakeValue(base Value, v value) Value {
- path := *base.path
+ p := base.path
+ if n, ok := v.(*nodeRef); ok {
+ if q := locateNode(p, n); q != nil {
+ p = q
+ }
+ }
+ path := *p
path.v = v
path.cache = v.evalPartial(base.ctx())
return Value{base.idx, &path}
}
+func locateNode(p *valueData, n *nodeRef) *valueData {
+ // the parent must exist.
+ for ; p != nil && p.cache != n.node.(value); p = p.parent {
+ }
+ return p
+}
+
func (v Value) ctx() *context {
return v.idx.newContext()
}
@@ -664,10 +677,11 @@
}
func (v Value) makeElem(x value) Value {
+ v, e := v.evalFull(x)
return Value{v.idx, &valueData{v.path, 0, arc{
optional: true,
v: x,
- cache: evalValue(v.ctx(), x),
+ cache: e,
}}}
}
@@ -678,16 +692,22 @@
return ctx.manifest(v.path.cache)
}
-func evalValue(ctx *context, v value) evaluated {
- x := v.evalPartial(ctx)
+func (v Value) evalFull(u value) (Value, evaluated) {
+ ctx := v.ctx()
+ x := u.evalPartial(ctx)
if st, ok := x.(*structLit); ok {
var err *bottom
x, err = st.expandFields(ctx)
if err != nil {
x = err
}
+ if x != st {
+ p := *v.path
+ p.cache = x
+ v.path = &p
+ }
}
- return x
+ return v, x
}
// Eval resolves the references of a value and returns the result.
@@ -696,7 +716,7 @@
if v.path == nil {
return v
}
- return remakeValue(v, evalValue(v.ctx(), v.path.v))
+ return remakeValue(v.evalFull(v.path.v))
}
// Default reports the default value and whether it existed. It returns the
@@ -705,10 +725,7 @@
if v.path == nil {
return v, false
}
- u := v.path.cache
- if u == nil {
- u = evalValue(v.ctx(), v.path.v)
- }
+ v, u := v.evalFull(v.path.v)
x := v.ctx().manifest(u)
if x != u {
return remakeValue(v, x), true
@@ -1161,14 +1178,8 @@
// structVal returns an structVal or an error if v is not a struct.
func (v Value) structValOpts(ctx *context, o options) (structValue, *bottom) {
v, _ = v.Default() // TODO: remove?
- if err := v.checkKind(ctx, structKind); err != nil {
- return structValue{}, err
- }
- obj := v.eval(ctx).(*structLit)
- // TODO: This is expansion appropriate?
- // TODO: return an incomplete error if there are still expansions remaining.
- obj, err := obj.expandFields(ctx) // expand comprehensions
+ obj, path, err := v.getStruct()
if err != nil {
return structValue{}, err
}
@@ -1208,35 +1219,42 @@
k++
}
arcs = arcs[:k]
- return structValue{ctx, v.path, obj, arcs}, nil
+ return structValue{ctx, path, obj, arcs}, nil
}
- return structValue{ctx, v.path, obj, obj.arcs}, nil
+ return structValue{ctx, path, obj, obj.arcs}, nil
}
// Struct returns the underlying struct of a value or an error if the value
// is not a struct.
func (v Value) Struct() (*Struct, error) {
- obj, err := v.getStruct()
+ obj, path, err := v.getStruct()
if err != nil {
return nil, v.toErr(err)
}
- return &Struct{v, obj}, nil
+ return &Struct{Value{v.idx, path}, obj}, nil
}
-func (v Value) getStruct() (*structLit, *bottom) {
+func (v Value) getStruct() (*structLit, *valueData, *bottom) {
ctx := v.ctx()
if err := v.checkKind(ctx, structKind); err != nil {
- return nil, err
+ return nil, nil, err
}
- obj := v.eval(ctx).(*structLit)
+ orig := v.eval(ctx).(*structLit)
// TODO: This is expansion appropriate?
- obj, err := obj.expandFields(ctx)
+ obj, err := orig.expandFields(ctx)
if err != nil {
- return nil, err
+ return nil, nil, err
}
- return obj, nil
+ path := v.path
+ if obj != orig {
+ p := *path
+ p.arc.cache = obj
+ path = &p
+ }
+
+ return obj, path, nil
}
// Struct represents a CUE struct value.
@@ -1605,8 +1623,7 @@
case *nodeRef:
// the parent must exist.
- for ; up != nil && up.cache != x.node.(value); up = up.parent {
- }
+ up = locateNode(up, x)
var v value
v, a = mkFromRoot(c, up, d+2)
if v == nil {
diff --git a/cue/types_test.go b/cue/types_test.go
index aa90cd5..4a9611e 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -2181,6 +2181,20 @@
},
src: ["x", "y"]`,
want: "v w tx",
+ }, {
+ input: `
+ v: w: x: a
+ a: 1
+ for i in [] {
+ }
+ `,
+ want: "a",
+ }, {
+ input: `
+ v: w: close({x: a})
+ a: 1
+ `,
+ want: "a",
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
@@ -2202,6 +2216,29 @@
}
}
+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)
+ }
+}
+
func TestReferences(t *testing.T) {
config1 := `
a: {
@@ -2374,10 +2411,10 @@
input: "v: 2 mod 5",
want: "mod 2 5",
}, {
- input: "v: a.b, a b: 4",
+ input: "v: a.b, a: b: 4",
want: `. <0>.a "b"`,
}, {
- input: `v: a["b"], a b: 3 `,
+ input: `v: a["b"], a: b: 3 `,
want: `[] <0>.a "b"`,
}, {
input: "v: a[2:5], a: [1, 2, 3, 4, 5]",