blob: 519146746467961d9c93b3bf37869143df896960 [file] [log] [blame]
// 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"
"cuelang.org/go/internal/core/adt"
"cuelang.org/go/internal/core/compile"
"cuelang.org/go/internal/core/runtime"
)
// 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.MakeRootConjunct(nil, e))
return obj
}
func compileExpr(ctx *adt.OpContext, expr ast.Expr) adt.Value {
c, err := compile.Expr(nil, ctx, pkgID(), 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
if f.Anonymous {
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 surprising 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 surprising 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 {
if internal.CoreValue != nil {
if ii, iv := internal.CoreValue(x); ii != nil {
i := ii.(*runtime.Runtime)
v := iv.(*adt.Vertex)
// TODO: panic if nto the same runtime.
_ = i
return v
}
}
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, pkgID(), 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())
if err != nil {
return ctx.AddErr(errors.Promote(err, "invalid float"))
}
return n
case *apd.Decimal:
// TODO: should we allow an "int" bit to be set here? It is a bit
// tricky, as we would also need to pass down the result of rounding.
// So more likely an API must return explicitly whether a value is
// a float or an int after all.
// The code to autodetect whether something is an integer can be done
// with this:
kind := adt.FloatKind
var d apd.Decimal
res, _ := apd.BaseContext.RoundToIntegralExact(&d, v)
if !res.Inexact() {
kind = adt.IntKind
}
n := &adt.Num{Src: ctx.Source(), K: kind}
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 = ctx.Newf("%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.Sprint(v))
if err != nil {
return ctx.AddErr(errors.Promote(err, "invalid float"))
}
return n
case float32:
n := &adt.Num{Src: src, K: adt.FloatKind}
_, _, err := n.X.SetString(fmt.Sprint(v))
if err != nil {
return 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{}
env := ctx.Env(0)
if env == nil {
env = &adt.Environment{}
}
v.AddStruct(obj, env, adt.CloseInfo{})
v.SetValue(ctx, adt.Finalized, &adt.StructMarker{})
t := value.Type()
for i := 0; i < value.NumField(); i++ {
sf := t.Field(i)
if sf.PkgPath != "" {
continue
}
val := value.Field(i)
if !nilIsTop && isNil(val) {
continue
}
if tag, _ := sf.Tag.Lookup("json"); tag == "-" {
continue
}
if isOmitEmpty(&sf) && 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(&sf)
if name == "-" {
continue
}
if sf.Anonymous && name == "" {
arc, ok := sub.(*adt.Vertex)
if ok {
v.Arcs = append(v.Arcs, arc.Arcs...)
}
continue
}
f := ctx.StringLabel(name)
obj.Decls = append(obj.Decls, &adt.Field{Label: f, Value: sub})
arc, ok := sub.(*adt.Vertex)
if ok {
a := *arc
arc = &a
arc.Label = f
} else {
arc = &adt.Vertex{Label: f, BaseValue: sub}
arc.UpdateStatus(adt.Finalized)
arc.AddConjunct(adt.MakeRootConjunct(nil, sub))
}
v.Arcs = append(v.Arcs, arc)
}
return v
case reflect.Map:
v := &adt.Vertex{BaseValue: &adt.StructMarker{}}
v.SetValue(ctx, adt.Finalized, &adt.StructMarker{})
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)
arc, ok := sub.(*adt.Vertex)
if ok {
a := *arc
arc = &a
arc.Label = f
} else {
arc = &adt.Vertex{Label: f, BaseValue: sub}
arc.UpdateStatus(adt.Finalized)
arc.AddConjunct(adt.MakeRootConjunct(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, pkgID(), 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)
}
// pkgID returns a package path that can never resolve to an existing package.
func pkgID() string {
return "_"
}