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