cue: better document Context.Encode and friends

The behavior has been slightly modified to be more in line
with Go's JSON encoding, where it makes sense.

Only `FillPath` is documented as `Fill` is deprecated.
The main docuemntation is at Context.Encode.

Fixes #676

Change-Id: I1d885cfbe655a41064a37b82a98ed66d3865a61e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9569
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/cue/context.go b/cue/context.go
index 3257fb7..e84f4fb 100644
--- a/cue/context.go
+++ b/cue/context.go
@@ -211,6 +211,86 @@
 //
 // The returned Value will represent an error, accessible through Err, if any
 // error occurred.
+//
+// Encode traverses the value v recursively. If an encountered value implements
+// the json.Marshaler interface and is not a nil pointer, Encode calls its
+// MarshalJSON method to produce JSON and convert that to CUE instead. If no
+// MarshalJSON method is present but the value implements encoding.TextMarshaler
+// instead, Encode calls its MarshalText method and encodes the result as a
+// string.
+//
+// Otherwise, Encode uses the following type-dependent default encodings:
+//
+// Boolean values encode as CUE booleans.
+//
+// Floating point, integer, and *big.Int and *big.Float values encode as CUE
+// numbers.
+//
+// String values encode as CUE strings coerced to valid UTF-8, replacing
+// sequences of invalid bytes with the Unicode replacement rune as per Unicode's
+// and W3C's recommendation.
+//
+// Array and slice values encode as CUE lists, except that []byte encodes as a
+// bytes value, and a nil slice encodes as the null.
+//
+// Struct values encode as CUE structs. Each exported struct field becomes a
+// member of the object, using the field name as the object key, unless the
+// field is omitted for one of the reasons given below.
+//
+// The encoding of each struct field can be customized by the format string
+// stored under the "json" key in the struct field's tag. The format string
+// gives the name of the field, possibly followed by a comma-separated list of
+// options. The name may be empty in order to specify options without overriding
+// the default field name.
+//
+// The "omitempty" option specifies that the field should be omitted from the
+// encoding if the field has an empty value, defined as false, 0, a nil pointer,
+// a nil interface value, and any empty array, slice, map, or string.
+//
+// See the documentation for Go's json.Marshal for more details on the field
+// tags and their meaning.
+//
+// Anonymous struct fields are usually encoded as if their inner exported
+// fields were fields in the outer struct, subject to the usual Go visibility
+// rules amended as described in the next paragraph. An anonymous struct field
+// with a name given in its JSON tag is treated as having that name, rather than
+// being anonymous. An anonymous struct field of interface type is treated the
+// same as having that type as its name, rather than being anonymous.
+//
+// The Go visibility rules for struct fields are amended for when deciding which
+// field to encode or decode. If there are multiple fields at the same level,
+// and that level is the least nested (and would therefore be the nesting level
+// selected by the usual Go rules), the following extra rules apply:
+//
+// 1) Of those fields, if any are JSON-tagged, only tagged fields are
+// considered, even if there are multiple untagged fields that would otherwise
+// conflict.
+//
+// 2) If there is exactly one field (tagged or not according to the first rule),
+// that is selected.
+//
+// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
+//
+// Map values encode as CUE structs. The map's key type must either be a string,
+// an integer type, or implement encoding.TextMarshaler. The map keys are sorted
+// and used as CUE struct field names by applying the following rules, subject
+// to the UTF-8 coercion described for string values above:
+//
+//  - keys of any string type are used directly
+//  - encoding.TextMarshalers are marshaled
+//  - integer keys are converted to strings
+//
+// Pointer values encode as the value pointed to. A nil pointer encodes as the
+// null CUE value.
+//
+// Interface values encode as the value contained in the interface. A nil
+// interface value encodes as the null CUE value. The NilIsAny EncodingOption
+// can be used to interpret nil as any (_) instead.
+//
+// Channel, complex, and function values cannot be encoded in CUE. Attempting to
+// encode such a value results in the returned value being an error, accessible
+// through the Err method.
+//
 func (c *Context) Encode(x interface{}, option ...EncodeOption) Value {
 	switch v := x.(type) {
 	case adt.Value:
diff --git a/cue/types.go b/cue/types.go
index 9e7aa70..a0c5aa5 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1585,7 +1585,8 @@
 // If x is a Value, it will be used as is. It panics if x is not created
 // from the same Runtime as v.
 //
-// Otherwise, the given Go value will be converted to CUE.
+// Otherwise, the given Go value will be converted to CUE using the same rules
+// as Context.Encode.
 //
 // Any reference in v referring to the value at the given path will resolve to x
 // in the newly created value. The resulting value is not validated.
@@ -1606,8 +1607,6 @@
 			panic("values are not from the same runtime")
 		}
 		expr = x.v
-	case adt.Node, adt.Feature:
-		panic("cannot set internal Value or Feature type")
 	case ast.Expr:
 		n := getScopePrefix(v, p)
 		expr = resolveExpr(ctx, n, x)
diff --git a/cue/types_test.go b/cue/types_test.go
index 1751b94..e949336 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1194,6 +1194,40 @@
 	}
 }
 
+func TestFillPathError(t *testing.T) {
+	r := &Runtime{}
+
+	type key struct{ a int }
+
+	testCases := []struct {
+		in   string
+		x    interface{}
+		path Path
+		err  string
+	}{{
+		// unsupported type.
+		in:  `_`,
+		x:   make(chan int),
+		err: "unsupported Go type (chan int)",
+	}}
+
+	for _, tc := range testCases {
+		t.Run("", func(t *testing.T) {
+			v := compileT(t, r, tc.in).Value()
+			v = v.FillPath(tc.path, tc.x)
+
+			err := v.Err()
+			if err == nil {
+				t.Errorf("unexpected success")
+			}
+
+			if got := err.Error(); !strings.Contains(got, tc.err) {
+				t.Errorf("\ngot:  %s\nwant: %s", got, tc.err)
+			}
+		})
+	}
+}
+
 func TestAllows(t *testing.T) {
 	r := &Runtime{}
 
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index c6bd023..afbe67c 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -438,7 +438,12 @@
 // func (v *Vertex) Evaluate()
 
 func (v *Vertex) Finalize(c *OpContext) {
+	// Saving and restoring the error context prevents v from panicking in
+	// case the caller did not handle existing errors in the context.
+	err := c.errs
+	c.errs = nil
 	c.Unify(v, Finalized)
+	c.errs = err
 }
 
 func (v *Vertex) AddErr(ctx *OpContext, b *Bottom) {
diff --git a/internal/core/convert/go.go b/internal/core/convert/go.go
index 5c4d646..b6cc20d 100644
--- a/internal/core/convert/go.go
+++ b/internal/core/convert/go.go
@@ -24,9 +24,9 @@
 	"sort"
 	"strconv"
 	"strings"
-	"unicode/utf8"
 
 	"github.com/cockroachdb/apd/v2"
+	"golang.org/x/text/encoding/unicode"
 
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/ast/astutil"
@@ -209,7 +209,7 @@
 func GoValueToExpr(ctx *adt.OpContext, nilIsTop bool, x interface{}) adt.Expr {
 	e := convertRec(ctx, nilIsTop, x)
 	if e == nil {
-		return ctx.AddErrf("unsupported Go type (%v)", e)
+		return ctx.AddErrf("unsupported Go type (%T)", x)
 	}
 	return e
 }
@@ -321,10 +321,8 @@
 	case bool:
 		return &adt.Bool{Src: ctx.Source(), B: v}
 	case string:
-		if !utf8.ValidString(v) {
-			return ctx.AddErrf("cannot convert result to string: invalid UTF-8")
-		}
-		return &adt.String{Src: ctx.Source(), Str: v}
+		s, _ := unicode.UTF8.NewEncoder().String(v)
+		return &adt.String{Src: ctx.Source(), Str: s}
 	case []byte:
 		return &adt.Bytes{Src: ctx.Source(), B: v}
 	case int:
@@ -377,9 +375,11 @@
 
 		case reflect.String:
 			str := value.String()
-			if !utf8.ValidString(str) {
-				return ctx.AddErrf("cannot convert result to string: invalid UTF-8")
-			}
+			str, _ = unicode.UTF8.NewEncoder().String(str)
+			// TODO: here and above: allow to fail on invalid strings.
+			// if !utf8.ValidString(str) {
+			// 	return ctx.AddErrf("cannot convert result to string: invalid UTF-8")
+			// }
 			return &adt.String{Src: ctx.Source(), Str: str}
 
 		case reflect.Int, reflect.Int8, reflect.Int16,
@@ -475,11 +475,17 @@
 
 			t := value.Type()
 			switch key := t.Key(); key.Kind() {
+			default:
+				if !key.Implements(textMarshaler) {
+					return ctx.AddErrf("unsupported Go type for map key (%v)", key)
+				}
+				fallthrough
 			case reflect.String,
 				reflect.Int, reflect.Int8, reflect.Int16,
 				reflect.Int32, reflect.Int64,
 				reflect.Uint, reflect.Uint8, reflect.Uint16,
 				reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+
 				keys := value.MapKeys()
 				sort.Slice(keys, func(i, j int) bool {
 					return fmt.Sprint(keys[i]) < fmt.Sprint(keys[j])
@@ -494,7 +500,7 @@
 					// mimic behavior of encoding/json: report error of
 					// unsupported type.
 					if sub == nil {
-						return ctx.AddErrf("unsupported Go type (%v)", val)
+						return ctx.AddErrf("unsupported Go type (%T)", val.Interface())
 					}
 					if isBottom(sub) {
 						return sub
@@ -514,9 +520,6 @@
 					}
 					v.Arcs = append(v.Arcs, arc)
 				}
-
-			default:
-				return ctx.AddErrf("unsupported Go type for map key (%v)", key)
 			}
 
 			return v
@@ -528,7 +531,8 @@
 				val := value.Index(i)
 				x := convertRec(ctx, nilIsTop, val.Interface())
 				if x == nil {
-					return ctx.AddErrf("unsupported Go type (%v)", val)
+					return ctx.AddErrf("unsupported Go type (%T)",
+						val.Interface())
 				}
 				if isBottom(x) {
 					return x
diff --git a/internal/core/convert/go_test.go b/internal/core/convert/go_test.go
index 9d3a35f..0b46873 100644
--- a/internal/core/convert/go_test.go
+++ b/internal/core/convert/go_test.go
@@ -14,7 +14,10 @@
 
 package convert_test
 
+// TODO: generate tests from Go's json encoder.
+
 import (
+	"encoding"
 	"math/big"
 	"reflect"
 	"testing"
@@ -32,7 +35,21 @@
 
 func mkBigInt(a int64) (v apd.Decimal) { v.SetInt64(a); return }
 
+type textMarshaller struct {
+	b string
+}
+
+func (t *textMarshaller) MarshalText() (b []byte, err error) {
+	return []byte(t.b), nil
+}
+
+var _ encoding.TextMarshaler = &textMarshaller{}
+
 func TestConvert(t *testing.T) {
+	type key struct {
+		a int
+	}
+	type stringType string
 	i34 := big.NewInt(34)
 	d35 := mkBigInt(35)
 	n36 := mkBigInt(-36)
@@ -51,7 +68,7 @@
 	}, {
 		"foo", `(string){ "foo" }`,
 	}, {
-		"\x80", "(_|_){\n  // [eval] cannot convert result to string: invalid UTF-8\n}",
+		"\x80", `(string){ "�" }`,
 	}, {
 		3, "(int){ 3 }",
 	}, {
@@ -198,7 +215,21 @@
   A: (string){ "" }
   B: (int){ 0 }
 }`,
-	}}
+	},
+		{map[key]string{{a: 1}: "foo"},
+			"(_|_){\n  // [eval] unsupported Go type for map key (convert_test.key)\n}"},
+		{map[*textMarshaller]string{{b: "bar"}: "foo"},
+			"(struct){\n  \"&{bar}\": (string){ \"foo\" }\n}"},
+		{map[int]string{1: "foo"},
+			"(struct){\n  \"1\": (string){ \"foo\" }\n}"},
+		{map[string]encoding.TextMarshaler{"foo": nil},
+			"(struct){\n  foo: (_){ _ }\n}"},
+		{make(chan int),
+			"(_|_){\n  // [eval] unsupported Go type (chan int)\n}"},
+		{[]interface{}{func() {}},
+			"(_|_){\n  // [eval] unsupported Go type (func())\n}"},
+		{stringType("\x80"), `(string){ "�" }`},
+	}
 	r := runtime.New()
 	for _, tc := range testCases {
 		ctx := adt.NewContext(r, &adt.Vertex{})