internal/core/eval: better incomplete error messages

This now also records the error for incomplete errors
along with the expressions that still need to be
resolved. This avoids having to recompute them later,
which also turned out to be bug prone, as this
happened outside the purview of the cycle handling.

Fixes #465
Fixes #550 (was fixed, but added tests)

Change-Id: I8b61493fd65070e1b6e1477c2bad106e77e2fec8
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7801
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/eval_e.txt b/cmd/cue/cmd/testdata/script/eval_e.txt
index 7723916..7844b7d 100644
--- a/cmd/cue/cmd/testdata/script/eval_e.txt
+++ b/cmd/cue/cmd/testdata/script/eval_e.txt
@@ -1,12 +1,34 @@
 ! cue eval -e nonExist
-cmp stderr expect-stderr
-cmp stdout expect-stdout
+cmp stderr expect/nonExist/stderr
+cmp stdout expect/nonExist/stdout
 
--- expect-stdout --
--- expect-stderr --
+# Issue #550: ignore incomplete error in different package
+cue eval -e incomplete
+cmp stderr expect/incomplete/stderr
+cmp stdout expect/incomplete/stdout
+
+
+-- expect/nonExist/stdout --
+-- expect/nonExist/stderr --
 reference "nonExist" not found:
     --expression:1:1
+-- expect/incomplete/stdout --
+
+-- expect/incomplete/stderr --
 -- partial.cue --
 package exitcode
 
+import (
+	pkg "foo.com/example"
+)
+
 a: 1
+
+incomplete: pkg.Settings
+
+-- cue.mod/pkg/foo.com/example/example.cue --
+package example
+
+Settings: {}
+blah: Settings.anyKey
+
diff --git a/cue/testdata/basicrewrite/010_lists.txtar b/cue/testdata/basicrewrite/010_lists.txtar
index 438ef25..9f65dc6 100644
--- a/cue/testdata/basicrewrite/010_lists.txtar
+++ b/cue/testdata/basicrewrite/010_lists.txtar
@@ -81,7 +81,7 @@
     ./in.cue:4:13
 e2: invalid list index d (type string):
     ./in.cue:5:12
-e3: invalid negative index -1:
+e3: invalid index -1 (index must be non-negative):
     ./in.cue:6:8
 e4.3: invalid value 8 (out of bound <=5):
     ./in.cue:7:24
@@ -114,7 +114,7 @@
     //     ./in.cue:5:12
   }
   e3: (_|_){
-    // [eval] e3: invalid negative index -1:
+    // [eval] e3: invalid index -1 (index must be non-negative):
     //     ./in.cue:6:8
   }
   e4: (_|_){
diff --git a/cue/testdata/comprehensions/errors.txtar b/cue/testdata/comprehensions/errors.txtar
new file mode 100644
index 0000000..483db98
--- /dev/null
+++ b/cue/testdata/comprehensions/errors.txtar
@@ -0,0 +1,71 @@
+-- in.cue --
+circularIf: {
+    #list: {
+        tail: #list | *null
+        if tail != null {
+        }
+    }
+}
+
+circularFor: {
+    #list: {
+        tail: #list | *null
+        for x in tail != null {
+        }
+    }
+}
+
+// Print a bit more sensible error message than "empty disjunction" here.
+// Issue #465
+userError: {
+    a: string | *_|_
+    if a != "" {
+    }
+}
+-- out/eval --
+Errors:
+circularFor.#list: invalid operand tail != null (found bool, want list or struct):
+    ./in.cue:12:18
+
+Result:
+(_|_){
+  // [eval]
+  circularIf: (struct){
+    #list: (#struct){
+      tail: (null){ null }
+    }
+  }
+  circularFor: (_|_){
+    // [eval]
+    #list: (_|_){
+      // [eval] circularFor.#list: invalid operand tail != null (found bool, want list or struct):
+      //     ./in.cue:12:18
+      tail: (null){ null }
+    }
+  }
+  userError: (_|_){
+    // [incomplete] userError.a: empty disjunction:
+    //     ./in.cue:21:8
+    a: (string){ string }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  circularIf: {
+    #list: {
+      tail: (〈1;#list〉|*null)
+      if (〈0;tail〉 != null) {}
+    }
+  }
+  circularFor: {
+    #list: {
+      tail: (〈1;#list〉|*null)
+      for _, x in (〈0;tail〉 != null) {}
+    }
+  }
+  userError: {
+    a: (string|*_|_(explicit error (_|_ literal) in source))
+    if (〈0;a〉 != "") {}
+  }
+}
diff --git a/cue/testdata/comprehensions/incomplete.txtar b/cue/testdata/comprehensions/incomplete.txtar
index 5381c49..945954d 100644
--- a/cue/testdata/comprehensions/incomplete.txtar
+++ b/cue/testdata/comprehensions/incomplete.txtar
@@ -19,7 +19,7 @@
     //     ./in.cue:5:19
   }
   c: (_|_){
-    // [incomplete] c: incomplete feed source value top (type _):
+    // [incomplete] c: cannot range over top (incomplete type _):
     //     ./in.cue:6:15
   }
 }
diff --git a/cue/testdata/cycle/023_reentrance.txtar b/cue/testdata/cycle/023_reentrance.txtar
index 385a49a..a4d2734 100644
--- a/cue/testdata/cycle/023_reentrance.txtar
+++ b/cue/testdata/cycle/023_reentrance.txtar
@@ -89,13 +89,19 @@
   fibRec: (struct){
     nn: (int){ int }
     out: (_|_){
-      // [incomplete] incomplete
-      // fibRec.out: undefined field out:
+      // [incomplete] fibRec.out: undefined field out:
       //     ./in.cue:3:40
+      // non-concrete value int in operand to >=:
+      //     ./in.cue:7:5
+      // non-concrete value int in operand to <:
+      //     ./in.cue:10:5
     }
   }
   fib: (_|_){
-    // [incomplete] incomplete
+    // [incomplete] fib: non-concrete value int in operand to >=:
+    //     ./in.cue:7:5
+    // fib: non-concrete value int in operand to <:
+    //     ./in.cue:10:5
     n: (int){ int }
   }
   fib2: (int){ 1 }
diff --git a/cue/testdata/disjunctions/errors.txtar b/cue/testdata/disjunctions/errors.txtar
index d9f810a..3bf3dba 100644
--- a/cue/testdata/disjunctions/errors.txtar
+++ b/cue/testdata/disjunctions/errors.txtar
@@ -24,6 +24,14 @@
     x: match: metrics: "foo": {}
 }
 
+// issue #465
+explicitDefaultError: {
+    a: string | *_|_
+
+    if a != "" {
+    }
+}
+
 -- out/eval --
 Errors:
 issue516.x: empty disjunction: 2 related errors:
@@ -115,6 +123,11 @@
       }
     }
   }
+  explicitDefaultError: (_|_){
+    // [incomplete] explicitDefaultError.a: empty disjunction:
+    //     ./in.cue:30:8
+    a: (string){ string }
+  }
 }
 -- out/compile --
 --- in.cue
@@ -153,4 +166,8 @@
       }
     }
   }
+  explicitDefaultError: {
+    a: (string|*_|_(explicit error (_|_ literal) in source))
+    if (〈0;a〉 != "") {}
+  }
 }
diff --git a/cue/testdata/fulleval/005_conditional_field.txtar b/cue/testdata/fulleval/005_conditional_field.txtar
index b200d78..5aeea2c 100644
--- a/cue/testdata/fulleval/005_conditional_field.txtar
+++ b/cue/testdata/fulleval/005_conditional_field.txtar
@@ -61,7 +61,8 @@
     a: (int){ 3 }
   }
   d: (_|_){
-    // [incomplete] incomplete
+    // [incomplete] d: non-concrete value int in operand to >:
+    //     ./in.cue:14:5
     a: (int){ int }
   }
   a: (string){ "foo" }
diff --git a/cue/testdata/fulleval/042_cross-dependent_comprehension.txtar b/cue/testdata/fulleval/042_cross-dependent_comprehension.txtar
index 902e4d5..48c666b 100644
--- a/cue/testdata/fulleval/042_cross-dependent_comprehension.txtar
+++ b/cue/testdata/fulleval/042_cross-dependent_comprehension.txtar
@@ -41,7 +41,7 @@
 -- out/eval --
 (struct){
   #a: (_|_){
-    // [incomplete] incomplete
+    // [incomplete] #a: incomplete bool value 'bool'
     b: (bool){ bool }
   }
   x: (#struct){
diff --git a/cue/testdata/references/incomplete.txtar b/cue/testdata/references/incomplete.txtar
new file mode 100644
index 0000000..e790969
--- /dev/null
+++ b/cue/testdata/references/incomplete.txtar
@@ -0,0 +1,116 @@
+-- in.cue --
+comprehensions: {
+    a: {}
+    b: {
+        if a.b {
+        }
+    }
+    c: {
+        for x in a.b { x }
+    }
+}
+
+openStruct: {
+    a: {}
+    b: a.c
+}
+
+selectFromTop: {
+    top: _
+    a: top.foo
+}
+
+indexOnTop: {
+    top: _
+    a: top[2]
+}
+
+incompleteIndex: {
+    top: _
+    a: [1][top]
+}
+
+-- out/eval --
+Errors:
+incompleteIndex.a: invalid index top (invalid type _):
+    ./in.cue:29:8
+
+Result:
+(_|_){
+  // [eval]
+  comprehensions: (struct){
+    a: (struct){
+    }
+    b: (_|_){
+      // [incomplete] comprehensions.b: undefined field b:
+      //     ./in.cue:4:14
+    }
+    c: (_|_){
+      // [incomplete] comprehensions.c: undefined field b:
+      //     ./in.cue:8:20
+    }
+  }
+  openStruct: (struct){
+    a: (struct){
+    }
+    b: (_|_){
+      // [incomplete] openStruct.b: undefined field c:
+      //     ./in.cue:14:10
+    }
+  }
+  selectFromTop: (struct){
+    top: (_){ _ }
+    a: (_|_){
+      // [incomplete] selectFromTop.a: top.foo undefined as top is incomplete (type _):
+      //     ./in.cue:19:8
+    }
+  }
+  indexOnTop: (struct){
+    top: (_){ _ }
+    a: (_|_){
+      // [incomplete] indexOnTop.a: top[2] undefined as top is incomplete (type _):
+      //     ./in.cue:24:8
+    }
+  }
+  incompleteIndex: (_|_){
+    // [eval]
+    top: (_){ _ }
+    a: (_|_){
+      // [eval] incompleteIndex.a: invalid index top (invalid type _):
+      //     ./in.cue:29:8
+    }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  comprehensions: {
+    a: {}
+    b: {
+      if 〈1;a〉.b {}
+    }
+    c: {
+      for _, x in 〈1;a〉.b {
+        〈1;x〉
+      }
+    }
+  }
+  openStruct: {
+    a: {}
+    b: 〈0;a〉.c
+  }
+  selectFromTop: {
+    top: _
+    a: 〈0;top〉.foo
+  }
+  indexOnTop: {
+    top: _
+    a: 〈0;top〉[2]
+  }
+  incompleteIndex: {
+    top: _
+    a: [
+      1,
+    ][〈0;top〉]
+  }
+}
diff --git a/cue/testdata/references/index.txtar b/cue/testdata/references/index.txtar
new file mode 100644
index 0000000..68b5681
--- /dev/null
+++ b/cue/testdata/references/index.txtar
@@ -0,0 +1,172 @@
+-- in.cue --
+intIndex:    [2][0]
+stringIndex: {foo: "bar"}["foo"]
+
+stringOfNumberIndex: {"3": 3}["3"]
+
+indexToDefault:        (*[0] | {})[0]
+outOfBoundsDisjunction: (*[] | {})[1]
+
+// Ensure these are errors.
+indexDoesNotDistribute:     (*[] | [1])[0]
+doesNotDistributeType: {
+    l: []
+    a: (*l | {"3": 3})["3"]
+}
+
+stringIndexToList: [2][""]
+indexOutOfBounds2: 2[2]
+booleanIndex: [][true]
+indexOutOfBounds3: [1, 2, 3][4]
+negativeIndex: [1, 2, 3][-1]
+
+varIndexTooLarge: {
+    n: 3
+    a: [1, 2, 3][n]
+}
+
+varNegativeIndex: {
+    n: -1
+    a: [1, 2, 3][n]
+}
+-- out/eval --
+Errors:
+outOfBoundsDisjunction: invalid list index 1 (out of bounds):
+    ./in.cue:7:36
+indexDoesNotDistribute: index out of range [0] with length 0:
+    ./in.cue:10:41
+doesNotDistributeType.a: invalid list index "3" (type string):
+    ./in.cue:13:24
+stringIndexToList: invalid list index _ (type string):
+    ./in.cue:16:24
+indexOutOfBounds2: invalid operand 2 (found int, want list or struct):
+    ./in.cue:17:20
+booleanIndex: invalid index true (invalid type bool):
+    ./in.cue:18:15
+indexOutOfBounds3: invalid list index 4 (out of bounds):
+    ./in.cue:19:30
+negativeIndex: invalid index -1 (index must be non-negative):
+    ./in.cue:20:16
+varIndexTooLarge.a: index out of range [3] with length 3:
+    ./in.cue:24:18
+varNegativeIndex.a: index n out of range [-1]:
+    ./in.cue:29:8
+
+Result:
+(_|_){
+  // [eval]
+  intIndex: (int){ 2 }
+  stringIndex: (string){ "bar" }
+  stringOfNumberIndex: (int){ 3 }
+  indexToDefault: (int){ 0 }
+  outOfBoundsDisjunction: (_|_){
+    // [eval] outOfBoundsDisjunction: invalid list index 1 (out of bounds):
+    //     ./in.cue:7:36
+  }
+  indexDoesNotDistribute: (_|_){
+    // [eval] indexDoesNotDistribute: index out of range [0] with length 0:
+    //     ./in.cue:10:41
+  }
+  doesNotDistributeType: (_|_){
+    // [eval]
+    l: (#list){
+    }
+    a: (_|_){
+      // [eval] doesNotDistributeType.a: invalid list index "3" (type string):
+      //     ./in.cue:13:24
+    }
+  }
+  stringIndexToList: (_|_){
+    // [eval] stringIndexToList: invalid list index _ (type string):
+    //     ./in.cue:16:24
+  }
+  indexOutOfBounds2: (_|_){
+    // [eval] indexOutOfBounds2: invalid operand 2 (found int, want list or struct):
+    //     ./in.cue:17:20
+  }
+  booleanIndex: (_|_){
+    // [eval] booleanIndex: invalid index true (invalid type bool):
+    //     ./in.cue:18:15
+  }
+  indexOutOfBounds3: (_|_){
+    // [eval] indexOutOfBounds3: invalid list index 4 (out of bounds):
+    //     ./in.cue:19:30
+  }
+  negativeIndex: (_|_){
+    // [eval] negativeIndex: invalid index -1 (index must be non-negative):
+    //     ./in.cue:20:16
+  }
+  varIndexTooLarge: (_|_){
+    // [eval]
+    n: (int){ 3 }
+    a: (_|_){
+      // [eval] varIndexTooLarge.a: index out of range [3] with length 3:
+      //     ./in.cue:24:18
+    }
+  }
+  varNegativeIndex: (_|_){
+    // [eval]
+    n: (int){ -1 }
+    a: (_|_){
+      // [eval] varNegativeIndex.a: index n out of range [-1]:
+      //     ./in.cue:29:8
+    }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  intIndex: [
+    2,
+  ][0]
+  stringIndex: {
+    foo: "bar"
+  }["foo"]
+  stringOfNumberIndex: {
+    "3": 3
+  }["3"]
+  indexToDefault: (*[
+    0,
+  ]|{})[0]
+  outOfBoundsDisjunction: (*[]|{})[1]
+  indexDoesNotDistribute: (*[]|[
+    1,
+  ])[0]
+  doesNotDistributeType: {
+    l: []
+    a: (*〈0;l〉|{
+      "3": 3
+    })["3"]
+  }
+  stringIndexToList: [
+    2,
+  ][""]
+  indexOutOfBounds2: 2[2]
+  booleanIndex: [][true]
+  indexOutOfBounds3: [
+    1,
+    2,
+    3,
+  ][4]
+  negativeIndex: [
+    1,
+    2,
+    3,
+  ][-1]
+  varIndexTooLarge: {
+    n: 3
+    a: [
+      1,
+      2,
+      3,
+    ][〈0;n〉]
+  }
+  varNegativeIndex: {
+    n: -1
+    a: [
+      1,
+      2,
+      3,
+    ][〈0;n〉]
+  }
+}
diff --git a/cue/testdata/references/optional.txtar b/cue/testdata/references/optional.txtar
new file mode 100644
index 0000000..2b1f3f3
--- /dev/null
+++ b/cue/testdata/references/optional.txtar
@@ -0,0 +1,23 @@
+-- in.cue --
+a: {
+    foo?: int
+
+    b: foo
+}
+-- out/compile --
+--- in.cue
+{
+  a: {
+    foo?: int
+    b: 〈0;foo〉
+  }
+}
+-- out/eval --
+(struct){
+  a: (struct){
+    b: (_|_){
+      // [incomplete] a.b: undefined field foo:
+      //     ./in.cue:4:8
+    }
+  }
+}
diff --git a/cue/testdata/references/package.txtar b/cue/testdata/references/package.txtar
new file mode 100644
index 0000000..d702ca7
--- /dev/null
+++ b/cue/testdata/references/package.txtar
@@ -0,0 +1,38 @@
+-- cue.mod/module.cue --
+module: "example.com"
+
+-- in.cue --
+package exitcode
+
+import (
+	pkg "foo.com/example"
+)
+
+a: 1
+
+incomplete: pkg.Settings
+
+-- cue.mod/pkg/foo.com/example/example.cue --
+package example
+
+// An incomplete error in a package is not transferred to a
+// package that is using it.
+//
+// Issue #550
+blah: Settings.anyKey
+Settings: {}
+
+-- out/ --
+
+-- out/eval --
+(struct){
+  a: (int){ 1 }
+  incomplete: (struct){
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  a: 1
+  incomplete: 〈import;"foo.com/example"〉.Settings
+}
diff --git a/cue/testdata/resolve/016_index.txtar b/cue/testdata/resolve/016_index.txtar
index 83d7cea..338f2cd 100644
--- a/cue/testdata/resolve/016_index.txtar
+++ b/cue/testdata/resolve/016_index.txtar
@@ -14,6 +14,10 @@
 e4: [1, 2, 3][3]
 e5: [1, 2, 3][-1]
 e6: (*[] | {})[1]
+e7: [1, 2, 3][3]
+e8: [1, 2, 3][-1]
+e9: (*[] | {})[1]
+
 def: {
 	a:  1
 	#b: 3
@@ -70,6 +74,17 @@
     3,
   ][-1]
   e6: (*[]|{})[1]
+  e7: [
+    1,
+    2,
+    3,
+  ][3]
+  e8: [
+    1,
+    2,
+    3,
+  ][-1]
+  e9: (*[]|{})[1]
   def: {
     a: 1
     #b: 3
@@ -80,20 +95,26 @@
 Errors:
 c: invalid list index "3" (type string):
     ./in.cue:3:20
-d: undefined field 0:
+d: index out of range [0] with length 0:
     ./in.cue:4:16
 e1: invalid list index _ (type string):
     ./in.cue:6:9
 e2: invalid operand 2 (found int, want list or struct):
     ./in.cue:7:5
-e3: invalid label type bool:
+e3: invalid index true (invalid type bool):
     ./in.cue:8:5
-e4: undefined field 3:
+e4: index out of range [3] with length 3:
     ./in.cue:9:15
-e5: invalid negative index -1:
+e5: invalid index -1 (index must be non-negative):
     ./in.cue:10:5
 e6: invalid list index 1 (out of bounds):
     ./in.cue:11:16
+e7: index out of range [3] with length 3:
+    ./in.cue:12:15
+e8: invalid index -1 (index must be non-negative):
+    ./in.cue:13:5
+e9: invalid list index 1 (out of bounds):
+    ./in.cue:14:16
 
 Result:
 (_|_){
@@ -105,7 +126,7 @@
     //     ./in.cue:3:20
   }
   d: (_|_){
-    // [eval] d: undefined field 0:
+    // [eval] d: index out of range [0] with length 0:
     //     ./in.cue:4:16
   }
   l: (#list){
@@ -119,27 +140,35 @@
     //     ./in.cue:7:5
   }
   e3: (_|_){
-    // [eval] e3: invalid label type bool:
+    // [eval] e3: invalid index true (invalid type bool):
     //     ./in.cue:8:5
   }
   e4: (_|_){
-    // [eval] e4: undefined field 3:
+    // [eval] e4: index out of range [3] with length 3:
     //     ./in.cue:9:15
   }
   e5: (_|_){
-    // [eval] e5: invalid negative index -1:
+    // [eval] e5: invalid index -1 (index must be non-negative):
     //     ./in.cue:10:5
   }
   e6: (_|_){
     // [eval] e6: invalid list index 1 (out of bounds):
     //     ./in.cue:11:16
   }
+  e7: (_|_){
+    // [eval] e7: index out of range [3] with length 3:
+    //     ./in.cue:12:15
+  }
+  e8: (_|_){
+    // [eval] e8: invalid index -1 (index must be non-negative):
+    //     ./in.cue:13:5
+  }
+  e9: (_|_){
+    // [eval] e9: invalid list index 1 (out of bounds):
+    //     ./in.cue:14:16
+  }
   def: (struct){
     a: (int){ 1 }
     #b: (int){ 3 }
   }
-  e7: (_|_){
-    // [incomplete] e7: undefined field b:
-    //     ./in.cue:16:9
-  }
 }
diff --git a/cue/testdata/resolve/038_incomplete_comprehensions.txtar b/cue/testdata/resolve/038_incomplete_comprehensions.txtar
index 0da5579..1e23cd7 100644
--- a/cue/testdata/resolve/038_incomplete_comprehensions.txtar
+++ b/cue/testdata/resolve/038_incomplete_comprehensions.txtar
@@ -50,7 +50,7 @@
 -- out/eval --
 (struct){
   A: (_|_){
-    // [incomplete] A: incomplete feed source value src (type _):
+    // [incomplete] A: cannot range over src (incomplete type _):
     //     ./in.cue:2:11
     src: (_){ _ }
     baz: (string){ "baz" }
diff --git a/cue/types_test.go b/cue/types_test.go
index 63d594e..21a56fe 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -2399,7 +2399,7 @@
 		}
 		y: _
 		`,
-		err: `x: incomplete feed source`,
+		err: `x: cannot range over y (incomplete type _)`,
 	}}
 	for i, tc := range testCases {
 		t.Run(fmt.Sprintf("%d/%v", i, tc.value), func(t *testing.T) {
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 330d9fd..1b9e3d6 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -418,10 +418,11 @@
 
 	v, complete := c.Evaluate(env, x)
 
-	v, ok := c.getDefault(v, true)
+	v, ok := c.getDefault(v)
 	if !ok {
 		return v, false
 	}
+	v = Unwrap(v)
 
 	if !IsConcrete(v) {
 		complete = false
@@ -441,7 +442,7 @@
 // value, or if there is more than one default value, it reports an "incomplete"
 // error and return false. In all other cases it will return true, even if
 // v is already an error. v may be nil, in which case it will also return nil.
-func (c *OpContext) getDefault(v Value, scalar bool) (result Value, ok bool) {
+func (c *OpContext) getDefault(v Value) (result Value, ok bool) {
 	var d *Disjunction
 	switch x := v.(type) {
 	default:
@@ -454,13 +455,10 @@
 			d = t
 
 		case *Vertex:
-			return c.getDefault(t, scalar)
+			return c.getDefault(t)
 
 		default:
-			if !scalar {
-				return x, true
-			}
-			return x.Value(), true
+			return x, true
 		}
 
 	case *Disjunction:
@@ -472,7 +470,7 @@
 			"unresolved disjunction %s (type %s)", c.Str(d), d.Kind())
 		return nil, false
 	}
-	return c.getDefault(d.Values[0], scalar)
+	return c.getDefault(d.Values[0])
 }
 
 // Evaluate evaluates an expression within the given environment and indicates
@@ -512,7 +510,8 @@
 func (c *OpContext) value(x Expr) (result Value) {
 	v := c.evalState(x, Partial)
 
-	v, _ = c.getDefault(v, true)
+	v, _ = c.getDefault(v)
+	v = Unwrap(v)
 	return v
 }
 
@@ -572,18 +571,18 @@
 		return &Vertex{}
 	}
 
-	var kind Kind
-	if x.BaseValue != nil {
-		kind = x.BaseValue.Kind()
-	}
+	// var kind Kind
+	// if x.BaseValue != nil {
+	// 	kind = x.BaseValue.Kind()
+	// }
 
-	switch kind {
-	case StructKind:
+	switch x.BaseValue.(type) {
+	case *StructMarker:
 		if l.Typ() == IntLabel {
 			c.addErrf(0, pos, "invalid struct selector %s (type int)", l)
 		}
 
-	case ListKind:
+	case *ListMarker:
 		switch {
 		case l.Typ() == IntLabel:
 			switch {
@@ -602,11 +601,24 @@
 			return nil
 		}
 
+	case nil:
+		// c.addErrf(IncompleteError, pos, "incomplete value %s", c.Str(x))
+		// return nil
+
+	case *Bottom:
+
 	default:
-		// TODO: ?
-		// if !l.IsDef() {
-		// 	c.addErrf(0, nil, "invalid selector %s (must be definition for non-structs)", l)
-		// }
+		kind := x.BaseValue.Kind()
+		if kind&(ListKind|StructKind) != 0 {
+			// c.addErrf(IncompleteError, pos,
+			// 	"cannot look up %s in incomplete type %s (type %s)",
+			// 	l, x.Source(), kind)
+			// return nil
+		} else if !l.IsDef() && !l.IsHidden() {
+			c.addErrf(0, pos,
+				"invalid selector %s for value of type %s", l, kind)
+			return nil
+		}
 	}
 
 	a := x.Lookup(l)
@@ -618,13 +630,21 @@
 		// TODO: if the struct was a literal struct, we can also treat it as
 		// closed and make this a permanent error.
 		label := l.SelectorString(c.Runtime)
-		c.addErrf(code, pos, "undefined field %s", label)
+
+		// TODO(errors): add path reference and make message
+		//       "undefined field %s in %s"
+		if l.IsInt() {
+			c.addErrf(code, pos, "index out of range [%d] with length %d",
+				l.Index(), len(x.Elems()))
+		} else {
+			c.addErrf(code, pos, "undefined field %s", label)
+		}
 	}
 	return a
 }
 
-func (c *OpContext) Label(x Value) Feature {
-	return labelFromValue(c, x)
+func (c *OpContext) Label(src Expr, x Value) Feature {
+	return labelFromValue(c, src, x)
 }
 
 func (c *OpContext) typeError(v Value, k Kind) {
@@ -665,14 +685,19 @@
 	return x.Source().Pos()
 }
 
-func (c *OpContext) node(x Expr, scalar bool) *Vertex {
+func (c *OpContext) node(orig Node, x Expr, scalar bool) *Vertex {
+	// TODO: always get the vertex. This allows a whole bunch of trickery
+	// down the line.
 	v := c.evalState(x, EvaluatingArcs)
 
-	v, ok := c.getDefault(v, scalar)
+	v, ok := c.getDefault(v)
 	if !ok {
 		// Error already generated by getDefault.
 		return emptyNode
 	}
+	if scalar {
+		v = Unwrap(v)
+	}
 
 	node, ok := v.(*Vertex)
 	if ok {
@@ -680,7 +705,15 @@
 	}
 	switch nv := v.(type) {
 	case nil:
-		c.addErrf(IncompleteError, pos(x), "incomplete value %s", c.Str(x))
+		switch orig.(type) {
+		case *ForClause:
+			c.addErrf(IncompleteError, pos(x),
+				"cannot range over %s (incomplete)",
+				c.Str(x))
+		default:
+			c.addErrf(IncompleteError, pos(x),
+				"%s undefined (%s is incomplete)", c.Str(orig), c.Str(x))
+		}
 		return emptyNode
 
 	case *Bottom:
@@ -696,10 +729,17 @@
 		}
 
 	default:
-		if v.Kind()&StructKind != 0 {
-			c.addErrf(IncompleteError, pos(x),
-				"incomplete feed source value %s (type %s)",
-				x.Source(), v.Kind())
+		if kind := v.Kind(); kind&StructKind != 0 {
+			switch orig.(type) {
+			case *ForClause:
+				c.addErrf(IncompleteError, pos(x),
+					"cannot range over %s (incomplete type %s)",
+					c.Str(x), kind)
+			default:
+				c.addErrf(IncompleteError, pos(x),
+					"%s undefined as %s is incomplete (type %s)",
+					c.Str(orig), c.Str(x), kind)
+			}
 			return emptyNode
 
 		} else if !ok {
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 216647e..34ce843 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -503,7 +503,7 @@
 	frame := ctx.PushState(e, x.Src)
 	v := ctx.value(x.Label)
 	ctx.PopState(frame)
-	f := ctx.Label(v)
+	f := ctx.Label(x.Label, v)
 	return ctx.lookup(e.Vertex, pos(x), f)
 }
 
@@ -585,7 +585,7 @@
 }
 
 func (x *SelectorExpr) resolve(c *OpContext) *Vertex {
-	n := c.node(x.X, x.Sel.IsRegular())
+	n := c.node(x, x.X, x.Sel.IsRegular())
 	return c.lookup(n, x.Src.Sel.Pos(), x.Sel)
 }
 
@@ -608,9 +608,9 @@
 
 func (x *IndexExpr) resolve(ctx *OpContext) *Vertex {
 	// TODO: support byte index.
-	n := ctx.node(x.X, true)
+	n := ctx.node(x, x.X, true)
 	i := ctx.value(x.Index)
-	f := ctx.Label(i)
+	f := ctx.Label(x.Index, i)
 	return ctx.lookup(n, x.Src.Index.Pos(), f)
 }
 
@@ -1174,7 +1174,7 @@
 }
 
 func (x *ForClause) yield(c *OpContext, f YieldFunc) {
-	n := c.node(x.Src, true)
+	n := c.node(x, x.Src, true)
 	for _, a := range n.Arcs {
 		c.Unify(c, a, Partial)
 
diff --git a/internal/core/adt/feature.go b/internal/core/adt/feature.go
index 0356e02..ce4e76b 100644
--- a/internal/core/adt/feature.go
+++ b/internal/core/adt/feature.go
@@ -124,7 +124,7 @@
 
 // StringLabel converts s to a string label.
 func (c *OpContext) StringLabel(s string) Feature {
-	return labelFromValue(c, &String{Str: s})
+	return labelFromValue(c, nil, &String{Str: s})
 }
 
 // MakeStringLabel creates a label for the given string.
@@ -162,7 +162,7 @@
 
 const msgGround = "invalid non-ground value %s (must be concrete %s)"
 
-func labelFromValue(ctx *OpContext, v Value) Feature {
+func labelFromValue(c *OpContext, src Expr, v Value) Feature {
 	var i int64
 	var t FeatureType
 	if isError(v) {
@@ -172,39 +172,56 @@
 	case IntKind, NumKind:
 		x, _ := v.(*Num)
 		if x == nil {
-			ctx.addErrf(IncompleteError, pos(v), msgGround, v, "int")
+			c.addErrf(IncompleteError, pos(v), msgGround, v, "int")
 			return InvalidLabel
 		}
 		t = IntLabel
 		var err error
 		i, err = x.X.Int64()
 		if err != nil || x.K != IntKind {
-			ctx.AddErrf("invalid label %v: %v", v, err)
+			if src == nil {
+				src = v
+			}
+			c.AddErrf("invalid index %v: %v", src, err)
 			return InvalidLabel
 		}
 		if i < 0 {
-			ctx.AddErrf("invalid negative index %s", ctx.Str(x))
+			switch src.(type) {
+			case nil, *Num, *UnaryExpr:
+				// If the value is a constant, we know it is always an error.
+				// UnaryExpr is an approximation for a constant value here.
+				c.AddErrf("invalid index %s (index must be non-negative)",
+					c.Str(x))
+			default:
+				// Use a different message is it is the result of evaluation.
+				c.AddErrf("index %s out of range [%s]", c.Str(src), c.Str(x))
+			}
 			return InvalidLabel
 		}
 
 	case StringKind:
 		x, _ := v.(*String)
 		if x == nil {
-			ctx.addErrf(IncompleteError, pos(v), msgGround, v, "string")
+			c.addErrf(IncompleteError, pos(v), msgGround, v, "string")
 			return InvalidLabel
 		}
 		t = StringLabel
-		i = ctx.StringToIndex(x.Str)
+		i = c.StringToIndex(x.Str)
 
 	default:
-		ctx.AddErrf("invalid label type %v", v.Kind())
+		if src != nil {
+			c.AddErrf("invalid index %s (invalid type %v)",
+				c.Str(src), v.Kind())
+		} else {
+			c.AddErrf("invalid index type %v", v.Kind())
+		}
 		return InvalidLabel
 	}
 
 	// TODO: set position if it exists.
 	f, err := MakeLabel(nil, i, t)
 	if err != nil {
-		ctx.AddErr(err)
+		c.AddErr(err)
 	}
 	return f
 }
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 30a9adf..b677d28 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -483,27 +483,20 @@
 
 	default:
 		if isEvaluating(n.node) {
-			// TODO: this does not yet validate all values.
-
 			if !n.done() { // && !ctx.IsTentative() {
 				// collect incomplete errors.
-				// 	len(n.ifClauses) == 0 &&
-				// 	len(n.forClauses) == 0 &&
 				var err *adt.Bottom // n.incomplete
-				// err = n.incomplete
 				for _, d := range n.dynamicFields {
-					x, _ := ctx.Concrete(d.env, d.field.Key, "dynamic field")
-					b, _ := x.(*adt.Bottom)
-					err = adt.CombineErrors(nil, err, b)
+					err = adt.CombineErrors(nil, err, d.err)
 				}
 				for _, c := range n.forClauses {
-					f := func(env *adt.Environment, st *adt.StructLit) {}
-					err = adt.CombineErrors(nil, err, ctx.Yield(c.env, c.yield, f))
+					err = adt.CombineErrors(nil, err, c.err)
+				}
+				for _, c := range n.ifClauses {
+					err = adt.CombineErrors(nil, err, c.err)
 				}
 				for _, x := range n.exprs {
-					x, _ := ctx.Evaluate(x.Env, x.Expr())
-					b, _ := x.(*adt.Bottom)
-					err = adt.CombineErrors(nil, err, b)
+					err = adt.CombineErrors(nil, err, x.err)
 				}
 				if err == nil {
 					// safeguard.
@@ -828,7 +821,7 @@
 	// Expression conjuncts
 	lists  []envList
 	vLists []*adt.Vertex
-	exprs  []adt.Conjunct
+	exprs  []envExpr
 
 	hasCycle    bool // has conjunct with structural cycle
 	hasNonCycle bool // has conjunct without structural cycle
@@ -1034,7 +1027,7 @@
 		}
 
 	case 1:
-		v = a[0].(adt.Value)
+		v = a[0].(adt.Value) // remove cast
 
 	default:
 		v = &adt.Conjunction{Values: a}
@@ -1055,16 +1048,23 @@
 	}
 }
 
+type envExpr struct {
+	c   adt.Conjunct
+	err *adt.Bottom
+}
+
 type envDynamic struct {
 	env   *adt.Environment
 	field *adt.DynamicField
 	id    adt.ID
+	err   *adt.Bottom
 }
 
 type envYield struct {
 	env   *adt.Environment
 	yield adt.Yielder
 	id    adt.ID
+	err   *adt.Bottom
 }
 
 type envList struct {
@@ -1159,16 +1159,12 @@
 	switch x := v.Expr().(type) {
 	case adt.Resolver:
 		arc, err := ctx.Resolve(v.Env, x)
-		if err != nil {
-			if err.IsIncomplete() {
-				n.incomplete = adt.CombineErrors(nil, n.incomplete, err)
-			} else {
-				n.addBottom(err)
-				break
-			}
+		if err != nil && !err.IsIncomplete() {
+			n.addBottom(err)
+			break
 		}
 		if arc == nil {
-			n.exprs = append(n.exprs, v)
+			n.exprs = append(n.exprs, envExpr{v, err})
 			break
 		}
 
@@ -1179,7 +1175,8 @@
 		// Could be unify?
 		val, complete := ctx.Evaluate(v.Env, v.Expr())
 		if !complete {
-			n.exprs = append(n.exprs, v)
+			b, _ := val.(*adt.Bottom)
+			n.exprs = append(n.exprs, envExpr{v, b})
 			break
 		}
 
@@ -1668,16 +1665,16 @@
 			n.aStruct = s
 			n.aStructID = closeID
 			hasOther = x
-			n.dynamicFields = append(n.dynamicFields, envDynamic{childEnv, x, closeID})
+			n.dynamicFields = append(n.dynamicFields, envDynamic{childEnv, x, closeID, nil})
 			opt.AddDynamic(ctx, childEnv, x)
 
 		case *adt.ForClause:
 			hasOther = x
-			n.forClauses = append(n.forClauses, envYield{childEnv, x, closeID})
+			n.forClauses = append(n.forClauses, envYield{childEnv, x, closeID, nil})
 
 		case adt.Yielder:
 			hasOther = x
-			n.ifClauses = append(n.ifClauses, envYield{childEnv, x, closeID})
+			n.ifClauses = append(n.ifClauses, envYield{childEnv, x, closeID, nil})
 
 		case adt.Expr:
 			hasEmbed = true
@@ -1795,7 +1792,7 @@
 	exprs := n.exprs
 	n.exprs = n.exprs[:0]
 	for _, x := range exprs {
-		n.addExprConjunct(x)
+		n.addExprConjunct(x.c)
 
 		// collect and and or
 	}
@@ -1818,6 +1815,7 @@
 		var f adt.Feature
 		v, complete := ctx.Evaluate(d.env, d.field.Key)
 		if !complete {
+			d.err, _ = v.(*adt.Bottom)
 			a[k] = d
 			k++
 			continue
@@ -1826,7 +1824,7 @@
 			n.addValueConjunct(nil, b, d.id)
 			continue
 		}
-		f = ctx.Label(v)
+		f = ctx.Label(d.field.Key, v)
 		n.insertField(f, adt.MakeConjunct(d.env, d.field, d.id))
 	}
 
@@ -1858,6 +1856,7 @@
 
 		if err := ctx.Yield(d.env, d.yield, f); err != nil {
 			if err.IsIncomplete() {
+				d.err = err
 				(*all)[k] = d
 				k++
 			} else {
diff --git a/internal/core/export/testdata/adt.txtar b/internal/core/export/testdata/adt.txtar
index e26c8c8..7d741c1 100644
--- a/internal/core/export/testdata/adt.txtar
+++ b/internal/core/export/testdata/adt.txtar
@@ -191,10 +191,10 @@
 [x]
 -- out/value --
 == Simplified
-_|_ // e3: undefined field 2
+_|_ // e3: index out of range [2] with length 2
 == Raw
-_|_ // e3: undefined field 2
+_|_ // e3: index out of range [2] with length 2
 == Final
-_|_ // e3: undefined field 2
+_|_ // e3: index out of range [2] with length 2
 == All
-_|_ // e3: undefined field 2
+_|_ // e3: index out of range [2] with length 2