internal/core/eval: fix cycle bug for comprehensions

don't evaluate comprehensions in active cycle

Fixes #509

Change-Id: I50b1cbda2da548fc4b1a2c5bb91e83f542a0f9ab
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7482
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 7c24827..aaccc3c 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -88,6 +88,43 @@
     })
 }
 
+// Issue #509 -- with comprehension
+b11: {
+    #list: {
+      tail: #list | *null
+      if tail != null {
+      }
+    }
+}
+
+// Issue #509 -- with comprehension
+b12: {
+    #list: {
+      V=value: int
+      T=tail: #list|*null
+      if T != null {
+        sum: V + T.sum
+      }
+      if T == null {
+        sum: V
+      }
+    }
+
+    list1: #list
+      list1: {
+        value: 1,
+        tail: {
+          value: 2
+          tail: {
+          value: 3
+          tail: {
+            value: 4
+          }
+        }
+      }
+    }
+}
+
 c1: {
   a: {
     b: {}
@@ -263,43 +300,43 @@
 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:141:8
-    ./in.cue:142:8
+    ./in.cue:178:8
+    ./in.cue:179:8
 e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:141:8
-    ./in.cue:142:8
+    ./in.cue:178:8
+    ./in.cue:179:8
 e3.a.0: structural cycle
 e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:141:8
-    ./in.cue:142:8
+    ./in.cue:178:8
+    ./in.cue:179:8
 e3.a.c: structural cycle
 e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:144:8
-    ./in.cue:145:8
+    ./in.cue:181:8
+    ./in.cue:182:8
 e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:144:8
-    ./in.cue:145:8
+    ./in.cue:181:8
+    ./in.cue:182:8
 e3.b.0: structural cycle
 e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:144:8
-    ./in.cue:145:8
+    ./in.cue:181:8
+    ./in.cue:182:8
 e3.b.c: structural cycle
 e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:149:13
-    ./in.cue:150:9
+    ./in.cue:186:13
+    ./in.cue:187:9
 e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:152:9
-    ./in.cue:153:13
+    ./in.cue:189:9
+    ./in.cue:190:13
 z1.z.f.h.h: structural cycle
 z1.z.g.h: structural cycle
 b4.x.y.0: structural cycle:
     ./in.cue:41:8
 d2.a.b.c.d.t: structural cycle:
-    ./in.cue:106:8
+    ./in.cue:143:8
 d2.r: structural cycle:
-    ./in.cue:106:8
+    ./in.cue:143:8
 0: structural cycle:
-    ./in.cue:116:19
+    ./in.cue:153:19
 
 Result:
 (_|_){
@@ -502,6 +539,35 @@
       d: (string){ string }
     }
   }
+  b11: (struct){
+    #list: (#struct){
+      tail: (null){ null }
+    }
+  }
+  b12: (struct){
+    #list: (#struct){
+      value: (int){ int }
+      tail: (null){ null }
+      sum: (int){ int }
+    }
+    list1: (#struct){
+      value: (int){ 1 }
+      tail: (#struct){
+        value: (int){ 2 }
+        tail: (#struct){
+          value: (int){ 3 }
+          tail: (#struct){
+            value: (int){ 4 }
+            tail: (null){ null }
+            sum: (int){ 4 }
+          }
+          sum: (int){ 7 }
+        }
+        sum: (int){ 9 }
+      }
+      sum: (int){ 10 }
+    }
+  }
   c1: (_|_){
     // [structural cycle]
     a: (_|_){
@@ -565,11 +631,11 @@
     // [structural cycle]
     x: (_|_){
       // [structural cycle] d2.a.b.c.d.t: structural cycle:
-      //     ./in.cue:106:8
+      //     ./in.cue:143:8
     }
     r: (_|_){
       // [structural cycle] d2.r: structural cycle:
-      //     ./in.cue:106:8
+      //     ./in.cue:143:8
       c: (_|_){// {
         //   d: {
         //     h: int
@@ -612,13 +678,13 @@
           // [structural cycle]
           c: (_|_){
             // [structural cycle] 0: structural cycle:
-            //     ./in.cue:116:19
+            //     ./in.cue:153:19
           }
         }
       }
       indirect: (_|_){
         // [structural cycle] 0: structural cycle:
-        //     ./in.cue:116:19
+        //     ./in.cue:153:19
       }
       i: (int){ 1 }
     }
@@ -630,13 +696,13 @@
           // [structural cycle]
           c: (_|_){
             // [structural cycle] 0: structural cycle:
-            //     ./in.cue:116:19
+            //     ./in.cue:153:19
           }
         }
       }
       indirect: (_|_){
         // [structural cycle] 0: structural cycle:
-        //     ./in.cue:116:19
+        //     ./in.cue:153:19
       }
       i: (int){ 0 }
     }
@@ -683,12 +749,12 @@
     // [eval]
     a: (_|_){
       // [eval] e3.a: conflicting values [a] and {c:a} (mismatched types list and struct):
-      //     ./in.cue:141:8
-      //     ./in.cue:142:8
+      //     ./in.cue:178:8
+      //     ./in.cue:179:8
       c: (_|_){
         // [eval] e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:141:8
-        //     ./in.cue:142:8
+        //     ./in.cue:178:8
+        //     ./in.cue:179:8
         // e3.a.c: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -697,8 +763,8 @@
       }
       0: (_|_){
         // [eval] e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:141:8
-        //     ./in.cue:142:8
+        //     ./in.cue:178:8
+        //     ./in.cue:179:8
         // e3.a.0: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -708,12 +774,12 @@
     }
     b: (_|_){
       // [eval] e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-      //     ./in.cue:144:8
-      //     ./in.cue:145:8
+      //     ./in.cue:181:8
+      //     ./in.cue:182:8
       c: (_|_){
         // [eval] e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:144:8
-        //     ./in.cue:145:8
+        //     ./in.cue:181:8
+        //     ./in.cue:182:8
         // e3.b.c: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -722,8 +788,8 @@
       }
       0: (_|_){
         // [eval] e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:144:8
-        //     ./in.cue:145:8
+        //     ./in.cue:181:8
+        //     ./in.cue:182:8
         // e3.b.0: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -738,8 +804,8 @@
       // [eval]
       0: (_|_){
         // [eval] e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:149:13
-        //     ./in.cue:150:9
+        //     ./in.cue:186:13
+        //     ./in.cue:187:9
         0: (struct){
           c: (int){ 1 }
         }
@@ -749,8 +815,8 @@
       // [eval]
       0: (_|_){
         // [eval] e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:152:9
-        //     ./in.cue:153:13
+        //     ./in.cue:189:9
+        //     ./in.cue:190:13
         0: (struct){
           c: (int){ 1 }
         }
@@ -1041,6 +1107,37 @@
       d: (string|〈1;a〉)
     })
   }
+  b11: {
+    #list: {
+      tail: (〈1;#list〉|*null)
+      if (〈0;tail〉 != null) {}
+    }
+  }
+  b12: {
+    #list: {
+      value: int
+      tail: (〈1;#list〉|*null)
+      if (〈0;tail〉 != null) {
+        sum: (〈1;value〉 + 〈1;tail〉.sum)
+      }
+      if (〈0;tail〉 == null) {
+        sum: 〈1;value〉
+      }
+    }
+    list1: 〈0;#list〉
+    list1: {
+      value: 1
+      tail: {
+        value: 2
+        tail: {
+          value: 3
+          tail: {
+            value: 4
+          }
+        }
+      }
+    }
+  }
   c1: {
     a: {
       b: {}
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 98ec358..e078801 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -1701,7 +1701,8 @@
 // TODO(errors): detect when a field is added to a struct that is already used
 // in a for clause.
 func (n *nodeContext) expandOne() (done bool) {
-	if n.done() {
+	// Don't expand incomplete expressions if we detected a cycle.
+	if n.done() || (n.hasCycle && !n.hasNonCycle) {
 		return false
 	}