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
+}