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]",