cue: expose difference between float, int, and number

Having this explicit difference is useful when
analyzing for code generation. This does not
necessarily mean this difference needs to be
exposed in the language itself.

This does not implement the proposed spec
change where integer literals are only literals.

Change-Id: Id2c45208409d43278a72c4171a1a1c28cd093d21
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1963
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/types.go b/cue/types.go
index 5c19d62..22bd98a 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -43,8 +43,13 @@
 	// BoolKind indicates a boolean value.
 	BoolKind
 
-	// NumberKind represents any kind of number.
-	NumberKind
+	// IntKind represents an integral number.
+	IntKind
+
+	// FloatKind represents a decimal float point number that cannot be
+	// converted to an integer. The underlying number may still be integral,
+	// but resulting from an operation that enforces the float type.
+	FloatKind
 
 	// StringKind indicates any kind of string.
 	StringKind
@@ -59,6 +64,9 @@
 	ListKind
 
 	nextKind
+
+	// NumberKind represents any kind of number.
+	NumberKind = IntKind | FloatKind
 )
 
 // An structValue represents a JSON object.
@@ -483,6 +491,9 @@
 // are not concrete. For instance, it will return BottomKind for the bounds
 // >=0.
 func (v Value) Kind() Kind {
+	if v.path == nil {
+		return BottomKind
+	}
 	k := v.eval(v.ctx()).kind()
 	if k.isGround() {
 		switch {
@@ -490,6 +501,10 @@
 			return NullKind
 		case k.isAnyOf(boolKind):
 			return BoolKind
+		case k&numKind == (intKind):
+			return IntKind
+		case k&numKind == (floatKind):
+			return FloatKind
 		case k.isAnyOf(numKind):
 			return NumberKind
 		case k.isAnyOf(bytesKind):
@@ -516,8 +531,10 @@
 				vk |= NullKind
 			case boolKind:
 				vk |= BoolKind
-			case intKind, floatKind:
-				vk |= NumberKind
+			case intKind:
+				vk |= IntKind
+			case floatKind:
+				vk |= FloatKind
 			case stringKind:
 				vk |= StringKind
 			case bytesKind:
diff --git a/cue/types_test.go b/cue/types_test.go
index f7ddab0..3d199d7 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -97,8 +97,8 @@
 		incompleteKind: NumberKind,
 	}, {
 		value:          `2.0`,
-		kind:           NumberKind,
-		incompleteKind: NumberKind,
+		kind:           FloatKind,
+		incompleteKind: FloatKind,
 	}, {
 		value:          `2.0Mi`,
 		kind:           NumberKind,
@@ -114,7 +114,7 @@
 	}, {
 		value:          `float`,
 		kind:           BottomKind,
-		incompleteKind: NumberKind,
+		incompleteKind: FloatKind,
 	}, {
 		value:          `"str"`,
 		kind:           StringKind,
@@ -247,6 +247,7 @@
 		exp     int
 		fmt     byte
 		prec    int
+		kind    Kind
 		err     string
 	}{{
 		value:   "1",
@@ -255,6 +256,7 @@
 		exp:     0,
 		float64: 1,
 		fmt:     'g',
+		kind:    NumberKind,
 	}, {
 		value:   "-1",
 		float:   "-1",
@@ -262,6 +264,7 @@
 		exp:     0,
 		float64: -1,
 		fmt:     'g',
+		kind:    NumberKind,
 	}, {
 		value:   "1.0",
 		float:   "1.0",
@@ -269,6 +272,7 @@
 		exp:     -1,
 		float64: 1.0,
 		fmt:     'g',
+		kind:    FloatKind,
 	}, {
 		value:   "2.6",
 		float:   "2.6",
@@ -276,6 +280,7 @@
 		exp:     -1,
 		float64: 2.6,
 		fmt:     'g',
+		kind:    FloatKind,
 	}, {
 		value:   "20.600",
 		float:   "20.60",
@@ -284,6 +289,7 @@
 		float64: 20.60,
 		prec:    2,
 		fmt:     'f',
+		kind:    FloatKind,
 	}, {
 		value:   "1/0",
 		float:   "∞",
@@ -291,6 +297,7 @@
 		prec:    2,
 		fmt:     'f',
 		err:     ErrAbove.Error(),
+		kind:    FloatKind,
 	}, {
 		value:   "-1/0",
 		float:   "-∞",
@@ -298,6 +305,7 @@
 		prec:    2,
 		fmt:     'f',
 		err:     ErrBelow.Error(),
+		kind:    FloatKind,
 	}, {
 		value:   "1.797693134862315708145274237317043567982e+308",
 		float:   "1.8e+308",
@@ -307,6 +315,7 @@
 		prec:    2,
 		fmt:     'g',
 		err:     ErrAbove.Error(),
+		kind:    FloatKind,
 	}, {
 		value:   "-1.797693134862315708145274237317043567982e+308",
 		float:   "-1.8e+308",
@@ -315,6 +324,7 @@
 		float64: math.Inf(-1),
 		prec:    2,
 		fmt:     'g',
+		kind:    FloatKind,
 		err:     ErrBelow.Error(),
 	}, {
 		value:   "4.940656458412465441765687928682213723650e-324",
@@ -324,6 +334,7 @@
 		float64: 0,
 		prec:    4,
 		fmt:     'g',
+		kind:    FloatKind,
 		err:     ErrBelow.Error(),
 	}, {
 		value:   "-4.940656458412465441765687928682213723650e-324",
@@ -333,12 +344,13 @@
 		float64: 0,
 		prec:    -1,
 		fmt:     'g',
+		kind:    FloatKind,
 		err:     ErrAbove.Error(),
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.value, func(t *testing.T) {
 			n := getInstance(t, tc.value).Value()
-			if n.Kind() != NumberKind {
+			if n.Kind() != tc.kind {
 				t.Fatal("Not a number")
 			}