cue: keep path in API error value

Change-Id: Ib5fbeec7a7a8c826335f08335b40de597963b2c9
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4940
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/cmd_import.txt b/cmd/cue/cmd/testdata/script/cmd_import.txt
index 0db42f7..530e21d 100644
--- a/cmd/cue/cmd/testdata/script/cmd_import.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_import.txt
@@ -2,7 +2,7 @@
 cmp stderr expect-stderr
 
 -- expect-stderr --
-reference "cli" not found
+command.pkg.t: reference "cli" not found
 -- task_tool.cue --
 package home
 
diff --git a/cmd/cue/cmd/testdata/script/export_err.txt b/cmd/cue/cmd/testdata/script/export_err.txt
index 1bd2881..aeb2fe2 100644
--- a/cmd/cue/cmd/testdata/script/export_err.txt
+++ b/cmd/cue/cmd/testdata/script/export_err.txt
@@ -2,7 +2,7 @@
 cmp stdout expect-stdout
 cmp stderr expect-stderr
 -- expect-stderr --
-a.b.2.c: cue: marshal error at path a.b.2.c: cannot convert incomplete value "int" to JSON:
+a.b.2.c: cannot convert incomplete value "int" to JSON:
     ./exporterr/export_err.cue:4:16
 -- expect-stdout --
 -- exporterr/export_err.cue --
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index ba92d99..d89bcba 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -112,7 +112,7 @@
 		`true`,
 	}, {
 		test("encoding/json", `json.Validate("{\"a\":10}", {a:<3})`),
-		`_|_(error in call to encoding/json.Validate: invalid value 10 (out of bound <3))`,
+		`_|_(error in call to encoding/json.Validate: a: invalid value 10 (out of bound <3))`,
 	}, {
 		test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<3})`),
 		`_|_(error in call to encoding/yaml.Validate: invalid value 4 (out of bound <3))`,
@@ -124,7 +124,7 @@
 		`_|_(error in call to encoding/yaml.Validate: value not an instance)`,
 	}, {
 		test("encoding/yaml", `yaml.ValidatePartial("a: 2\n---\na: 4", {a:<3})`),
-		`_|_(error in call to encoding/yaml.ValidatePartial: invalid value 4 (out of bound <3))`,
+		`_|_(error in call to encoding/yaml.ValidatePartial: a: invalid value 4 (out of bound <3))`,
 	}, {
 		test("encoding/yaml", `yaml.ValidatePartial("a: 2\n---\na: 4", {a:<5})`),
 		`true`,
@@ -269,13 +269,13 @@
 		`[{a: 1, v: 2},{a: 1, v: 3},{a: 2, v: 1}]`,
 	}, {
 		test("list", `list.Sort([{a:1}, {b:2}], list.Ascending)`),
-		`_|_(error in call to list.Sort: conflicting values close(T, close(T)) and {b: 2} (mismatched types number|string and struct))`,
+		`_|_(error in call to list.Sort: less: conflicting values close(T, close(T)) and {b: 2} (mismatched types number|string and struct))`,
 	}, {
 		test("list", `list.SortStrings(["b", "a"])`),
 		`["a","b"]`,
 	}, {
 		test("list", `list.SortStrings([1, 2])`),
-		`_|_(invalid list element 0 in argument 0 to list.SortStrings: cannot use value 1 (type int) as string)`,
+		`_|_(invalid list element 0 in argument 0 to list.SortStrings: 0: cannot use value 1 (type int) as string)`,
 	}, {
 		test("list", `list.Sum([1, 2, 3, 4])`),
 		`10`,
@@ -387,7 +387,7 @@
 		`"Hello World!"`,
 	}, {
 		test("strings", `strings.Join([1, 2], " ")`),
-		`_|_(invalid list element 0 in argument 0 to strings.Join: cannot use value 1 (type int) as string)`,
+		`_|_(invalid list element 0 in argument 0 to strings.Join: 0: cannot use value 1 (type int) as string)`,
 	}, {
 		test("strings", `strings.ByteAt("a", 0)`),
 		strconv.Itoa('a'),
diff --git a/cue/errors.go b/cue/errors.go
index 07c308e..b9e988f 100644
--- a/cue/errors.go
+++ b/cue/errors.go
@@ -33,6 +33,10 @@
 	errors.Message
 }
 
+func (n *nodeError) Error() string {
+	return errors.String(n)
+}
+
 func nodeErrorf(n ast.Node, format string, args ...interface{}) *nodeError {
 	return &nodeError{
 		n:       n,
@@ -81,7 +85,7 @@
 }
 
 func (e *valueError) Error() string {
-	return fmt.Sprint(e.err)
+	return errors.String(e)
 }
 
 func (e *valueError) Position() token.Pos {
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index f57aa66..11debee 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -460,6 +460,44 @@
 	return w.String()
 }
 
+// String generates a short message from a given Error.
+func String(err Error) string {
+	w := &strings.Builder{}
+	writeErr(w, err)
+	return w.String()
+}
+
+func writeErr(w io.Writer, err Error) {
+	if path := strings.Join(err.Path(), "."); path != "" {
+		_, _ = io.WriteString(w, path)
+		_, _ = io.WriteString(w, ": ")
+	}
+
+	for {
+		u := errors.Unwrap(err)
+
+		printed := false
+		msg, args := err.Msg()
+		if msg != "" || u == nil { // print at least something
+			fmt.Fprintf(w, msg, args...)
+			printed = true
+		}
+
+		if u == nil {
+			break
+		}
+
+		if printed {
+			_, _ = io.WriteString(w, ": ")
+		}
+		err, _ = u.(Error)
+		if err == nil {
+			fmt.Fprint(w, u)
+			break
+		}
+	}
+}
+
 func defaultFprintf(w io.Writer, format string, args ...interface{}) {
 	fmt.Fprintf(w, format, args...)
 }
@@ -503,16 +541,18 @@
 		positions = append(positions, s)
 	}
 
-	if path := Path(err); path != nil {
-		fprintf(w, "%s: ", strings.Join(path, "."))
+	if e, ok := err.(Error); ok {
+		writeErr(w, e)
+	} else {
+		fprintf(w, "%v", err)
 	}
 
 	if len(positions) == 0 {
-		fprintf(w, "%v\n", err)
+		fprintf(w, "\n")
 		return
 	}
 
-	fprintf(w, "%v:\n", err)
+	fprintf(w, ":\n")
 	for _, pos := range positions {
 		fprintf(w, "    %s\n", pos)
 	}
diff --git a/cue/examples_test.go b/cue/examples_test.go
index 7f48b96..ddb3f78 100644
--- a/cue/examples_test.go
+++ b/cue/examples_test.go
@@ -113,5 +113,5 @@
 
 	// Output:
 	// V2 is backwards compatible with V1: <nil>
-	// V3 is backwards compatible with V2: conflicting values <=150 and string (mismatched types number and string)
+	// V3 is backwards compatible with V2: V2: conflicting values <=150 and string (mismatched types number and string)
 }
diff --git a/cue/types.go b/cue/types.go
index 0ba8a4f..3b60faf 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -172,7 +172,7 @@
 		ctx := o.ctx
 		x := ctx.mkErr(o.obj, codeNotExist, "value %q not found", key)
 		v := x.evalPartial(ctx)
-		return Value{ctx.index, &valueData{o.path, 0, arc{cache: v, v: x}}}
+		return Value{ctx.index, &valueData{o.path.parent, 0, arc{feature: o.path.feature, cache: v, v: x}}}
 	}
 	return newChildValue(o, i)
 }
@@ -221,12 +221,7 @@
 }
 
 func (e *marshalError) Error() string {
-	path := e.Path()
-	if len(path) == 0 {
-		return fmt.Sprintf("cue: marshal error: %v", e.err)
-	}
-	p := strings.Join(path, ".")
-	return fmt.Sprintf("cue: marshal error at path %s: %v", p, e.err)
+	return fmt.Sprintf("cue: marshal error: %v", e.err)
 }
 
 func (e *marshalError) Path() []string               { return e.err.Path() }
@@ -602,6 +597,22 @@
 	path *valueData
 }
 
+func newErrValue(v Value, b *bottom) Value {
+	ctx := v.ctx()
+	p := v.path
+	if p == nil {
+		return newValueRoot(ctx, b)
+	}
+	return Value{
+		ctx.index,
+		&valueData{p.parent, p.index, arc{
+			feature: p.arc.feature,
+			cache:   b,
+			v:       b,
+		}},
+	}
+}
+
 func newValueRoot(ctx *context, x value) Value {
 	v := x.evalPartial(ctx)
 	return Value{ctx.index, &valueData{nil, 0, arc{cache: v, v: x}}}
@@ -1175,20 +1186,28 @@
 
 // Struct returns the underlying struct of a value or an error if the value
 // is not a struct.
-func (v Value) Struct(opts ...Option) (*Struct, error) {
+func (v Value) Struct() (*Struct, error) {
+	obj, err := v.getStruct()
+	if err != nil {
+		return nil, v.toErr(err)
+	}
+	return &Struct{v, obj}, nil
+}
+
+func (v Value) getStruct() (*structLit, *bottom) {
 	ctx := v.ctx()
 	if err := v.checkKind(ctx, structKind); err != nil {
-		return nil, v.toErr(err)
+		return nil, err
 	}
 	obj := v.eval(ctx).(*structLit)
 
 	// TODO: This is expansion appropriate?
 	obj, err := obj.expandFields(ctx)
 	if err != nil {
-		return nil, v.toErr(err)
+		return nil, err
 	}
 
-	return &Struct{v, obj}, nil
+	return obj, nil
 }
 
 // Struct represents a CUE struct value.
@@ -1285,7 +1304,7 @@
 		obj, err := v.structValData(ctx)
 		if err != nil {
 			// TODO: return a Value at the same location and a new error?
-			return newValueRoot(ctx, err)
+			return newErrValue(v, err)
 		}
 		v = obj.Lookup(k)
 	}
@@ -1382,6 +1401,13 @@
 	if w.path == nil {
 		return v
 	}
+	if v.Err() != nil {
+		// TODO: perhaps keep both errors.
+		return v
+	}
+	if w.Err() != nil {
+		return w
+	}
 	a := v.path.v
 	b := w.path.v
 	src := binSrc(token.NoPos, opUnify, a, b)
diff --git a/cue/types_test.go b/cue/types_test.go
index 8ad986f..0f6b2d7 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1880,7 +1880,7 @@
 		err:   `c.0: cannot convert incomplete value`,
 	}, {
 		value: `{a: [{b: [0, {c: string}] }] }`,
-		err:   `path a.0.b.1.c: cannot convert incomplete value`,
+		err:   `a.0.b.1.c: cannot convert incomplete value`,
 	}, {
 		value: `{foo?: 1, bar?: 2, baz: 3}`,
 		json:  `{"baz":3}`,
diff --git a/cuego/examples_test.go b/cuego/examples_test.go
index b56a323..257771e 100644
--- a/cuego/examples_test.go
+++ b/cuego/examples_test.go
@@ -91,6 +91,6 @@
 	//Output:
 	// error: <nil>
 	// validate: <nil>
-	// validate: invalid value 39 (out of bound <=12)
-	// validate: invalid value "foo.jso" (does not match =~".json$")
+	// validate: MinCount: invalid value 39 (out of bound <=12)
+	// validate: Filename: invalid value "foo.jso" (does not match =~".json$")
 }