cue: track incompleteness across marshal errors

Fixes #235

Change-Id: Iafee5b2d584a79ea738e6b60ce6872b22a3c70a5
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4423
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index bdad738..e1c161e 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -18,6 +18,7 @@
 package cue
 
 import (
+	"encoding/json"
 	"fmt"
 	"io"
 	"math/big"
@@ -289,6 +290,10 @@
 		case nil:
 		case *callError:
 			ret = err.b
+		case *json.MarshalerError:
+			if err, ok := err.Err.(*marshalError); ok && err.b != nil {
+				ret = err.b
+			}
 		default:
 			// TODO: store the underlying error explicitly
 			ret = ctx.mkErr(src, x, "error in call to %s: %v", x.name(ctx), err)
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 3733af9..6637245 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"flag"
+	"strings"
 	"testing"
 )
 
@@ -2886,17 +2887,30 @@
 			`B :: <11>C{foo: string, a: <12>C{foo: <13>C{[string]: <14>(_: string)-><15>.foo, }}}, ` +
 			`b: <16>C{foo: "key", a: <17>C{foo: <18>C{["key"]: <19>(_: string)-><20>.foo, }}}` +
 			`}`,
+	}, {
+		desc: "json Marshaling detects incomplete",
+		in: `
+		import "encoding/json"
+		a: json.Marshal({ a: string} )
+
+		foo: { a: 3, b: foo.c }
+		b: json.Marshal(foo)
+		`,
+		out: `<0>{` +
+			`a: <1>.Marshal (<2>{a: string}), ` +
+			`foo: <3>{a: 3, b: <4>.foo.c}, ` +
+			`b: <1>.Marshal (<4>.foo)}`,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
 
 func TestX(t *testing.T) {
-	t.Skip()
-
 	// Don't remove. For debugging.
-	testCases := []testCase{{
-		in: `
-		`,
-	}}
-	rewriteHelper(t, testCases, evalFull)
+	in := `
+	`
+
+	if strings.TrimSpace(in) == "" {
+		t.Skip()
+	}
+	rewriteHelper(t, []testCase{{in: in}}, evalFull)
 }
diff --git a/cue/types.go b/cue/types.go
index 9976180..02db68c 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -205,14 +205,15 @@
 
 type marshalError struct {
 	err errors.Error
+	b   *bottom
 }
 
 func toMarshalErr(v Value, b *bottom) error {
-	return &marshalError{v.toErr(b)}
+	return &marshalError{v.toErr(b), b}
 }
 
-func marshalErrf(v Value, src source, msg string, args ...interface{}) error {
-	arguments := append([]interface{}{msg}, args...)
+func marshalErrf(v Value, src source, code errCode, msg string, args ...interface{}) error {
+	arguments := append([]interface{}{code, msg}, args...)
 	b := v.idx.mkErr(src, arguments...)
 	return toMarshalErr(v, b)
 }
@@ -240,9 +241,9 @@
 	case *marshalError:
 		return x
 	case errors.Error:
-		return &marshalError{x}
+		return &marshalError{x, nil}
 	default:
-		return &marshalError{errors.Wrapf(err, token.NoPos, "json error")}
+		return &marshalError{errors.Wrapf(err, token.NoPos, "json error"), nil}
 	}
 }
 
@@ -805,12 +806,12 @@
 		return nil, toMarshalErr(v, x.(*bottom))
 	default:
 		if k.hasReferences() {
-			return nil, marshalErrf(v, x, "value %q contains unresolved references", ctx.str(x))
+			return nil, marshalErrf(v, x, codeIncomplete, "value %q contains unresolved references", ctx.str(x))
 		}
 		if !k.isGround() {
-			return nil, marshalErrf(v, x, "cannot convert incomplete value %q to JSON", ctx.str(x))
+			return nil, marshalErrf(v, x, codeIncomplete, "cannot convert incomplete value %q to JSON", ctx.str(x))
 		}
-		return nil, marshalErrf(v, x, "cannot convert value %q of type %T to JSON", ctx.str(x), x)
+		return nil, marshalErrf(v, x, 0, "cannot convert value %q of type %T to JSON", ctx.str(x), x)
 	}
 }