cue: ignore optional fields for Equals
Equal is only defined for for concrete values, so it
makes sense to ignore optional fields.
Issue #200
Issue #207
Change-Id: I96e9fc36a986cc9e95a412cdab4409a600eeab84
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4300
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/subsume.go b/cue/subsume.go
index d9a2257..5cc134f 100644
--- a/cue/subsume.go
+++ b/cue/subsume.go
@@ -24,8 +24,18 @@
// subChoose ensures values are elected before doing a subsumption. This
// feature is on the conservative side and may result in false negatives.
subChoose subsumeMode = 1 << iota
+
+ // subNoOptional ignores optional fields for the purpose of subsumption.
+ // This option is predominantly intended for implementing equality checks.
+ subNoOptional
)
+// TODO: improve upon this highly inefficient implementation. There should
+// be a dedicated equal function once the dust settles.
+func equals(c *context, x, y value) bool {
+ return subsumes(c, x, y, subNoOptional) && subsumes(c, y, x, subNoOptional)
+}
+
// subsumes checks gt subsumes lt. If any of the values contains references or
// unevaluated expressions, structural subsumption is performed. This means
// subsumption is conservative; it may return false when a guarantee for
@@ -84,10 +94,14 @@
}
func (x *structLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
+ ignoreOptional := mode&subNoOptional != 0
if o, ok := v.(*structLit); ok {
// TODO: consider what to do with templates. Perhaps we should always
// do subsumption on fully evaluated structs.
- if len(x.comprehensions) > 0 || x.optionals != nil {
+ if x.optionals != nil && !ignoreOptional {
+ return false
+ }
+ if len(x.comprehensions) > 0 {
return false
}
if x.emit != nil {
@@ -98,6 +112,9 @@
// all arcs in n must exist in v and its values must subsume.
for _, a := range x.arcs {
+ if a.optional && ignoreOptional {
+ continue
+ }
b := o.lookup(ctx, a.feature)
if !a.optional && b.optional {
return false
@@ -114,10 +131,13 @@
}
// For closed structs, all arcs in b must exist in a.
if x.closeStatus.shouldClose() {
- if !o.closeStatus.shouldClose() {
+ if !ignoreOptional && !o.closeStatus.shouldClose() {
return false
}
for _, b := range o.arcs {
+ if ignoreOptional && b.optional {
+ continue
+ }
a := x.lookup(ctx, b.feature)
if a.val() == nil {
return false
diff --git a/cue/subsume_test.go b/cue/subsume_test.go
index decf340..0a95aa0 100644
--- a/cue/subsume_test.go
+++ b/cue/subsume_test.go
@@ -372,6 +372,16 @@
// TODO: handle optionals.
431: {subsumes: false, in: `a: {[_]: int}, b: {[_]: 2}`},
+ 440: {subsumes: true, in: `a: {foo?: 1}, b: {}`, mode: subNoOptional},
+ 441: {subsumes: true, in: `a: {}, b: {foo?: 1}`, mode: subNoOptional},
+ 442: {subsumes: true, in: `a: {foo?: 1}, b: {foo: 1}`, mode: subNoOptional},
+ 443: {subsumes: true, in: `a: {foo?: 1}, b: {foo?: 1}`, mode: subNoOptional},
+ 444: {subsumes: false, in: `a: {foo: 1}, b: {foo?: 1}`, mode: subNoOptional},
+ 445: {subsumes: true, in: `a: close({}), b: {foo?: 1}`, mode: subNoOptional},
+ 446: {subsumes: true, in: `a: close({}), b: close({foo?: 1})`, mode: subNoOptional},
+ 447: {subsumes: true, in: `a: {}, b: close({})`, mode: subNoOptional},
+ 448: {subsumes: true, in: `a: {}, b: close({foo?: 1})`, mode: subNoOptional},
+
// Lists
506: {subsumes: true, in: `a: [], b: [] `},
507: {subsumes: true, in: `a: [1], b: [1] `},
@@ -392,8 +402,10 @@
605: {subsumes: true, in: `a: {a: 1}, b: close({a: 1, b: 1})`},
606: {subsumes: true, in: `a: close({b?: 1}), b: close({b: 1})`},
607: {subsumes: false, in: `a: close({b: 1}), b: close({b?: 1})`},
+ 608: {subsumes: true, in: `a: {}, b: close({})`},
+ 609: {subsumes: true, in: `a: {}, b: close({foo?: 1})`},
- // Definitions are not values.
+ // Definitions are not regular fields.
610: {subsumes: false, in: `a: {a :: 1}, b: {a: 1}`},
611: {subsumes: false, in: `a: {a: 1}, b: {a :: 1}`},
}
diff --git a/cue/types.go b/cue/types.go
index 754af56..273fd24 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1363,7 +1363,7 @@
return u
}
-// Equals reports whether two values are equal.
+// Equals reports whether two values are equal, ignoring optional fields.
// The result is undefined for incomplete values.
func (v Value) Equals(other Value) bool {
if v.path == nil || other.path == nil {
@@ -1371,8 +1371,7 @@
}
x := v.path.val()
y := other.path.val()
- // TODO: improve upon this highly inefficient implementation.
- return subsumes(v.ctx(), x, y, 0) && subsumes(v.ctx(), y, x, 0)
+ return equals(v.ctx(), x, y)
}
// Format prints a debug version of a value.