Skip to content

Commit 332ea47

Browse files
committed
cue: improve coverage of Go value conversion
- cover more types - make behavior more like encoding/json - better test coverage - better error messages Change-Id: Ia017726cf027589ba694a6b7fa5bcd5396c2eab9 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2741 Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent 5a3cce0 commit 332ea47

File tree

4 files changed

+180
-52
lines changed

4 files changed

+180
-52
lines changed

cue/go.go

Lines changed: 135 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ func isZero(v reflect.Value) bool {
188188
}
189189

190190
func convert(ctx *context, src source, x interface{}) evaluated {
191+
v := convertRec(ctx, src, x)
192+
if v == nil {
193+
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", v)
194+
}
195+
return v
196+
}
197+
198+
func convertRec(ctx *context, src source, x interface{}) evaluated {
191199
switch v := x.(type) {
192200
case nil:
193201
// Interpret a nil pointer as an undefined value that is only
@@ -279,27 +287,50 @@ func convert(ctx *context, src source, x interface{}) evaluated {
279287
return toUint(ctx, src, uint64(v))
280288
case uint64:
281289
return toUint(ctx, src, uint64(v))
290+
case uintptr:
291+
return toUint(ctx, src, uint64(v))
282292
case float64:
283293
r := newNum(src, floatKind)
284294
r.v.SetString(fmt.Sprintf("%g", v))
285295
return r
296+
case float32:
297+
r := newNum(src, floatKind)
298+
r.v.SetString(fmt.Sprintf("%g", v))
299+
return r
286300

287301
case reflect.Value:
288302
if v.CanInterface() {
289-
return convert(ctx, src, v.Interface())
303+
return convertRec(ctx, src, v.Interface())
290304
}
291305

292306
default:
293307
value := reflect.ValueOf(v)
294308
switch value.Kind() {
309+
case reflect.Bool:
310+
return &boolLit{src.base(), value.Bool()}
311+
312+
case reflect.String:
313+
return &stringLit{src.base(), value.String(), nil}
314+
315+
case reflect.Int, reflect.Int8, reflect.Int16,
316+
reflect.Int32, reflect.Int64:
317+
return toInt(ctx, src, value.Int())
318+
319+
case reflect.Uint, reflect.Uint8, reflect.Uint16,
320+
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
321+
return toUint(ctx, src, value.Uint())
322+
323+
case reflect.Float32, reflect.Float64:
324+
return convertRec(ctx, src, value.Float())
325+
295326
case reflect.Ptr:
296327
if value.IsNil() {
297328
// Interpret a nil pointer as an undefined value that is only
298329
// null by default, but may still be set: *null | _.
299330
elem := goTypeToValue(ctx, false, reflect.TypeOf(v).Elem())
300331
return makeNullable(elem, false).(evaluated)
301332
}
302-
return convert(ctx, src, value.Elem().Interface())
333+
return convertRec(ctx, src, value.Elem().Interface())
303334

304335
case reflect.Struct:
305336
obj := newStruct(src)
@@ -313,7 +344,15 @@ func convert(ctx *context, src source, x interface{}) evaluated {
313344
if isOmitEmpty(&t) && isZero(val) {
314345
continue
315346
}
316-
sub := convert(ctx, src, val.Interface())
347+
sub := convertRec(ctx, src, val.Interface())
348+
if sub == nil {
349+
// mimic behavior of encoding/json: skip fields of unsupported types
350+
continue
351+
}
352+
if isBottom(sub) {
353+
return sub
354+
}
355+
317356
// leave errors like we do during normal evaluation or do we
318357
// want to return the error?
319358
name := getName(&t)
@@ -328,21 +367,49 @@ func convert(ctx *context, src source, x interface{}) evaluated {
328367

329368
case reflect.Map:
330369
obj := newStruct(src)
370+
371+
sorted := []string{}
372+
keys := []string{}
331373
t := value.Type()
332-
if t.Key().Kind() != reflect.String {
333-
return ctx.mkErr(src, "builtin map key not a string, but unsupported type %s", t.Key().String())
374+
switch key := t.Key(); key.Kind() {
375+
case reflect.String,
376+
reflect.Int, reflect.Int8, reflect.Int16,
377+
reflect.Int32, reflect.Int64,
378+
reflect.Uint, reflect.Uint8, reflect.Uint16,
379+
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
380+
for _, k := range value.MapKeys() {
381+
s := fmt.Sprint(k)
382+
keys = append(keys, s)
383+
sorted = append(sorted, s)
384+
385+
val := value.MapIndex(k).Interface()
386+
sub := convertRec(ctx, src, val)
387+
// mimic behavior of encoding/json: report error of
388+
// unsupported type.
389+
if sub == nil {
390+
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", val)
391+
}
392+
if isBottom(sub) {
393+
return sub
394+
}
395+
396+
// Set feature later.
397+
obj.arcs = append(obj.arcs, arc{feature: 0, v: sub})
398+
}
399+
400+
default:
401+
return ctx.mkErr(baseValue{}, "unsupported Go type for map key (%v)", key)
334402
}
335-
keys := []string{}
336-
for _, k := range value.MapKeys() {
337-
keys = append(keys, k.String())
403+
404+
// Assign label in normalized order.
405+
sort.Strings(sorted)
406+
for _, k := range sorted {
407+
ctx.strLabel(k)
338408
}
339-
sort.Strings(keys)
340-
for _, k := range keys {
341-
sub := convert(ctx, src, value.MapIndex(reflect.ValueOf(k)).Interface())
342-
// leave errors like we do during normal evaluation or do we
343-
// want to return the error?
344-
f := ctx.strLabel(k)
345-
obj.arcs = append(obj.arcs, arc{feature: f, v: sub})
409+
410+
// Now assign the labels to the arcs.
411+
for i, k := range keys {
412+
obj.arcs[i].feature = ctx.strLabel(k)
346413
}
347414
sort.Sort(obj)
348415
return obj
@@ -351,7 +418,11 @@ func convert(ctx *context, src source, x interface{}) evaluated {
351418
list := &list{baseValue: src.base()}
352419
arcs := []arc{}
353420
for i := 0; i < value.Len(); i++ {
354-
x := convert(ctx, src, value.Index(i).Interface())
421+
val := value.Index(i).Interface()
422+
x := convertRec(ctx, src, val)
423+
if x == nil {
424+
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", val)
425+
}
355426
if isBottom(x) {
356427
return x
357428
}
@@ -365,7 +436,7 @@ func convert(ctx *context, src source, x interface{}) evaluated {
365436
return list
366437
}
367438
}
368-
return ctx.mkErr(src, "builtin returned unsupported type %T", x)
439+
return nil
369440
}
370441

371442
func toInt(ctx *context, src source, x int64) evaluated {
@@ -398,11 +469,33 @@ var (
398469
//
399470
// TODO: if this value will always be unified with a concrete type in Go, then
400471
// many of the fields may be omitted.
401-
func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value) {
472+
func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) value {
473+
v := goTypeToValueRec(ctx, allowNullDefault, t)
474+
if v == nil {
475+
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", t)
476+
}
477+
return v
478+
}
479+
480+
func goTypeToValueRec(ctx *context, allowNullDefault bool, t reflect.Type) (e value) {
402481
if e, ok := ctx.typeCache.Load(t); ok {
403482
return e.(value)
404483
}
405484

485+
switch reflect.Zero(t).Interface().(type) {
486+
case *big.Int, big.Int:
487+
e = &basicType{k: intKind}
488+
goto store
489+
490+
case *big.Float, big.Float, *big.Rat, big.Rat:
491+
e = &basicType{k: numKind}
492+
goto store
493+
494+
case *apd.Decimal, apd.Decimal:
495+
e = &basicType{k: numKind}
496+
goto store
497+
}
498+
406499
// Even if this is for types that we know cast to a certain type, it can't
407500
// hurt to return top, as in these cases the concrete values will be
408501
// strict instances and there cannot be any tags that further constrain
@@ -417,7 +510,7 @@ func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value
417510
for elem.Kind() == reflect.Ptr {
418511
elem = elem.Elem()
419512
}
420-
e = goTypeToValue(ctx, false, elem)
513+
e = goTypeToValueRec(ctx, false, elem)
421514
if allowNullDefault {
422515
e = wrapOrNull(e)
423516
}
@@ -451,22 +544,6 @@ func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value
451544
e = &basicType{k: floatKind}
452545

453546
case reflect.Struct:
454-
// Some of these values have MarshalJSON methods, but that may not be
455-
// the case for older Go versions.
456-
name := fmt.Sprint(t)
457-
switch name {
458-
case "big.Int":
459-
e = &basicType{k: intKind}
460-
goto store
461-
case "big.Rat", "big.Float", "apd.Decimal":
462-
e = &basicType{k: floatKind}
463-
goto store
464-
case "time.Time":
465-
// We let the concrete value decide.
466-
e = topSentinel
467-
goto store
468-
}
469-
470547
// First iterate to create struct, then iterate another time to
471548
// resolve field tags to allow field tags to refer to the struct fields.
472549
tags := map[label]string{}
@@ -479,8 +556,8 @@ func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value
479556
continue
480557
}
481558
_, ok := f.Tag.Lookup("cue")
482-
elem := goTypeToValue(ctx, !ok, f.Type)
483-
if elem == nil {
559+
elem := goTypeToValueRec(ctx, !ok, f.Type)
560+
if elem == nil || isBottom(elem) {
484561
continue // Ignore fields for unsupported types
485562
}
486563

@@ -506,6 +583,9 @@ func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value
506583

507584
for label, tag := range tags {
508585
v := parseTag(ctx, obj, label, tag)
586+
if isBottom(v) {
587+
return v
588+
}
509589
for i, a := range obj.arcs {
510590
if a.feature == label {
511591
// Instead of unifying with the existing type, we substitute
@@ -522,7 +602,10 @@ func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value
522602
if t.Elem().Kind() == reflect.Uint8 {
523603
e = &basicType{k: bytesKind}
524604
} else {
525-
elem := goTypeToValue(ctx, allowNullDefault, t.Elem())
605+
elem := goTypeToValueRec(ctx, allowNullDefault, t.Elem())
606+
if elem == nil {
607+
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", t.Elem())
608+
}
526609

527610
var ln value = &top{}
528611
if t.Kind() == reflect.Array {
@@ -535,16 +618,24 @@ func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value
535618
}
536619

537620
case reflect.Map:
538-
if key := t.Key(); key.Kind() != reflect.String {
539-
// What does the JSON library do here?
540-
e = ctx.mkErr(baseValue{}, "type %v not supported as key type", key)
541-
break
621+
switch key := t.Key(); key.Kind() {
622+
case reflect.String, reflect.Int, reflect.Int8, reflect.Int16,
623+
reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8,
624+
reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
625+
default:
626+
return ctx.mkErr(baseValue{}, "unsupported Go type for map key (%v)", key)
542627
}
543628

544629
obj := newStruct(baseValue{})
545630
sig := &params{}
546631
sig.add(ctx.label("_", true), &basicType{k: stringKind})
547-
v := goTypeToValue(ctx, allowNullDefault, t.Elem())
632+
v := goTypeToValueRec(ctx, allowNullDefault, t.Elem())
633+
if v == nil {
634+
return ctx.mkErr(baseValue{}, "unsupported Go type (%v)", t.Elem())
635+
}
636+
if isBottom(v) {
637+
return v
638+
}
548639
obj.template = &lambdaExpr{params: sig, value: v}
549640

550641
e = wrapOrNull(obj)
@@ -559,7 +650,7 @@ store:
559650
}
560651

561652
func wrapOrNull(e value) value {
562-
if e.kind().isAnyOf(nullKind) {
653+
if e == nil || isBottom(e) || e.kind().isAnyOf(nullKind) {
563654
return e
564655
}
565656
return makeNullable(e, true)

0 commit comments

Comments
 (0)