internal/core/eval: fix spurious cycle for bulk constraints

Fixes #502

Change-Id: I697007d0f7d0fabfedb919c10f4a044b1a6b45de
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7501
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/cycle/issue502.txtar b/cue/testdata/cycle/issue502.txtar
new file mode 100644
index 0000000..7888450
--- /dev/null
+++ b/cue/testdata/cycle/issue502.txtar
@@ -0,0 +1,236 @@
+-- in.cue --
+#T : {
+  config: _ 
+  body: _
+  mas: [string]: {
+    cfg: _
+    _link: #T
+    link: _link & {config: cfg}
+  }
+}
+
+#a: #T & {
+  config: a: int
+  body: config.a
+  mas: one: {
+    cfg: b: config.a
+    _link: {
+      config: b: int
+      body: config.b 
+      mas: two : {
+        cfg: c: config.b 
+        _link: {
+          config: c: int 
+          body: config.c
+        }
+      }
+    }
+  }
+}
+
+a: #a & {config: a: 34}
+-- out/eval --
+(struct){
+  #T: (#struct){
+    config: (_){ _ }
+    body: (_){ _ }
+    mas: (#struct){
+    }
+  }
+  #a: (#struct){
+    config: (#struct){
+      a: (int){ int }
+    }
+    body: (int){ int }
+    mas: (#struct){
+      one: (#struct){
+        cfg: (#struct){
+          b: (int){ int }
+        }
+        _link: (#struct){
+          config: (#struct){
+            b: (int){ int }
+          }
+          body: (int){ int }
+          mas: (#struct){
+            two: (#struct){
+              cfg: (#struct){
+                c: (int){ int }
+              }
+              _link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+              link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+            }
+          }
+        }
+        link: (#struct){
+          config: (#struct){
+            b: (int){ int }
+          }
+          body: (int){ int }
+          mas: (#struct){
+            two: (#struct){
+              cfg: (#struct){
+                c: (int){ int }
+              }
+              _link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+              link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  a: (#struct){
+    config: (#struct){
+      a: (int){ 34 }
+    }
+    body: (int){ 34 }
+    mas: (#struct){
+      one: (#struct){
+        cfg: (#struct){
+          b: (int){ 34 }
+        }
+        _link: (#struct){
+          config: (#struct){
+            b: (int){ int }
+          }
+          body: (int){ int }
+          mas: (#struct){
+            two: (#struct){
+              cfg: (#struct){
+                c: (int){ int }
+              }
+              _link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+              link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+            }
+          }
+        }
+        link: (#struct){
+          config: (#struct){
+            b: (int){ 34 }
+          }
+          body: (int){ 34 }
+          mas: (#struct){
+            two: (#struct){
+              cfg: (#struct){
+                c: (int){ 34 }
+              }
+              _link: (#struct){
+                config: (#struct){
+                  c: (int){ int }
+                }
+                body: (int){ int }
+                mas: (#struct){
+                }
+              }
+              link: (#struct){
+                config: (#struct){
+                  c: (int){ 34 }
+                }
+                body: (int){ 34 }
+                mas: (#struct){
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  #T: {
+    config: _
+    body: _
+    mas: {
+      [string]: {
+        cfg: _
+        _link: 〈3;#T〉
+        link: (〈0;_link〉 & {
+          config: 〈1;cfg〉
+        })
+      }
+    }
+  }
+  #a: (〈0;#T〉 & {
+    config: {
+      a: int
+    }
+    body: 〈0;config〉.a
+    mas: {
+      one: {
+        cfg: {
+          b: 〈3;config〉.a
+        }
+        _link: {
+          config: {
+            b: int
+          }
+          body: 〈0;config〉.b
+          mas: {
+            two: {
+              cfg: {
+                c: 〈3;config〉.b
+              }
+              _link: {
+                config: {
+                  c: int
+                }
+                body: 〈0;config〉.c
+              }
+            }
+          }
+        }
+      }
+    }
+  })
+  a: (〈0;#a〉 & {
+    config: {
+      a: 34
+    }
+  })
+}
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index aaccc3c..ddbba75 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -125,6 +125,85 @@
     }
 }
 
+// Issue #502 -- unused bulk constraints are not cyclic
+p1: {
+  #T: {
+    a: [string]: link: #T
+  }
+
+  a: #T & {
+    a: one: link: a: two: {}
+  }
+}
+
+// Issue #502 -- but they are if it is invoked within the struct.
+p2: {
+  #T: {
+    a: [string]: link: #T
+    a: b: {}
+  }
+
+  a: #T & {
+    a: one: link: a: two: {}
+  }
+}
+
+// Issue #502 -- or added later.
+p3: {
+  #S: #T: {
+    a: [string]: link: #T
+  }
+
+  #U: {
+    #S
+    #T: a: b: {}
+  }
+
+  a: #U.#T & {
+    a: one: link: a: two: {}
+  }
+}
+
+// Issue #502 -- unused bulk constraints are not cyclic
+p4: {
+  #T: {
+    a: [...{link: #T}]
+  }
+
+  a: #T & {
+    a: [{link: a: [{}]}]
+  }
+}
+
+// Issue #502 -- but they are if it is invoked within the struct.
+p5: {
+  #T: {
+    a: [...{link: #T}]
+    a: [{}]
+  }
+
+  a: #T & {
+    a: [{link: a: [{}]}]
+  }
+}
+
+// Issue #502 -- or added later.
+p6: {
+  #S: #T: {
+    a: [...{link: #T}]
+  }
+
+  #U: {
+    #S
+    #T: a: [{}]
+  }
+
+  a: #U.#T & {
+    a: [{link: a: [{}]}]
+  }
+}
+
+
 c1: {
   a: {
     b: {}
@@ -300,43 +379,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:178:8
-    ./in.cue:179:8
+    ./in.cue:257:8
+    ./in.cue:258:8
 e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:178:8
-    ./in.cue:179:8
+    ./in.cue:257:8
+    ./in.cue:258:8
 e3.a.0: structural cycle
 e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:178:8
-    ./in.cue:179:8
+    ./in.cue:257:8
+    ./in.cue:258:8
 e3.a.c: structural cycle
 e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:181:8
-    ./in.cue:182:8
+    ./in.cue:260:8
+    ./in.cue:261:8
 e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:181:8
-    ./in.cue:182:8
+    ./in.cue:260:8
+    ./in.cue:261:8
 e3.b.0: structural cycle
 e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:181:8
-    ./in.cue:182:8
+    ./in.cue:260:8
+    ./in.cue:261:8
 e3.b.c: structural cycle
 e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:186:13
-    ./in.cue:187:9
+    ./in.cue:265:13
+    ./in.cue:266:9
 e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:189:9
-    ./in.cue:190:13
-z1.z.f.h.h: structural cycle
+    ./in.cue:268:9
+    ./in.cue:269: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
 d2.a.b.c.d.t: structural cycle:
-    ./in.cue:143:8
+    ./in.cue:222:8
 d2.r: structural cycle:
-    ./in.cue:143:8
+    ./in.cue:222:8
 0: structural cycle:
-    ./in.cue:153:19
+    ./in.cue:232:19
 
 Result:
 (_|_){
@@ -470,8 +552,10 @@
   }
   b7: (_|_){
     // [structural cycle]
-    b: (#list){
-      0: (#list){
+    b: (_|_){
+      // [structural cycle]
+      0: (_|_){
+        // [structural cycle] b7.a.0: structural cycle
         0: (int){ 1 }
       }
     }
@@ -568,6 +652,212 @@
       sum: (int){ 10 }
     }
   }
+  p1: (struct){
+    #T: (#struct){
+      a: (#struct){
+      }
+    }
+    a: (#struct){
+      a: (#struct){
+        one: (#struct){
+          link: (#struct){
+            a: (#struct){
+              two: (#struct){
+                link: (#struct){
+                  a: (#struct){
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  p2: (_|_){
+    // [structural cycle]
+    #T: (_|_){
+      // [structural cycle]
+      a: (_|_){
+        // [structural cycle]
+        b: (_|_){
+          // [structural cycle]
+          link: (_|_){
+            // [structural cycle] p2.#T.a.b.link: structural cycle
+            a: (_|_){// {
+              //   [string]: {
+              //     link: 〈3;#T〉
+              //   }
+              // } & {
+              //   b: {}
+              // }
+            }
+          }
+        }
+      }
+    }
+    a: (_|_){
+      // [structural cycle]
+      a: (struct){
+        one: (struct){
+          link: (struct){
+            a: (struct){
+              two: (struct){
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  p3: (_|_){
+    // [structural cycle]
+    #S: (#struct){
+      #T: (#struct){
+        a: (#struct){
+        }
+      }
+    }
+    #U: (_|_){
+      // [structural cycle]
+      #T: (_|_){
+        // [structural cycle]
+        a: (_|_){
+          // [structural cycle]
+          b: (_|_){
+            // [structural cycle]
+            link: (_|_){
+              // [structural cycle] p3.#U.#T.a.b.link: structural cycle
+              a: (_|_){// {
+                //   [string]: {
+                //     link: 〈3;#T〉
+                //   }
+                // } & {
+                //   b: {}
+                // }
+              }
+            }
+          }
+        }
+      }
+    }
+    a: (_|_){
+      // [structural cycle] p3.#U.#T.a.b.link: structural cycle
+      a: (struct){
+        one: (struct){
+          link: (struct){
+            a: (struct){
+              two: (struct){
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  p4: (struct){
+    #T: (#struct){
+      a: (list){
+      }
+    }
+    a: (#struct){
+      a: (#list){
+        0: (#struct){
+          link: (#struct){
+            a: (#list){
+              0: (#struct){
+                link: (#struct){
+                  a: (list){
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  p5: (_|_){
+    // [structural cycle]
+    #T: (_|_){
+      // [structural cycle]
+      a: (_|_){
+        // [structural cycle]
+        0: (_|_){
+          // [structural cycle]
+          link: (_|_){
+            // [structural cycle] p5.#T.a.0.link: structural cycle
+            a: (_|_){// [
+              //   ...{
+              //     link: 〈2;#T〉
+              //   },
+              // ] & [
+              //   {},
+              // ]
+            }
+          }
+        }
+      }
+    }
+    a: (_|_){
+      // [structural cycle]
+      a: (#list){
+        0: (struct){
+          link: (struct){
+            a: (#list){
+              0: (struct){
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  p6: (_|_){
+    // [structural cycle]
+    #S: (#struct){
+      #T: (#struct){
+        a: (list){
+        }
+      }
+    }
+    #U: (_|_){
+      // [structural cycle]
+      #T: (_|_){
+        // [structural cycle]
+        a: (_|_){
+          // [structural cycle]
+          0: (_|_){
+            // [structural cycle]
+            link: (_|_){
+              // [structural cycle] p6.#U.#T.a.0.link: structural cycle
+              a: (_|_){// [
+                //   ...{
+                //     link: 〈2;#T〉
+                //   },
+                // ] & [
+                //   {},
+                // ]
+              }
+            }
+          }
+        }
+      }
+    }
+    a: (_|_){
+      // [structural cycle] p6.#U.#T.a.0.link: structural cycle
+      a: (#list){
+        0: (struct){
+          link: (struct){
+            a: (#list){
+              0: (struct){
+              }
+            }
+          }
+        }
+      }
+    }
+  }
   c1: (_|_){
     // [structural cycle]
     a: (_|_){
@@ -631,11 +921,11 @@
     // [structural cycle]
     x: (_|_){
       // [structural cycle] d2.a.b.c.d.t: structural cycle:
-      //     ./in.cue:143:8
+      //     ./in.cue:222:8
     }
     r: (_|_){
       // [structural cycle] d2.r: structural cycle:
-      //     ./in.cue:143:8
+      //     ./in.cue:222:8
       c: (_|_){// {
         //   d: {
         //     h: int
@@ -678,32 +968,19 @@
           // [structural cycle]
           c: (_|_){
             // [structural cycle] 0: structural cycle:
-            //     ./in.cue:153:19
+            //     ./in.cue:232:19
           }
         }
       }
       indirect: (_|_){
         // [structural cycle] 0: structural cycle:
-        //     ./in.cue:153:19
+        //     ./in.cue:232:19
       }
       i: (int){ 1 }
     }
     x: (_|_){
-      // [structural cycle]
-      a: (_|_){
-        // [structural cycle]
-        b: (_|_){
-          // [structural cycle]
-          c: (_|_){
-            // [structural cycle] 0: structural cycle:
-            //     ./in.cue:153:19
-          }
-        }
-      }
-      indirect: (_|_){
-        // [structural cycle] 0: structural cycle:
-        //     ./in.cue:153:19
-      }
+      // [structural cycle] 0: structural cycle:
+      //     ./in.cue:232:19
       i: (int){ 0 }
     }
   }
@@ -749,12 +1026,12 @@
     // [eval]
     a: (_|_){
       // [eval] e3.a: conflicting values [a] and {c:a} (mismatched types list and struct):
-      //     ./in.cue:178:8
-      //     ./in.cue:179:8
+      //     ./in.cue:257:8
+      //     ./in.cue:258:8
       c: (_|_){
         // [eval] e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:178:8
-        //     ./in.cue:179:8
+        //     ./in.cue:257:8
+        //     ./in.cue:258:8
         // e3.a.c: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -763,8 +1040,8 @@
       }
       0: (_|_){
         // [eval] e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:178:8
-        //     ./in.cue:179:8
+        //     ./in.cue:257:8
+        //     ./in.cue:258:8
         // e3.a.0: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -774,12 +1051,12 @@
     }
     b: (_|_){
       // [eval] e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-      //     ./in.cue:181:8
-      //     ./in.cue:182:8
+      //     ./in.cue:260:8
+      //     ./in.cue:261:8
       c: (_|_){
         // [eval] e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:181:8
-        //     ./in.cue:182:8
+        //     ./in.cue:260:8
+        //     ./in.cue:261:8
         // e3.b.c: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -788,8 +1065,8 @@
       }
       0: (_|_){
         // [eval] e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:181:8
-        //     ./in.cue:182:8
+        //     ./in.cue:260:8
+        //     ./in.cue:261:8
         // e3.b.0: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -804,8 +1081,8 @@
       // [eval]
       0: (_|_){
         // [eval] e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:186:13
-        //     ./in.cue:187:9
+        //     ./in.cue:265:13
+        //     ./in.cue:266:9
         0: (struct){
           c: (int){ 1 }
         }
@@ -815,8 +1092,8 @@
       // [eval]
       0: (_|_){
         // [eval] e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:189:9
-        //     ./in.cue:190:13
+        //     ./in.cue:268:9
+        //     ./in.cue:269:13
         0: (struct){
           c: (int){ 1 }
         }
@@ -926,7 +1203,7 @@
         h: (_|_){
           // [structural cycle]
           h: (_|_){
-            // [structural cycle] z1.z.f.h.h: structural cycle
+            // [structural cycle] z1.z.g.h: structural cycle
           }
         }
       }
@@ -1138,6 +1415,152 @@
       }
     }
   }
+  p1: {
+    #T: {
+      a: {
+        [string]: {
+          link: 〈3;#T〉
+        }
+      }
+    }
+    a: (〈0;#T〉 & {
+      a: {
+        one: {
+          link: {
+            a: {
+              two: {}
+            }
+          }
+        }
+      }
+    })
+  }
+  p2: {
+    #T: {
+      a: {
+        [string]: {
+          link: 〈3;#T〉
+        }
+      }
+      a: {
+        b: {}
+      }
+    }
+    a: (〈0;#T〉 & {
+      a: {
+        one: {
+          link: {
+            a: {
+              two: {}
+            }
+          }
+        }
+      }
+    })
+  }
+  p3: {
+    #S: {
+      #T: {
+        a: {
+          [string]: {
+            link: 〈3;#T〉
+          }
+        }
+      }
+    }
+    #U: {
+      〈1;#S〉
+      #T: {
+        a: {
+          b: {}
+        }
+      }
+    }
+    a: (〈0;#U〉.#T & {
+      a: {
+        one: {
+          link: {
+            a: {
+              two: {}
+            }
+          }
+        }
+      }
+    })
+  }
+  p4: {
+    #T: {
+      a: [
+        ...{
+          link: 〈2;#T〉
+        },
+      ]
+    }
+    a: (〈0;#T〉 & {
+      a: [
+        {
+          link: {
+            a: [
+              {},
+            ]
+          }
+        },
+      ]
+    })
+  }
+  p5: {
+    #T: {
+      a: [
+        ...{
+          link: 〈2;#T〉
+        },
+      ]
+      a: [
+        {},
+      ]
+    }
+    a: (〈0;#T〉 & {
+      a: [
+        {
+          link: {
+            a: [
+              {},
+            ]
+          }
+        },
+      ]
+    })
+  }
+  p6: {
+    #S: {
+      #T: {
+        a: [
+          ...{
+            link: 〈2;#T〉
+          },
+        ]
+      }
+    }
+    #U: {
+      〈1;#S〉
+      #T: {
+        a: [
+          {},
+        ]
+      }
+    }
+    a: (〈0;#U〉.#T & {
+      a: [
+        {
+          link: {
+            a: [
+              {},
+            ]
+          }
+        },
+      ]
+    })
+  }
   c1: {
     a: {
       b: {}
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 7511b51..f0fd940 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -328,6 +328,10 @@
 		return nil, err
 	}
 
+	if arc.ChildErrors != nil && arc.ChildErrors.Code == StructuralCycleError {
+		return nil, arc.ChildErrors
+	}
+
 	return arc, err
 }
 
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index e078801..39d8597 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -388,6 +388,7 @@
 			ci := closedInfo.clone()
 			v.Closed = ci
 			// TODO(performance): use closedInfo.Compact.
+			// TODO: should be clear the list flag here?
 		}
 
 		v.UpdateStatus(adt.Evaluating)
@@ -1174,11 +1175,6 @@
 		}
 
 		status := arc.Status()
-		for _, d := range v.Env.Deref {
-			if d == arc {
-				status = adt.EvaluatingArcs
-			}
-		}
 
 		switch status {
 		case adt.Evaluating:
diff --git a/internal/core/eval/optionals.go b/internal/core/eval/optionals.go
index 7edabca..674f7ef 100644
--- a/internal/core/eval/optionals.go
+++ b/internal/core/eval/optionals.go
@@ -117,6 +117,8 @@
 
 	bulkEnv := *env
 	bulkEnv.DynamicLabel = arc.Label
+	bulkEnv.Deref = nil
+	bulkEnv.Cycles = nil
 
 	// match bulk optional fields / pattern properties
 	matched := false
@@ -132,9 +134,13 @@
 		return
 	}
 
+	addEnv := *env
+	addEnv.Deref = nil
+	addEnv.Cycles = nil
+
 	// match others
 	for _, x := range o.additional {
-		arc.AddConjunct(adt.MakeConjunct(env, x, o.id))
+		arc.AddConjunct(adt.MakeConjunct(&addEnv, x, o.id))
 	}
 }