cue: make integer literals integer only

Most of the spec was already updated.
This avoids a lot of unintuitive behavior where
stemming from the fact int did not subsume 1.

Dealing with floats is now more cumbersome,
but users can use the more convenient number.
It may be worth it renaming int to integer or to
rename number to num to be consistent. But
maybe the naming is okay as is.

Change-Id: Iaf58f131a2507e9b7c1856bdf65cef5aa10998ad
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1982
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
index d16a8f3..c08a05b 100644
--- a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
+++ b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
@@ -1,4 +1,4 @@
-unsupported op &(number, (string)*):
+unsupported op &(int, (string)*):
     $CWD/testdata/tasks/task_tool.cue:29:9
     tool/cli:4:9
     
diff --git a/cmd/cue/cmd/testdata/trim/trim.cue b/cmd/cue/cmd/testdata/trim/trim.cue
index 7a4b264..9d1c203 100644
--- a/cmd/cue/cmd/testdata/trim/trim.cue
+++ b/cmd/cue/cmd/testdata/trim/trim.cue
@@ -32,9 +32,6 @@
 	e: string
 	f: ">> here <<"
 
-	// The template does not require that this field be an integer (it may be
-	// a float), and thus this field specified additional information and
-	// cannot be removed.
 	n: int
 
 	struct: {a: 3.0}
diff --git a/cmd/cue/cmd/testdata/trim/trim.out b/cmd/cue/cmd/testdata/trim/trim.out
index 63b5cc2..61353fe 100644
--- a/cmd/cue/cmd/testdata/trim/trim.out
+++ b/cmd/cue/cmd/testdata/trim/trim.out
@@ -28,11 +28,6 @@
 	b:      "foo"
 	c:      45
 
-	// The template does not require that this field be an integer (it may be
-	// a float), and thus this field specified additional information and
-	// cannot be removed.
-	n: int
-
 	sList: [{b: "foo"}, {}]
 }
 
diff --git a/cue/ast.go b/cue/ast.go
index 3ff7483..d2bed29 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -433,7 +433,7 @@
 		list := &list{baseValue: newExpr(n), elem: s}
 		list.initLit()
 		if n.Ellipsis != token.NoPos || n.Type != nil {
-			list.len = &bound{list.baseValue, opGeq, list.len}
+			list.len = newBound(list.baseValue, opGeq, intKind, list.len)
 			if n.Type != nil {
 				list.typ = v.walk(n.Type)
 			}
@@ -482,11 +482,12 @@
 			}
 		case token.GEQ, token.GTR, token.LSS, token.LEQ,
 			token.NEQ, token.MAT, token.NMAT:
-			value = &bound{
+			value = newBound(
 				newExpr(n),
 				tokenMap[n.Op],
+				topKind|nonGround,
 				v.walk(n.X),
-			}
+			)
 
 		case token.MUL:
 			return v.error(n, "preference mark not allowed at this position")
diff --git a/cue/binop.go b/cue/binop.go
index 82bb1c9..0b1831b 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -304,11 +304,11 @@
 		}
 		switch y := other.(type) {
 		case *basicType:
-			v := unify(ctx, src, xv, y)
-			if v == xv {
+			k := unifyType(x.k, y.kind())
+			if k == x.k {
 				return x
 			}
-			return &bound{newSrc.base(), x.op, v}
+			return newBound(newSrc.base(), x.op, k, xv)
 
 		case *bound:
 			yv := y.value.(evaluated)
@@ -770,7 +770,7 @@
 
 func (x *numLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
 	switch y := other.(type) {
-	case *basicType:
+	case *basicType, *bound: // for better error reporting
 		if op == opUnify {
 			return y.binOp(ctx, src, op, x)
 		}
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 3b617ab..83cd80d 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -94,7 +94,7 @@
 		`"Hello World!"`,
 	}, {
 		test("strings", `strings.Join([1, 2], " ")`),
-		`_|_(<0>.Join ([1,2]," "):list element 1: not of right kind (number vs string))`,
+		`_|_(<0>.Join ([1,2]," "):list element 1: not of right kind (int vs string))`,
 	}, {
 		test("math/bits", `bits.Or(0x8, 0x1)`),
 		`9`,
diff --git a/cue/debug.go b/cue/debug.go
index 718040a..9863631 100644
--- a/cue/debug.go
+++ b/cue/debug.go
@@ -333,6 +333,12 @@
 	case *durationLit:
 		write(x.d.String())
 	case *bound:
+		switch x.k & numKind {
+		case intKind:
+			p.writef("int & ")
+		case floatKind:
+			p.writef("float & ")
+		}
 		p.writef("%v", x.op)
 		p.debugStr(x.value)
 	case *interpolation:
diff --git a/cue/eval.go b/cue/eval.go
index f627cee..681b21d 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -181,7 +181,7 @@
 	if v == x.value {
 		return x
 	}
-	return &bound{x.baseValue, x.op, v}
+	return newBound(x.baseValue, x.op, x.k, v)
 }
 
 func (x *interpolation) evalPartial(ctx *context) (result evaluated) {
diff --git a/cue/go_test.go b/cue/go_test.go
index a2f1d92..8bb6907 100644
--- a/cue/go_test.go
+++ b/cue/go_test.go
@@ -145,7 +145,7 @@
 			F *big.Float
 		}{},
 		// TODO: indicate that B is explicitly an int only.
-		`<0>{A: ((>=-9223372036854775808 & <=9223372036854775807) & (>=0 & <100)), ` +
+		`<0>{A: ((int & >=-9223372036854775808 & int & <=9223372036854775807) & (>=0 & <100)), ` +
 			`B: >=0, ` +
 			`C?: _, ` +
 			`D: int, ` +
@@ -160,7 +160,7 @@
 			L []byte
 			T time.Time
 		}{},
-		`(*null | <0>{A: ((>=-32768 & <=32767) & (>=0 & <100)), ` +
+		`(*null | <0>{A: ((int & >=-32768 & int & <=32767) & (>=0 & <100)), ` +
 			`C: string, ` +
 			`D: bool, ` +
 			`F: float, ` +
@@ -174,9 +174,9 @@
 			C int8 `cue:"A+B"`
 		}{},
 		// TODO: should B be marked as optional?
-		`<0>{A: ((>=-128 & <=127) & (<0>.C - <0>.B)), ` +
-			`B?: ((>=-128 & <=127) & (<0>.C - <0>.A)), ` +
-			`C: ((>=-128 & <=127) & (<0>.A + <0>.B))}`,
+		`<0>{A: ((int & >=-128 & int & <=127) & (<0>.C - <0>.B)), ` +
+			`B?: ((int & >=-128 & int & <=127) & (<0>.C - <0>.A)), ` +
+			`C: ((int & >=-128 & int & <=127) & (<0>.A + <0>.B))}`,
 	}, {
 		[]string{},
 		`(*null | [, ...string])`,
@@ -188,7 +188,7 @@
 		`(*null | ` +
 			`<0>{<>: <1>(_: string)-><2>{` +
 			`A?: (*null | ` +
-			`<3>{<>: <4>(_: string)->(>=0 & <=18446744073709551615), })}, })`,
+			`<3>{<>: <4>(_: string)->(int & >=0 & int & <=18446744073709551615), })}, })`,
 	}, {
 		map[float32]int{},
 		`_|_(type float32 not supported as key type)`,
diff --git a/cue/kind.go b/cue/kind.go
index fdc4aed..b8b3b37 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -191,7 +191,14 @@
 	if valBits == bottomKind {
 		k := nullKind
 		switch op {
+		case opLss, opLeq, opGtr, opGeq:
+			if a.isAnyOf(numKind) && b.isAnyOf(numKind) {
+				return boolKind, false
+			}
 		case opEql, opNeq:
+			if a.isAnyOf(numKind) && b.isAnyOf(numKind) {
+				return boolKind, false
+			}
 			fallthrough
 		case opUnify:
 			if a&nullKind != 0 {
@@ -201,6 +208,10 @@
 				return k, true
 			}
 			return bottomKind, false
+		case opRem, opQuo, opMul, opAdd, opSub:
+			if a.isAnyOf(numKind) && b.isAnyOf(numKind) {
+				return floatKind, false
+			}
 		}
 		if op == opMul {
 			if a.isAnyOf(listKind|stringKind|bytesKind) && b.isAnyOf(intKind) {
@@ -266,11 +277,11 @@
 			return u&scalarKinds | catBits, false
 		}
 	case opRem:
-		if u.isAnyOf(floatKind) {
+		if u.isAnyOf(numKind) {
 			return floatKind | catBits, false
 		}
 	case opQuo:
-		if u.isAnyOf(floatKind) {
+		if u.isAnyOf(numKind) {
 			return floatKind | catBits, false
 		}
 	case opIRem, opIMod:
diff --git a/cue/kind_test.go b/cue/kind_test.go
index 6483a90..ac5a76a 100644
--- a/cue/kind_test.go
+++ b/cue/kind_test.go
@@ -39,7 +39,7 @@
 		op:   opMul,
 		a:    floatKind,
 		b:    intKind,
-		want: bottomKind,
+		want: floatKind,
 	}, {
 		op:   opMul,
 		a:    listKind,
diff --git a/cue/lit.go b/cue/lit.go
index 74e3ccd..8e5b3e0 100644
--- a/cue/lit.go
+++ b/cue/lit.go
@@ -306,7 +306,7 @@
 			mul |= mulDec
 		}
 		n := &numLit{
-			numBase: newNumBase(p.node, newNumInfo(numKind, mul, 10, p.useSep)),
+			numBase: newNumBase(p.node, newNumInfo(intKind, mul, 10, p.useSep)),
 		}
 		n.v.UnmarshalText(p.buf)
 		p.ctx.Mul(&n.v, &n.v, mulToRat[mul])
@@ -335,7 +335,7 @@
 		f.v.UnmarshalText(p.buf)
 		return f
 	}
-	i := &numLit{numBase: newNumBase(p.node, newNumInfo(numKind, 0, base, p.useSep))}
+	i := &numLit{numBase: newNumBase(p.node, newNumInfo(intKind, 0, base, p.useSep))}
 	i.v.Coeff.SetString(string(p.buf), base)
 	return i
 }
diff --git a/cue/lit_test.go b/cue/lit_test.go
index 5c3c7f7..6ad6905 100644
--- a/cue/lit_test.go
+++ b/cue/lit_test.go
@@ -25,7 +25,7 @@
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
-var defIntBase = newNumBase(&ast.BasicLit{}, newNumInfo(numKind, 0, 10, false))
+var defIntBase = newNumBase(&ast.BasicLit{}, newNumInfo(intKind, 0, 10, false))
 var defRatBase = newNumBase(&ast.BasicLit{}, newNumInfo(floatKind, 0, 10, false))
 
 func mkInt(a int64) *numLit {
@@ -79,12 +79,12 @@
 func TestLiterals(t *testing.T) {
 	mkMul := func(x int64, m multiplier, base int) *numLit {
 		return &numLit{
-			newNumBase(&ast.BasicLit{}, newNumInfo(numKind, m, base, false)),
+			newNumBase(&ast.BasicLit{}, newNumInfo(intKind, m, base, false)),
 			mkBigInt(x),
 		}
 	}
 	hk := &numLit{
-		newNumBase(&ast.BasicLit{}, newNumInfo(numKind, 0, 10, true)),
+		newNumBase(&ast.BasicLit{}, newNumInfo(intKind, 0, 10, true)),
 		mkBigInt(100000),
 	}
 	testCases := []struct {
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 82e9cf6..6cb4050 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -137,7 +137,7 @@
 			`s1: =~"c", ` +
 			`s2: (!="b" & =~"[a-z]"), ` +
 
-			`e1: _|_(("foo" =~ 1):unsupported op =~(string, number)), ` +
+			`e1: _|_(("foo" =~ 1):unsupported op =~(string, int)), ` +
 			`e2: _|_(("foo" !~ true):unsupported op !~(string, bool)), ` +
 			`e3: _|_((!="a" & <5):unsupported op &((string)*, (number)*))}`,
 	}, {
@@ -159,10 +159,11 @@
 			v5: i1 div i2
 
 			e0: 2 + "a"
-			e1: 2.0 / i1
-			e2: i1 / 2.0
-			e3: 3.0 % i2
-			e4: i1 % 2.0
+			// these are now all alloweed
+			// e1: 2.0 / i1
+			// e2: i1 / 2.0
+			// e3: 3.0 % i2
+			// e4: i1 % 2.0
 			e5: 1.0 div 2
 			e6: 2 rem 2.0
 			e7: 2 quo 2.0
@@ -180,15 +181,15 @@
 			`v4: 2.0, ` +
 			`v5: 0, ` +
 
-			`e0: _|_((2 + "a"):unsupported op +(number, string)), ` +
-			`e1: _|_((2.0 / 1):unsupported op /(float, int)), ` +
-			`e2: _|_((1 / 2.0):unsupported op /(int, float)), ` +
-			`e3: _|_((3.0 % 2):unsupported op %(float, int)), ` +
-			`e4: _|_((1 % 2.0):unsupported op %(int, float)), ` +
-			`e5: _|_((1.0 div 2):unsupported op div(float, number)), ` +
-			`e6: _|_((2 rem 2.0):unsupported op rem(number, float)), ` +
-			`e7: _|_((2 quo 2.0):unsupported op quo(number, float)), ` +
-			`e8: _|_((1.0 mod 1):unsupported op mod(float, number))}`,
+			`e0: _|_((2 + "a"):unsupported op +(int, string)), ` +
+			// `e1: _|_((2.0 / 1):unsupported op /(float, int)), ` +
+			// `e2: _|_((1 / 2.0):unsupported op /(int, float)), ` +
+			// `e3: _|_((3.0 % 2):unsupported op %(float, int)), ` +
+			// `e4: _|_((1 % 2.0):unsupported op %(int, float)), ` +
+			`e5: _|_((1.0 div 2):unsupported op div(float, int)), ` +
+			`e6: _|_((2 rem 2.0):unsupported op rem(int, float)), ` +
+			`e7: _|_((2 quo 2.0):unsupported op quo(int, float)), ` +
+			`e8: _|_((1.0 mod 1):unsupported op mod(float, int))}`,
 	}, {
 		desc: "integer-specific arithmetic",
 		in: `
@@ -223,17 +224,17 @@
 			// TODO: handle divide by zero
 			`,
 		out: `<0>{q1: 2, q2: -2, q3: -2, q4: 2, ` +
-			`qe1: _|_((2.0 quo 1):unsupported op quo(float, number)), ` +
-			`qe2: _|_((2 quo 1.0):unsupported op quo(number, float)), ` +
+			`qe1: _|_((2.0 quo 1):unsupported op quo(float, int)), ` +
+			`qe2: _|_((2 quo 1.0):unsupported op quo(int, float)), ` +
 			`r1: 1, r2: 1, r3: -1, r4: -1, re1: ` +
-			`_|_((2.0 rem 1):unsupported op rem(float, number)), ` +
-			`re2: _|_((2 rem 1.0):unsupported op rem(number, float)), ` +
+			`_|_((2.0 rem 1):unsupported op rem(float, int)), ` +
+			`re2: _|_((2 rem 1.0):unsupported op rem(int, float)), ` +
 			`d1: 2, d2: -2, d3: -3, d4: 3, ` +
-			`de1: _|_((2.0 div 1):unsupported op div(float, number)), ` +
-			`de2: _|_((2 div 1.0):unsupported op div(number, float)), ` +
+			`de1: _|_((2.0 div 1):unsupported op div(float, int)), ` +
+			`de2: _|_((2 div 1.0):unsupported op div(int, float)), ` +
 			`m1: 1, m2: 1, m3: 1, m4: 1, ` +
-			`me1: _|_((2.0 mod 1):unsupported op mod(float, number)), ` +
-			`me2: _|_((2 mod 1.0):unsupported op mod(number, float))}`,
+			`me1: _|_((2.0 mod 1):unsupported op mod(float, int)), ` +
+			`me2: _|_((2 mod 1.0):unsupported op mod(int, float))}`,
 	}, {
 		desc: "booleans",
 		in: `
@@ -337,7 +338,7 @@
 			e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8]
 			e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5]
 			`,
-		out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, number)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: [1,2,4,_|_((<=5 & 8):8 not within bound <=5)], e5: [1,2,4,_|_((<=5 & 8):8 not within bound <=5)]}`,
+		out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, int)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: [1,2,4,_|_((<=5 & 8):8 not within bound <=5)], e5: [1,2,4,_|_((<=5 & 8):8 not within bound <=5)]}`,
 	}, {
 		desc: "list arithmetic",
 		in: `
@@ -371,7 +372,7 @@
 			g: {a: 1}["b"]
 			h: [3].b
 			`,
-		out: `<0>{obj: <1>{a: 1, b: 2}, index: 2, mulidx: 3, e: _|_(4:invalid struct index 4 (type number)), f: _|_(<2>{a: 1}.b:undefined field "b"), g: _|_(<3>{a: 1}["b"]:undefined field "b"), h: _|_([3]:invalid operation: [3].b (type list does not support selection))}`,
+		out: `<0>{obj: <1>{a: 1, b: 2}, index: 2, mulidx: 3, e: _|_(4:invalid struct index 4 (type int)), f: _|_(<2>{a: 1}.b:undefined field "b"), g: _|_(<3>{a: 1}["b"]:undefined field "b"), h: _|_([3]:invalid operation: [3].b (type list does not support selection))}`,
 	}, {
 		desc: "obj unify",
 		in: `
@@ -384,7 +385,7 @@
 			e: 1                       // 1 & {a:3}
 			e: {a:3}
 			`,
-		out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):unsupported op &(number, struct))}",
+		out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):unsupported op &(int, struct))}",
 	}, {
 		desc: "disjunctions",
 		in: `
@@ -436,7 +437,7 @@
 			p: +true
 			m: -false
 		`,
-		out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):unsupported op &((int)*, (string)*)), e2: _|_((1 & string):unsupported op &(number, (string)*)), b: _|_(!int:unary '!' requires bool value, found (int)*), p: _|_(+true:unary '+' requires numeric value, found bool), m: _|_(-false:unary '-' requires numeric value, found bool)}`,
+		out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):unsupported op &((int)*, (string)*)), e2: _|_((1 & string):unsupported op &(int, (string)*)), b: _|_(!int:unary '!' requires bool value, found (int)*), p: _|_(+true:unary '+' requires numeric value, found bool), m: _|_(-false:unary '-' requires numeric value, found bool)}`,
 	}, {
 		desc: "comparison",
 		in: `
@@ -450,7 +451,7 @@
 			seq: "a" + "b" == "ab"
 			err: 2 == "s"
 		`,
-		out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):unsupported op ==(number, string))}`,
+		out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):unsupported op ==(int, string))}`,
 	}, {
 		desc: "null",
 		in: `
@@ -593,7 +594,7 @@
 				e1: 2.0 % (3&int)
 				e2: int & 4.0/2.0
 				`,
-		out: `<0>{v1: 5e+11, v2: true, n1: 1, v5: 2, e1: _|_((2.0 % 3):unsupported op %(float, int)), e2: _|_((int & 2):unsupported op &((int)*, float))}`,
+		out: `<0>{v1: 5e+11, v2: true, n1: 1, v5: 2, e1: 2.0, e2: _|_((int & 2):unsupported op &((int)*, float))}`,
 	}, {
 		desc: "inequality",
 		in: `
@@ -662,7 +663,7 @@
 
 			s20: >=10 & <=10             // 10
 
-			s22:  >5 & <=6                // no simplification
+			s22:  >5 & <=6               // no simplification
 			s22a: >5 & (<=6 & int)       // 6
 			s22b: (int & >5) & <=6       // 6
 			s22c: >=5 & (<6 & int)       // 5
@@ -722,7 +723,7 @@
 			`s23d: 1, ` +
 			`s23e: (>0.0 & <2.0), ` +
 
-			`s30: >0, ` +
+			`s30: int & >0, ` +
 
 			`e1: _|_((!=null & null):null excluded by !=null), ` +
 			`e2: _|_((!=null & null):null excluded by !=null), ` +
@@ -764,7 +765,7 @@
 			e5: [1,2,3][-1]
 			e6: (*[]|{})[1]
 		`,
-		out: `<0>{a: 2, b: "bar", c: _|_("3":invalid list index "3" (type string)), l: [], d: _|_([]:index 0 out of bounds), e1: _|_("":invalid list index "" (type string)), e2: _|_(2:invalid operation: 2[2] (type number does not support indexing)), e3: _|_(true:invalid list index true (type bool)), e4: _|_([1,2,3]:index 3 out of bounds), e5: _|_(-1:invalid list index -1 (index must be non-negative)), e6: _|_([]:index 1 out of bounds)}`,
+		out: `<0>{a: 2, b: "bar", c: _|_("3":invalid list index "3" (type string)), l: [], d: _|_([]:index 0 out of bounds), e1: _|_("":invalid list index "" (type string)), e2: _|_(2:invalid operation: 2[2] (type int does not support indexing)), e3: _|_(true:invalid list index true (type bool)), e4: _|_([1,2,3]:index 3 out of bounds), e5: _|_(-1:invalid list index -1 (index must be non-negative)), e6: _|_([]:index 1 out of bounds)}`,
 	}, {
 		desc: "string index",
 		in: `
@@ -800,7 +801,7 @@
 			e7: [2][:"9"]
 
 		`,
-		out: `<0>{a: [], b: [], e1: _|_(1:slice bounds out of range), e2: _|_([0]:negative slice index), e3: _|_([0]:invalid slice index: 1 > 0), e4: _|_(2:slice bounds out of range), e5: _|_(4:cannot slice 4 (type number)), e6: _|_("":invalid slice index "" (type string)), e7: _|_("9":invalid slice index "9" (type string))}`,
+		out: `<0>{a: [], b: [], e1: _|_(1:slice bounds out of range), e2: _|_([0]:negative slice index), e3: _|_([0]:invalid slice index: 1 > 0), e4: _|_(2:slice bounds out of range), e5: _|_(4:cannot slice 4 (type int)), e6: _|_("":invalid slice index "" (type string)), e7: _|_("9":invalid slice index "9" (type string))}`,
 	}, {
 		desc: "string slice",
 		in: `
@@ -1047,7 +1048,7 @@
 		in: `
 			a: "a" & 1
 			`,
-		out: `<0>{a: _|_(("a" & 1):unsupported op &(string, number))}`,
+		out: `<0>{a: _|_(("a" & 1):unsupported op &(string, int))}`,
 	}, {
 		desc: "structs",
 		in: `
@@ -1185,7 +1186,7 @@
 				k: 1
 			}
 			b: {
-				<x>: { x: 0, y: 1 | int }
+				<x>: { x: 0, y: *1 | int }
 				v: {}
 				w: { x: 0 }
 			}
@@ -1196,7 +1197,7 @@
 				bar: _
 			}
 			`,
-		out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (1 | int)} & <6>{}), v: <7>{x: 0, y: (1 | int)}, w: <8>{x: 0, y: (1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
+		out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: (*1 | int)}, w: <8>{x: 0, y: (*1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
 	}, {
 		desc: "range unification",
 		in: `
@@ -1257,8 +1258,8 @@
 			`a8: 5, ` +
 
 			// TODO: improve error
-			`a9: _|_((6 & <=5):unsupported op &(number, (number)*)), ` +
-			`a10: _|_((0 & >=1):unsupported op &(number, (number)*)), ` +
+			`a9: _|_((<=5 & 6):6 not within bound <=5), ` +
+			`a10: _|_((>=1 & 0):0 not within bound >=1), ` +
 
 			`b1: (>=1 & <=5), ` +
 			`b2: 1, ` +
@@ -1274,14 +1275,14 @@
 			`b12: (>=3 & <=5), ` +
 			`b13: 5, ` +
 			`b14: _|_(incompatible bounds >=6 and <=5), ` +
-			`c1: (>=1 & <=5), ` +
-			`c2: (<=5 & >=1), ` +
+			`c1: (int & >=1 & <=5), ` +
+			`c2: (<=5 & int & >=1), ` +
 			`c3: _|_((string & >=1):unsupported op &((string)*, (number)*)), ` +
 			`c4: _|_(((>=1 & <=5) & string):unsupported op &((number)*, (string)*)), ` +
 			`s1: "e", ` +
 			`s2: "ee", ` +
 			`n1: (>=1 & <=2), ` +
-			`n2: _|_((int & >=1.1):unsupported op &((int)*, (float)*)), ` +
+			`n2: (int & >=1.1 & <=1.3), ` +
 			`n3: 2, ` +
 			`n4: 0.09999, ` +
 			`n5: 2.5}`,
@@ -1298,7 +1299,7 @@
 			e1: 100_000
 		`,
 		out: `<0>{k1: 44, k2: -8000000000, ` +
-			`e1: _|_((<=32767 & 100000):100000 not within bound <=32767)}`,
+			`e1: _|_((int & <=32767 & 100000):100000 not within bound int & <=32767)}`,
 	}, {
 		desc: "field comprehensions",
 		in: `
@@ -1545,7 +1546,7 @@
 		in: `
 				a: 8000.9
 				a: 7080 | int`,
-		out: `<0>{a: _|_((8000.9 & (7080 | int)):empty disjunction: cannot unify numbers 7080 and 8000.9)}`,
+		out: `<0>{a: _|_((8000.9 & int):unsupported op &(float, (int)*))}`,
 	}, {
 		desc: "resolve all disjunctions",
 		in: `
@@ -1710,10 +1711,10 @@
 		desc: "normalization",
 		in: `
 			a: string | string
-			b: *1 | *int  // 1 == int(1) | float(1)
+			b: *1 | *int
 			c: *1.0 | *float
 		`,
-		out: `<0>{a: string, b: _|_((*1 | *int):more than one default remaining (1 and int)), c: float}`,
+		out: `<0>{a: string, b: int, c: float}`,
 	}, {
 		desc: "default disambiguation",
 		in: `
@@ -1808,9 +1809,9 @@
 		MyIP: Inst & [_, _, 10, 10 ]
 		`,
 		out: `<0>{` +
-			`IP: [(>=0 & <=255),(>=0 & <=255),(>=0 & <=255),(>=0 & <=255)], ` +
-			`Private: [192,168,(>=0 & <=255),(>=0 & <=255)], ` +
-			`Inst: [10,10,(>=0 & <=255),(>=0 & <=255)], ` +
+			`IP: [(int & >=0 & int & <=255),(int & >=0 & int & <=255),(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
+			`Private: [192,168,(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
+			`Inst: [10,10,(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
 			`MyIP: [10,10,10,10]` +
 			`}`,
 	}, {
diff --git a/cue/rewrite.go b/cue/rewrite.go
index 9515d3c..eda68c4 100644
--- a/cue/rewrite.go
+++ b/cue/rewrite.go
@@ -88,7 +88,7 @@
 	if v == x.value {
 		return x
 	}
-	return &bound{x.baseValue, x.op, v}
+	return newBound(x.baseValue, x.op, x.k, v)
 }
 
 func (x *interpolation) rewrite(ctx *context, fn rewriteFunc) value {
diff --git a/cue/subsume_test.go b/cue/subsume_test.go
index e5f3f8c..0a7d79a 100644
--- a/cue/subsume_test.go
+++ b/cue/subsume_test.go
@@ -112,9 +112,9 @@
 		44: {subsumes: true, in: `a: 1.0, b: 1.0 `},
 		45: {subsumes: true, in: `a: 3.0, b: 3.0 `},
 		46: {subsumes: false, in: `a: 1.0, b: 1 `},
-		47: {subsumes: true, in: `a: 1, b: 1.0 `},
-		48: {subsumes: true, in: `a: 3, b: 3.0`},
-		49: {subsumes: false, in: `a: int, b: 1`},
+		47: {subsumes: false, in: `a: 1, b: 1.0 `},
+		48: {subsumes: false, in: `a: 3, b: 3.0`},
+		49: {subsumes: true, in: `a: int, b: 1`},
 		50: {subsumes: true, in: `a: int, b: int & 1`},
 		51: {subsumes: true, in: `a: float, b: 1.0`},
 		52: {subsumes: false, in: `a: float, b: 1`},
@@ -246,7 +246,7 @@
 		150: {subsumes: false, in: `a: number | *1, b: number | *2`},
 		151: {subsumes: true, in: `a: number | *2, b: number | *2`},
 		152: {subsumes: true, in: `a: int | *float, b: int | *2.0`},
-		153: {subsumes: true, in: `a: int | *2, b: int | *2.0`},
+		153: {subsumes: false, in: `a: int | *2, b: int | *2.0`},
 		154: {subsumes: true, in: `a: number | *2 | *3, b: number | *2`},
 		155: {subsumes: true, in: `a: number, b: number | *2`},
 
diff --git a/cue/types_test.go b/cue/types_test.go
index 0429e90..06d92fc 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -102,8 +102,8 @@
 		incompleteKind: BoolKind,
 	}, {
 		value:          `2`,
-		kind:           NumberKind,
-		incompleteKind: NumberKind,
+		kind:           IntKind,
+		incompleteKind: IntKind,
 		concrete:       true,
 	}, {
 		value:          `2.0`,
@@ -112,13 +112,13 @@
 		concrete:       true,
 	}, {
 		value:          `2.0Mi`,
-		kind:           NumberKind,
-		incompleteKind: NumberKind,
+		kind:           IntKind,
+		incompleteKind: IntKind,
 		concrete:       true,
 	}, {
 		value:          `14_000`,
-		kind:           NumberKind,
-		incompleteKind: NumberKind,
+		kind:           IntKind,
+		incompleteKind: IntKind,
 		concrete:       true,
 	}, {
 		value:          `>=0 & <5`,
@@ -280,7 +280,7 @@
 		exp:     0,
 		float64: 1,
 		fmt:     'g',
-		kind:    NumberKind,
+		kind:    IntKind,
 	}, {
 		value:   "-1",
 		float:   "-1",
@@ -288,7 +288,7 @@
 		exp:     0,
 		float64: -1,
 		fmt:     'g',
-		kind:    NumberKind,
+		kind:    IntKind,
 	}, {
 		value:   "1.0",
 		float:   "1.0",
@@ -727,7 +727,7 @@
 		length: "2",
 	}, {
 		input:  "[1, 3, ...]",
-		length: ">=2",
+		length: "int & >=2",
 	}, {
 		input:  `"foo"`,
 		length: "3",
@@ -740,7 +740,7 @@
 		// 	length: "2",
 	}, {
 		input:  "3",
-		length: "_|_(3:len not supported for type 24)",
+		length: "_|_(3:len not supported for type 8)",
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.input, func(t *testing.T) {
@@ -1030,7 +1030,7 @@
 	}, {
 		config: config,
 		path:   strList("b", "d", "lookup in non-struct"),
-		str:    "not of right kind (number vs struct)",
+		str:    "not of right kind (int vs struct)",
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.str, func(t *testing.T) {
diff --git a/cue/value.go b/cue/value.go
index 19541f9..36268f4 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -393,21 +393,28 @@
 
 type bound struct {
 	baseValue
-	op    op // opNeq, opLss, opLeq, opGeq, or opGtr
+	op    op   // opNeq, opLss, opLeq, opGeq, or opGtr
+	k     kind // mostly used for number kind
 	value value
 }
 
-func (x *bound) kind() kind {
-	k := x.value.kind()
-	if x.op == opNeq && k&atomKind == nullKind {
-		k = typeKinds &^ nullKind
+func newBound(base baseValue, op op, k kind, v value) *bound {
+	kv := v.kind()
+	if kv.isAnyOf(numKind) {
+		kv |= numKind
+	} else if op == opNeq && kv&atomKind == nullKind {
+		kv = typeKinds &^ nullKind
 	}
-	return k | nonGround
+	return &bound{base, op, unifyType(k&topKind, kv) | nonGround, v}
+}
+
+func (x *bound) kind() kind {
+	return x.k
 }
 
 func mkIntRange(a, b string) evaluated {
-	from := &bound{op: opGeq, value: parseInt(intKind, a)}
-	to := &bound{op: opLeq, value: parseInt(intKind, b)}
+	from := newBound(baseValue{}, opGeq, intKind, parseInt(intKind, a))
+	to := newBound(baseValue{}, opLeq, intKind, parseInt(intKind, b))
 	e := &unification{
 		binSrc(token.NoPos, opUnify, from, to),
 		[]evaluated{from, to},
@@ -422,8 +429,8 @@
 }
 
 func mkFloatRange(a, b string) evaluated {
-	from := &bound{op: opGeq, value: parseFloat(a)}
-	to := &bound{op: opLeq, value: parseFloat(b)}
+	from := newBound(baseValue{}, opGeq, numKind, parseFloat(a))
+	to := newBound(baseValue{}, opLeq, numKind, parseFloat(b))
 	e := &unification{
 		binSrc(token.NoPos, opUnify, from, to),
 		[]evaluated{from, to},
@@ -449,7 +456,7 @@
 
 	// Do not include an alias for "byte", as it would be too easily confused
 	// with the builtin "bytes".
-	"uint":    &bound{op: opGeq, value: parseInt(intKind, "0")},
+	"uint":    newBound(baseValue{}, opGeq, intKind, parseInt(intKind, "0")),
 	"uint8":   mkIntRange("0", "255"),
 	"uint16":  mkIntRange("0", "65535"),
 	"uint32":  mkIntRange("0", "4294967295"),
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 2dbc8ac..5263f29 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -1934,6 +1934,7 @@
 - Integer values are comparable and ordered, in the usual way.
 - Floating-point values are comparable and ordered, as per the definitions
   for binary coded decimals in the IEEE-754-2008 standard.
+- Floating point numbers may be compared with integers.
 - String values are comparable and ordered, lexically byte-wise after
   normalization to Unicode normal form NFC.
 - Struct are not comparable.
@@ -1948,6 +1949,7 @@
 
 ```
 3 < 4       // true
+3 < 4.0     // true
 null == 2   // false
 null != {}  // true
 {} == {}    // _|_: structs are not comparable against structs