cue/errors: correct handling of wrapping list errors

This cleans up several kinds of error reporting.

It also creates more consistency in error reporting
between the different cue tools.

Fixes #553

Change-Id: Ie511e3df1c88225b5f05281790baf00df8b3ba01
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7402
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/eval.go b/cmd/cue/cmd/eval.go
index 8d94aa8..0d90deb 100644
--- a/cmd/cue/cmd/eval.go
+++ b/cmd/cue/cmd/eval.go
@@ -20,7 +20,6 @@
 	"github.com/spf13/cobra"
 
 	"cuelang.org/go/cue"
-	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/format"
 	"cuelang.org/go/internal"
 	"cuelang.org/go/internal/encoding"
@@ -141,7 +140,10 @@
 			errHeader()
 			return err
 		}
-		if flagConcrete.Bool(cmd) && !flagIgnore.Bool(cmd) {
+
+		// TODO(#553): this can be removed once v.Syntax() below retains line
+		// information.
+		if (e.IsConcrete() || flagConcrete.Bool(cmd)) && !flagIgnore.Bool(cmd) {
 			if err := v.Validate(cue.Concrete(true)); err != nil {
 				errHeader()
 				exitOnErr(cmd, err, false)
@@ -149,7 +151,7 @@
 			}
 		}
 
-		f := getSyntax(v, syn)
+		f := internal.ToFile(v.Syntax(syn...))
 		f.Filename = id
 		err := e.EncodeFile(f)
 		if err != nil {
@@ -160,7 +162,3 @@
 	exitOnErr(cmd, iter.err(), true)
 	return nil
 }
-
-func getSyntax(v cue.Value, opts []cue.Option) *ast.File {
-	return internal.ToFile(v.Syntax(opts...))
-}
diff --git a/cmd/cue/cmd/testdata/script/export_err.txt b/cmd/cue/cmd/testdata/script/export_err.txt
index 79206ab..6e6a415 100644
--- a/cmd/cue/cmd/testdata/script/export_err.txt
+++ b/cmd/cue/cmd/testdata/script/export_err.txt
@@ -1,13 +1,25 @@
+! cue eval ./exporterr --out json
+cmp stdout expect-stdout
+cmp stderr expect-stderr
+
+! cue eval ./exporterr -c
+cmp stdout expect-stdout
+cmp stderr expect-stderr
+
 ! cue export ./exporterr
 cmp stdout expect-stdout
 cmp stderr expect-stderr
 -- expect-stderr --
-a.b.2.c: cannot convert incomplete value "int" to JSON
+a.b.2.c: incomplete value int
+out: invalid interpolation: undefined field d:
+    ./exporterr/export_err.cue:7:6
 -- expect-stdout --
 -- exporterr/export_err.cue --
 package exporterr
 
-a: {
-	b: [0, 1, {c: int}, 3]
-}
+a: b: [0, 1, {c: int}, 3]
+
+// Issue #553
+b: c: "hello"
+out: "d is \(b.d)"
 -- exporterr/cue.mod --
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 7a28c25..6914985 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -143,11 +143,23 @@
 // 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 {
-	return &posError{
-		pos:     p,
-		Message: NewMessage(format, args),
-		err:     err,
+	a, ok := err.(list)
+	if !ok {
+		return &posError{
+			pos:     p,
+			Message: NewMessage(format, args),
+			err:     err,
+		}
 	}
+	b := make([]Error, len(a))
+	for i, err := range a {
+		b[i] = &posError{
+			pos:     p,
+			Message: NewMessage(format, args),
+			err:     err,
+		}
+	}
+	return list(b)
 }
 
 // Promote converts a regular Go error to an Error if it isn't already one.
diff --git a/cue/testdata/interpolation/incomplete.txtar b/cue/testdata/interpolation/incomplete.txtar
new file mode 100644
index 0000000..29a5343
--- /dev/null
+++ b/cue/testdata/interpolation/incomplete.txtar
@@ -0,0 +1,47 @@
+Issue #553
+
+-- in.cue --
+a: "foo"
+b: "boo"
+commands: {
+    #c: {
+        help: "help!"
+    }
+}
+out: """
+    a is \(a)
+    b is \(b)
+
+    c is \(commands.#c.help)
+    d is \(commands.#d.help)
+    """
+-- out/eval --
+(struct){
+  a: (string){ "foo" }
+  b: (string){ "boo" }
+  commands: (struct){
+    #c: (#struct){
+      help: (string){ "help!" }
+    }
+  }
+  out: (_|_){
+    // [incomplete] out: invalid interpolation: undefined field #d:
+    //     ./in.cue:8:6
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  a: "foo"
+  b: "boo"
+  commands: {
+    #c: {
+      help: "help!"
+    }
+  }
+  out: "a is \(〈0;a〉)
+  b is \(〈0;b〉)
+  
+  c is \(〈0;commands〉.#c.help)
+  d is \(〈0;commands〉.#d.help)"
+}
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index fbb019f..b6e09ae 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -42,6 +42,16 @@
 	encFile      func(*ast.File) error
 	encValue     func(cue.Value) error
 	autoSimplify bool
+	concrete     bool
+}
+
+// IsConcrete reports whether the output is required to be concrete.
+//
+// INTERNAL ONLY: this is just to work around a problem related to issue #553
+// of catching errors ony after syntax generation, dropping line number
+// information.
+func (e *Encoder) IsConcrete() bool {
+	return e.concrete
 }
 
 func (e Encoder) Close() error {
@@ -86,6 +96,7 @@
 		if err != nil {
 			return nil, err
 		}
+		e.concrete = !fi.Incomplete
 
 		synOpts := []cue.Option{}
 		if !fi.KeepDefaults || !fi.Incomplete {
@@ -135,6 +146,7 @@
 		e.encFile = func(f *ast.File) error { return format(f.Filename, f) }
 
 	case build.JSON, build.JSONL:
+		e.concrete = true
 		d := json.NewEncoder(w)
 		d.SetIndent("", "    ")
 		d.SetEscapeHTML(cfg.EscapeHTML)
@@ -147,6 +159,7 @@
 		}
 
 	case build.YAML:
+		e.concrete = true
 		streamed := false
 		e.encValue = func(v cue.Value) error {
 			if streamed {
@@ -163,6 +176,7 @@
 		}
 
 	case build.Text:
+		e.concrete = true
 		e.encValue = func(v cue.Value) error {
 			s, err := v.String()
 			if err != nil {
@@ -197,6 +211,10 @@
 		}
 		return e.encodeFile(f, nil)
 	}
+	v := inst.Value()
+	if err := v.Validate(cue.Concrete(e.concrete)); err != nil {
+		return err
+	}
 	if e.encValue != nil {
 		return e.encValue(inst.Value())
 	}
@@ -216,6 +234,10 @@
 	if interpret != nil {
 		return e.Encode(inst)
 	}
+	v := inst.Value()
+	if err := v.Validate(cue.Concrete(e.concrete)); err != nil {
+		return err
+	}
 	return e.encValue(inst.Value())
 }