cue/errors: add Wrap

introduces new internal wrapping error that allows
harmonizing wrapped errors.

Issue #52

Change-Id: I93844d4cc7592521b70b6402a9670a941c2f6dfc
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9447
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/cmd_err.txt b/cmd/cue/cmd/testdata/script/cmd_err.txt
index a3b31d8..3319baa 100644
--- a/cmd/cue/cmd/testdata/script/cmd_err.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_err.txt
@@ -10,6 +10,7 @@
     ./task_tool.cue:6:8
     ./task_tool.cue:7:9
     tool/file:15:3
+    tool/file:15:16
 -- task_tool.cue --
 package home
 
diff --git a/cmd/cue/cmd/testdata/script/cmd_errpos.txt b/cmd/cue/cmd/testdata/script/cmd_errpos.txt
index 328fcac..f2db948 100644
--- a/cmd/cue/cmd/testdata/script/cmd_errpos.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_errpos.txt
@@ -12,6 +12,7 @@
 command.prompter.filename: invalid string argument: non-concrete value string:
     ./task_tool.cue:9:10
     tool/file:9:3
+    tool/file:9:16
 -- task_tool.cue --
 package foo
 
diff --git a/cmd/cue/cmd/testdata/script/export_err.txt b/cmd/cue/cmd/testdata/script/export_err.txt
index 6e6a415..ae52f07 100644
--- a/cmd/cue/cmd/testdata/script/export_err.txt
+++ b/cmd/cue/cmd/testdata/script/export_err.txt
@@ -13,6 +13,7 @@
 a.b.2.c: incomplete value int
 out: invalid interpolation: undefined field d:
     ./exporterr/export_err.cue:7:6
+    ./exporterr/export_err.cue:7:16
 -- expect-stdout --
 -- exporterr/export_err.cue --
 package exporterr
diff --git a/cmd/cue/cmd/testdata/script/vet_yaml.txt b/cmd/cue/cmd/testdata/script/vet_yaml.txt
index e61ed73..0370ffd 100644
--- a/cmd/cue/cmd/testdata/script/vet_yaml.txt
+++ b/cmd/cue/cmd/testdata/script/vet_yaml.txt
@@ -4,6 +4,7 @@
 -- expect-stderr --
 phrases: invalid value "phrases:\n  # A quote from Mark Twain.\n  quote1:\n    lang: en\n    attribution: Mark Twain\n\n  # A Norwegian proverb.\n  proverb:\n    lang: no\n    text: Stemmen som sier at du ikke klarer det, lyver." (does not satisfy encoding/yaml.Validate({phrases:{},#Phrase:{lang:=~"^[a-zA-Z0-9-_]{2,}$" | false,text:!=""},#LanguageTag:=~"^[a-zA-Z0-9-_]{2,}$" | false})): error in call to encoding/yaml.Validate: incomplete value !="":
     ./yaml.cue:19:10
+    ./yaml.cue:11:17
     ./yaml.cue:21:10
 -- yaml.cue --
 import "encoding/yaml"
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 20b5bd0..e8e5a0e 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -170,25 +170,77 @@
 // Wrapf creates an Error with the associated position and message. The provided
 // error is added for inspection context.
 func Wrapf(err error, p token.Pos, format string, args ...interface{}) Error {
-	a, ok := err.(list)
-	if !ok {
-		return &posError{
-			pos:     p,
-			Message: NewMessage(format, args),
-			err:     err,
-		}
+	pErr := &posError{
+		pos:     p,
+		Message: NewMessage(format, args),
 	}
-	b := make([]Error, len(a))
-	for i, err := range a {
-		b[i] = &posError{
-			pos:     p,
-			Message: NewMessage(format, args),
-			err:     err,
-		}
-	}
-	return list(b)
+	return Wrap(pErr, err)
 }
 
+// Wrap creates a new error where child is a subordinate error of parent.
+// If child is list of Errors, the result will itself be a list of errors
+// where child is a subordinate error of each parent.
+func Wrap(parent Error, child error) Error {
+	if child == nil {
+		return parent
+	}
+	a, ok := child.(list)
+	if !ok {
+		return &wrapped{parent, child}
+	}
+	b := make(list, len(a))
+	for i, err := range a {
+		b[i] = &wrapped{parent, err}
+	}
+	return b
+}
+
+type wrapped struct {
+	main Error
+	wrap error
+}
+
+// Error implements the error interface.
+func (e *wrapped) Error() string {
+	switch msg := e.main.Error(); {
+	case e.wrap == nil:
+		return msg
+	case msg == "":
+		return e.wrap.Error()
+	default:
+		return fmt.Sprintf("%s: %s", msg, e.wrap)
+	}
+}
+
+func (e *wrapped) Msg() (format string, args []interface{}) {
+	return e.main.Msg()
+}
+
+func (e *wrapped) Path() []string {
+	if p := Path(e.main); p != nil {
+		return p
+	}
+	return Path(e.wrap)
+}
+
+func (e *wrapped) InputPositions() []token.Pos {
+	return append(e.main.InputPositions(), Positions(e.wrap)...)
+}
+
+func (e *wrapped) Position() token.Pos {
+	if p := e.main.Position(); p != token.NoPos {
+		return p
+	}
+	if wrap, ok := e.wrap.(Error); ok {
+		return wrap.Position()
+	}
+	return token.NoPos
+}
+
+func (e *wrapped) Unwrap() error { return e.wrap }
+
+func (e *wrapped) Cause() error { return e.wrap }
+
 // Promote converts a regular Go error to an Error if it isn't already one.
 func Promote(err error, msg string) Error {
 	switch x := err.(type) {
@@ -209,27 +261,11 @@
 	pos    token.Pos
 	inputs []token.Pos
 	Message
-
-	// The underlying error that triggered this one, if any.
-	err error
 }
 
-func (e *posError) Path() []string              { return Path(e.err) }
+func (e *posError) Path() []string              { return nil }
 func (e *posError) InputPositions() []token.Pos { return e.inputs }
 func (e *posError) Position() token.Pos         { return e.pos }
-func (e *posError) Unwrap() error               { return e.err }
-func (e *posError) Cause() error                { return e.err }
-
-// Error implements the error interface.
-func (e *posError) Error() string {
-	if e.err == nil {
-		return e.Message.Error()
-	}
-	if e.Message.format == "" {
-		return e.err.Error()
-	}
-	return fmt.Sprintf("%s: %s", e.Message.Error(), e.err)
-}
 
 // Append combines two errors, flattening Lists as necessary.
 func Append(a, b Error) Error {
diff --git a/cue/testdata/eval/dynamic_field.txtar b/cue/testdata/eval/dynamic_field.txtar
index 003ee5e..b352593 100644
--- a/cue/testdata/eval/dynamic_field.txtar
+++ b/cue/testdata/eval/dynamic_field.txtar
@@ -25,6 +25,8 @@
 Errors:
 invalid interpolation: conflicting values 2 and 1:
     ./in.cue:12:31
+    ./in.cue:12:34
+    ./in.cue:12:36
 
 Result:
 (_|_){
@@ -40,6 +42,8 @@
     issue799: (_|_){
       // [eval] invalid interpolation: conflicting values 2 and 1:
       //     ./in.cue:12:31
+      //     ./in.cue:12:34
+      //     ./in.cue:12:36
       key: (int){ &(>=-2147483648, <=2147483647, int) }
     }
   }
diff --git a/cue/testdata/fulleval/055_issue318.txtar b/cue/testdata/fulleval/055_issue318.txtar
index b43df71..7b13832 100644
--- a/cue/testdata/fulleval/055_issue318.txtar
+++ b/cue/testdata/fulleval/055_issue318.txtar
@@ -45,8 +45,10 @@
 Errors:
 #T.out1: invalid interpolation: undefined field y:
     ./in.cue:3:8
+    ./in.cue:3:24
 #T.out2: invalid interpolation: undefined field y:
     ./in.cue:4:8
+    ./in.cue:4:15
 #T.vy: undefined field y:
     ./in.cue:6:12
 
@@ -61,10 +63,12 @@
     out1: (_|_){
       // [eval] #T.out1: invalid interpolation: undefined field y:
       //     ./in.cue:3:8
+      //     ./in.cue:3:24
     }
     out2: (_|_){
       // [eval] #T.out2: invalid interpolation: undefined field y:
       //     ./in.cue:4:8
+      //     ./in.cue:4:15
     }
     vx: (string){ string }
     vy: (_|_){
diff --git a/cue/testdata/interpolation/041_interpolation.txtar b/cue/testdata/interpolation/041_interpolation.txtar
index 8f2dd9b..ea18b8e 100644
--- a/cue/testdata/interpolation/041_interpolation.txtar
+++ b/cue/testdata/interpolation/041_interpolation.txtar
@@ -35,6 +35,7 @@
 Errors:
 e: invalid interpolation: cannot use [] (type list) as type (bool|string|bytes|number):
     ./in.cue:7:4
+    ./in.cue:7:7
 
 Result:
 (_|_){
@@ -49,10 +50,12 @@
   u: (_|_){
     // [incomplete] u: invalid interpolation: non-concrete value _ (type _):
     //     ./in.cue:5:4
+    //     ./in.cue:5:7
   }
   r: (_){ _ }
   e: (_|_){
     // [eval] e: invalid interpolation: cannot use [] (type list) as type (bool|string|bytes|number):
     //     ./in.cue:7:4
+    //     ./in.cue:7:7
   }
 }
diff --git a/cue/testdata/interpolation/incomplete.txtar b/cue/testdata/interpolation/incomplete.txtar
index 29a5343..8d52908 100644
--- a/cue/testdata/interpolation/incomplete.txtar
+++ b/cue/testdata/interpolation/incomplete.txtar
@@ -27,6 +27,7 @@
   out: (_|_){
     // [incomplete] out: invalid interpolation: undefined field #d:
     //     ./in.cue:8:6
+    //     ./in.cue:13:21
   }
 }
 -- out/compile --
diff --git a/cue/testdata/references/errors.txtar b/cue/testdata/references/errors.txtar
index e33c536..4e33acf 100644
--- a/cue/testdata/references/errors.txtar
+++ b/cue/testdata/references/errors.txtar
@@ -80,10 +80,12 @@
     r1: (_|_){
       // [incomplete] missingFieldNestedInInterpolation.r1: invalid interpolation: undefined field b:
       //     ./references.cue:27:9
+      //     ./references.cue:27:14
     }
     r2: (_|_){
       // [incomplete] missingFieldNestedInInterpolation.r2: invalid interpolation: undefined field d:
       //     ./references.cue:30:9
+      //     ./references.cue:30:14
     }
   }
 }
diff --git a/internal/core/adt/errors.go b/internal/core/adt/errors.go
index c990092..20cfc6d 100644
--- a/internal/core/adt/errors.go
+++ b/internal/core/adt/errors.go
@@ -211,7 +211,6 @@
 	v      *Vertex
 	pos    token.Pos
 	auxpos []token.Pos
-	err    errors.Error
 	errors.Message
 }
 
@@ -325,7 +324,3 @@
 	}
 	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 2e44ce8..714b815 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -1484,13 +1484,12 @@
 	}
 
 	vErr := c.NewPosf(src, "invalid value %s (does not satisfy %s)", args[0], buf.String())
-	vErr.err = err
 
 	for _, v := range args {
 		vErr.AddPosition(v)
 	}
 
-	return &Bottom{Code: severeness, Err: vErr}
+	return &Bottom{Code: severeness, Err: errors.Wrap(vErr, err)}
 }
 
 // A Disjunction represents a disjunction, where each disjunct may or may not
diff --git a/internal/task/task.go b/internal/task/task.go
index 1b43bb4..e8e585f 100644
--- a/internal/task/task.go
+++ b/internal/task/task.go
@@ -88,9 +88,8 @@
 		task:    c.Obj,
 		v:       v,
 		Message: errors.NewMessage(format, args),
-		err:     wrap,
 	}
-	c.Err = errors.Append(c.Err, err)
+	c.Err = errors.Append(c.Err, errors.Wrap(err, wrap))
 }
 
 // taskError wraps some error values to retain position information about the
@@ -99,7 +98,6 @@
 	task cue.Value
 	v    cue.Value
 	errors.Message
-	err error
 }
 
 var _ errors.Error = &taskError{}
@@ -126,8 +124,6 @@
 	return a
 }
 
-func (t *taskError) Unwrap() error { return t.err }
-
 // A RunnerFunc creates a Runner.
 type RunnerFunc func(v cue.Value) (Runner, error)
 
diff --git a/pkg/encoding/json/testdata/gen.txtar b/pkg/encoding/json/testdata/gen.txtar
index f59b379..f54b900 100644
--- a/pkg/encoding/json/testdata/gen.txtar
+++ b/pkg/encoding/json/testdata/gen.txtar
@@ -17,7 +17,9 @@
 t9: json.MarshalStream([{a: 1}, {b: int | *2}])
 -- out/json --
 Errors:
-a: error in call to encoding/json.Validate: invalid value 10 (out of bound <3)
+a: error in call to encoding/json.Validate: invalid value 10 (out of bound <3):
+    ./in.cue:4:36
+    json.Validate:1:6
 
 Result:
 import "encoding/json"
diff --git a/pkg/encoding/yaml/testdata/gen.txtar b/pkg/encoding/yaml/testdata/gen.txtar
index 034ef27..846b9a3 100644
--- a/pkg/encoding/yaml/testdata/gen.txtar
+++ b/pkg/encoding/yaml/testdata/gen.txtar
@@ -14,9 +14,13 @@
 t9: yaml.MarshalStream([{a: 1}, {b: int | *2}])
 -- out/yaml --
 Errors:
-a: error in call to encoding/yaml.Validate: invalid value 4 (out of bound <3)
-a: error in call to encoding/yaml.ValidatePartial: invalid value 4 (out of bound <3)
 b: error in call to encoding/yaml.Validate: incomplete value int
+a: error in call to encoding/yaml.Validate: invalid value 4 (out of bound <3):
+    ./in.cue:3:41
+    yaml.Validate:3:5
+a: error in call to encoding/yaml.ValidatePartial: invalid value 4 (out of bound <3):
+    ./in.cue:6:48
+    yaml.ValidatePartial:3:5
 
 Result:
 t1: _|_ // error in call to encoding/yaml.Validate: a: invalid value 4 (out of bound <3) (and 1 more errors)
diff --git a/pkg/list/testdata/gen.txtar b/pkg/list/testdata/gen.txtar
index 90269d4..bb1a9ac 100644
--- a/pkg/list/testdata/gen.txtar
+++ b/pkg/list/testdata/gen.txtar
@@ -65,7 +65,6 @@
 Errors:
 error in call to list.Avg: empty list
 error in call to list.Drop: negative index
-error in call to list.FlattenN: cannot use value "foo" (type string) as list
 error in call to list.Max: empty list
 error in call to list.Min: empty list
 error in call to list.Range: end must be greater than start when step is positive
@@ -76,13 +75,23 @@
 error in call to list.Slice: slice bounds out of range
 error in call to list.Take: negative index
 Ascending.x: error in call to list.Sort: 2 errors in empty disjunction:
-Ascending.x: error in call to list.Sort: conflicting values number and {b:2} (mismatched types number and struct)
-Ascending.x: error in call to list.Sort: conflicting values string and {b:2} (mismatched types string and struct)
+Ascending.x: error in call to list.Sort: conflicting values number and {b:2} (mismatched types number and struct):
+    ./in.cue:46:24
+    list:10:9
+Ascending.x: error in call to list.Sort: conflicting values string and {b:2} (mismatched types string and struct):
+    ./in.cue:46:24
+    list:10:18
 Ascending.y: error in call to list.Sort: 2 errors in empty disjunction:
-Ascending.y: error in call to list.Sort: conflicting values number and {a:1} (mismatched types number and struct)
-Ascending.y: error in call to list.Sort: conflicting values string and {a:1} (mismatched types string and struct)
+Ascending.y: error in call to list.Sort: conflicting values number and {a:1} (mismatched types number and struct):
+    ./in.cue:46:17
+    list:10:9
+Ascending.y: error in call to list.Sort: conflicting values string and {a:1} (mismatched types string and struct):
+    ./in.cue:60:17
+    list:10:18
 t3: cannot use "foo" (type string) as list in argument 1 to list.Avg:
     ./in.cue:5:14
+error in call to list.FlattenN: cannot use value "foo" (type string) as list:
+    ./in.cue:15:20
 t14: cannot use "foo" (type string) as int in argument 2 to list.FlattenN:
     ./in.cue:16:24
 t17: cannot use "foo" (type string) as list in argument 1 to list.Max:
diff --git a/pkg/list/testdata/list.txtar b/pkg/list/testdata/list.txtar
index 05f94e4..5ebcd3a 100644
--- a/pkg/list/testdata/list.txtar
+++ b/pkg/list/testdata/list.txtar
@@ -43,10 +43,11 @@
 }
 -- out/list --
 Errors:
-error in call to list.Concat: cannot use value 1 (type int) as list
 error in call to list.Repeat: negative count
 concat.t7.v: cannot use 1 (type int) as list in argument 1 to list.Concat:
     ./in.cue:30:12
+error in call to list.Concat: cannot use value 1 (type int) as list:
+    ./in.cue:31:13
 
 Result:
 repeat: {
diff --git a/pkg/math/testdata/gen.txtar b/pkg/math/testdata/gen.txtar
index 1c51600..268c96b 100644
--- a/pkg/math/testdata/gen.txtar
+++ b/pkg/math/testdata/gen.txtar
@@ -25,9 +25,10 @@
 t33: math.Dim(5, 7.2)
 -- out/math --
 Errors:
-error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000
 t3: cannot call non-function math.Pi (type float):
     ./in.cue:5:5
+error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000:
+    ./in.cue:6:5
 cannot use 2.0E+400 (type float) as float64 in argument 0 to math.Asin: value was rounded up:
     ./in.cue:8:5