cue: fix type checking for numeric arithmetic ops

Addresses issue cuelang/cue#16 (numbers only)

Change-Id: I8b873dc1362e3a6432032a5282b3803f8c491c52
diff --git a/cue/kind.go b/cue/kind.go
index 57b9928..a6c3e2e 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -173,7 +173,7 @@
 // - keep type compatibility mapped at a central place
 // - reduce the amount op type switching.
 // - simplifies testing
-func matchBinOpKind(op op, a, b kind) (k kind, invert bool) {
+func matchBinOpKind(op op, a, b kind) (k kind, swap bool) {
 	if op == opDisjunction {
 		return a | b, false
 	}
@@ -185,14 +185,16 @@
 	a = a & typeKinds
 	b = b & typeKinds
 	if valBits == bottomKind {
-		if op == opEql || op == opNeq || op == opUnify {
-			// Set invert for better error messages
-			// invert = aGround && !bGround
+		k := nullKind
+		switch op {
+		case opEql, opNeq:
+			fallthrough
+		case opUnify:
 			if a&nullKind != 0 {
-				return boolKind, false
+				return k, false
 			}
 			if b&nullKind != 0 {
-				return boolKind, true
+				return k, true
 			}
 			return bottomKind, false
 		}
@@ -219,12 +221,6 @@
 			}
 		case op.isCmp():
 			return boolKind, false
-		case op.allowImplicitNumCast():
-			if a.isAnyOf(intKind) ||
-				(a.isAnyOf(floatKind) && !b.isAnyOf(intKind)) {
-				return b | catBits, false
-			}
-			return a | catBits, false
 		}
 		return bottomKind, false
 	}
@@ -235,17 +231,17 @@
 	case op != opUnify && op != opLand && op != opLor && op != opNeq:
 
 	default:
-		invert = aGround && !bGround
+		swap = aGround && !bGround
 	}
 	// a and b have overlapping types.
 	switch op {
 	case opUnify:
 		// Increase likelihood of unification succeeding on first try.
-		return u, invert
+		return u, swap
 
 	case opLand, opLor:
 		if u.isAnyOf(boolKind) {
-			return boolKind | catBits, invert
+			return boolKind | catBits, swap
 		}
 	case opEql, opNeq, opMat, opNMat:
 		if u.isAnyOf(fixedKinds) {
@@ -265,11 +261,11 @@
 			return u&scalarKinds | catBits, false
 		}
 	case opRem:
-		if u.isAnyOf(durationKind | intKind) {
-			return u&(durationKind|intKind) | catBits, false
+		if u.isAnyOf(floatKind) {
+			return floatKind | catBits, false
 		}
 	case opQuo:
-		if u.isAnyOf(durationKind | numKind) {
+		if u.isAnyOf(floatKind) {
 			return floatKind | catBits, false
 		}
 	case opIRem, opIMod:
diff --git a/cue/kind_test.go b/cue/kind_test.go
index 0deccfd..6483a90 100644
--- a/cue/kind_test.go
+++ b/cue/kind_test.go
@@ -28,10 +28,20 @@
 	}{{
 		op:   opMul,
 		a:    floatKind,
-		b:    intKind,
+		b:    numKind,
 		want: floatKind,
 	}, {
 		op:   opMul,
+		a:    intKind,
+		b:    numKind,
+		want: intKind,
+	}, {
+		op:   opMul,
+		a:    floatKind,
+		b:    intKind,
+		want: bottomKind,
+	}, {
+		op:   opMul,
 		a:    listKind,
 		b:    intKind,
 		want: listKind,
diff --git a/cue/op.go b/cue/op.go
index 258343a..7d020aa 100644
--- a/cue/op.go
+++ b/cue/op.go
@@ -30,13 +30,6 @@
 	return opEql <= op && op <= opGeq
 }
 
-// allowImplicitNumCast returns whether an operator is allowed between two
-// different kind of numeric types without an explicit cast.
-// TODO: remove
-func (op op) allowImplicitNumCast() bool {
-	return opAdd <= op && op <= opQuo
-}
-
 type op uint16
 
 const (
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index b6fbaa6..18b5c6a 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -143,15 +143,54 @@
 	}, {
 		desc: "arithmetic",
 		in: `
+			i1: 1 & int
+			i2: 2 & int
+
 			sum: -1 + +2        // 1
 			str: "foo" + "bar"  // "foobar"
 			div1: 2.0 / 3 * 6   // 4
 			div2: 2 / 3 * 6     // 4
 			rem: 2 % 3          // 2
-			e: 2 + "a"          // _|_: unsupported op +(int, string))
 			b: 1 != 4
+
+			v1: 1.0T/2.0
+			v2: 2.0 == 2
+			v3: 2.0/3.0
+			v4: 2.0%3.0
+			v5: i1 div i2
+
+			e0: 2 + "a"
+			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
+			e8: 1.0 mod 1
 			`,
-		out: `<0>{sum: 1, str: "foobar", div1: 4.00000000000000000000000, div2: 4.00000000000000000000000, rem: 2, e: _|_((2 + "a"):unsupported op +(number, string)), b: true}`,
+		out: `<0>{i1: 1, i2: 2, ` +
+			`sum: 1, ` +
+			`str: "foobar", ` +
+			`div1: 4.00000000000000000000000, ` +
+			`div2: 4.00000000000000000000000, ` +
+			`rem: 2, ` +
+			`b: true, ` +
+			`v1: 5e+11, ` +
+			`v2: true, ` +
+			`v3: 0.666666666666666666666667, ` +
+			`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))}`,
 	}, {
 		desc: "integer-specific arithmetic",
 		in: `
@@ -482,14 +521,14 @@
 	}, {
 		desc: "arithmetic",
 		in: `
-				v1: 1.0T/2.0  //
+				v1: 1.0T/2.0
 				v2: 2.0 == 2
-				i1: 1
-				v5: 2.0 / i1  // TODO: should probably fail
-				e1: 2.0 % 3
+				n1: 1
+				v5: 2.0 / n1
+				e1: 2.0 % (3&int)
 				e2: int & 4.0/2.0
 				`,
-		out: `<0>{v1: 5e+11, v2: true, i1: 1, v5: 2, e1: _|_((2.0 % 3):unsupported op %(float, number)), e2: _|_((int & 2):unsupported op &((int)*, float))}`,
+		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))}`,
 	}, {
 		desc: "inequality",
 		in: `