internal/core/eval: fix spurious cycle detection

When evaluating a Vertex for a selector, outside the usual
arc evaluation path, it is not necessarily on a cycle path.
Delay marking such a Vertex as "EvaluatingArcs" to prevent
spurious cycle detection. If there is indeed a cycle, then
this will be marked as such as the cycle will be triggered
at the usual path anyway.

Fixes #588

Change-Id: Ieae3eebd0157847537cfd02c06d3ffe64e04987d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7622
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index ddc4ad5..2734599 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -23,6 +23,18 @@
     a: a | int
 }
 
+a7: {
+    a: c.x
+    b: {
+        x: c
+        y: "foo"
+    }
+    c: {
+        x: b.y
+        y: 3
+    }
+}
+
 b1: {
     b: a & [1]
     a: [a|int]
@@ -387,8 +399,8 @@
 b14.root.b.1.1: structural cycle
 b4.x.y.0: structural cycle
 b6.b.a.0: conflicting values 1 and [1] (mismatched types int and list):
-    ./in.cue:51:16
-    ./in.cue:51:17
+    ./in.cue:63:16
+    ./in.cue:63:17
 b6.b.a.0.0: structural cycle
 b6.x.a.0: structural cycle
 b7.a.0: structural cycle
@@ -401,46 +413,46 @@
 e2.a.c: structural cycle
 e2.b.c: structural cycle
 e3.a: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:277:8
-    ./in.cue:278:8
+    ./in.cue:289:8
+    ./in.cue:290:8
 e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:277:8
-    ./in.cue:278:8
+    ./in.cue:289:8
+    ./in.cue:290:8
 e3.a.0: structural cycle
 e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:277:8
-    ./in.cue:278:8
+    ./in.cue:289:8
+    ./in.cue:290:8
 e3.a.c: structural cycle
 e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:280:8
-    ./in.cue:281:8
+    ./in.cue:292:8
+    ./in.cue:293:8
 e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:280:8
-    ./in.cue:281:8
+    ./in.cue:292:8
+    ./in.cue:293:8
 e3.b.0: structural cycle
 e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:280:8
-    ./in.cue:281:8
+    ./in.cue:292:8
+    ./in.cue:293:8
 e3.b.c: structural cycle
 e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:285:13
-    ./in.cue:286:9
+    ./in.cue:297:13
+    ./in.cue:298:9
 e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:288:9
-    ./in.cue:289:13
+    ./in.cue:300:9
+    ./in.cue:301:13
 p2.#T.a.b.link: structural cycle
 p3.#U.#T.a.b.link: structural cycle
 p5.#T.a.0.link: structural cycle
 p6.#U.#T.a.0.link: structural cycle
 z1.z.g.h: structural cycle
 b4.x.y.0: structural cycle:
-    ./in.cue:41:8
+    ./in.cue:53:8
 d2.a.b.c.d.t: structural cycle:
-    ./in.cue:242:8
+    ./in.cue:254:8
 d2.r: structural cycle:
-    ./in.cue:242:8
+    ./in.cue:254:8
 0: structural cycle:
-    ./in.cue:252:19
+    ./in.cue:264:19
 
 Result:
 (_|_){
@@ -483,6 +495,20 @@
   a6: (struct){
     a: (_){ |((_){ _ }, (int){ int }) }
   }
+  a7: (struct){
+    a: (string){ "foo" }
+    b: (struct){
+      x: (struct){
+        x: (string){ "foo" }
+        y: (int){ 3 }
+      }
+      y: (string){ "foo" }
+    }
+    c: (struct){
+      x: (string){ "foo" }
+      y: (int){ 3 }
+    }
+  }
   b1: (struct){
     b: (#list){
       0: (int){ 1 }
@@ -515,7 +541,7 @@
     // [structural cycle]
     b: (_|_){
       // [structural cycle] b4.x.y.0: structural cycle:
-      //     ./in.cue:41:8
+      //     ./in.cue:53:8
       0: (int){ 1 }
     }
     x: (_|_){
@@ -552,8 +578,8 @@
         // [eval]
         0: (_|_){
           // [eval] b6.b.a.0: conflicting values 1 and [1] (mismatched types int and list):
-          //     ./in.cue:51:16
-          //     ./in.cue:51:17
+          //     ./in.cue:63:16
+          //     ./in.cue:63:17
           0: (_|_){
             // [structural cycle] b6.b.a.0.0: structural cycle
           }
@@ -997,11 +1023,11 @@
     // [structural cycle]
     x: (_|_){
       // [structural cycle] d2.a.b.c.d.t: structural cycle:
-      //     ./in.cue:242:8
+      //     ./in.cue:254:8
     }
     r: (_|_){
       // [structural cycle] d2.r: structural cycle:
-      //     ./in.cue:242:8
+      //     ./in.cue:254:8
       c: (_|_){// {
         //   d: {
         //     h: int
@@ -1044,19 +1070,19 @@
           // [structural cycle]
           c: (_|_){
             // [structural cycle] 0: structural cycle:
-            //     ./in.cue:252:19
+            //     ./in.cue:264:19
           }
         }
       }
       indirect: (_|_){
         // [structural cycle] 0: structural cycle:
-        //     ./in.cue:252:19
+        //     ./in.cue:264:19
       }
       i: (int){ 1 }
     }
     x: (_|_){
       // [structural cycle] 0: structural cycle:
-      //     ./in.cue:252:19
+      //     ./in.cue:264:19
       i: (int){ 0 }
     }
   }
@@ -1102,12 +1128,12 @@
     // [eval]
     a: (_|_){
       // [eval] e3.a: conflicting values [a] and {c:a} (mismatched types list and struct):
-      //     ./in.cue:277:8
-      //     ./in.cue:278:8
+      //     ./in.cue:289:8
+      //     ./in.cue:290:8
       c: (_|_){
         // [eval] e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:277:8
-        //     ./in.cue:278:8
+        //     ./in.cue:289:8
+        //     ./in.cue:290:8
         // e3.a.c: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -1116,8 +1142,8 @@
       }
       0: (_|_){
         // [eval] e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:277:8
-        //     ./in.cue:278:8
+        //     ./in.cue:289:8
+        //     ./in.cue:290:8
         // e3.a.0: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -1127,12 +1153,12 @@
     }
     b: (_|_){
       // [eval] e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-      //     ./in.cue:280:8
-      //     ./in.cue:281:8
+      //     ./in.cue:292:8
+      //     ./in.cue:293:8
       c: (_|_){
         // [eval] e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:280:8
-        //     ./in.cue:281:8
+        //     ./in.cue:292:8
+        //     ./in.cue:293:8
         // e3.b.c: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -1141,8 +1167,8 @@
       }
       0: (_|_){
         // [eval] e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:280:8
-        //     ./in.cue:281:8
+        //     ./in.cue:292:8
+        //     ./in.cue:293:8
         // e3.b.0: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -1157,8 +1183,8 @@
       // [eval]
       0: (_|_){
         // [eval] e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:285:13
-        //     ./in.cue:286:9
+        //     ./in.cue:297:13
+        //     ./in.cue:298:9
         0: (struct){
           c: (int){ 1 }
         }
@@ -1168,8 +1194,8 @@
       // [eval]
       0: (_|_){
         // [eval] e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:288:9
-        //     ./in.cue:289:13
+        //     ./in.cue:300:9
+        //     ./in.cue:301:13
         0: (struct){
           c: (int){ 1 }
         }
@@ -1357,6 +1383,17 @@
   a6: {
     a: (〈0;a〉|int)
   }
+  a7: {
+    a: 〈0;c〉.x
+    b: {
+      x: 〈1;c〉
+      y: "foo"
+    }
+    c: {
+      x: 〈1;b〉.y
+      y: 3
+    }
+  }
   b1: {
     b: (〈0;a〉 & [
       1,
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index e5aeba4..2e8806c 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -228,6 +228,10 @@
 	// nodeContext to allow reusing the computations done so far.
 	Partial
 
+	// FieldSetDefined indicates that the value and conjuncts of all fields have
+	// been completed, but that the individual arcs are not yet evaluated.
+	FieldSetDefined
+
 	// EvaluatingArcs indicates that the arcs of the Vertex are currently being
 	// evaluated. If this is encountered it indicates a structural cycle.
 	// Value does not have to be nil
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index d8c4876..e0f7819 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -585,7 +585,7 @@
 }
 
 func (x *SelectorExpr) resolve(c *OpContext) *Vertex {
-	n := c.node(x.X, EvaluatingArcs)
+	n := c.node(x.X, FieldSetDefined)
 	return c.lookup(n, x.Src.Sel.Pos(), x.Sel)
 }
 
diff --git a/internal/core/eval/disjunct.go b/internal/core/eval/disjunct.go
index 2faa272..057e596 100644
--- a/internal/core/eval/disjunct.go
+++ b/internal/core/eval/disjunct.go
@@ -126,8 +126,8 @@
 		envDisjunct{env, a, x.NumDefaults, cloneID})
 }
 
-func (n *nodeContext) updateResult() (isFinal bool) {
-	n.postDisjunct()
+func (n *nodeContext) updateResult(state adt.VertexStatus) (isFinal bool) {
+	n.postDisjunct(state)
 
 	if n.hasErr() {
 		return n.isFinal
@@ -182,8 +182,8 @@
 	return n.isFinal
 }
 
-func (n *nodeContext) tryDisjuncts() (finished bool) {
-	if !n.insertDisjuncts() || !n.updateResult() {
+func (n *nodeContext) tryDisjuncts(state adt.VertexStatus) (finished bool) {
+	if !n.insertDisjuncts() || !n.updateResult(state) {
 		if !n.isFinal {
 			return false // More iterations to do.
 		}
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 006ee26..079550c 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -132,7 +132,7 @@
 func (e *Evaluator) Eval(v *adt.Vertex) errors.Error {
 	if v.Value == nil {
 		ctx := adt.NewContext(e.r, e, v)
-		e.Unify(ctx, v, adt.Finalized)
+		e.Unify(ctx, v, adt.Partial)
 	}
 
 	// extract error if needed.
@@ -268,7 +268,7 @@
 
 	// defer c.PopVertex(c.PushVertex(v))
 
-	if state <= v.Status()+1 {
+	if state <= v.Status() {
 		return
 	}
 
@@ -434,7 +434,7 @@
 
 		// Handle disjunctions. If there are no disjunctions, this call is
 		// equivalent to calling n.postDisjunct.
-		if n.tryDisjuncts() {
+		if n.tryDisjuncts(state) {
 			if v.Value == nil {
 				v.Value = n.getValidators()
 			}
@@ -452,7 +452,7 @@
 	return ok
 }
 
-func (n *nodeContext) postDisjunct() {
+func (n *nodeContext) postDisjunct(state adt.VertexStatus) {
 	ctx := n.ctx
 
 	for {
@@ -595,6 +595,12 @@
 
 	n.updateClosedInfo()
 
+	n.completeArcs(state)
+}
+
+func (n *nodeContext) completeArcs(state adt.VertexStatus) {
+	ctx := n.ctx
+
 	if cyclic := n.hasCycle && !n.hasNonCycle; cyclic {
 		n.node.Value = adt.CombineErrors(nil, n.node.Value, &adt.Bottom{
 			Code:  adt.StructuralCycleError,
@@ -607,7 +613,9 @@
 		for _, a := range n.node.Arcs {
 			// Call UpdateStatus here to be absolutely sure the status is set
 			// correctly and that we are not regressing.
-			n.node.UpdateStatus(adt.EvaluatingArcs)
+			if state == adt.Finalized {
+				n.node.UpdateStatus(adt.EvaluatingArcs)
+			}
 			n.eval.Unify(ctx, a, adt.Finalized)
 			if err, _ := a.Value.(*adt.Bottom); err != nil {
 				n.node.AddChildError(err)