cue: allow comparison against null

this is partly in preparation for unary comparators, where
this is essential.

Change-Id: I73454a3b9ea446d61097404042f3260d6ec4593a
diff --git a/cue/binop.go b/cue/binop.go
index 682dd89..409d4a0 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -510,16 +510,26 @@
 
 func (x *nullLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
 	// TODO: consider using binSrc instead of src.base() for better traceability.
-	switch op {
-	case opEql:
-		return &boolLit{baseValue: src.base(), b: true}
-	case opNeq:
-		return &boolLit{baseValue: src.base(), b: false}
-	case opUnify:
-		return x
+	switch other.(type) {
+	case *nullLit:
+		switch op {
+		case opEql:
+			return &boolLit{baseValue: src.base(), b: true}
+		case opNeq:
+			return &boolLit{baseValue: src.base(), b: false}
+		case opUnify:
+			return x
+		}
+
 	default:
-		panic("unimplemented")
+		switch op {
+		case opEql:
+			return &boolLit{baseValue: src.base(), b: false}
+		case opNeq:
+			return &boolLit{baseValue: src.base(), b: true}
+		}
 	}
+	return ctx.mkIncompatible(src, op, x, other)
 }
 
 func (x *boolLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
diff --git a/cue/kind.go b/cue/kind.go
index 110ea48..86a16d0 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -54,8 +54,8 @@
 	// to move the reference to an assertion clause.
 	referenceKind
 
-	atomKind     = (listKind - 1)
-	concreteKind = (lambdaKind - 1)
+	atomKind     = (listKind - 1) &^ unknownKind
+	concreteKind = (lambdaKind - 1) &^ unknownKind
 
 	// doneKind indicates a value can not further develop on its own (i.e. not a
 	// reference). If doneKind is not set, but the result is ground, it
@@ -65,10 +65,10 @@
 	// unless the range is restricted by a root type.
 	numKind = intKind | floatKind
 
-	comparableKind = listKind - 1
+	comparableKind = (listKind - 1) &^ unknownKind
 	stringableKind = scalarKinds | stringKind
 	topKind        = (referenceKind - 1) // all kinds, but not references
-	typeKinds      = nonGround - 1
+	typeKinds      = (nonGround - 1) &^ unknownKind
 	okKinds        = typeKinds &^ bottomKind
 	fixedKinds     = okKinds &^ (structKind | lambdaKind)
 	scalarKinds    = numKind | durationKind
@@ -180,10 +180,20 @@
 	u := unifyType(a, b)
 	valBits := u & typeKinds
 	catBits := u &^ typeKinds
+	aGround := a&nonGround == 0
+	bGround := b&nonGround == 0
 	a = a & typeKinds
 	b = b & typeKinds
 	if valBits == bottomKind {
-		if op == opEql || op == opNeq {
+		if op == opEql || op == opNeq || op == opUnify {
+			// Set invert for better error messages
+			// invert = aGround && !bGround
+			if a&nullKind != 0 {
+				return boolKind, false
+			}
+			if b&nullKind != 0 {
+				return boolKind, true
+			}
 			return bottomKind, false
 		}
 		if a.isAnyOf(listKind) && b.isAnyOf(intKind) {
@@ -225,7 +235,7 @@
 	case op != opUnify && op != opLand && op != opLor:
 
 	default:
-		invert = a&nonGround == 0 && b&nonGround != 0
+		invert = aGround && !bGround
 	}
 	// a and b have overlapping types.
 	switch op {
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index c3d50a4..d9bef3a 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -334,12 +334,12 @@
 			unf: null & null
 
 			// errors
-			eqe1: null == 1
-			eqe2: 1 == null
-			nee1: "s" != null
+			eq1: null == 1
+			eq2: 1 == null
+			ne1: "s" != null
 			call: null()
 		`,
-		out: `<0>{eql: true, neq: false, unf: null, eqe1: _|_((null == 1):unsupported op ==(null, number)), eqe2: _|_((1 == null):unsupported op ==(number, null)), nee1: _|_(("s" != null):unsupported op !=(string, null)), call: _|_(null:cannot call non-function null (type null))}`,
+		out: `<0>{eql: true, neq: false, unf: null, eq1: false, eq2: false, ne1: true, call: _|_(null:cannot call non-function null (type null))}`,
 	}, {
 		desc: "self-reference cycles",
 		in: `
@@ -451,6 +451,17 @@
 				`,
 		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))}`,
 	}, {
+		desc: "inequality",
+		in: `
+			a: 1 != 2
+			b: 1 != null
+			c: true == null
+			d: null != {}
+			e: null == []
+			f: 0 == 0.0    // types are unified first TODO: make this consistent
+		`,
+		out: `<0>{a: true, b: true, c: false, d: true, e: false, f: true}`,
+	}, {
 		desc: "null coalescing",
 		in: `
 				a: null
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 8fba6dd..d68024a 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -1598,6 +1598,8 @@
 The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered.
 These terms and the result of the comparisons are defined as follows:
 
+- Null is comparable with itself and any other type.
+  Two null values are always equal, null is unequal with anything else.
 - Boolean values are comparable.
   Two boolean values are equal if they are either both true or both false.
 - Integer values are comparable and ordered, in the usual way.
@@ -1606,16 +1608,12 @@
 - String values are comparable and ordered, lexically byte-wise after
   normalization to Unicode normal form NFC.
 - Struct are not comparable.
-  Two struct values are equal if their corresponding non-blank fields are equal.
-- Lists are comparable.
-  Two list values are equal if their corresponding elements are equal.
+- Lists are not comparable.
 ```
-c: 3 < 4
-
-x: int
-y: int
-
-b3: x == y // b3 has type bool
+a: 3 < 4       // true
+b: null == 2   // false
+c: null != {}  // true
+d: {} == {}    // _|_: structs are not comparable against structs
 ```
 
 <!-- jba