| // 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 cue |
| |
| import ( |
| "encoding" |
| "encoding/json" |
| "fmt" |
| "math/big" |
| "reflect" |
| "sort" |
| "strings" |
| |
| "cuelang.org/go/cue/ast" |
| "cuelang.org/go/cue/parser" |
| "cuelang.org/go/cue/token" |
| "cuelang.org/go/internal" |
| "github.com/cockroachdb/apd/v2" |
| ) |
| |
| // This file contains functionality for converting Go to CUE. |
| // |
| // The code in this file is a prototype implementation and is far from |
| // optimized. |
| |
| func init() { |
| internal.FromGoValue = func(runtime, x interface{}, allowDefault bool) interface{} { |
| return convertValue(runtime.(*Runtime), x, allowDefault) |
| } |
| |
| internal.FromGoType = func(runtime, x interface{}) interface{} { |
| return convertType(runtime.(*Runtime), x) |
| } |
| } |
| |
| func convertValue(r *Runtime, x interface{}, allowDefault bool) Value { |
| ctx := r.index().newContext() |
| v := convert(ctx, baseValue{}, allowDefault, x) |
| return newValueRoot(ctx, v) |
| } |
| |
| func convertType(r *Runtime, x interface{}) Value { |
| ctx := r.index().newContext() |
| v := convertGoType(r, 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("<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("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, allowDefault bool, x interface{}) evaluated { |
| v := convertRec(ctx, src, allowDefault, x) |
| if v == nil { |
| return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", v) |
| } |
| return v |
| } |
| |
| func convertRec(ctx *context, src source, allowDefault bool, x interface{}) evaluated { |
| switch v := x.(type) { |
| case nil: |
| if !allowDefault { |
| 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 makeNullable(&top{src.base()}, false).(evaluated) |
| |
| case ast.Expr: |
| x := newVisitorCtx(ctx, nil, nil, nil) |
| return ctx.manifest(x.walk(v)) |
| |
| 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) |
| _, err := ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0)) |
| if err != nil { |
| return ctx.mkErr(src, err) |
| } |
| 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: |
| return &boolLit{src.base(), v} |
| case string: |
| return &stringLit{src.base(), v, nil} |
| case []byte: |
| return &bytesLit{src.base(), v, nil} |
| case int: |
| return toInt(ctx, src, int64(v)) |
| case int8: |
| return toInt(ctx, src, int64(v)) |
| case int16: |
| return toInt(ctx, src, int64(v)) |
| case int32: |
| return toInt(ctx, src, int64(v)) |
| case int64: |
| return toInt(ctx, src, int64(v)) |
| case uint: |
| return toUint(ctx, src, uint64(v)) |
| case uint8: |
| return toUint(ctx, src, uint64(v)) |
| case uint16: |
| return toUint(ctx, src, uint64(v)) |
| case uint32: |
| return toUint(ctx, src, uint64(v)) |
| case uint64: |
| return toUint(ctx, src, uint64(v)) |
| case uintptr: |
| return toUint(ctx, src, uint64(v)) |
| case float64: |
| r := newNum(src, floatKind) |
| _, _, _ = r.v.SetString(fmt.Sprintf("%g", v)) |
| return r |
| case float32: |
| r := newNum(src, floatKind) |
| _, _, _ = r.v.SetString(fmt.Sprintf("%g", v)) |
| return r |
| |
| case reflect.Value: |
| if v.CanInterface() { |
| return convertRec(ctx, src, allowDefault, v.Interface()) |
| } |
| |
| default: |
| value := reflect.ValueOf(v) |
| switch value.Kind() { |
| case reflect.Bool: |
| return &boolLit{src.base(), value.Bool()} |
| |
| case reflect.String: |
| return &stringLit{src.base(), value.String(), nil} |
| |
| case reflect.Int, reflect.Int8, reflect.Int16, |
| reflect.Int32, reflect.Int64: |
| return toInt(ctx, src, value.Int()) |
| |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, |
| reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| return toUint(ctx, src, value.Uint()) |
| |
| case reflect.Float32, reflect.Float64: |
| return convertRec(ctx, src, allowDefault, value.Float()) |
| |
| case reflect.Ptr: |
| if value.IsNil() { |
| if !allowDefault { |
| return &nullLit{src.base()} |
| } |
| // Interpret a nil pointer as an undefined value that is only |
| // null by default, but may still be set: *null | _. |
| elem := goTypeToValue(ctx, false, reflect.TypeOf(v).Elem()) |
| return makeNullable(elem, false).(evaluated) |
| } |
| return convertRec(ctx, src, allowDefault, value.Elem().Interface()) |
| |
| case reflect.Struct: |
| obj := newStruct(src) |
| t := value.Type() |
| for i := 0; i < value.NumField(); i++ { |
| t := t.Field(i) |
| if t.PkgPath != "" { |
| continue |
| } |
| val := value.Field(i) |
| if isOmitEmpty(&t) && isZero(val) { |
| continue |
| } |
| sub := convertRec(ctx, src, allowDefault, val.Interface()) |
| if sub == nil { |
| // mimic behavior of encoding/json: skip fields of unsupported types |
| continue |
| } |
| if isBottom(sub) { |
| 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.strLabel(name) |
| obj.arcs = append(obj.arcs, arc{feature: f, v: sub}) |
| } |
| sort.Sort(obj) |
| return obj |
| |
| case reflect.Map: |
| obj := newStruct(src) |
| |
| sorted := []string{} |
| keys := []string{} |
| 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: |
| for _, k := range value.MapKeys() { |
| s := fmt.Sprint(k) |
| keys = append(keys, s) |
| sorted = append(sorted, s) |
| |
| val := value.MapIndex(k).Interface() |
| sub := convertRec(ctx, src, allowDefault, val) |
| // mimic behavior of encoding/json: report error of |
| // unsupported type. |
| if sub == nil { |
| return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", val) |
| } |
| if isBottom(sub) { |
| return sub |
| } |
| |
| // Set feature later. |
| obj.arcs = append(obj.arcs, arc{feature: 0, v: sub}) |
| } |
| |
| default: |
| return ctx.mkErr(baseValue{}, "unsupported Go type for map key (%v)", key) |
| } |
| |
| // Assign label in normalized order. |
| sort.Strings(sorted) |
| for _, k := range sorted { |
| ctx.strLabel(k) |
| } |
| |
| // Now assign the labels to the arcs. |
| for i, k := range keys { |
| obj.arcs[i].feature = ctx.strLabel(k) |
| } |
| sort.Sort(obj) |
| return obj |
| |
| case reflect.Slice, reflect.Array: |
| list := &list{baseValue: src.base()} |
| arcs := []arc{} |
| for i := 0; i < value.Len(); i++ { |
| val := value.Index(i).Interface() |
| x := convertRec(ctx, src, allowDefault, val) |
| if x == nil { |
| return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", val) |
| } |
| if isBottom(x) { |
| return x |
| } |
| arcs = append(arcs, arc{feature: label(len(arcs)), v: x}) |
| } |
| list.elem = &structLit{baseValue: list.baseValue, arcs: arcs} |
| list.initLit() |
| // There is no need to set the type of the list, as the list will |
| // be of fixed size and all elements will already have a defined |
| // value. |
| return list |
| } |
| } |
| return nil |
| } |
| |
| func toInt(ctx *context, src source, x int64) evaluated { |
| n := newNum(src, intKind) |
| n.v.SetInt64(x) |
| return n |
| } |
| |
| func toUint(ctx *context, src source, x uint64) evaluated { |
| n := newNum(src, floatKind) |
| n.v.Coeff.SetUint64(x) |
| return n |
| } |
| |
| func convertGoType(r *Runtime, t reflect.Type) value { |
| ctx := r.index().newContext() |
| // TODO: this can be much more efficient. |
| ctx.mutex.Lock() |
| defer ctx.mutex.Unlock() |
| return goTypeToValue(ctx, true, 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, allowNullDefault bool, t reflect.Type) value { |
| v := goTypeToValueRec(ctx, allowNullDefault, t) |
| if v == nil { |
| return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", t) |
| } |
| return v |
| } |
| |
| func goTypeToValueRec(ctx *context, allowNullDefault bool, t reflect.Type) (e value) { |
| if e, ok := ctx.typeCache.Load(t); ok { |
| return e.(value) |
| } |
| |
| switch reflect.Zero(t).Interface().(type) { |
| case *big.Int, big.Int: |
| e = &basicType{k: intKind} |
| goto store |
| |
| case *big.Float, big.Float, *big.Rat, big.Rat: |
| e = &basicType{k: numKind} |
| goto store |
| |
| case *apd.Decimal, apd.Decimal: |
| e = &basicType{k: numKind} |
| 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 |
| } |
| |
| 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 = &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: |
| // 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{}) |
| ctx.typeCache.Store(t, obj) |
| |
| 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 elem == nil || isBottom(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 |
| } |
| 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) |
| if isBottom(v) { |
| return v |
| } |
| 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, token.NoPos, opUnify, a.v, v) |
| } |
| } |
| } |
| |
| return obj |
| |
| case reflect.Array, reflect.Slice: |
| if t.Elem().Kind() == reflect.Uint8 { |
| e = &basicType{k: bytesKind} |
| } else { |
| elem := goTypeToValueRec(ctx, allowNullDefault, t.Elem()) |
| if elem == nil { |
| return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", t.Elem()) |
| } |
| |
| var ln value = &top{} |
| if t.Kind() == reflect.Array { |
| ln = toInt(ctx, baseValue{}, int64(t.Len())) |
| } |
| e = &list{elem: &structLit{}, typ: elem, len: ln} |
| } |
| 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: |
| return ctx.mkErr(baseValue{}, "unsupported Go type for map key (%v)", key) |
| } |
| |
| obj := newStruct(baseValue{}) |
| sig := ¶ms{} |
| sig.add(ctx.label("_", true), &basicType{k: stringKind}) |
| v := goTypeToValueRec(ctx, allowNullDefault, t.Elem()) |
| if v == nil { |
| return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", t.Elem()) |
| } |
| if isBottom(v) { |
| return v |
| } |
| obj.template = &lambdaExpr{params: sig, value: v} |
| |
| e = wrapOrNull(obj) |
| } |
| |
| store: |
| // TODO: store error if not nil? |
| if e != nil { |
| ctx.typeCache.Store(t, e) |
| } |
| return e |
| } |
| |
| func wrapOrNull(e value) value { |
| if e == nil || isBottom(e) || e.kind().isAnyOf(nullKind) { |
| return e |
| } |
| return makeNullable(e, true) |
| } |
| |
| func makeNullable(e value, nullIsDefault bool) value { |
| return &disjunction{ |
| baseValue: baseValue{e}, |
| values: []dValue{ |
| {val: &nullLit{}, marked: nullIsDefault}, |
| {val: e}}, |
| hasDefaults: nullIsDefault, |
| } |
| } |