From c8b7fe0c7be85cb757188cc4357d7eb607efafa2 Mon Sep 17 00:00:00 2001 From: Marcel van Lohuizen Date: Sun, 11 Apr 2021 12:20:04 +0200 Subject: [PATCH] internal/core/export: export floats as float literals This avoids round-tripping problems when floats can be represented as integers. Fixes #896 The real solution is probably to make integer a direct subclass of float. This, however, is a far more substantial change. See #253. Change-Id: I1fa532677a4ba2dcbe85446fcd7134f5bc3a542d Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9381 Reviewed-by: CUE cueckoo Reviewed-by: Paul Jolly --- encoding/openapi/types.go | 2 +- internal/core/adt/feature.go | 2 +- internal/core/convert/go_test.go | 62 ++++++++++-------- internal/core/export/testdata/num.txtar | 86 +++++++++++++++++++++++++ internal/core/export/value.go | 8 ++- 5 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 internal/core/export/testdata/num.txtar diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go index 7306660ffda..50a962e7c7c 100644 --- a/encoding/openapi/types.go +++ b/encoding/openapi/types.go @@ -48,7 +48,7 @@ var cueToOpenAPI = map[string]string{ ">=-2147483648 & <=2147483647 & int": "int32", ">=-9223372036854775808 & <=9223372036854775807 & int": "int64", - ">=-340282346638528859811704183484516925440 & <=340282346638528859811704183484516925440": "float", + ">=-340282346638528859811704183484516925440.0 & <=340282346638528859811704183484516925440.0": "float", ">=-1.797693134862315708145274237317043567981e+308 & <=1.797693134862315708145274237317043567981e+308": "double", } diff --git a/internal/core/adt/feature.go b/internal/core/adt/feature.go index d6f44b26256..29585c53cab 100644 --- a/internal/core/adt/feature.go +++ b/internal/core/adt/feature.go @@ -196,7 +196,7 @@ func labelFromValue(c *OpContext, src Expr, v Value) Feature { if src == nil { src = v } - c.AddErrf("invalid index %v: %v", src, err) + c.AddErrf("invalid index %v: %v", c.Str(src), err) return InvalidLabel } if i < 0 { diff --git a/internal/core/convert/go_test.go b/internal/core/convert/go_test.go index 6b49f131d3a..1a034c746b0 100644 --- a/internal/core/convert/go_test.go +++ b/internal/core/convert/go_test.go @@ -42,51 +42,53 @@ func TestConvert(t *testing.T) { goVal interface{} want string }{{ - nil, "_", + nil, "(_){ _ }", }, { - true, "true", + true, "(bool){ true }", }, { - false, "false", + false, "(bool){ false }", }, { - errors.New("oh noes"), "_|_(oh noes)", + errors.New("oh noes"), "(_|_){\n // [eval] oh noes\n}", }, { - "foo", `"foo"`, + "foo", `(string){ "foo" }`, }, { - "\x80", `_|_(cannot convert result to string: invalid UTF-8)`, + "\x80", "(_|_){\n // [eval] cannot convert result to string: invalid UTF-8\n}", }, { - 3, "3", + 3, "(int){ 3 }", }, { - uint(3), "3", + uint(3), "(int){ 3 }", }, { - uint8(3), "3", + uint8(3), "(int){ 3 }", }, { - uint16(3), "3", + uint16(3), "(int){ 3 }", }, { - uint32(3), "3", + uint32(3), "(int){ 3 }", }, { - uint64(3), "3", + uint64(3), "(int){ 3 }", }, { - int8(-3), "-3", + int8(-3), "(int){ -3 }", }, { - int16(-3), "-3", + int16(-3), "(int){ -3 }", }, { - int32(-3), "-3", + int32(-3), "(int){ -3 }", }, { - int64(-3), "-3", + int64(-3), "(int){ -3 }", }, { - float64(3.1), "3.1", + float64(3), "(float){ 3 }", }, { - float32(3.1), "3.1", + float64(3.1), "(float){ 3.1 }", }, { - uintptr(3), "3", + float32(3.1), "(float){ 3.1 }", }, { - &i34, "34", + uintptr(3), "(int){ 3 }", }, { - &f37, "37", + &i34, "(int){ 34 }", }, { - &d35, "35", + &f37, "(float){ 37 }", }, { - &n36, "-36", + &d35, "(int){ 35 }", + }, { + &n36, "(int){ -36 }", }, { []int{1, 2, 3, 4}, `(#list){ 0: (int){ 1 } @@ -123,9 +125,9 @@ func TestConvert(t *testing.T) { } }`, }, { - map[bool]int{}, "_|_(unsupported Go type for map key (bool))", + map[bool]int{}, "(_|_){\n // [eval] unsupported Go type for map key (bool)\n}", }, { - map[struct{}]int{{}: 2}, "_|_(unsupported Go type for map key (struct {}))", + map[struct{}]int{{}: 2}, "(_|_){\n // [eval] unsupported Go type for map key (struct {})\n}", }, { map[int]int{1: 2}, `(struct){ "1": (int){ 2 } @@ -177,9 +179,9 @@ func TestConvert(t *testing.T) { A: (int){ 3 } }`, }, { - (*struct{ A int })(nil), "_", + (*struct{ A int })(nil), "(_){ _ }", }, { - reflect.ValueOf(3), "3", + reflect.ValueOf(3), "(int){ 3 }", }, { time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC), `(string){ "2019-04-01T00:00:00Z" }`, }, { @@ -203,7 +205,11 @@ func TestConvert(t *testing.T) { ctx := adt.NewContext(r, &adt.Vertex{}) t.Run("", func(t *testing.T) { v := convert.GoValueToValue(ctx, tc.goVal, true) - got := debug.NodeString(ctx, v, nil) + n, ok := v.(*adt.Vertex) + if !ok { + n = &adt.Vertex{BaseValue: v} + } + got := debug.NodeString(ctx, n, nil) if got != tc.want { t.Error(cmp.Diff(got, tc.want)) } diff --git a/internal/core/export/testdata/num.txtar b/internal/core/export/testdata/num.txtar new file mode 100644 index 00000000000..07ebd2d01ae --- /dev/null +++ b/internal/core/export/testdata/num.txtar @@ -0,0 +1,86 @@ +-- in.cue -- +import "strings" + +floats: { + a: 3. + b: float & 3. +} +numbers: { + a: number + a: 3 + b: number + b: 3. +} +-- out/definition -- +floats: { + a: 3.0 + b: 3.0 +} +numbers: { + a: 3 + b: 3.0 +} +-- out/doc -- +[] +[floats] +[floats a] +[floats b] +[numbers] +[numbers a] +[numbers b] +-- out/value -- +== Simplified +{ + floats: { + a: 3.0 + b: 3.0 + } + numbers: { + a: 3 + b: 3.0 + } +} +== Raw +{ + floats: { + a: 3.0 + b: 3.0 + } + numbers: { + a: 3 + b: 3.0 + } +} +== Final +{ + floats: { + a: 3.0 + b: 3.0 + } + numbers: { + a: 3 + b: 3.0 + } +} +== All +{ + floats: { + a: 3.0 + b: 3.0 + } + numbers: { + a: 3 + b: 3.0 + } +} +== Eval +{ + floats: { + a: 3.0 + b: 3.0 + } + numbers: { + a: 3 + b: 3.0 + } +} diff --git a/internal/core/export/value.go b/internal/core/export/value.go index 720c3d71be5..150858113ce 100644 --- a/internal/core/export/value.go +++ b/internal/core/export/value.go @@ -16,6 +16,7 @@ package export import ( "fmt" + "strings" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/ast/astutil" @@ -245,8 +246,11 @@ func (e *exporter) num(n *adt.Num, orig []adt.Conjunct) *ast.BasicLit { if n.K&adt.IntKind != 0 { kind = token.INT } - return &ast.BasicLit{Kind: kind, Value: n.X.String()} - + s := n.X.String() + if kind == token.FLOAT && !strings.ContainsAny(s, "eE.") { + s += "." + } + return &ast.BasicLit{Kind: kind, Value: s} } func (e *exporter) string(n *adt.String, orig []adt.Conjunct) *ast.BasicLit {