internal/core/eval: fix Evaluator sync bug

Environment was not set up for correct usage in two cases.

For Let expressions, it just happend to always end up at the
correct node in the tests before.

For relNode, this was only exposed for discontinuous
evaluations, which are actually quite rare.

Also, added a bit more aggressive error reporting for
list comprehensions.

Change-Id: Iba14b539a9f84a2960c3170afe68acbeb93ae1ec
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6681
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/testdata/comprehensions/lists.txtar b/cue/testdata/comprehensions/lists.txtar
new file mode 100644
index 0000000..05db0d8
--- /dev/null
+++ b/cue/testdata/comprehensions/lists.txtar
@@ -0,0 +1,45 @@
+-- in.cue --
+
+a: [{a: 1}, {b: 2 & 3}]
+
+b: [ for x in a { x } ]
+-- out/eval --
+Errors:
+a.1.b: incompatible values 3 and 2
+
+Result:
+(_|_){
+  // [eval]
+  a: (_|_){
+    // [eval]
+    0: (struct){
+      a: (int){ 1 }
+    }
+    1: (_|_){
+      // [eval]
+      b: (_|_){
+        // [eval] a.1.b: incompatible values 3 and 2
+      }
+    }
+  }
+  b: (_|_){
+    // [eval] a.1.b: incompatible values 3 and 2
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  a: [
+    {
+      a: 1
+    },
+    {
+      b: (2 & 3)
+    },
+  ]
+  b: [
+    for _, x in 〈0;a〉 {
+      〈1;x〉
+    },
+  ]
+}
diff --git a/cue/testdata/cycle/023_reentrance.txtar b/cue/testdata/cycle/023_reentrance.txtar
index b60f9eb..385a49a 100644
--- a/cue/testdata/cycle/023_reentrance.txtar
+++ b/cue/testdata/cycle/023_reentrance.txtar
@@ -79,6 +79,11 @@
   }).out
 }
 -- out/eval --
+Errors:
+structural cycle:
+    ./in.cue:8:9
+
+Result:
 (_|_){
   // [structural cycle]
   fibRec: (struct){
@@ -95,9 +100,11 @@
   }
   fib2: (int){ 1 }
   fib7: (_|_){
-    // [structural cycle]
+    // [structural cycle] structural cycle:
+    //     ./in.cue:8:9
   }
   fib12: (_|_){
-    // [structural cycle]
+    // [structural cycle] structural cycle:
+    //     ./in.cue:8:9
   }
 }
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index 66e04ae..2324ceb 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -246,6 +246,10 @@
 e3.b.c: structural cycle
 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:79:8
 d2.r: structural cycle:
     ./in.cue:79:8
 
@@ -321,7 +325,8 @@
   b4: (_|_){
     // [structural cycle]
     b: (_|_){
-      // [structural cycle]
+      // [structural cycle] b4.x.y.0: structural cycle:
+      //     ./in.cue:41:8
       0: (int){ 1 }
     }
     x: (_|_){
@@ -448,13 +453,14 @@
       }
     }
     x: (_|_){
-      // [structural cycle]
+      // [structural cycle] d1.a.b.c.d.t: structural cycle
     }
   }
   d2: (_|_){
     // [structural cycle]
     x: (_|_){
-      // [structural cycle]
+      // [structural cycle] d2.a.b.c.d.t: structural cycle:
+      //     ./in.cue:79:8
     }
     r: (_|_){
       // [structural cycle] d2.r: structural cycle:
diff --git a/cue/testdata/eval/discontinuous.txtar b/cue/testdata/eval/discontinuous.txtar
new file mode 100644
index 0000000..246cae6
--- /dev/null
+++ b/cue/testdata/eval/discontinuous.txtar
@@ -0,0 +1,76 @@
+This test tests a case where a child node needs to be evaluated
+before evaluating a parent has completed.
+
+-- in.cue --
+a: [ for c in foo.bar.baz {
+	c
+}]
+
+a: [{name: "http"}]
+
+foo: {
+	x.D
+
+	bar: baz: [{port: 8080}]
+}
+
+x: {
+	D: bar: DSpec
+	DSpec: {}
+}
+-- out/compile --
+--- in.cue
+{
+  a: [
+    for _, c in 〈0;foo〉.bar.baz {
+      〈1;c〉
+    },
+  ]
+  a: [
+    {
+      name: "http"
+    },
+  ]
+  foo: {
+    〈1;x〉.D
+    bar: {
+      baz: [
+        {
+          port: 8080
+        },
+      ]
+    }
+  }
+  x: {
+    D: {
+      bar: 〈1;DSpec〉
+    }
+    DSpec: {}
+  }
+}
+-- out/eval --
+(struct){
+  a: (#list){
+    0: (struct){
+      port: (int){ 8080 }
+      name: (string){ "http" }
+    }
+  }
+  foo: (struct){
+    bar: (struct){
+      baz: (#list){
+        0: (struct){
+          port: (int){ 8080 }
+        }
+      }
+    }
+  }
+  x: (struct){
+    D: (struct){
+      bar: (struct){
+      }
+    }
+    DSpec: (struct){
+    }
+  }
+}
diff --git a/cue/testdata/references/let.txtar b/cue/testdata/references/let.txtar
new file mode 100644
index 0000000..46298af
--- /dev/null
+++ b/cue/testdata/references/let.txtar
@@ -0,0 +1,214 @@
+-- in.cue --
+
+a1list: [{1}]
+let A1 = a1list
+a1: 100*A1[0] + A1[0]
+
+a2list: [{2}]
+let A2 = a2list
+a2: b: 100*A2[0] + A2[0]
+
+a3list: [{3}]
+let A3 = a3list
+a3: b: c: 100*A3[0] + A3[0]
+
+a4list: [{4}]
+let A4 = a4list
+a4: [ for x in A4 { v: 404 } ]
+
+a5list: [{5}]
+let A5 = a5list
+a5: b: [ for x in A5 { v: 505 } ]
+
+a6list: [{6}]
+let A6 = a6list
+a6: b: c: [ for x in A6 { v: 606 } ]
+
+a7list: [{7}]
+let A7 = a7list
+a7: { for x in A7 { v: 707 } }
+
+a8list: [{8}]
+let A8 = a8list
+a8: b: { for x in A8 { v: 808 } }
+
+a9list: [{9}]
+let A9 = a9list
+a9: b: c: { for x in A9 { v: 909 } }
+
+
+-- out/compile --
+--- in.cue
+{
+  a1list: [
+    {
+      1
+    },
+  ]
+  a1: ((100 * 〈0;let A1〉[0]) + 〈0;let A1〉[0])
+  a2list: [
+    {
+      2
+    },
+  ]
+  a2: {
+    b: ((100 * 〈1;let A2〉[0]) + 〈1;let A2〉[0])
+  }
+  a3list: [
+    {
+      3
+    },
+  ]
+  a3: {
+    b: {
+      c: ((100 * 〈2;let A3〉[0]) + 〈2;let A3〉[0])
+    }
+  }
+  a4list: [
+    {
+      4
+    },
+  ]
+  a4: [
+    for _, x in 〈0;let A4〉 {
+      v: 404
+    },
+  ]
+  a5list: [
+    {
+      5
+    },
+  ]
+  a5: {
+    b: [
+      for _, x in 〈1;let A5〉 {
+        v: 505
+      },
+    ]
+  }
+  a6list: [
+    {
+      6
+    },
+  ]
+  a6: {
+    b: {
+      c: [
+        for _, x in 〈2;let A6〉 {
+          v: 606
+        },
+      ]
+    }
+  }
+  a7list: [
+    {
+      7
+    },
+  ]
+  a7: {
+    for _, x in 〈1;let A7〉 {
+      v: 707
+    }
+  }
+  a8list: [
+    {
+      8
+    },
+  ]
+  a8: {
+    b: {
+      for _, x in 〈2;let A8〉 {
+        v: 808
+      }
+    }
+  }
+  a9list: [
+    {
+      9
+    },
+  ]
+  a9: {
+    b: {
+      c: {
+        for _, x in 〈3;let A9〉 {
+          v: 909
+        }
+      }
+    }
+  }
+}
+-- out/eval --
+(struct){
+  a1list: (#list){
+    0: (int){ 1 }
+  }
+  a1: (int){ 101 }
+  a2list: (#list){
+    0: (int){ 2 }
+  }
+  a2: (struct){
+    b: (int){ 202 }
+  }
+  a3list: (#list){
+    0: (int){ 3 }
+  }
+  a3: (struct){
+    b: (struct){
+      c: (int){ 303 }
+    }
+  }
+  a4list: (#list){
+    0: (int){ 4 }
+  }
+  a4: (#list){
+    0: (struct){
+      v: (int){ 404 }
+    }
+  }
+  a5list: (#list){
+    0: (int){ 5 }
+  }
+  a5: (struct){
+    b: (#list){
+      0: (struct){
+        v: (int){ 505 }
+      }
+    }
+  }
+  a6list: (#list){
+    0: (int){ 6 }
+  }
+  a6: (struct){
+    b: (struct){
+      c: (#list){
+        0: (struct){
+          v: (int){ 606 }
+        }
+      }
+    }
+  }
+  a7list: (#list){
+    0: (int){ 7 }
+  }
+  a7: (struct){
+    v: (int){ 707 }
+  }
+  a8list: (#list){
+    0: (int){ 8 }
+  }
+  a8: (struct){
+    b: (struct){
+      v: (int){ 808 }
+    }
+  }
+  a9list: (#list){
+    0: (int){ 9 }
+  }
+  a9: (struct){
+    b: (struct){
+      c: (struct){
+        v: (int){ 909 }
+      }
+    }
+  }
+}
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index 784e648..999d4d8 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -137,7 +137,10 @@
 		if e.cache == nil {
 			e.cache = map[Expr]Value{}
 		}
+		env, src := c.e, c.src
+		c.e, c.src = e, x.Source()
 		v = c.eval(x)
+		c.e, c.src = env, src
 		e.cache[x] = v
 	}
 	return v
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 29ffed5..9d8f3af 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -178,6 +178,7 @@
 	for ; upCount > 0; upCount-- {
 		e = e.Up
 	}
+	c.Unify(c, e.Vertex, Partial)
 	return e.Vertex
 }
 
@@ -614,15 +615,17 @@
 		if isError(v) {
 			if v == nil {
 				c.addErrf(IncompleteError, pos(x), "incomplete value %s", c.Str(x))
+				return emptyNode
 			}
-			return emptyNode
 		}
 		if v.Kind()&StructKind != 0 {
 			c.addErrf(IncompleteError, pos(x),
 				"incomplete feed source value %s (type %s)",
 				x.Source(), v.Kind())
+		} else if b, ok := v.(*Bottom); ok {
+			c.AddBottom(b)
 		} else {
-			c.addErrf(0, pos(x),
+			c.addErrf(0, pos(x), // TODO(error): better message.
 				"invalid operand %s (found %s, want list or struct)",
 				x.Source(), v.Kind())
 
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index a6444c7..7cbe503 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -1610,8 +1610,8 @@
 					n.insertField(label, adt.MakeConjunct(e, st))
 				})
 				hasComprehension = true
-				if err.IsIncomplete() {
-
+				if err != nil && !err.IsIncomplete() {
+					n.addBottom(err)
 				}
 
 			case *adt.Ellipsis: