internal/core/validate: initial implementation

This implements the Validate method of the old
implementation.

As CUE now recursively evaluates unconditionally,
this now is as simple as walking
the values and checking for properties.

Change-Id: Ibdbba6e2b268c2f02be46dc24132c7f7bc7039b1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6506
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/basicrewrite/000_errors.txtar b/cue/testdata/basicrewrite/000_errors.txtar
index 251380a..6229e79 100644
--- a/cue/testdata/basicrewrite/000_errors.txtar
+++ b/cue/testdata/basicrewrite/000_errors.txtar
@@ -26,6 +26,13 @@
   e: (_|_(from source) == _|_(from source))
 }
 -- out/eval --
+Errors:
+from source:
+    ./in.cue:1:4
+from source:
+    ./in.cue:2:11
+
+Result:
 (_|_){
   // [user]
   a: (_|_){
diff --git a/cue/testdata/basicrewrite/001_regexp.txtar b/cue/testdata/basicrewrite/001_regexp.txtar
index 86167aa..b11bdeb 100644
--- a/cue/testdata/basicrewrite/001_regexp.txtar
+++ b/cue/testdata/basicrewrite/001_regexp.txtar
@@ -61,6 +61,15 @@
   e3: (!="a" & <5)
 }
 -- out/eval --
+Errors:
+invalid value *adt.BoundValue (mismatched types number and string)
+invalid value *adt.Vertex (out of bound *adt.BoundValue)
+cannot use *adt.Num (type int) as type (string|bytes):
+    ./in.cue:18:5
+cannot use *adt.Bool (type bool) as type (string|bytes):
+    ./in.cue:19:5
+
+Result:
 (_|_){
   // [eval]
   c1: (bool){ true }
diff --git a/cue/testdata/basicrewrite/002_arithmetic.txtar b/cue/testdata/basicrewrite/002_arithmetic.txtar
index ca98463..2f4292a 100644
--- a/cue/testdata/basicrewrite/002_arithmetic.txtar
+++ b/cue/testdata/basicrewrite/002_arithmetic.txtar
@@ -94,6 +94,31 @@
   e8: (1.0 mod 1)
 }
 -- out/eval --
+Errors:
+failed arithmetic: division by zero:
+    ./in.cue:8:10
+failed arithmetic: division undefined:
+    ./in.cue:9:10
+division by zero:
+    ./in.cue:13:9
+division by zero:
+    ./in.cue:14:9
+division by zero:
+    ./in.cue:15:9
+division by zero:
+    ./in.cue:16:9
+invalid operands *adt.Num and *adt.String to '+' (type int and string):
+    ./in.cue:23:5
+invalid operands *adt.Num and *adt.Num to 'div' (type float and int):
+    ./in.cue:29:5
+invalid operands *adt.Num and *adt.Num to 'rem' (type int and float):
+    ./in.cue:30:5
+invalid operands *adt.Num and *adt.Num to 'quo' (type int and float):
+    ./in.cue:31:5
+invalid operands *adt.Num and *adt.Num to 'mod' (type float and int):
+    ./in.cue:32:5
+
+Result:
 (_|_){
   // [eval]
   i1: (int){ 1 }
diff --git a/cue/testdata/basicrewrite/003_integer-specific_arithmetic.txtar b/cue/testdata/basicrewrite/003_integer-specific_arithmetic.txtar
index 046f66e..0bfa968 100644
--- a/cue/testdata/basicrewrite/003_integer-specific_arithmetic.txtar
+++ b/cue/testdata/basicrewrite/003_integer-specific_arithmetic.txtar
@@ -86,6 +86,25 @@
   me2: (2 mod 1.0)
 }
 -- out/eval --
+Errors:
+invalid operands *adt.Num and *adt.Num to 'quo' (type float and int):
+    ./in.cue:5:6
+invalid operands *adt.Num and *adt.Num to 'quo' (type int and float):
+    ./in.cue:6:6
+invalid operands *adt.Num and *adt.Num to 'rem' (type float and int):
+    ./in.cue:12:6
+invalid operands *adt.Num and *adt.Num to 'rem' (type int and float):
+    ./in.cue:13:6
+invalid operands *adt.Num and *adt.Num to 'div' (type float and int):
+    ./in.cue:19:6
+invalid operands *adt.Num and *adt.Num to 'div' (type int and float):
+    ./in.cue:20:6
+invalid operands *adt.Num and *adt.Num to 'mod' (type float and int):
+    ./in.cue:26:6
+invalid operands *adt.Num and *adt.Num to 'mod' (type int and float):
+    ./in.cue:27:6
+
+Result:
 (_|_){
   // [eval]
   q1: (int){ 2 }
diff --git a/cue/testdata/basicrewrite/004_booleans.txtar b/cue/testdata/basicrewrite/004_booleans.txtar
index 2d969ce..b97eda7 100644
--- a/cue/testdata/basicrewrite/004_booleans.txtar
+++ b/cue/testdata/basicrewrite/004_booleans.txtar
@@ -26,6 +26,10 @@
   e: !true
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Bool and *adt.Bool
+
+Result:
 (_|_){
   // [eval]
   t: (bool){ true }
diff --git a/cue/testdata/basicrewrite/005_boolean_arithmetic.txtar b/cue/testdata/basicrewrite/005_boolean_arithmetic.txtar
index 818f601..47c88d2 100644
--- a/cue/testdata/basicrewrite/005_boolean_arithmetic.txtar
+++ b/cue/testdata/basicrewrite/005_boolean_arithmetic.txtar
@@ -29,6 +29,10 @@
   f: (true & false)
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Bool and *adt.Bool
+
+Result:
 (_|_){
   // [eval]
   a: (bool){ true }
diff --git a/cue/testdata/basicrewrite/006_basic_type.txtar b/cue/testdata/basicrewrite/006_basic_type.txtar
index 01e714b..e1a7cf2 100644
--- a/cue/testdata/basicrewrite/006_basic_type.txtar
+++ b/cue/testdata/basicrewrite/006_basic_type.txtar
@@ -33,6 +33,10 @@
   f: bool
 }
 -- out/eval --
+Errors:
+invalid value *adt.BasicType (mismatched types float and int)
+
+Result:
 (_|_){
   // [eval]
   a: (int){ 1 }
diff --git a/cue/testdata/basicrewrite/007_strings_and_bytes.txtar b/cue/testdata/basicrewrite/007_strings_and_bytes.txtar
index c24c382..92fcb50 100644
--- a/cue/testdata/basicrewrite/007_strings_and_bytes.txtar
+++ b/cue/testdata/basicrewrite/007_strings_and_bytes.txtar
@@ -40,6 +40,13 @@
   e1: ('b' + "c")
 }
 -- out/eval --
+Errors:
+invalid operands *adt.String and *adt.Bytes to '+' (type string and bytes):
+    ./in.cue:10:5
+invalid operands *adt.Bytes and *adt.String to '+' (type bytes and string):
+    ./in.cue:11:5
+
+Result:
 (_|_){
   // [eval]
   s0: (string){ "foobar" }
diff --git a/cue/testdata/basicrewrite/010_lists.txtar b/cue/testdata/basicrewrite/010_lists.txtar
index 50130fc..9cb4fc8 100644
--- a/cue/testdata/basicrewrite/010_lists.txtar
+++ b/cue/testdata/basicrewrite/010_lists.txtar
@@ -75,6 +75,15 @@
   ])
 }
 -- out/eval --
+Errors:
+conflicting types
+invalid value *adt.Num (out of bound *adt.BoundValue)
+invalid list index d (type string):
+    ./in.cue:5:12
+invalid negative index *adt.Num:
+    ./in.cue:6:8
+
+Result:
 (_|_){
   // [eval]
   list: (#list){
diff --git a/cue/testdata/basicrewrite/011_list_arithmetic.txtar b/cue/testdata/basicrewrite/011_list_arithmetic.txtar
index c1d26a8..23636f1 100644
--- a/cue/testdata/basicrewrite/011_list_arithmetic.txtar
+++ b/cue/testdata/basicrewrite/011_list_arithmetic.txtar
@@ -46,6 +46,11 @@
   e: (〈0;list〉 * -1)
 }
 -- out/eval --
+Errors:
+cannot convert negative number to uint64:
+    ./in.cue:10:9
+
+Result:
 (_|_){
   // [eval]
   list: (#list){
diff --git a/cue/testdata/basicrewrite/012_selecting.txtar b/cue/testdata/basicrewrite/012_selecting.txtar
index 8f3a41e..59ca51e 100644
--- a/cue/testdata/basicrewrite/012_selecting.txtar
+++ b/cue/testdata/basicrewrite/012_selecting.txtar
@@ -59,6 +59,13 @@
   ].b
 }
 -- out/eval --
+Errors:
+invalid struct selector 4 (type int):
+    ./in.cue:4:16
+invalid list index b (type string):
+    ./in.cue:7:13
+
+Result:
 (_|_){
   // [eval]
   obj: (struct){
diff --git a/cue/testdata/basicrewrite/013_obj_unify.txtar b/cue/testdata/basicrewrite/013_obj_unify.txtar
index 072fcb0..ee5a69a 100644
--- a/cue/testdata/basicrewrite/013_obj_unify.txtar
+++ b/cue/testdata/basicrewrite/013_obj_unify.txtar
@@ -74,6 +74,10 @@
   }
 }
 -- out/eval --
+Errors:
+conflicting values struct and int
+
+Result:
 (_|_){
   // [eval]
   o1: (struct){
diff --git a/cue/testdata/basicrewrite/015_types.txtar b/cue/testdata/basicrewrite/015_types.txtar
index dcf4145..ae5fc31 100644
--- a/cue/testdata/basicrewrite/015_types.txtar
+++ b/cue/testdata/basicrewrite/015_types.txtar
@@ -38,6 +38,16 @@
   m: -false
 }
 -- out/eval --
+Errors:
+invalid value *adt.BasicType (mismatched types string and int)
+value can never become concrete:
+    ./in.cue:7:5
+invalid operation +*adt.UnaryExpr (+ bool):
+    ./in.cue:8:5
+invalid operation -*adt.UnaryExpr (- bool):
+    ./in.cue:9:5
+
+Result:
 (_|_){
   // [eval]
   i: (int){ int }
diff --git a/cue/testdata/basicrewrite/016_comparison.txtar b/cue/testdata/basicrewrite/016_comparison.txtar
index 6b98206..84b04c7 100644
--- a/cue/testdata/basicrewrite/016_comparison.txtar
+++ b/cue/testdata/basicrewrite/016_comparison.txtar
@@ -37,6 +37,11 @@
   err: (2 == "s")
 }
 -- out/eval --
+Errors:
+invalid operands *adt.Num and *adt.String to '==' (type int and string):
+    ./in.cue:9:6
+
+Result:
 (_|_){
   // [eval]
   lss: (bool){ true }
diff --git a/cue/testdata/basicrewrite/017_null.txtar b/cue/testdata/basicrewrite/017_null.txtar
index c58a9a5..3fcd292 100644
--- a/cue/testdata/basicrewrite/017_null.txtar
+++ b/cue/testdata/basicrewrite/017_null.txtar
@@ -36,6 +36,11 @@
   call: null()
 }
 -- out/eval --
+Errors:
+cannot call non-function *adt.Null (type null):
+    ./in.cue:9:7
+
+Result:
 (_|_){
   // [eval]
   eql: (bool){ true }
diff --git a/cue/testdata/comprehensions/for.txtar b/cue/testdata/comprehensions/for.txtar
index 3f511b6..909b818 100644
--- a/cue/testdata/comprehensions/for.txtar
+++ b/cue/testdata/comprehensions/for.txtar
@@ -8,6 +8,11 @@
 k: { for v in e { v } }
 e: int
 -- out/eval --
+Errors:
+invalid operand e (found int, want list or struct):
+    ./in.cue:7:15
+
+Result:
 (_|_){
   // [eval]
   b: (struct){
diff --git a/cue/testdata/cycle/021_delayed_constraint_failure.txtar b/cue/testdata/cycle/021_delayed_constraint_failure.txtar
index 47a55f4..1363cf5 100644
--- a/cue/testdata/cycle/021_delayed_constraint_failure.txtar
+++ b/cue/testdata/cycle/021_delayed_constraint_failure.txtar
@@ -25,6 +25,10 @@
   x: (〈0;x〉 + 1)
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Num and *adt.Num
+
+Result:
 (_|_){
   // [eval]
   a: (int){ 100 }
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 a6402eb..81993c8 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
@@ -27,6 +27,10 @@
   }
 }
 -- out/eval --
+Errors:
+incompatible values *adt.String and *adt.String
+
+Result:
 (_|_){
   // [eval]
   a: (_|_){
diff --git a/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar b/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar
index 6021da7..c9e465e 100644
--- a/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar
+++ b/cue/testdata/cycle/051_resolved_self-reference_cycles_with_disjunction.txtar
@@ -158,6 +158,11 @@
   z3: 8
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Num and *adt.Num:
+    ./in.cue:44:6
+
+Result:
 (_|_){
   // [eval]
   xa1: (int){ 8 }
diff --git a/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar b/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar
index c69587d..eccea37 100644
--- a/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar
+++ b/cue/testdata/cycle/052_resolved_self-reference_cycles_with_disjunction_with_defaults.txtar
@@ -128,6 +128,11 @@
   z3: 8
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Num and *adt.Num:
+    ./in.cue:37:6
+
+Result:
 (_|_){
   // [eval]
   xa1: (int){ 8 }
diff --git a/cue/testdata/definitions/026_combined_definitions.txtar b/cue/testdata/definitions/026_combined_definitions.txtar
index 87f2eaf..ee3b170 100644
--- a/cue/testdata/definitions/026_combined_definitions.txtar
+++ b/cue/testdata/definitions/026_combined_definitions.txtar
@@ -117,6 +117,11 @@
   }
 }
 -- out/eval --
+Errors:
+field `b` not allowed
+field `c` not allowed
+
+Result:
 (_|_){
   // [eval]
   #D1: (#struct){
diff --git a/cue/testdata/definitions/032_definitions_with_embedding.txtar b/cue/testdata/definitions/032_definitions_with_embedding.txtar
index 1286a02..b7a19c5 100644
--- a/cue/testdata/definitions/032_definitions_with_embedding.txtar
+++ b/cue/testdata/definitions/032_definitions_with_embedding.txtar
@@ -80,6 +80,10 @@
   })
 }
 -- out/eval --
+Errors:
+field `d` not allowed
+
+Result:
 (_|_){
   // [eval]
   #E: (#struct){
diff --git "a/cue/testdata/definitions/033_Issue_\043153.txtar" "b/cue/testdata/definitions/033_Issue_\043153.txtar"
index a3e84a32..175494e 100644
--- "a/cue/testdata/definitions/033_Issue_\043153.txtar"
+++ "b/cue/testdata/definitions/033_Issue_\043153.txtar"
@@ -61,6 +61,10 @@
   })
 }
 -- out/eval --
+Errors:
+field `b` not allowed
+
+Result:
 (_|_){
   // [eval]
   listOfCloseds: (_|_){
diff --git a/cue/testdata/definitions/037_closing_with_comprehensions.txtar b/cue/testdata/definitions/037_closing_with_comprehensions.txtar
index 285a79e..e7db8de 100644
--- a/cue/testdata/definitions/037_closing_with_comprehensions.txtar
+++ b/cue/testdata/definitions/037_closing_with_comprehensions.txtar
@@ -103,6 +103,11 @@
   })
 }
 -- out/eval --
+Errors:
+cannot mix bulk optional fields with dynamic fields, embeddings, or comprehensions within the same struct
+field `f3` not allowed
+
+Result:
 (_|_){
   // [eval]
   #A: (#struct){
diff --git a/cue/testdata/definitions/037_conjunction_of_optional_sets.txtar b/cue/testdata/definitions/037_conjunction_of_optional_sets.txtar
index d55c6e5..9847e4e 100644
--- a/cue/testdata/definitions/037_conjunction_of_optional_sets.txtar
+++ b/cue/testdata/definitions/037_conjunction_of_optional_sets.txtar
@@ -51,6 +51,10 @@
   })
 }
 -- out/eval --
+Errors:
+field `aaa` not allowed
+
+Result:
 (_|_){
   // [eval]
   #A: (#struct){
diff --git a/cue/testdata/definitions/038_continue_recursive_closing_for_optionals.txtar b/cue/testdata/definitions/038_continue_recursive_closing_for_optionals.txtar
index fbe88f7..8132c7d 100644
--- a/cue/testdata/definitions/038_continue_recursive_closing_for_optionals.txtar
+++ b/cue/testdata/definitions/038_continue_recursive_closing_for_optionals.txtar
@@ -37,6 +37,10 @@
   })
 }
 -- out/eval --
+Errors:
+field `b` not allowed
+
+Result:
 (_|_){
   // [eval]
   #S: (#struct){
diff --git a/cue/testdata/eval/closed_disjunction.txtar b/cue/testdata/eval/closed_disjunction.txtar
index 8ecf601..c2e142a 100644
--- a/cue/testdata/eval/closed_disjunction.txtar
+++ b/cue/testdata/eval/closed_disjunction.txtar
@@ -14,6 +14,10 @@
     d: 4
 }
 -- out/eval --
+Errors:
+field `d` not allowed
+
+Result:
 (_|_){
   // [eval]
   #A: (#struct){
diff --git a/cue/testdata/eval/closedness.txtar b/cue/testdata/eval/closedness.txtar
index 417429e..99e05de 100644
--- a/cue/testdata/eval/closedness.txtar
+++ b/cue/testdata/eval/closedness.txtar
@@ -17,6 +17,10 @@
     }
 }
 -- out/eval --
+Errors:
+field `e` not allowed
+
+Result:
 (_|_){
   // [eval]
   #E: (#struct){
diff --git a/cue/testdata/eval/disjunctions.txtar b/cue/testdata/eval/disjunctions.txtar
index 426940f..90e4f91 100644
--- a/cue/testdata/eval/disjunctions.txtar
+++ b/cue/testdata/eval/disjunctions.txtar
@@ -16,6 +16,10 @@
 f: b & { name: "str", val: 3 }
 
 -- out/eval --
+Errors:
+invalid value *adt.BasicType (mismatched types string and int)
+
+Result:
 (_|_){
   // [eval]
   a: (int){ |(*(int){ 1 }, (int){ int }) }
diff --git a/cue/testdata/export/007.txtar b/cue/testdata/export/007.txtar
index b4ad798..c7f9841 100644
--- a/cue/testdata/export/007.txtar
+++ b/cue/testdata/export/007.txtar
@@ -26,6 +26,11 @@
   }
 }
 -- out/eval --
+Errors:
+undefined field c:
+    ./in.cue:1:41
+
+Result:
 (_|_){
   // [eval]
   #a: (#struct){
diff --git a/cue/testdata/export/008.txtar b/cue/testdata/export/008.txtar
index 7001856..6678e86 100644
--- a/cue/testdata/export/008.txtar
+++ b/cue/testdata/export/008.txtar
@@ -15,6 +15,10 @@
   }
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Num and *adt.Num
+
+Result:
 (_|_){
   // [eval]
   a: (_|_){
diff --git a/cue/testdata/export/019.txtar b/cue/testdata/export/019.txtar
index 3f0ab87..b935b9e 100644
--- a/cue/testdata/export/019.txtar
+++ b/cue/testdata/export/019.txtar
@@ -15,6 +15,11 @@
   }
 }
 -- out/eval --
+Errors:
+invalid interpolation: cannot use *adt.Conjunction (type number) as type string:
+    ./in.cue:1:20
+
+Result:
 (_|_){
   // [eval]
   a: (number){ &(>=0, <=10) }
diff --git "a/cue/testdata/fulleval/024_Issue_\04323.txtar" "b/cue/testdata/fulleval/024_Issue_\04323.txtar"
index effdf5d..bf2d4a9 100644
--- "a/cue/testdata/fulleval/024_Issue_\04323.txtar"
+++ "b/cue/testdata/fulleval/024_Issue_\04323.txtar"
@@ -27,6 +27,10 @@
   })
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Num and *adt.Num
+
+Result:
 (_|_){
   // [eval]
   x: (struct){ |((struct){
diff --git a/cue/testdata/fulleval/031_comparison_against_bottom.txtar b/cue/testdata/fulleval/031_comparison_against_bottom.txtar
index 8ed9d73..1bd8227 100644
--- a/cue/testdata/fulleval/031_comparison_against_bottom.txtar
+++ b/cue/testdata/fulleval/031_comparison_against_bottom.txtar
@@ -60,6 +60,11 @@
   err: (1 & 2)
 }
 -- out/eval --
+Errors:
+incompatible values *adt.Num and *adt.Num:
+    ./in.cue:2:4
+
+Result:
 (_|_){
   // [eval]
   a: (bool){ true }
diff --git a/cue/testdata/fulleval/035_optionals_with_label_filters.txtar b/cue/testdata/fulleval/035_optionals_with_label_filters.txtar
index 834cce6..004895c 100644
--- a/cue/testdata/fulleval/035_optionals_with_label_filters.txtar
+++ b/cue/testdata/fulleval/035_optionals_with_label_filters.txtar
@@ -101,6 +101,13 @@
   }
 }
 -- out/eval --
+Errors:
+field `cmd` not allowed
+field `foo1` not allowed
+field `fooTest1` not allowed
+invalid value *adt.Vertex (out of bound *adt.BoundValue)
+
+Result:
 (_|_){
   // [eval]
   #JobID: (string){ =~"^[a-zA-Z]*$" }
diff --git a/cue/testdata/fulleval/055_issue318.txtar b/cue/testdata/fulleval/055_issue318.txtar
index 1da6b56..ba787a3 100644
--- a/cue/testdata/fulleval/055_issue318.txtar
+++ b/cue/testdata/fulleval/055_issue318.txtar
@@ -42,6 +42,15 @@
   }
 }
 -- out/eval --
+Errors:
+invalid interpolation: undefined field y:
+    ./in.cue:3:8
+invalid interpolation: undefined field y:
+    ./in.cue:4:8
+undefined field y:
+    ./in.cue:6:12
+
+Result:
 (_|_){
   // [eval]
   #T: (_|_){
diff --git a/cue/testdata/interpolation/041_interpolation.txtar b/cue/testdata/interpolation/041_interpolation.txtar
index d86a24b..a664fb9 100644
--- a/cue/testdata/interpolation/041_interpolation.txtar
+++ b/cue/testdata/interpolation/041_interpolation.txtar
@@ -32,6 +32,11 @@
   e: "\([])"
 }
 -- out/eval --
+Errors:
+invalid interpolation: cannot use *adt.Vertex (type list) as type string:
+    ./in.cue:7:4
+
+Result:
 (_|_){
   // [eval]
   a: (string){ "4" }
diff --git a/cue/testdata/lists/019_list_types.txtar b/cue/testdata/lists/019_list_types.txtar
index 80fd580..fc789f2 100644
--- a/cue/testdata/lists/019_list_types.txtar
+++ b/cue/testdata/lists/019_list_types.txtar
@@ -117,6 +117,10 @@
   ]
 }
 -- out/eval --
+Errors:
+incompatible list lengths (1 and 2)
+
+Result:
 (_|_){
   // [eval]
   l0: (#list){
diff --git a/cue/testdata/lists/020_list_arithmetic.txtar b/cue/testdata/lists/020_list_arithmetic.txtar
index ab11e05..6802546 100644
--- a/cue/testdata/lists/020_list_arithmetic.txtar
+++ b/cue/testdata/lists/020_list_arithmetic.txtar
@@ -337,6 +337,11 @@
   ])
 }
 -- out/eval --
+Errors:
+value can never become concrete:
+    ./in.cue:6:12
+
+Result:
 (_|_){
   // [eval]
   l0: (#list){
diff --git a/cue/testdata/resolve/006_arithmetic.txtar b/cue/testdata/resolve/006_arithmetic.txtar
index 7e57d3c..288a4b1 100644
--- a/cue/testdata/resolve/006_arithmetic.txtar
+++ b/cue/testdata/resolve/006_arithmetic.txtar
@@ -29,6 +29,10 @@
   e2: (int & (4.0 / 2.0))
 }
 -- out/eval --
+Errors:
+invalid value *adt.Num (mismatched types float and int)
+
+Result:
 (_|_){
   // [eval]
   v1: (float){ 5.0000000000E+11 }
diff --git a/cue/testdata/resolve/011_bounds.txtar b/cue/testdata/resolve/011_bounds.txtar
index 775c728..ae2af98 100644
--- a/cue/testdata/resolve/011_bounds.txtar
+++ b/cue/testdata/resolve/011_bounds.txtar
@@ -137,6 +137,14 @@
   e9: (>"a" & <1)
 }
 -- out/eval --
+Errors:
+bounds *adt.BoundValue *adt.BoundValue
+invalid value *adt.BoundValue (mismatched types (bool|string|bytes|list|struct|number) and null)
+invalid value *adt.BoundValue (mismatched types number and string)
+invalid value *adt.Null (mismatched types null and (bool|string|bytes|list|struct|number))
+invalid value *adt.Num (out of bound *adt.BoundValue)
+
+Result:
 (_|_){
   // [eval]
   i1: (int){ 5 }
diff --git a/cue/testdata/resolve/012_bound_conversions.txtar b/cue/testdata/resolve/012_bound_conversions.txtar
index 8f2e779..ed2f919 100644
--- a/cue/testdata/resolve/012_bound_conversions.txtar
+++ b/cue/testdata/resolve/012_bound_conversions.txtar
@@ -47,6 +47,11 @@
   c4: (1.2 & ((>=1 & <2) & int))
 }
 -- out/eval --
+Errors:
+invalid value *adt.BasicType (mismatched types int and float)
+invalid value *adt.Num (out of bound *adt.BoundValue)
+
+Result:
 (_|_){
   // [eval]
   r0: (int){ 1 }
diff --git a/cue/testdata/resolve/016_index.txtar b/cue/testdata/resolve/016_index.txtar
index 3486c2c..b655962 100644
--- a/cue/testdata/resolve/016_index.txtar
+++ b/cue/testdata/resolve/016_index.txtar
@@ -77,6 +77,25 @@
   e7: 〈0;def〉["b"]
 }
 -- out/eval --
+Errors:
+invalid list index "3" (type string):
+    ./in.cue:3:20
+undefined field 0:
+    ./in.cue:4:16
+invalid list index _ (type string):
+    ./in.cue:6:9
+invalid operand 2 (found int, want list or struct):
+    ./in.cue:7:5
+invalid label type bool:
+    ./in.cue:8:5
+undefined field 3:
+    ./in.cue:9:15
+invalid negative index *adt.Num:
+    ./in.cue:10:5
+invalid list index 1 (out of bounds):
+    ./in.cue:11:16
+
+Result:
 (_|_){
   // [eval]
   a: (int){ 2 }
diff --git a/cue/testdata/resolve/018_slice.txtar b/cue/testdata/resolve/018_slice.txtar
index 7d50290..6884914 100644
--- a/cue/testdata/resolve/018_slice.txtar
+++ b/cue/testdata/resolve/018_slice.txtar
@@ -52,6 +52,23 @@
   ][:"9"]
 }
 -- out/eval --
+Errors:
+index 1 out of range:
+    ./in.cue:3:5
+cannot convert negative number to uint64:
+    ./in.cue:4:5
+invalid slice index: 1 > 0:
+    ./in.cue:5:5
+index 2 out of range:
+    ./in.cue:6:5
+cannot slice *adt.Num (type int):
+    ./in.cue:7:5
+cannot use *adt.String (type string) as type int in slice index:
+    ./in.cue:8:5
+cannot use *adt.String (type string) as type int in slice index:
+    ./in.cue:9:5
+
+Result:
 (_|_){
   // [eval]
   a: (#list){
diff --git a/cue/testdata/resolve/023_correct_error_messages.txtar b/cue/testdata/resolve/023_correct_error_messages.txtar
index 92f2d61..d8ac97d 100644
--- a/cue/testdata/resolve/023_correct_error_messages.txtar
+++ b/cue/testdata/resolve/023_correct_error_messages.txtar
@@ -14,6 +14,10 @@
   a: ("a" & 1)
 }
 -- out/eval --
+Errors:
+invalid value *adt.Num (mismatched types int and string)
+
+Result:
 (_|_){
   // [eval]
   a: (_|_){
diff --git a/cue/testdata/resolve/025_definitions.txtar b/cue/testdata/resolve/025_definitions.txtar
index 29e5e84..ba11189 100644
--- a/cue/testdata/resolve/025_definitions.txtar
+++ b/cue/testdata/resolve/025_definitions.txtar
@@ -118,6 +118,10 @@
   }
 }
 -- out/eval --
+Errors:
+field `feild` not allowed
+
+Result:
 (_|_){
   // [eval]
   #Foo: (#struct){
diff --git a/cue/testdata/resolve/029_non-closed_definition_carries_over_closedness_to_enclosed_template.txtar b/cue/testdata/resolve/029_non-closed_definition_carries_over_closedness_to_enclosed_template.txtar
index 1f38c91..3acc290 100644
--- a/cue/testdata/resolve/029_non-closed_definition_carries_over_closedness_to_enclosed_template.txtar
+++ b/cue/testdata/resolve/029_non-closed_definition_carries_over_closedness_to_enclosed_template.txtar
@@ -103,6 +103,12 @@
   })
 }
 -- out/eval --
+Errors:
+field `b` not allowed
+field `c` not allowed
+field `d` not allowed
+
+Result:
 (_|_){
   // [eval]
   #S: (#struct){
diff --git a/cue/testdata/resolve/030_definitions_with_disjunctions.txtar b/cue/testdata/resolve/030_definitions_with_disjunctions.txtar
index b911766..709e0ed 100644
--- a/cue/testdata/resolve/030_definitions_with_disjunctions.txtar
+++ b/cue/testdata/resolve/030_definitions_with_disjunctions.txtar
@@ -60,6 +60,10 @@
   }
 }
 -- out/eval --
+Errors:
+field `c` not allowed
+
+Result:
 (_|_){
   // [eval]
   #Foo: (struct){ |((#struct){
diff --git a/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar b/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar
index 2eca941..57ac0de 100644
--- a/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar
+++ b/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar
@@ -63,6 +63,11 @@
   })
 }
 -- out/eval --
+Errors:
+field `e` not allowed
+field `extra` not allowed
+
+Result:
 (_|_){
   // [eval]
   #S: (#struct){
diff --git a/cue/testdata/resolve/045_range_unification.txtar b/cue/testdata/resolve/045_range_unification.txtar
index 95d0279..42668f6 100644
--- a/cue/testdata/resolve/045_range_unification.txtar
+++ b/cue/testdata/resolve/045_range_unification.txtar
@@ -133,6 +133,13 @@
   n5: ((>=1 & <=5) & 2.5)
 }
 -- out/eval --
+Errors:
+bounds *adt.BoundValue *adt.BoundValue
+invalid value *adt.BasicType (mismatched types string and number)
+invalid value *adt.BoundValue (mismatched types number and string)
+invalid value *adt.Num (out of bound *adt.BoundValue)
+
+Result:
 (_|_){
   // [eval]
   a1: (int){ 3 }
diff --git a/cue/testdata/resolve/046_predefined_ranges.txtar b/cue/testdata/resolve/046_predefined_ranges.txtar
index 1597a35..83840a7 100644
--- a/cue/testdata/resolve/046_predefined_ranges.txtar
+++ b/cue/testdata/resolve/046_predefined_ranges.txtar
@@ -28,6 +28,10 @@
   e1: 100000
 }
 -- out/eval --
+Errors:
+invalid value *adt.Num (out of bound *adt.BoundValue)
+
+Result:
 (_|_){
   // [eval]
   k1: (int){ 44 }
diff --git a/cue/testdata/resolve/048_builtins.txtar b/cue/testdata/resolve/048_builtins.txtar
index bcb6fab..0312445 100644
--- a/cue/testdata/resolve/048_builtins.txtar
+++ b/cue/testdata/resolve/048_builtins.txtar
@@ -45,6 +45,10 @@
 -- out/legacy-debug --
 <0>{a1: <1>{a: (=~"oo" & =~"fo"), b: =~"oo", c: =~"fo"}, a2: <2>{a: "foo", b: =~"oo", c: =~"fo"}, a3: <3>{a: _|_((=~"oo" & "bar"):invalid value "bar" (does not match =~"oo")), b: =~"oo", c: =~"fo"}, o1: <4>{a: string, b: string, c: "bar"}, o2: <5>{a: "foo", b: string, c: "bar"}, o3: <6>{a: _|_(("baz" & "foo"):empty disjunction: conflicting values "baz" and "foo";("bar" & "foo"):empty disjunction: conflicting values "bar" and "foo"), b: "baz", c: "bar"}}
 -- out/eval --
+Errors:
+invalid value *adt.Vertex (out of bound *adt.BoundValue)
+
+Result:
 (_|_){
   // [eval]
   a1: (struct){
diff --git a/internal/core/eval/eval_test.go b/internal/core/eval/eval_test.go
index b8cb2bf..99601b8 100644
--- a/internal/core/eval/eval_test.go
+++ b/internal/core/eval/eval_test.go
@@ -25,6 +25,7 @@
 	"cuelang.org/go/internal/core/compile"
 	"cuelang.org/go/internal/core/debug"
 	"cuelang.org/go/internal/core/runtime"
+	"cuelang.org/go/internal/core/validate"
 	"cuelang.org/go/internal/cuetxtar"
 	"cuelang.org/go/pkg/strings"
 )
@@ -64,6 +65,14 @@
 
 		err = e.Eval(v)
 		t.WriteErrors(err)
+		if b := validate.Validate(r, v, &validate.Config{
+			AllErrors: true,
+		}); b != nil {
+			fmt.Fprintln(t, "Errors:")
+			t.WriteErrors(b.Err)
+			fmt.Fprintln(t, "")
+			fmt.Fprintln(t, "Result:")
+		}
 
 		if v == nil {
 			return
diff --git a/internal/core/validate/validate.go b/internal/core/validate/validate.go
new file mode 100644
index 0000000..2f20f82
--- /dev/null
+++ b/internal/core/validate/validate.go
@@ -0,0 +1,110 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package validate
+
+import (
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/internal/core/debug"
+)
+
+type Config struct {
+	// Concrete, if true, requires that all values be concrete.
+	Concrete bool
+
+	// DisallowCycles indicates that there may not be cycles.
+	DisallowCycles bool
+
+	// AllErrors continues descending into a Vertex, even if errors are found.
+	AllErrors bool
+
+	// TODO: omitOptional, if this is becomes relevant.
+}
+
+// Validate checks that a value has certain properties. The value must have
+// been evaluated.
+func Validate(r adt.Runtime, v *adt.Vertex, cfg *Config) *adt.Bottom {
+	if cfg == nil {
+		cfg = &Config{}
+	}
+	x := validator{Config: *cfg, runtime: r}
+	x.validate(v)
+	return x.err
+}
+
+type validator struct {
+	Config
+	err          *adt.Bottom
+	inDefinition int
+	runtime      adt.Runtime
+}
+
+func (v *validator) add(b *adt.Bottom) {
+	if !v.AllErrors {
+		v.err = adt.CombineErrors(nil, v.err, b)
+		return
+	}
+	if !b.ChildError {
+		v.err = adt.CombineErrors(nil, v.err, b)
+	}
+}
+
+func (v *validator) validate(x *adt.Vertex) {
+	if b, _ := x.Value.(*adt.Bottom); b != nil {
+		switch b.Code {
+		case adt.CycleError:
+			if v.Concrete || v.DisallowCycles {
+				v.add(b)
+			}
+
+		case adt.IncompleteError, adt.NotExistError:
+			if v.Concrete {
+				v.add(b)
+			}
+
+		default:
+			v.add(b)
+		}
+		if !b.HasRecursive {
+			return
+		}
+
+	} else if v.Concrete && v.inDefinition == 0 && !adt.IsConcrete(x) {
+		p := token.NoPos
+		if src := x.Value.Source(); src != nil {
+			p = src.Pos()
+		}
+		// TODO: use ValueError to get full path.
+		v.add(&adt.Bottom{
+			Code: adt.IncompleteError,
+			Err: errors.Newf(p, "incomplete value %v",
+				debug.NodeString(v.runtime, x.Value, nil)),
+		})
+	}
+
+	for _, a := range x.Arcs {
+		if !v.AllErrors && v.err != nil {
+			break
+		}
+		if a.Label.IsRegular() {
+			v.validate(a)
+		} else {
+			v.inDefinition++
+			v.validate(a)
+			v.inDefinition--
+		}
+	}
+}
diff --git a/internal/core/validate/validate_test.go b/internal/core/validate/validate_test.go
new file mode 100644
index 0000000..ab70286
--- /dev/null
+++ b/internal/core/validate/validate_test.go
@@ -0,0 +1,180 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package validate
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/internal/core/compile"
+	"cuelang.org/go/internal/core/eval"
+	"cuelang.org/go/internal/core/runtime"
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestValidate(t *testing.T) {
+	testCases := []struct {
+		desc   string
+		in     string
+		out    string
+		lookup string
+		cfg    *Config
+	}{{
+		desc: "no error, but not concrete, even with definition label",
+		cfg:  &Config{Concrete: true},
+		in: `
+		#foo: { use: string }
+		`,
+		lookup: "#foo",
+		out:    "incomplete\nincomplete value string",
+	}, {
+		desc: "definitions not considered for completeness",
+		cfg:  &Config{Concrete: true},
+		in: `
+		#foo: { use: string }
+		`,
+	}, {
+		desc: "hidden fields not considered for completeness",
+		cfg:  &Config{Concrete: true},
+		in: `
+		_foo: { use: string }
+		`,
+	}, {
+		desc: "hidden fields not considered for completeness",
+		in: `
+		_foo: { use: string }
+		`,
+	}, {
+		desc: "evaluation error at top",
+		in: `
+		1 & 2
+		`,
+		out: "eval\nincompatible values 2 and 1",
+	}, {
+		desc: "evaluation error in field",
+		in: `
+		x: 1 & 2
+		`,
+		out: "eval\nincompatible values 2 and 1",
+	}, {
+		desc: "first error",
+		in: `
+		x: 1 & 2
+		y: 2 & 4
+		`,
+		out: "eval\nincompatible values 2 and 1",
+	}, {
+		desc: "all errors",
+		cfg:  &Config{AllErrors: true},
+		in: `
+		x: 1 & 2
+		y: 2 & 4
+		`,
+		out: `eval
+incompatible values 2 and 1
+incompatible values 4 and 2`,
+	}, {
+		desc: "incomplete",
+		cfg:  &Config{Concrete: true},
+		in: `
+		y: 2 + x
+		x: string
+		`,
+		out: "incomplete\nnon-concrete value string in operand to +:\n    test:2:6",
+	}, {
+		desc: "allowed incomplete cycle",
+		in: `
+		y: x
+		x: y
+		`,
+	}, {
+		desc: "allowed incomplete when disallowing cycles",
+		cfg:  &Config{DisallowCycles: true},
+		in: `
+		y: string
+		x: y
+		`,
+	}, {
+		desc: "disallow cycle",
+		cfg:  &Config{DisallowCycles: true},
+		in: `
+		y: x + 1
+		x: y - 1
+		`,
+		out: "cycle\ncycle error",
+	}, {
+		desc: "disallow cycle",
+		cfg:  &Config{DisallowCycles: true},
+		in: `
+		a: b - 100
+		b: a + 100
+		c: [c[1], c[0]]		`,
+		out: "cycle\ncycle error",
+	}, {
+		desc: "treat cycles as incomplete when not disallowing",
+		cfg:  &Config{},
+		in: `
+		y: x + 1
+		x: y - 1
+		`,
+	}, {
+		// Note: this is already handled by evaluation, as terminal errors
+		// are percolated up.
+		desc: "catch most serious error",
+		cfg:  &Config{Concrete: true},
+		in: `
+		y: string
+		x: 1 & 2
+		`,
+		out: "eval\nincompatible values 2 and 1",
+	}}
+
+	r := runtime.New()
+	ctx := eval.NewContext(r, nil)
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			f, err := parser.ParseFile("test", tc.in)
+			if err != nil {
+				t.Fatal(err)
+			}
+			v, err := compile.Files(nil, r, f)
+			if err != nil {
+				t.Fatal(err)
+			}
+			ctx.Unify(ctx, v, adt.Finalized)
+			if tc.lookup != "" {
+				v = v.Lookup(adt.MakeIdentLabel(r, tc.lookup))
+			}
+
+			b := Validate(r, v, tc.cfg)
+
+			w := &strings.Builder{}
+			if b != nil {
+				fmt.Fprintln(w, b.Code)
+				errors.Print(w, b.Err, nil)
+			}
+
+			got := strings.TrimSpace(w.String())
+			if tc.out != got {
+				t.Error(cmp.Diff(tc.out, got))
+			}
+		})
+	}
+}