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 <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go
index 7306660..50a962e 100644
--- a/encoding/openapi/types.go
+++ b/encoding/openapi/types.go
@@ -48,7 +48,7 @@
 
 	">=-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 d6f44b2..29585c5 100644
--- a/internal/core/adt/feature.go
+++ b/internal/core/adt/feature.go
@@ -196,7 +196,7 @@
 			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 6b49f13..1a034c7 100644
--- a/internal/core/convert/go_test.go
+++ b/internal/core/convert/go_test.go
@@ -42,51 +42,53 @@
 		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 @@
   }
 }`,
 	}, {
-		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 @@
   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 @@
 		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 0000000..07ebd2d
--- /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 720c3d7..1508581 100644
--- a/internal/core/export/value.go
+++ b/internal/core/export/value.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strings"
 
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/ast/astutil"
@@ -245,8 +246,11 @@
 	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 {