cue: fixes number groundness inference

When a integer was inferred from restricting
comparators with a difference greater than
1.

Also implemented restricting float ranges
when unified with integers.

Fixes #108

Change-Id: Ie64a0670392463cc5340ee9f3771ded9c6f2a15d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3301
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/binop.go b/cue/binop.go
index 2d0f849..0395753 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -394,8 +394,28 @@
 					break
 				}
 
-				var d apd.Decimal
-				cond, err := apd.BaseContext.Sub(&d, &b.v, &a.v)
+				var d, lo, hi apd.Decimal
+				lo.Set(&a.v)
+				hi.Set(&b.v)
+				if k&floatKind == 0 {
+					// Readjust bounds for integers.
+					if x.op == opGeq {
+						// >=3.4  ==>  >=4
+						_, _ = apd.BaseContext.Ceil(&lo, &a.v)
+					} else {
+						// >3.4  ==>  >3
+						_, _ = apd.BaseContext.Floor(&lo, &a.v)
+					}
+					if y.op == opLeq {
+						// <=2.3  ==>  <= 2
+						_, _ = apd.BaseContext.Floor(&hi, &b.v)
+					} else {
+						// <2.3   ==>  < 3
+						_, _ = apd.BaseContext.Ceil(&hi, &b.v)
+					}
+				}
+
+				cond, err := apd.BaseContext.Sub(&d, &hi, &lo)
 				if cond.Inexact() || err != nil {
 					break
 				}
@@ -432,25 +452,28 @@
 				case diff == 1:
 					if k&floatKind == 0 {
 						if x.op == opGeq && y.op == opLss {
-							return a
+							return &numLit{numBase: a.numBase, v: lo}
 						}
 						if x.op == opGtr && y.op == opLeq {
-							return b
+							return &numLit{numBase: b.numBase, v: hi}
 						}
 					}
 
 				case diff == 2:
 					if k&floatKind == 0 && x.op == opGtr && y.op == opLss {
-						_, _ = apd.BaseContext.Add(&d, d.SetInt64(1), &a.v)
+						_, _ = apd.BaseContext.Add(&d, d.SetInt64(1), &lo)
 						n := *a
-						n.k = k
-						n.v = d
+						n.k = k & numKind
+						n.v.Set(&d)
 						return &n
 					}
 
 				case diff == 0:
 					if x.op == opGeq && y.op == opLeq {
-						return a
+						n := *a
+						n.k = k & numKind
+						n.v.Set(&lo)
+						return &n
 					}
 					fallthrough
 
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 4697e85..6239022 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -763,6 +763,34 @@
 			`e8: _|_(conflicting bounds >11 and <=11), ` +
 			`e9: _|_((>"a" & <1):conflicting values >"a" and <1 (mismatched types string and number))}`,
 	}, {
+		desc: "bound conversions",
+		in: `
+		r1: int & >0.1 & <1.9
+		r2: int & >=0.1 & <1.9
+		r3: int & >=-1.9 & <=-0.1
+		r4: int & >-1.9 & <=-0.1
+
+		r5: >=1.1 & <=1.1
+		r6: r5 & 1.1
+
+		c1: (1.2 & >1.3) & <2
+		c2: 1.2 & (>1.3 & <2)
+
+		c3: 1.2 & (>=1 & <2)
+		c4: 1.2 & (>=1 & <2 & int)
+		`,
+		out: `<0>{` +
+			`r1: 1, ` +
+			`r2: 1, ` +
+			`r3: -1, ` +
+			`r4: -1, ` +
+			`r5: 1.1, ` +
+			`r6: 1.1, ` +
+			`c1: _|_((>1.3 & 1.2):invalid value 1.2 (out of bound >1.3)), ` +
+			`c2: _|_((>1.3 & 1.2):invalid value 1.2 (out of bound >1.3)), ` +
+			`c3: 1.2, ` +
+			`c4: _|_((1.2 & ((>=1 & <2) & int)):conflicting values 1.2 and ((>=1 & <2) & int) (mismatched types float and int))}`,
+	}, {
 		desc: "custom validators",
 		in: `
 		import "strings"
@@ -1552,7 +1580,7 @@
 			`s1: "e", ` +
 			`s2: "ee", ` +
 			`n1: (>=1 & <=2), ` +
-			`n2: (int & >=1.1 & <=1.3), ` +
+			`n2: _|_(conflicting bounds int & >=1.1 and <=1.3), ` +
 			`n3: 2, ` +
 			`n4: 0.09999, ` +
 			`n5: 2.5}`,
@@ -2323,10 +2351,7 @@
 
 	// Don't remove. For debugging.
 	testCases := []testCase{{
-		in: `
-		g1: 1
-		"g\(1)"?: 1
-	`,
+		in: ``,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
diff --git a/cue/types_test.go b/cue/types_test.go
index efc384d..02ee635 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1796,6 +1796,32 @@
 		// Issue #107
 		value: `a: 1.0/1`,
 		json:  `{"a":1}`,
+	}, {
+		// Issue #108
+		value: `
+		a: int
+		a: >0
+		a: <2
+
+		b: int
+		b: >=0.9
+		b: <1.1
+
+		c: int
+		c: >1
+		c: <=2
+
+		d: int
+		d: >=1
+		d: <=1.5
+
+		e: int
+		e: >=1
+		e: <=1.32
+
+		f: >=1.1 & <=1.1
+		`,
+		json: `{"a":1,"b":1,"c":2,"d":1,"e":1,"f":1.1}`,
 	}}
 	for i, tc := range testCases {
 		t.Run(fmt.Sprintf("%d/%v", i, tc.value), func(t *testing.T) {