internal/core/adt: improve errors for validator conflicts

This also handles possible nil interface issues, which
are a result of the API.

Perhaps builtin implementations should not return Bottom,
but it _is_ a nice way to signal something is a validator.

The returned Bottom gets rewrapped with a standard
validator message up the stack, which also addes more
position information.

Fixes #627

Change-Id: I358a403432a74042410e34ed13847f294ad607e2
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8206
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 679d493..aba1e14 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -106,13 +106,13 @@
 		test("struct", `struct.MinFields(2) & {a: 1}`),
 		// TODO: original value may be better.
 		// `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)))`,
-		`_|_(struct has 1 fields < MinFields(2))`,
+		`_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (1 < 2))`,
 	}, {
 		test("time", `time.Time & "1937-01-01T12:00:27.87+00:20"`),
 		`"1937-01-01T12:00:27.87+00:20"`,
 	}, {
 		test("time", `time.Time & "no time"`),
-		`_|_(error in call to time.Time: invalid time "no time")`,
+		`_|_(invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time")`,
 	}, {
 		test("time", `time.Unix(1500000000, 123456)`),
 		`"2017-07-14T02:40:00.000123456Z"`,
diff --git a/cue/testdata/builtins/validators.txtar b/cue/testdata/builtins/validators.txtar
index bafa49e..541297b 100644
--- a/cue/testdata/builtins/validators.txtar
+++ b/cue/testdata/builtins/validators.txtar
@@ -123,7 +123,9 @@
   incompleteError2: (struct){
     MyType: (struct){
       kv: (_|_){
-        // [incomplete] struct has 0 fields < MinFields(1)
+        // [incomplete] incompleteError2.MyType.kv: invalid value {} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1):
+        //     ./in.cue:22:17
+        //     ./in.cue:22:34
       }
     }
     foo: (struct){
@@ -144,7 +146,9 @@
   violation: (struct){
     #MyType: (#struct){
       kv: (_|_){
-        // [incomplete] struct has 0 fields < MinFields(1)
+        // [incomplete] violation.#MyType.kv: invalid value {} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1):
+        //     ./in.cue:49:17
+        //     ./in.cue:49:34
       }
     }
     foo: (#struct){
diff --git a/cue/testdata/resolve/013_custom_validators.txtar b/cue/testdata/resolve/013_custom_validators.txtar
index 6ec7803..aade1ce 100644
--- a/cue/testdata/resolve/013_custom_validators.txtar
+++ b/cue/testdata/resolve/013_custom_validators.txtar
@@ -23,6 +23,7 @@
 Errors:
 b: invalid value "dog" (does not satisfy strings.ContainsAny("c")):
     ./in.cue:6:4
+    ./in.cue:6:24
 
 Result:
 (_|_){
@@ -31,6 +32,7 @@
   b: (_|_){
     // [eval] b: invalid value "dog" (does not satisfy strings.ContainsAny("c")):
     //     ./in.cue:6:4
+    //     ./in.cue:6:24
   }
   c: (string){ "dog" }
 }
diff --git a/internal/core/adt/errors.go b/internal/core/adt/errors.go
index ee55836..002ecc7 100644
--- a/internal/core/adt/errors.go
+++ b/internal/core/adt/errors.go
@@ -207,6 +207,7 @@
 	v      *Vertex
 	pos    token.Pos
 	auxpos []token.Pos
+	err    errors.Error
 	errors.Message
 }
 
@@ -302,3 +303,7 @@
 	}
 	return a
 }
+
+func (e ValueError) Unwrap() error {
+	return e.err
+}
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 74031fa..cb25c4c 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -1232,13 +1232,20 @@
 }
 
 func validateWithBuiltin(c *OpContext, src token.Pos, b *Builtin, args []Value) *Bottom {
+	var severeness ErrorCode
+	var err errors.Error
+
 	res := b.call(c, src, args)
 	switch v := res.(type) {
 	case nil:
 		return nil
 
 	case *Bottom:
-		return v
+		if v == nil {
+			return nil // caught elsewhere, but be defensive.
+		}
+		severeness = v.Code
+		err = v.Err
 
 	case *Bool:
 		if v.B {
@@ -1262,7 +1269,15 @@
 		}
 		buf.WriteString(")")
 	}
-	return c.NewErrf("invalid value %s (does not satisfy %s)", c.Str(args[0]), buf.String())
+
+	vErr := c.NewPosf(src, "invalid value %s (does not satisfy %s)", c.Str(args[0]), buf.String())
+	vErr.err = err
+
+	for _, v := range args {
+		vErr.AddPosition(v)
+	}
+
+	return &Bottom{Code: severeness, Err: vErr}
 }
 
 // A Disjunction represents a disjunction, where each disjunct may or may not
diff --git a/pkg/internal/builtin.go b/pkg/internal/builtin.go
index ab23868..7105378 100644
--- a/pkg/internal/builtin.go
+++ b/pkg/internal/builtin.go
@@ -143,10 +143,20 @@
 		case nil:
 			// Validators may return a nil in case validation passes.
 			return nil
+		case *adt.Bottom:
+			// deal with API limitation: catch nil interface issue.
+			if v != nil {
+				return v
+			}
+			return nil
 		case adt.Value:
 			return v
 		case bottomer:
-			return v.Bottom()
+			// deal with API limitation: catch nil interface issue.
+			if b := v.Bottom(); b != nil {
+				return b
+			}
+			return nil
 		}
 		if c.Err != nil {
 			return nil
diff --git a/pkg/regexp/testdata/gen.txtar b/pkg/regexp/testdata/gen.txtar
index 8560e28..c0f213d 100644
--- a/pkg/regexp/testdata/gen.txtar
+++ b/pkg/regexp/testdata/gen.txtar
@@ -23,7 +23,7 @@
 error in call to regexp.FindAll: no match
 error in call to regexp.FindAllNamedSubmatch: no match
 error in call to regexp.FindAllSubmatch: no match
-error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
+t14: invalid value "invalid)" (does not satisfy regexp.Valid): error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
 
 Result:
 t1: "foo"
@@ -53,5 +53,5 @@
 }]
 t12: _|_ // error in call to regexp.FindAllNamedSubmatch: no match (and 1 more errors)
 t13: "valid"
-t14: _|_ // error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
+t14: _|_ // t14: invalid value "invalid)" (does not satisfy regexp.Valid): error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
 
diff --git a/pkg/strings/testdata/gen.txtar b/pkg/strings/testdata/gen.txtar
index 837820b..636fd38 100644
--- a/pkg/strings/testdata/gen.txtar
+++ b/pkg/strings/testdata/gen.txtar
@@ -25,12 +25,16 @@
 0: error in call to strings.Join: element 0 of list argument 0: cannot use value 1 (type int) as string
 t10: invalid value "quux" (does not satisfy strings.MaxRunes(3)):
     ./in.cue:12:6
+    ./in.cue:12:23
 t12: invalid value "e" (does not satisfy strings.MaxRunes(0)):
     ./in.cue:14:6
+    ./in.cue:14:23
 t16: invalid value "hello" (does not satisfy strings.MaxRunes(3)):
     ./in.cue:18:6
+    ./in.cue:18:23
 t17: invalid value "hello" (does not satisfy strings.MinRunes(10)):
     ./in.cue:19:6
+    ./in.cue:19:23
 
 Result:
 t1: "Hello World!"
diff --git a/pkg/struct/struct.go b/pkg/struct/struct.go
index d9b034e..2dd37ca 100644
--- a/pkg/struct/struct.go
+++ b/pkg/struct/struct.go
@@ -36,7 +36,7 @@
 	if count < n {
 		return &adt.Bottom{
 			Code: adt.IncompleteError, // could still be resolved
-			Err:  errors.Newf(token.NoPos, "struct has %d fields < MinFields(%d)", count, n),
+			Err:  errors.Newf(token.NoPos, "len(fields) < MinFields(%[2]d) (%[1]d < %[2]d)", count, n),
 		}
 	}
 	return nil
diff --git a/pkg/struct/testdata/gen.txtar b/pkg/struct/testdata/gen.txtar
index 1ef363b..a73f573 100644
--- a/pkg/struct/testdata/gen.txtar
+++ b/pkg/struct/testdata/gen.txtar
@@ -15,6 +15,7 @@
     ./in.cue:3:27
 t4: invalid value {a:1} (does not satisfy struct.MaxFields(0)):
     ./in.cue:6:5
+    ./in.cue:6:22
 
 Result:
 import "struct"
diff --git a/pkg/time/testdata/gen.txtar b/pkg/time/testdata/gen.txtar
index 6c02b50..235744d 100644
--- a/pkg/time/testdata/gen.txtar
+++ b/pkg/time/testdata/gen.txtar
@@ -8,10 +8,10 @@
 t3: time.Unix(1500000000, 123456)
 -- out/time --
 Errors:
-error in call to time.Time: invalid time "no time"
+t2: invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time"
 
 Result:
 t1: "1937-01-01T12:00:27.87+00:20"
-t2: _|_ // error in call to time.Time: invalid time "no time"
+t2: _|_ // t2: invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time"
 t3: "2017-07-14T02:40:00.000123456Z"