internal/core/adt: catch errors for circular failures

This mechansim can ultimately also be used for
communicating any change that makes a Vertex
more specific.

Change-Id: Ia5d86fd4cc7da53290998aa5e08de12a474ec440
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8113
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/cycle/021_delayed_constraint_failure.txtar b/cue/testdata/cycle/021_delayed_constraint_failure.txtar
index 72b64a5..5469ec6 100644
--- a/cue/testdata/cycle/021_delayed_constraint_failure.txtar
+++ b/cue/testdata/cycle/021_delayed_constraint_failure.txtar
@@ -36,7 +36,11 @@
 Result:
 (_|_){
   // [eval]
-  a: (int){ 100 }
+  a: (_|_){
+    // [eval] b: conflicting values 210 and 200:
+    //     ./in.cue:2:4
+    //     ./in.cue:3:4
+  }
   b: (_|_){
     // [eval] b: conflicting values 210 and 200:
     //     ./in.cue:2:4
diff --git a/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar b/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar
index e764e84..875d6e0 100644
--- a/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar
+++ b/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar
@@ -42,6 +42,10 @@
       //     ./in.cue:2:5
       //     ./in.cue:5:7
     }
-    y: (string){ "hey!" }
+    y: (_|_){
+      // [eval] a.x: conflicting values "hey!?" and "hey":
+      //     ./in.cue:2:5
+      //     ./in.cue:5:7
+    }
   }
 }
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index 5bc75d0..16a45bc 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -167,10 +167,9 @@
 					Conjuncts: v.Conjuncts,
 				}
 				w.UpdateStatus(v.Status())
-				return w
+				v = w
 			}
 		}
-		return x
 
 	case nil:
 		if v.state != nil {
@@ -186,6 +185,10 @@
 		panic("nil value")
 	}
 
+	if v.status < Finalized && v.state != nil {
+		v.state.addNotify(c.vertex)
+	}
+
 	return v
 }
 
@@ -274,6 +277,8 @@
 		for n.maybeSetCache(); n.expandOne(); n.maybeSetCache() {
 		}
 
+		n.doNotify()
+
 		if !n.done() {
 			if len(n.disjunctions) > 0 && v.BaseValue == cycle {
 				// We disallow entering computations of disjunctions with
@@ -372,6 +377,23 @@
 	}
 }
 
+func (n *nodeContext) doNotify() {
+	if n.errs != nil && len(n.notify) > 0 {
+		for _, v := range n.notify {
+			if v.state == nil {
+				if b, ok := v.BaseValue.(*Bottom); ok {
+					v.BaseValue = CombineErrors(nil, b, n.errs)
+				} else {
+					v.BaseValue = n.errs
+				}
+			} else {
+				v.state.addBottom(n.errs)
+			}
+		}
+		n.notify = n.notify[:0]
+	}
+}
+
 func isStruct(v *Vertex) bool {
 	_, ok := v.BaseValue.(*StructMarker)
 	return ok
@@ -647,6 +669,10 @@
 	checks     []Validator // BuiltinValidator, other bound values.
 	errs       *Bottom
 
+	// notify is used to communicate errors in cyclic dependencies.
+	// TODO: also use this to communicate increasingly more concrete values.
+	notify []*Vertex
+
 	// Struct information
 	dynamicFields []envDynamic
 	ifClauses     []envYield
@@ -671,6 +697,12 @@
 	disjunctErrs []*Bottom
 }
 
+func (n *nodeContext) addNotify(v *Vertex) {
+	if v != nil {
+		n.notify = append(n.notify, v)
+	}
+}
+
 func (n *nodeContext) clone() *nodeContext {
 	d := n.ctx.Unifier.newNodeContext(n.ctx, n.node)
 
@@ -696,6 +728,7 @@
 	d.hasNonCycle = n.hasNonCycle
 
 	// d.arcMap = append(d.arcMap, n.arcMap...) // XXX add?
+	d.notify = append(d.notify, n.notify...)
 	d.checks = append(d.checks, n.checks...)
 	d.dynamicFields = append(d.dynamicFields, n.dynamicFields...)
 	d.ifClauses = append(d.ifClauses, n.ifClauses...)