cue: add function to convert Go type to a CUE value

Note that this is different from converting a value.

Updates #24

Change-Id: Ibb83c1e2169ee637c549d2961f54954581188503
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1788
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/go.go b/cue/go.go
index a45fbc6..34755e4 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -15,27 +15,223 @@
 package cue
 
 import (
+	"encoding"
+	"encoding/json"
 	"fmt"
 	"math/big"
 	"reflect"
 	"sort"
 	"strings"
+	"sync"
 
-	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
 	"github.com/cockroachdb/apd"
 )
 
 // This file contains functionality for converting Go to CUE.
 
+func convertValue(inst *Instance, x interface{}) Value {
+	ctx := inst.index.newContext()
+	v := convert(ctx, baseValue{}, x)
+	return newValueRoot(ctx, v)
+}
+
+func convertType(inst *Instance, x interface{}) Value {
+	ctx := inst.index.newContext()
+	v := convertGoType(inst, reflect.TypeOf(x))
+	return newValueRoot(ctx, v)
+
+}
+
+// parseTag parses a CUE expression from a cue tag.
+func parseTag(ctx *context, obj *structLit, field label, tag string) value {
+	if p := strings.Index(tag, ","); p >= 0 {
+		tag = tag[:p]
+	}
+	if tag == "" {
+		return &top{}
+	}
+	expr, err := parser.ParseExpr(ctx.index.fset, "<field:>", tag)
+	if err != nil {
+		field := ctx.labelStr(field)
+		return ctx.mkErr(baseValue{}, "invalid tag %q for field %q: %v", tag, field, err)
+	}
+	v := newVisitor(ctx.index, nil, nil, obj)
+	return v.walk(expr)
+}
+
+// TODO: should we allow mapping names in cue tags? This only seems like a good
+// idea if we ever want to allow mapping CUE to a different name than JSON.
+var tagsWithNames = []string{"json", "yaml", "protobuf"}
+
+func getName(f *reflect.StructField) string {
+	name := f.Name
+	for _, s := range tagsWithNames {
+		if tag, ok := f.Tag.Lookup(s); ok {
+			if p := strings.Index(tag, ","); p >= 0 {
+				tag = tag[:p]
+			}
+			if tag != "" {
+				name = tag
+				break
+			}
+		}
+	}
+	return name
+}
+
+// isOptional indicates whether a field should be marked as optional.
+func isOptional(f *reflect.StructField) bool {
+	isOptional := false
+	switch f.Type.Kind() {
+	case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Interface, reflect.Slice:
+		// Note: it may be confusing to distinguish between an empty slice and
+		// a nil slice. However, it is also surprizing to not be able to specify
+		// a default value for a slice. So for now we will allow it.
+		isOptional = true
+	}
+	if tag, ok := f.Tag.Lookup("cue"); ok {
+		// TODO: only if first field is not empty.
+		isOptional = false
+		for _, f := range strings.Split(tag, ",")[1:] {
+			switch f {
+			case "opt":
+				isOptional = true
+			case "req":
+				return false
+			}
+		}
+	} else if tag, ok = f.Tag.Lookup("json"); ok {
+		isOptional = false
+		for _, f := range strings.Split(tag, ",")[1:] {
+			if f == "omitempty" {
+				return true
+			}
+		}
+	}
+	return isOptional
+}
+
+// isOmitEmpty means that the zero value is interpreted as undefined.
+func isOmitEmpty(f *reflect.StructField) bool {
+	isOmitEmpty := false
+	switch f.Type.Kind() {
+	case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Interface, reflect.Slice:
+		// Note: it may be confusing to distinguish between an empty slice and
+		// a nil slice. However, it is also surprizing to not be able to specify
+		// a default value for a slice. So for now we will allow it.
+		isOmitEmpty = true
+
+	default:
+		// TODO: we can also infer omit empty if a type cannot be nil if there
+		// is a constraint that unconditionally disallows the zero value.
+	}
+	tag, ok := f.Tag.Lookup("json")
+	if ok {
+		isOmitEmpty = false
+		for _, f := range strings.Split(tag, ",")[1:] {
+			if f == "omitempty" {
+				return true
+			}
+		}
+	}
+	return isOmitEmpty
+}
+
+// parseJSON parses JSON into a CUE value. b must be valid JSON.
+func parseJSON(ctx *context, b []byte) evaluated {
+	expr, err := parser.ParseExpr(ctx.index.fset, "json", b)
+	if err != nil {
+		panic(err) // cannot happen
+	}
+	v := newVisitor(ctx.index, nil, nil, nil)
+	return v.walk(expr).evalPartial(ctx)
+}
+
+func isZero(v reflect.Value) bool {
+	x := v.Interface()
+	if x == nil {
+		return true
+	}
+	switch k := v.Kind(); k {
+	case reflect.Struct, reflect.Array:
+		// we never allow optional values for these types.
+		return false
+
+	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
+		reflect.Slice:
+		// Note that for maps we preserve the distinction between a nil map and
+		// an empty map.
+		return v.IsNil()
+
+	case reflect.String:
+		return v.Len() == 0
+
+	default:
+		return x == reflect.Zero(v.Type()).Interface()
+	}
+}
+
 func convert(ctx *context, src source, x interface{}) evaluated {
 	switch v := x.(type) {
-	case evaluated:
-		return v
 	case nil:
-		return &nullLit{src.base()}
-	case ast.Expr:
-		x := newVisitorCtx(ctx, nil, nil, nil)
-		return ctx.manifest(x.walk(v))
+		// Interpret a nil pointer as an undefined value that is only
+		// null by default, but may still be set: *null | _.
+		return &disjunction{values: []dValue{
+			{val: &nullLit{src.base()}, marked: true},
+			{val: &top{src.base()}}},
+		}
+
+	case *big.Int:
+		n := newNum(src, intKind)
+		n.v.Coeff.Set(v)
+		if v.Sign() < 0 {
+			n.v.Coeff.Neg(&n.v.Coeff)
+			n.v.Negative = true
+		}
+		return n
+
+	case *big.Rat:
+		// should we represent this as a binary operation?
+		n := newNum(src, numKind)
+		ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
+		if !v.IsInt() {
+			n.k = floatKind
+		}
+		return n
+
+	case *big.Float:
+		n := newNum(src, floatKind)
+		n.v.SetString(v.String())
+		return n
+
+	case *apd.Decimal:
+		n := newNum(src, floatKind|intKind)
+		n.v.Set(v)
+		if !n.isInt(ctx) {
+			n.k = floatKind
+		}
+		return n
+
+	case json.Marshaler:
+		b, err := v.MarshalJSON()
+		if err != nil {
+			return ctx.mkErr(src, err)
+		}
+
+		return parseJSON(ctx, b)
+
+	case encoding.TextMarshaler:
+		b, err := v.MarshalText()
+		if err != nil {
+			return ctx.mkErr(src, err)
+		}
+		b, err = json.Marshal(string(b))
+		if err != nil {
+			return ctx.mkErr(src, err)
+		}
+		return parseJSON(ctx, b)
+
 	case error:
 		return ctx.mkErr(src, v.Error())
 	case bool:
@@ -68,32 +264,7 @@
 		r := newNum(src, floatKind)
 		r.v.SetString(fmt.Sprintf("%g", v))
 		return r
-	case *big.Int:
-		n := newNum(src, intKind)
-		n.v.Coeff.Set(v)
-		if v.Sign() < 0 {
-			n.v.Coeff.Neg(&n.v.Coeff)
-			n.v.Negative = true
-		}
-		return n
-	case *big.Rat:
-		n := newNum(src, numKind)
-		ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
-		if !v.IsInt() {
-			n.k = floatKind
-		}
-		return n
-	case *big.Float:
-		n := newNum(src, floatKind)
-		n.v.SetString(v.String())
-		return n
-	case *apd.Decimal:
-		n := newNum(src, floatKind|intKind)
-		n.v.Set(v)
-		if !n.isInt(ctx) {
-			n.k = floatKind
-		}
-		return n
+
 	case reflect.Value:
 		if v.CanInterface() {
 			return convert(ctx, src, v.Interface())
@@ -104,9 +275,15 @@
 		switch value.Kind() {
 		case reflect.Ptr:
 			if value.IsNil() {
-				return &nullLit{src.base()}
+				// Interpret a nil pointer as an undefined value that is only
+				// null by default, but may still be set: *null | _.
+				return &disjunction{values: []dValue{
+					{val: &nullLit{src.base()}, marked: true},
+					{val: &top{src.base()}}},
+				}
 			}
 			return convert(ctx, src, value.Elem().Interface())
+
 		case reflect.Struct:
 			obj := newStruct(src)
 			t := value.Type()
@@ -115,20 +292,16 @@
 				if t.PkgPath != "" {
 					continue
 				}
-				sub := convert(ctx, src, value.Field(i).Interface())
+				val := value.Field(i)
+				if isOmitEmpty(&t) && isZero(val) {
+					continue
+				}
+				sub := convert(ctx, src, val.Interface())
 				// leave errors like we do during normal evaluation or do we
 				// want to return the error?
-				name := t.Name
-				for _, s := range []string{"cue", "json", "protobuf"} {
-					if tag, ok := t.Tag.Lookup(s); ok {
-						if p := strings.Index(tag, ","); p >= 0 {
-							tag = tag[:p]
-						}
-						if tag != "" {
-							name = tag
-							break
-						}
-					}
+				name := getName(&t)
+				if name == "-" {
+					continue
 				}
 				f := ctx.strLabel(name)
 				obj.arcs = append(obj.arcs, arc{feature: f, v: sub})
@@ -187,3 +360,191 @@
 	n.v.Coeff.SetUint64(x)
 	return n
 }
+
+var (
+	typeCache sync.Map // map[reflect.Type]evaluated
+	mutex     sync.Mutex
+)
+
+func convertGoType(inst *Instance, t reflect.Type) value {
+	ctx := inst.newContext()
+	// TODO: this can be much more efficient.
+	mutex.Lock()
+	defer mutex.Unlock()
+	return goTypeToValue(ctx, t)
+}
+
+var (
+	jsonMarshaler = reflect.TypeOf(new(json.Marshaler)).Elem()
+	textMarshaler = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
+	topSentinel   = &top{}
+)
+
+// goTypeToValue converts a Go Type to a value.
+//
+// TODO: if this value will always be unified with a concrete type in Go, then
+// many of the fields may be omitted.
+func goTypeToValue(ctx *context, t reflect.Type) (e value) {
+	if e, ok := typeCache.Load(t); ok {
+		return e.(value)
+	}
+
+	// Even if this is for types that we know cast to a certain type, it can't
+	// hurt to return top, as in these cases the concrete values will be
+	// strict instances and there cannot be any tags that further constrain
+	// the values.
+	if t.Implements(jsonMarshaler) || t.Implements(textMarshaler) {
+		return topSentinel
+	}
+
+	switch k := t.Kind(); k {
+	case reflect.Ptr:
+		elem := t.Elem()
+		for elem.Kind() == reflect.Ptr {
+			elem = elem.Elem()
+		}
+		e = wrapOrNull(goTypeToValue(ctx, elem))
+
+	case reflect.Interface:
+		switch t.Name() {
+		case "error":
+			// This is really null | _|_. There is no error if the error is null.
+			e = &nullLit{} // null
+		default:
+			e = topSentinel // `_`
+		}
+
+	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		e = predefinedRanges[t.Kind().String()]
+
+	case reflect.Uint, reflect.Uintptr:
+		e = predefinedRanges["uint64"]
+
+	case reflect.Int:
+		e = predefinedRanges["int64"]
+
+	case reflect.String:
+		e = &basicType{k: stringKind}
+
+	case reflect.Bool:
+		e = &basicType{k: boolKind}
+
+	case reflect.Float32, reflect.Float64:
+		e = &basicType{k: floatKind}
+
+	case reflect.Struct:
+		// Some of these values have MarshalJSON methods, but that may not be
+		// the case for older Go versions.
+		name := fmt.Sprint(t)
+		switch name {
+		case "big.Int":
+			e = &basicType{k: intKind}
+			goto store
+		case "big.Rat", "big.Float", "apd.Decimal":
+			e = &basicType{k: floatKind}
+			goto store
+		case "time.Time":
+			// We let the concrete value decide.
+			e = topSentinel
+			goto store
+		}
+
+		// First iterate to create struct, then iterate another time to
+		// resolve field tags to allow field tags to refer to the struct fields.
+		tags := map[label]string{}
+		obj := newStruct(baseValue{})
+		typeCache.Store(t, obj)
+
+		for i := 0; i < t.NumField(); i++ {
+			f := t.Field(i)
+			if f.PkgPath != "" {
+				continue
+			}
+			elem := goTypeToValue(ctx, f.Type)
+
+			// leave errors like we do during normal evaluation or do we
+			// want to return the error?
+			name := getName(&f)
+			if name == "-" {
+				continue
+			}
+			l := ctx.strLabel(name)
+			obj.arcs = append(obj.arcs, arc{
+				feature: l,
+				// The GO JSON decoder always allows a value to be undefined.
+				optional: isOptional(&f),
+				v:        elem,
+			})
+
+			if tag, ok := f.Tag.Lookup("cue"); ok {
+				tags[l] = tag
+			}
+		}
+		sort.Sort(obj)
+
+		for label, tag := range tags {
+			v := parseTag(ctx, obj, label, tag)
+			for i, a := range obj.arcs {
+				if a.feature == label {
+					// Instead of unifying with the existing type, we substitute
+					// with the constraints from the tags. The type constraints
+					// will be implied when unified with a concrete value.
+					obj.arcs[i].v = mkBin(ctx, 0, opUnify, a.v, v)
+					// obj.arcs[i].v = v
+				}
+			}
+		}
+
+		return obj
+
+	case reflect.Array, reflect.Slice:
+		if t.Elem().Kind() == reflect.Uint8 {
+			e = &basicType{k: bytesKind}
+		} else {
+			elem := goTypeToValue(ctx, t.Elem())
+
+			var ln value = &top{}
+			if t.Kind() == reflect.Array {
+				ln = toInt(ctx, baseValue{}, int64(t.Len()))
+			}
+			e = &list{typ: elem, len: ln}
+		}
+		if k == reflect.Slice {
+			e = wrapOrNull(e)
+		}
+
+	case reflect.Map:
+		if key := t.Key(); key.Kind() != reflect.String {
+			// What does the JSON library do here?
+			e = ctx.mkErr(baseValue{}, "type %v not supported as key type", key)
+			break
+		}
+
+		obj := newStruct(baseValue{})
+		sig := &params{}
+		sig.add(ctx.label("_", true), &basicType{k: stringKind})
+		v := goTypeToValue(ctx, t.Elem())
+		obj.template = &lambdaExpr{params: sig, value: v}
+
+		e = wrapOrNull(obj)
+	}
+
+store:
+	// TODO: store error if not nil?
+	if e != nil {
+		typeCache.Store(t, e)
+	}
+	return e
+}
+
+func wrapOrNull(e value) value {
+	if e.kind().isAnyOf(nullKind) {
+		return e
+	}
+	e = &disjunction{values: []dValue{
+		{val: &nullLit{}, marked: true},
+		{val: e}},
+	}
+	return e
+}
diff --git a/cue/go_test.go b/cue/go_test.go
index 88895c3..a2f1d92 100644
--- a/cue/go_test.go
+++ b/cue/go_test.go
@@ -15,24 +15,25 @@
 package cue
 
 import (
-	"go/ast"
 	"math/big"
 	"reflect"
 	"testing"
 	"time"
 
+	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/errors"
 )
 
 func TestConvert(t *testing.T) {
 	i34 := big.NewInt(34)
 	d34 := mkBigInt(34)
+	n34 := mkBigInt(-34)
 	f34 := big.NewFloat(34.0000)
 	testCases := []struct {
 		goVal interface{}
 		want  string
 	}{{
-		nil, "null",
+		nil, "(*null | _)",
 	}, {
 		true, "true",
 	}, {
@@ -70,6 +71,8 @@
 	}, {
 		&d34, "34",
 	}, {
+		&n34, "-34",
+	}, {
 		[]int{1, 2, 3, 4}, "[1,2,3,4]",
 	}, {
 		[]interface{}{}, "[]",
@@ -109,7 +112,7 @@
 	}, {
 		&struct{ A int }{3}, "<0>{A: 3}",
 	}, {
-		(*struct{ A int })(nil), "null",
+		(*struct{ A int })(nil), "(*null | _)",
 	}, {
 		reflect.ValueOf(3), "3",
 	}, {
@@ -128,3 +131,78 @@
 		})
 	}
 }
+
+func TestConvertType(t *testing.T) {
+	testCases := []struct {
+		goTyp interface{}
+		want  string
+	}{{
+		struct {
+			A int      `cue:">=0&<100"`
+			B *big.Int `cue:">=0"`
+			C *big.Int
+			D big.Int
+			F *big.Float
+		}{},
+		// TODO: indicate that B is explicitly an int only.
+		`<0>{A: ((>=-9223372036854775808 & <=9223372036854775807) & (>=0 & <100)), ` +
+			`B: >=0, ` +
+			`C?: _, ` +
+			`D: int, ` +
+			`F?: _}`,
+	}, {
+		&struct {
+			A int16 `cue:">=0&<100"`
+			B error `json:"b"`
+			C string
+			D bool
+			F float64
+			L []byte
+			T time.Time
+		}{},
+		`(*null | <0>{A: ((>=-32768 & <=32767) & (>=0 & <100)), ` +
+			`C: string, ` +
+			`D: bool, ` +
+			`F: float, ` +
+			`b: null, ` +
+			`L?: (*null | bytes), ` +
+			`T: _})`,
+	}, {
+		struct {
+			A int8 `cue:"C-B"`
+			B int8 `cue:"C-A,opt"`
+			C int8 `cue:"A+B"`
+		}{},
+		// TODO: should B be marked as optional?
+		`<0>{A: ((>=-128 & <=127) & (<0>.C - <0>.B)), ` +
+			`B?: ((>=-128 & <=127) & (<0>.C - <0>.A)), ` +
+			`C: ((>=-128 & <=127) & (<0>.A + <0>.B))}`,
+	}, {
+		[]string{},
+		`(*null | [, ...string])`,
+	}, {
+		[4]string{},
+		`4*[string]`,
+	}, {
+		map[string]struct{ A map[string]uint }{},
+		`(*null | ` +
+			`<0>{<>: <1>(_: string)-><2>{` +
+			`A?: (*null | ` +
+			`<3>{<>: <4>(_: string)->(>=0 & <=18446744073709551615), })}, })`,
+	}, {
+		map[float32]int{},
+		`_|_(type float32 not supported as key type)`,
+	}}
+	inst := getInstance(t, "foo")
+
+	for _, tc := range testCases {
+		ctx := inst.newContext()
+		t.Run("", func(t *testing.T) {
+			v := goTypeToValue(ctx, reflect.TypeOf(tc.goTyp))
+			got := debugStr(ctx, v)
+			if got != tc.want {
+				t.Errorf("\n got %q;\nwant %q", got, tc.want)
+			}
+		})
+	}
+}