internal/core/adt: refactor node lookup

This change distinguish path for evaluating values (evalState)
versus evaluating nodes (new: unifyNode).

Goals:
1) allow evaluation state to pass to calls. This allwos for more
precise partial evaluation.
2) more control over when to use addNotify.

For instance: for for loops, it is necessary to evaluate to
the AllArcs state, as all arcs need to be known at the
time of iteration.
For looking up values in a struct, however, it is okay to
have partial evaluation of struct arcs when looking up
in mode Partial.

Issue #667
Issue #661

Change-Id: Ib456dffc9d0161e8899c55ffdbe48c7a4b51bc17
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8321
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/core/adt/adt.go b/internal/core/adt/adt.go
index d746190..f67f1cb 100644
--- a/internal/core/adt/adt.go
+++ b/internal/core/adt/adt.go
@@ -114,7 +114,7 @@
 // a value.
 type Resolver interface {
 	Node
-	resolve(ctx *OpContext) *Vertex
+	resolve(ctx *OpContext, state VertexStatus) *Vertex
 }
 
 type YieldFunc func(env *Environment, s *StructLit)
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 4a9ce1d..443fc90 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -398,7 +398,7 @@
 func (c *OpContext) Resolve(env *Environment, r Resolver) (*Vertex, *Bottom) {
 	s := c.PushState(env, r.Source())
 
-	arc := r.resolve(c)
+	arc := r.resolve(c, AllArcs)
 	// TODO: check for cycle errors?
 
 	err := c.PopState(s)
@@ -583,7 +583,7 @@
 		return v
 
 	case Resolver:
-		arc := x.resolve(c)
+		arc := x.resolve(c, state)
 		if c.HasErr() {
 			return nil
 		}
@@ -600,6 +600,67 @@
 	}
 }
 
+// unifyNode returns a possibly partially evaluated node value.
+//
+// TODO: maybe return *Vertex, *Bottom
+func (c *OpContext) unifyNode(v Expr, state VertexStatus) (result Value) {
+	savedSrc := c.src
+	c.src = v.Source()
+	err := c.errs
+	c.errs = nil
+
+	defer func() {
+		c.errs = CombineErrors(c.src, c.errs, err)
+		// TODO: remove this when we handle errors more principally.
+		if b, ok := result.(*Bottom); ok && c.src != nil &&
+			b.Code == CycleError &&
+			b.Err.Position() == token.NoPos &&
+			len(b.Err.InputPositions()) == 0 {
+			bb := *b
+			bb.Err = errors.Wrapf(b.Err, c.src.Pos(), "")
+			result = &bb
+		}
+		c.errs = CombineErrors(c.src, c.errs, result)
+		if c.errs != nil {
+			result = c.errs
+		}
+		c.src = savedSrc
+	}()
+
+	switch x := v.(type) {
+	case Value:
+		return x
+
+	case Evaluator:
+		v := x.evaluate(c)
+		return v
+
+	case Resolver:
+		v := x.resolve(c, state)
+		if c.HasErr() {
+			return nil
+		}
+		if v == nil {
+			return nil
+		}
+
+		if v.BaseValue == nil || v.BaseValue == cycle {
+			// Use node itself to allow for cycle detection.
+			c.Unifier.Unify(c, v, state)
+		}
+
+		if b, _ := v.BaseValue.(*Bottom); b != nil && b.Code != IncompleteError {
+			return b
+		}
+
+		return v
+
+	default:
+		// This can only happen, really, if v == nil, which is not allowed.
+		panic(fmt.Sprintf("unexpected Expr type %T", v))
+	}
+}
+
 func (c *OpContext) lookup(x *Vertex, pos token.Pos, l Feature) *Vertex {
 	if l == InvalidLabel || x == nil {
 		// TODO: is it possible to have an invalid label here? Maybe through the
@@ -726,12 +787,11 @@
 	return x.Source().Pos()
 }
 
-func (c *OpContext) node(orig Node, x Expr, scalar bool) *Vertex {
+func (c *OpContext) node(orig Node, x Expr, scalar bool, state VertexStatus) *Vertex {
 	// TODO: always get the vertex. This allows a whole bunch of trickery
 	// down the line.
-	// This must be partial, because
-	// TODO: this should always be "AllArcs"
-	v := c.evalState(x, AllArcs)
+
+	v := c.unifyNode(x, AllArcs)
 
 	v, ok := c.getDefault(v)
 	if !ok {
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index ba5aabd..494199e 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -533,8 +533,11 @@
 func (x *NodeLink) Kind() Kind {
 	return x.Node.Kind()
 }
-func (x *NodeLink) Source() ast.Node             { return x.Node.Source() }
-func (x *NodeLink) resolve(c *OpContext) *Vertex { return x.Node }
+func (x *NodeLink) Source() ast.Node { return x.Node.Source() }
+
+func (x *NodeLink) resolve(c *OpContext, state VertexStatus) *Vertex {
+	return x.Node
+}
 
 // A FieldReference represents a lexical reference to a field.
 //
@@ -553,7 +556,7 @@
 	return x.Src
 }
 
-func (x *FieldReference) resolve(c *OpContext) *Vertex {
+func (x *FieldReference) resolve(c *OpContext, state VertexStatus) *Vertex {
 	n := c.relNode(x.UpCount)
 	pos := pos(x)
 	return c.lookup(n, pos, x.Label)
@@ -616,7 +619,7 @@
 	return x.Src
 }
 
-func (x *DynamicReference) resolve(ctx *OpContext) *Vertex {
+func (x *DynamicReference) resolve(ctx *OpContext, state VertexStatus) *Vertex {
 	e := ctx.Env(x.UpCount)
 	frame := ctx.PushState(e, x.Src)
 	v := ctx.value(x.Label)
@@ -644,7 +647,7 @@
 	return x.Src
 }
 
-func (x *ImportReference) resolve(ctx *OpContext) *Vertex {
+func (x *ImportReference) resolve(ctx *OpContext, state VertexStatus) *Vertex {
 	path := x.ImportPath.StringValue(ctx)
 	v, _ := ctx.Runtime.LoadImport(path)
 	return v
@@ -668,7 +671,7 @@
 	return x.Src
 }
 
-func (x *LetReference) resolve(c *OpContext) *Vertex {
+func (x *LetReference) resolve(c *OpContext, state VertexStatus) *Vertex {
 	e := c.Env(x.UpCount)
 	label := e.Vertex.Label
 	if x.X == nil {
@@ -702,8 +705,8 @@
 	return x.Src
 }
 
-func (x *SelectorExpr) resolve(c *OpContext) *Vertex {
-	n := c.node(x, x.X, x.Sel.IsRegular())
+func (x *SelectorExpr) resolve(c *OpContext, state VertexStatus) *Vertex {
+	n := c.node(x, x.X, x.Sel.IsRegular(), state)
 	if n == emptyNode {
 		return n
 	}
@@ -727,9 +730,9 @@
 	return x.Src
 }
 
-func (x *IndexExpr) resolve(ctx *OpContext) *Vertex {
+func (x *IndexExpr) resolve(ctx *OpContext, state VertexStatus) *Vertex {
 	// TODO: support byte index.
-	n := ctx.node(x, x.X, true)
+	n := ctx.node(x, x.X, true, state)
 	i := ctx.value(x.Index)
 	if n == emptyNode {
 		return n
@@ -1379,7 +1382,7 @@
 }
 
 func (x *ForClause) yield(c *OpContext, f YieldFunc) {
-	n := c.node(x, x.Src, true)
+	n := c.node(x, x.Src, true, AllArcs)
 	for _, a := range n.Arcs {
 		if !a.Label.IsRegular() {
 			continue