internal/core/convert: initial commit

Change-Id: Ifc51d85dbef3995ae198cf092058ce3010412681
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6505
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/compile/erralias.txtar b/cue/testdata/compile/erralias.txtar
index 985a0c1..c30c1a9 100644
--- a/cue/testdata/compile/erralias.txtar
+++ b/cue/testdata/compile/erralias.txtar
@@ -10,6 +10,8 @@
 c: {}
 for x in c { a: E }
 
+Z1=[Z2=string]: Z1+Z2
+
 -- out/compile --
 unreferenced alias or let clause X:
     ./in.cue:1:1
@@ -29,4 +31,5 @@
   for _, x in 〈0;c〉 {
     a: _|_(reference "E" not found)
   }
+  [string]: (〈0;(〈0;-〉)〉 + 〈0;-〉)
 }
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 3718304..a9d37e3 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"reflect"
 	"regexp"
 
 	"github.com/cockroachdb/apd/v2"
@@ -64,6 +65,13 @@
 	// LoadImport loads a unique Vertex associated with a given import path. It
 	// returns an error if no import for this package could be found.
 	LoadImport(importPath string) (*Vertex, errors.Error)
+
+	// StoreType associates a CUE expression with a Go type.
+	StoreType(t reflect.Type, src ast.Expr, expr Expr)
+
+	// LoadType retrieves a previously stored CUE expression for a given Go
+	// type if available.
+	LoadType(t reflect.Type) (src ast.Expr, expr Expr, ok bool)
 }
 
 type Config struct {
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
index f965240..ad1ff56 100644
--- a/internal/core/compile/compile.go
+++ b/internal/core/compile/compile.go
@@ -29,6 +29,12 @@
 
 // Config configures a compilation.
 type Config struct {
+	// Scope specifies a node in which to look up unresolved references. This
+	// is useful for evaluating expressions within an already evaluated
+	// configuration.
+	//
+	// TODO
+	Scope *adt.Vertex
 }
 
 // Files compiles the given files as a single instance. It disregards
@@ -47,6 +53,23 @@
 	return v, nil
 }
 
+func Expr(cfg *Config, r adt.Runtime, x ast.Expr) (adt.Conjunct, errors.Error) {
+	if cfg == nil {
+		cfg = &Config{}
+	}
+	c := &compiler{
+		Config: *cfg,
+		index:  r,
+	}
+
+	v := c.compileExpr(x)
+
+	if c.errs != nil {
+		return v, c.errs
+	}
+	return v, nil
+}
+
 func newCompiler(cfg *Config, r adt.Runtime) *compiler {
 	c := &compiler{
 		index: r,
@@ -204,8 +227,11 @@
 	return res
 }
 
-func (c *compiler) compileExpr(x ast.Expr) adt.Expr {
-	return c.expr(x)
+func (c *compiler) compileExpr(x ast.Expr) adt.Conjunct {
+	expr := c.expr(x)
+
+	env := &adt.Environment{}
+	return adt.MakeConjunct(env, expr)
 }
 
 // resolve assumes that all existing resolutions are legal. Validation should
@@ -379,7 +405,7 @@
 			e := aliasEntry{source: a}
 
 			switch lab.(type) {
-			case *ast.Ident, *ast.BasicLit:
+			case *ast.Ident, *ast.BasicLit, *ast.ListLit:
 				// Even though we won't need the alias, we still register it
 				// for duplicate and failed reference detection.
 			default:
@@ -425,10 +451,12 @@
 				// error
 				return c.errf(x, "list label must have one element")
 			}
+			var label adt.Feature
 			elem := l.Elts[0]
 			// TODO: record alias for error handling? In principle it is okay
 			// to have duplicates, but we do want it to be used.
 			if a, ok := elem.(*ast.Alias); ok {
+				label = c.label(a.Ident)
 				elem = a.Expr
 			}
 
@@ -436,6 +464,7 @@
 				Src:    x,
 				Filter: c.expr(elem),
 				Value:  value,
+				Label:  label,
 			}
 
 		case *ast.Interpolation: // *ast.ParenExpr,
diff --git a/internal/core/convert/go.go b/internal/core/convert/go.go
new file mode 100644
index 0000000..98f168e
--- /dev/null
+++ b/internal/core/convert/go.go
@@ -0,0 +1,769 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package convert allows converting to and from Go values and Types.
+package convert
+
+import (
+	"encoding"
+	"encoding/json"
+	"fmt"
+	"math/big"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+	"unicode/utf8"
+
+	"github.com/cockroachdb/apd/v2"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/ast/astutil"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/internal/core/compile"
+)
+
+// This file contains functionality for converting Go to CUE.
+//
+// The code in this file is a prototype implementation and is far from
+// optimized.
+
+func GoValueToValue(ctx *adt.OpContext, x interface{}, nilIsTop bool) adt.Value {
+	v := GoValueToExpr(ctx, nilIsTop, x)
+	// TODO: return Value
+	return toValue(v)
+}
+
+func GoTypeToExpr(ctx *adt.OpContext, x interface{}) (adt.Expr, errors.Error) {
+	v := convertGoType(ctx, reflect.TypeOf(x))
+	if err := ctx.Err(); err != nil {
+		return v, err.Err
+	}
+	return v, nil
+}
+
+func toValue(e adt.Expr) adt.Value {
+	if v, ok := e.(adt.Value); ok {
+		return v
+	}
+	obj := &adt.Vertex{}
+	obj.AddConjunct(adt.MakeConjunct(nil, e))
+	return obj
+}
+
+func compileExpr(ctx *adt.OpContext, expr ast.Expr) adt.Value {
+	c, err := compile.Expr(nil, ctx, expr)
+	if err != nil {
+		return &adt.Bottom{Err: errors.Promote(err, "compile")}
+	}
+	return adt.Resolve(ctx, c)
+}
+
+// parseTag parses a CUE expression from a cue tag.
+func parseTag(ctx *adt.OpContext, obj *ast.StructLit, field, tag string) ast.Expr {
+	if p := strings.Index(tag, ","); p >= 0 {
+		tag = tag[:p]
+	}
+	if tag == "" {
+		return topSentinel
+	}
+	expr, err := parser.ParseExpr("<field:>", tag)
+	if err != nil {
+		err := errors.Promote(err, "parser")
+		ctx.AddErr(errors.Wrapf(err, ctx.Pos(),
+			"invalid tag %q for field %q", tag, field))
+		return &ast.BadExpr{}
+	}
+	return 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 *adt.OpContext, b []byte) adt.Value {
+	expr, err := parser.ParseExpr("json", b)
+	if err != nil {
+		panic(err) // cannot happen
+	}
+	return compileExpr(ctx, expr)
+}
+
+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 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 e
+}
+
+func isNil(x reflect.Value) bool {
+	switch x.Kind() {
+	// Only check for supported types; ignore func and chan.
+	case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface:
+		return x.IsNil()
+	}
+	return false
+}
+
+func convertRec(ctx *adt.OpContext, nilIsTop bool, x interface{}) adt.Value {
+	src := ctx.Source()
+	switch v := x.(type) {
+	case nil:
+		if nilIsTop {
+			ident, _ := ctx.Source().(*ast.Ident)
+			return &adt.Top{Src: ident}
+		}
+		return &adt.Null{Src: ctx.Source()}
+
+	case *ast.File:
+		x, err := compile.Files(nil, ctx, v)
+		if err != nil {
+			return &adt.Bottom{Err: errors.Promote(err, "compile")}
+		}
+		if len(x.Conjuncts) != 1 {
+			panic("unexpected length")
+		}
+		return x
+
+	case ast.Expr:
+		return compileExpr(ctx, v)
+
+	case *big.Int:
+		return &adt.Num{Src: src, K: adt.IntKind, X: *apd.NewWithBigInt(v, 0)}
+
+	case *big.Rat:
+		// should we represent this as a binary operation?
+		n := &adt.Num{Src: src, K: adt.IntKind}
+		_, err := apd.BaseContext.Quo(&n.X, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
+		if err != nil {
+			return ctx.AddErrf("could not convert *big.Rat: %v", err)
+		}
+		if !v.IsInt() {
+			n.K = adt.FloatKind
+		}
+		return n
+
+	case *big.Float:
+		n := &adt.Num{Src: src, K: adt.FloatKind}
+		_, _, err := n.X.SetString(v.String())
+		ctx.AddErr(errors.Promote(err, "invalid float"))
+		return n
+
+	case *apd.Decimal:
+		// TODO: set num or float based on value of number.
+		n := &adt.Num{Src: ctx.Source(), K: adt.NumKind}
+		n.X = *v
+		return n
+
+	case json.Marshaler:
+		b, err := v.MarshalJSON()
+		if err != nil {
+			return ctx.AddErr(errors.Promote(err, "json.Marshaler"))
+		}
+
+		return parseJSON(ctx, b)
+
+	case encoding.TextMarshaler:
+		b, err := v.MarshalText()
+		if err != nil {
+			return ctx.AddErr(errors.Promote(err, "encoding.TextMarshaler"))
+		}
+		b, err = json.Marshal(string(b))
+		if err != nil {
+			return ctx.AddErr(errors.Promote(err, "json"))
+		}
+		return parseJSON(ctx, b)
+
+	case error:
+		var errs errors.Error
+		switch x := v.(type) {
+		case errors.Error:
+			errs = x
+		default:
+			errs = errors.Newf(token.NoPos, "%s", x.Error())
+		}
+		return &adt.Bottom{Err: errs}
+	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}
+	case []byte:
+		return &adt.Bytes{Src: ctx.Source(), B: v}
+	case int:
+		return toInt(ctx, int64(v))
+	case int8:
+		return toInt(ctx, int64(v))
+	case int16:
+		return toInt(ctx, int64(v))
+	case int32:
+		return toInt(ctx, int64(v))
+	case int64:
+		return toInt(ctx, int64(v))
+	case uint:
+		return toUint(ctx, uint64(v))
+	case uint8:
+		return toUint(ctx, uint64(v))
+	case uint16:
+		return toUint(ctx, uint64(v))
+	case uint32:
+		return toUint(ctx, uint64(v))
+	case uint64:
+		return toUint(ctx, uint64(v))
+	case uintptr:
+		return toUint(ctx, uint64(v))
+	case float64:
+		n := &adt.Num{Src: src, K: adt.FloatKind}
+		_, _, err := n.X.SetString(fmt.Sprintf("%g", v))
+		ctx.AddErr(errors.Promote(err, "invalid float"))
+		return n
+	case float32:
+		n := &adt.Num{Src: src, K: adt.FloatKind}
+		_, _, err := n.X.SetString(fmt.Sprintf("%g", v))
+		ctx.AddErr(errors.Promote(err, "invalid float"))
+		return n
+
+	case reflect.Value:
+		if v.CanInterface() {
+			return convertRec(ctx, nilIsTop, v.Interface())
+		}
+
+	default:
+		value := reflect.ValueOf(v)
+		switch value.Kind() {
+		case reflect.Bool:
+			return &adt.Bool{Src: ctx.Source(), B: value.Bool()}
+
+		case reflect.String:
+			str := value.String()
+			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,
+			reflect.Int32, reflect.Int64:
+			return toInt(ctx, value.Int())
+
+		case reflect.Uint, reflect.Uint8, reflect.Uint16,
+			reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+			return toUint(ctx, value.Uint())
+
+		case reflect.Float32, reflect.Float64:
+			return convertRec(ctx, nilIsTop, value.Float())
+
+		case reflect.Ptr:
+			if value.IsNil() {
+				if nilIsTop {
+					ident, _ := ctx.Source().(*ast.Ident)
+					return &adt.Top{Src: ident}
+				}
+				return &adt.Null{Src: ctx.Source()}
+			}
+			return convertRec(ctx, nilIsTop, value.Elem().Interface())
+
+		case reflect.Struct:
+			obj := &adt.StructLit{Src: src}
+			v := &adt.Vertex{Value: &adt.StructMarker{}}
+			v.AddConjunct(adt.MakeConjunct(nil, obj))
+
+			t := value.Type()
+			for i := 0; i < value.NumField(); i++ {
+				t := t.Field(i)
+				if t.PkgPath != "" {
+					continue
+				}
+				val := value.Field(i)
+				if !nilIsTop && isNil(val) {
+					continue
+				}
+				if tag, _ := t.Tag.Lookup("json"); tag == "-" {
+					continue
+				}
+				if isOmitEmpty(&t) && isZero(val) {
+					continue
+				}
+				sub := convertRec(ctx, nilIsTop, val.Interface())
+				if sub == nil {
+					// mimic behavior of encoding/json: skip fields of unsupported types
+					continue
+				}
+				if _, ok := sub.(*adt.Bottom); ok {
+					return sub
+				}
+
+				// leave errors like we do during normal evaluation or do we
+				// want to return the error?
+				name := getName(&t)
+				if name == "-" {
+					continue
+				}
+				f := ctx.StringLabel(name)
+				obj.Decls = append(obj.Decls, &adt.Field{Label: f, Value: sub})
+				arc, ok := sub.(*adt.Vertex)
+				if ok {
+					arc.Label = f
+				} else {
+					arc = &adt.Vertex{Label: f, Value: sub}
+					arc.AddConjunct(adt.MakeConjunct(nil, sub))
+				}
+				v.Arcs = append(v.Arcs, arc)
+			}
+
+			return v
+
+		case reflect.Map:
+			obj := &adt.StructLit{Src: src}
+			v := &adt.Vertex{Value: &adt.StructMarker{}}
+			v.AddConjunct(adt.MakeConjunct(nil, obj))
+
+			t := value.Type()
+			switch key := t.Key(); key.Kind() {
+			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])
+				})
+				for _, k := range keys {
+					val := value.MapIndex(k)
+					// if isNil(val) {
+					// 	continue
+					// }
+
+					sub := convertRec(ctx, nilIsTop, val.Interface())
+					// mimic behavior of encoding/json: report error of
+					// unsupported type.
+					if sub == nil {
+						return ctx.AddErrf("unsupported Go type (%v)", val)
+					}
+					if isBottom(sub) {
+						return sub
+					}
+
+					s := fmt.Sprint(k)
+					f := ctx.StringLabel(s)
+					obj.Decls = append(obj.Decls, &adt.Field{
+						Label: f,
+						Value: sub,
+					})
+					arc, ok := sub.(*adt.Vertex)
+					if ok {
+						arc.Label = f
+					} else {
+						arc = &adt.Vertex{Label: f, Value: sub}
+						arc.AddConjunct(adt.MakeConjunct(nil, sub))
+					}
+					v.Arcs = append(v.Arcs, arc)
+				}
+
+			default:
+				return ctx.AddErrf("unsupported Go type for map key (%v)", key)
+			}
+
+			return v
+
+		case reflect.Slice, reflect.Array:
+			var values []adt.Value
+
+			for i := 0; i < value.Len(); i++ {
+				val := value.Index(i)
+				x := convertRec(ctx, nilIsTop, val.Interface())
+				if x == nil {
+					return ctx.AddErrf("unsupported Go type (%v)", val)
+				}
+				if isBottom(x) {
+					return x
+				}
+				values = append(values, x)
+			}
+
+			return ctx.NewList(values...)
+		}
+	}
+	return nil
+}
+
+func toInt(ctx *adt.OpContext, x int64) adt.Value {
+	n := &adt.Num{Src: ctx.Source(), K: adt.IntKind}
+	n.X = *apd.New(x, 0)
+	return n
+}
+
+func toUint(ctx *adt.OpContext, x uint64) adt.Value {
+	n := &adt.Num{Src: ctx.Source(), K: adt.IntKind}
+	n.X.Coeff.SetUint64(x)
+	return n
+}
+
+func convertGoType(ctx *adt.OpContext, t reflect.Type) adt.Expr {
+	// TODO: this can be much more efficient.
+	// TODO: synchronize
+	return goTypeToValue(ctx, true, t)
+}
+
+var (
+	jsonMarshaler = reflect.TypeOf(new(json.Marshaler)).Elem()
+	textMarshaler = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
+	topSentinel   = ast.NewIdent("_")
+)
+
+// 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 *adt.OpContext, allowNullDefault bool, t reflect.Type) adt.Expr {
+	if _, t, ok := ctx.LoadType(t); ok {
+		return t
+	}
+
+	_, v := goTypeToValueRec(ctx, allowNullDefault, t)
+	if v == nil {
+		return ctx.AddErrf("unsupported Go type (%v)", t)
+	}
+	return v
+}
+
+func goTypeToValueRec(ctx *adt.OpContext, allowNullDefault bool, t reflect.Type) (e ast.Expr, expr adt.Expr) {
+	if src, t, ok := ctx.LoadType(t); ok {
+		return src, t
+	}
+
+	switch reflect.Zero(t).Interface().(type) {
+	case *big.Int, big.Int:
+		e = ast.NewIdent("int")
+		goto store
+
+	case *big.Float, big.Float, *big.Rat, big.Rat:
+		e = ast.NewIdent("number")
+		goto store
+
+	case *apd.Decimal, apd.Decimal:
+		e = ast.NewIdent("number")
+		goto store
+	}
+
+	// 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, nil
+	}
+
+	switch k := t.Kind(); k {
+	case reflect.Ptr:
+		elem := t.Elem()
+		for elem.Kind() == reflect.Ptr {
+			elem = elem.Elem()
+		}
+		e, _ = goTypeToValueRec(ctx, false, elem)
+		if allowNullDefault {
+			e = wrapOrNull(e)
+		}
+
+	case reflect.Interface:
+		switch t.Name() {
+		case "error":
+			// This is really null | _|_. There is no error if the error is null.
+			e = ast.NewNull()
+		default:
+			e = topSentinel // `_`
+		}
+
+	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		e = compile.LookupRange(t.Kind().String()).Source().(ast.Expr)
+
+	case reflect.Uint, reflect.Uintptr:
+		e = compile.LookupRange("uint64").Source().(ast.Expr)
+
+	case reflect.Int:
+		e = compile.LookupRange("int64").Source().(ast.Expr)
+
+	case reflect.String:
+		e = ast.NewIdent("__string")
+
+	case reflect.Bool:
+		e = ast.NewIdent("__bool")
+
+	case reflect.Float32, reflect.Float64:
+		e = ast.NewIdent("__number")
+
+	case reflect.Struct:
+		obj := &ast.StructLit{}
+
+		// TODO: dirty trick: set this to a temporary Vertex and then update the
+		// arcs and conjuncts of this vertex below. This will allow circular
+		// references. Maybe have a special kind of "hardlink" reference.
+		ctx.StoreType(t, obj, nil)
+
+		for i := 0; i < t.NumField(); i++ {
+			f := t.Field(i)
+			if f.PkgPath != "" {
+				continue
+			}
+			_, ok := f.Tag.Lookup("cue")
+			elem, _ := goTypeToValueRec(ctx, !ok, f.Type)
+			if isBad(elem) {
+				continue // Ignore fields for unsupported types
+			}
+
+			// leave errors like we do during normal evaluation or do we
+			// want to return the error?
+			name := getName(&f)
+			if name == "-" {
+				continue
+			}
+
+			if tag, ok := f.Tag.Lookup("cue"); ok {
+				v := parseTag(ctx, obj, name, tag)
+				if isBad(v) {
+					return v, nil
+				}
+				elem = ast.NewBinExpr(token.AND, elem, v)
+			}
+			// TODO: if an identifier starts with __ (or otherwise is not a
+			// valid CUE name), make it a string and create a map to a new
+			// name for references.
+
+			// The GO JSON decoder always allows a value to be undefined.
+			d := &ast.Field{Label: ast.NewIdent(name), Value: elem}
+			if isOptional(&f) {
+				d.Optional = token.Blank.Pos()
+			}
+			obj.Elts = append(obj.Elts, d)
+		}
+
+		// TODO: should we validate references here? Can be done using
+		// astutil.ToFile and astutil.Resolve.
+
+		e = obj
+
+	case reflect.Array, reflect.Slice:
+		if t.Elem().Kind() == reflect.Uint8 {
+			e = ast.NewIdent("__bytes")
+		} else {
+			elem, _ := goTypeToValueRec(ctx, allowNullDefault, t.Elem())
+			if elem == nil {
+				b := ctx.AddErrf("unsupported Go type (%v)", t.Elem())
+				return &ast.BadExpr{}, b
+			}
+
+			if t.Kind() == reflect.Array {
+				e = ast.NewBinExpr(token.MUL,
+					ast.NewLit(token.INT, strconv.Itoa(t.Len())),
+					ast.NewList(elem))
+			} else {
+				e = ast.NewList(&ast.Ellipsis{Type: elem})
+			}
+		}
+		if k == reflect.Slice {
+			e = wrapOrNull(e)
+		}
+
+	case reflect.Map:
+		switch key := t.Key(); key.Kind() {
+		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:
+		default:
+			b := ctx.AddErrf("unsupported Go type for map key (%v)", key)
+			return &ast.BadExpr{}, b
+		}
+
+		v, x := goTypeToValueRec(ctx, allowNullDefault, t.Elem())
+		if v == nil {
+			b := ctx.AddErrf("unsupported Go type (%v)", t.Elem())
+			return &ast.BadExpr{}, b
+		}
+		if isBad(v) {
+			return v, x
+		}
+
+		e = ast.NewStruct(&ast.Field{
+			Label: ast.NewList(ast.NewIdent("__string")),
+			Value: v,
+		})
+
+		e = wrapOrNull(e)
+	}
+
+store:
+	// TODO: store error if not nil?
+	if e != nil {
+		f := &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: e}}}
+		astutil.Resolve(f, func(_ token.Pos, msg string, args ...interface{}) {
+			ctx.AddErrf(msg, args...)
+		})
+		var x adt.Expr
+		c, err := compile.Expr(nil, ctx, e)
+		if err != nil {
+			b := &adt.Bottom{Err: err}
+			ctx.AddBottom(b)
+			x = b
+		} else {
+			x = c.Expr()
+		}
+		ctx.StoreType(t, e, x)
+		return e, x
+	}
+	return e, nil
+}
+
+func isBottom(x adt.Node) bool {
+	if x == nil {
+		return true
+	}
+	b, _ := x.(*adt.Bottom)
+	return b != nil
+}
+
+func isBad(x ast.Expr) bool {
+	if x == nil {
+		return true
+	}
+	if bad, _ := x.(*ast.BadExpr); bad != nil {
+		return true
+	}
+	return false
+}
+
+func wrapOrNull(e ast.Expr) ast.Expr {
+	switch x := e.(type) {
+	case *ast.BasicLit:
+		if x.Kind == token.NULL {
+			return x
+		}
+	case *ast.BadExpr:
+		return e
+	}
+	return makeNullable(e, true)
+}
+
+func makeNullable(e ast.Expr, nullIsDefault bool) ast.Expr {
+	var null ast.Expr = ast.NewNull()
+	if nullIsDefault {
+		null = &ast.UnaryExpr{Op: token.MUL, X: null}
+	}
+	return ast.NewBinExpr(token.OR, null, e)
+}
diff --git a/internal/core/convert/go_test.go b/internal/core/convert/go_test.go
new file mode 100644
index 0000000..21492c2
--- /dev/null
+++ b/internal/core/convert/go_test.go
@@ -0,0 +1,337 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package convert
+
+import (
+	"math/big"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/cockroachdb/apd/v2"
+	"github.com/google/go-cmp/cmp"
+
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/internal/core/debug"
+	"cuelang.org/go/internal/core/eval"
+	"cuelang.org/go/internal/core/runtime"
+)
+
+func mkBigInt(a int64) (v apd.Decimal) { v.SetInt64(a); return }
+
+func TestConvert(t *testing.T) {
+	i34 := big.NewInt(34)
+	d35 := mkBigInt(35)
+	n36 := mkBigInt(-36)
+	f37 := big.NewFloat(37.0000)
+	testCases := []struct {
+		goVal interface{}
+		want  string
+	}{{
+		nil, "_",
+	}, {
+		true, "true",
+	}, {
+		false, "false",
+	}, {
+		errors.New("oh noes"), "_|_(oh noes)",
+	}, {
+		"foo", `"foo"`,
+	}, {
+		"\x80", `_|_(cannot convert result to string: invalid UTF-8)`,
+	}, {
+		3, "3",
+	}, {
+		uint(3), "3",
+	}, {
+		uint8(3), "3",
+	}, {
+		uint16(3), "3",
+	}, {
+		uint32(3), "3",
+	}, {
+		uint64(3), "3",
+	}, {
+		int8(-3), "-3",
+	}, {
+		int16(-3), "-3",
+	}, {
+		int32(-3), "-3",
+	}, {
+		int64(-3), "-3",
+	}, {
+		float64(3.1), "3.1",
+	}, {
+		float32(3.1), "3.1",
+	}, {
+		uintptr(3), "3",
+	}, {
+		&i34, "34",
+	}, {
+		&f37, "37",
+	}, {
+		&d35, "35",
+	}, {
+		&n36, "-36",
+	}, {
+		[]int{1, 2, 3, 4}, `(#list){
+  0: (int){ 1 }
+  1: (int){ 2 }
+  2: (int){ 3 }
+  3: (int){ 4 }
+}`,
+	}, {
+		struct {
+			A int
+			B *int
+		}{3, nil},
+		"(struct){\n  A: (int){ 3 }\n}",
+	}, {
+		[]interface{}{}, "(#list){\n}",
+	}, {
+		[]interface{}{nil}, "(#list){\n  0: (_){ _ }\n}",
+	}, {
+		map[string]interface{}{"a": 1, "x": nil}, `(struct){
+  a: (int){ 1 }
+  x: (_){ _ }
+}`,
+	}, {
+		map[string][]int{
+			"a": {1},
+			"b": {3, 4},
+		}, `(struct){
+  a: (#list){
+    0: (int){ 1 }
+  }
+  b: (#list){
+    0: (int){ 3 }
+    1: (int){ 4 }
+  }
+}`,
+	}, {
+		map[bool]int{}, "_|_(unsupported Go type for map key (bool))",
+	}, {
+		map[struct{}]int{{}: 2}, "_|_(unsupported Go type for map key (struct {}))",
+	}, {
+		map[int]int{1: 2}, `(struct){
+  "1": (int){ 2 }
+}`,
+	}, {
+		struct {
+			a int
+			b int
+		}{3, 4},
+		"(struct){\n}",
+	}, {
+		struct {
+			A int
+			B int `json:"-"`
+			C int `json:",omitempty"`
+		}{3, 4, 0},
+		`(struct){
+  A: (int){ 3 }
+}`,
+	}, {
+		struct {
+			A int
+			B int
+		}{3, 4},
+		`(struct){
+  A: (int){ 3 }
+  B: (int){ 4 }
+}`,
+	}, {
+		struct {
+			A int `json:"a"`
+			B int `yaml:"b"`
+		}{3, 4},
+		`(struct){
+  a: (int){ 3 }
+  b: (int){ 4 }
+}`,
+	}, {
+		struct {
+			A int `json:"" yaml:"" protobuf:"aa"`
+			B int `yaml:"cc" json:"bb" protobuf:"aa"`
+		}{3, 4},
+		`(struct){
+  aa: (int){ 3 }
+  bb: (int){ 4 }
+}`,
+	}, {
+		&struct{ A int }{3}, `(struct){
+  A: (int){ 3 }
+}`,
+	}, {
+		(*struct{ A int })(nil), "_",
+	}, {
+		reflect.ValueOf(3), "3",
+	}, {
+		time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC), `(string){ "2019-04-01T00:00:00Z" }`,
+	}}
+	r := runtime.New()
+	for _, tc := range testCases {
+		e := eval.New(r)
+		ctx := adt.NewContext(r, e, &adt.Vertex{})
+		t.Run("", func(t *testing.T) {
+			v := GoValueToValue(ctx, tc.goVal, true)
+			got := debug.NodeString(ctx, v, nil)
+			if got != tc.want {
+				t.Error(cmp.Diff(got, tc.want))
+			}
+		})
+	}
+}
+
+func TestX(t *testing.T) {
+	t.Skip()
+
+	x := []string{}
+
+	r := runtime.New()
+	e := eval.New(r)
+	ctx := adt.NewContext(r, e, &adt.Vertex{})
+
+	v := GoValueToValue(ctx, x, false)
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+	got := debug.NodeString(ctx, v, nil)
+	t.Error(got)
+}
+
+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.
+		`{
+  A: (((int & >=-9223372036854775808) & <=9223372036854775807) & (>=0 & <100))
+  B: (int & >=0)
+  C?: int
+  D: int
+  F?: number
+}`,
+	}, {
+		&struct {
+			A int16 `cue:">=0&<100"`
+			B error `json:"b,"`
+			C string
+			D bool
+			F float64
+			L []byte
+			T time.Time
+			G func()
+		}{},
+		`(*null|{
+  A: (((int & >=-32768) & <=32767) & (>=0 & <100))
+  b: null
+  C: string
+  D: bool
+  F: number
+  L?: (*null|bytes)
+  T: _
+})`,
+	}, {
+		struct {
+			A int `cue:"<"` // invalid
+		}{},
+		"_|_(invalid tag \"<\" for field \"A\": expected operand, found 'EOF' (and 1 more errors))",
+	}, {
+		struct {
+			A int `json:"-"` // skip
+			D *apd.Decimal
+			P ***apd.Decimal
+			I interface{ Foo() }
+			T string `cue:""` // allowed
+			h int
+		}{},
+		`{
+  D?: number
+  P?: (*null|number)
+  I?: _
+  T: (string & _)
+}`,
+	}, {
+		struct {
+			A int8 `cue:"C-B"`
+			B int8 `cue:"C-A,opt"`
+			C int8 `cue:"A+B"`
+		}{},
+		// TODO: should B be marked as optional?
+		`{
+  A: (((int & >=-128) & <=127) & (〈0;C〉 - 〈0;B〉))
+  B?: (((int & >=-128) & <=127) & (〈0;C〉 - 〈0;A〉))
+  C: (((int & >=-128) & <=127) & (〈0;A〉 + 〈0;B〉))
+}`,
+	}, {
+		[]string{},
+		`(*null|[
+  ...string,
+])`,
+	}, {
+		[4]string{},
+		`(4 * [
+  string,
+])`,
+	}, {
+		[]func(){},
+		"_|_(unsupported Go type (func()))",
+	}, {
+		map[string]struct{ A map[string]uint }{},
+		`(*null|{
+  [string]: {
+    A?: (*null|{
+      [string]: ((int & >=0) & <=18446744073709551615)
+    })
+  }
+})`,
+	}, {
+		map[float32]int{},
+		`_|_(unsupported Go type for map key (float32))`,
+	}, {
+		map[int]map[float32]int{},
+		`_|_(unsupported Go type for map key (float32))`,
+	}, {
+		map[int]func(){},
+		`_|_(unsupported Go type (func()))`,
+	}, {
+		time.Now, // a function
+		"_|_(unsupported Go type (func() time.Time))",
+	}}
+
+	r := runtime.New()
+
+	for _, tc := range testCases {
+		t.Run("", func(t *testing.T) {
+			e := eval.New(r)
+			ctx := adt.NewContext(r, e, &adt.Vertex{})
+			v, _ := GoTypeToExpr(ctx, tc.goTyp)
+			got := debug.NodeString(ctx, v, nil)
+			if got != tc.want {
+				t.Errorf("\n got %q;\nwant %q", got, tc.want)
+			}
+		})
+	}
+}
diff --git a/internal/core/runtime/index.go b/internal/core/runtime/index.go
index 569e8ad..8af0f41 100644
--- a/internal/core/runtime/index.go
+++ b/internal/core/runtime/index.go
@@ -15,7 +15,11 @@
 package runtime
 
 import (
+	"reflect"
 	"sync"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/internal/core/adt"
 )
 
 // index maps conversions from label names to internal codes.
@@ -71,3 +75,25 @@
 	x.labels = append(x.labels, s)
 	return int64(index)
 }
+
+func (x *index) StoreType(t reflect.Type, src ast.Expr, expr adt.Expr) {
+	if expr == nil {
+		x.typeCache.Store(t, src)
+	} else {
+		x.typeCache.Store(t, expr)
+	}
+}
+
+func (x *index) LoadType(t reflect.Type) (src ast.Expr, expr adt.Expr, ok bool) {
+	v, ok := x.typeCache.Load(t)
+	if ok {
+		switch x := v.(type) {
+		case ast.Expr:
+			return x, nil, true
+		case adt.Expr:
+			src, _ = x.Source().(ast.Expr)
+			return src, x, true
+		}
+	}
+	return nil, nil, false
+}