internal/core/subsume: various bug fixes
And making semantics close to that of v0.2.
Fixes #566
Change-Id: I97c5f9733c40d92b40f5e780cd1d6e2632de06ff
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7743
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/types.go b/cue/types.go
index fa0d082..e8f97f2 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1718,8 +1718,9 @@
// Value v and w must be obtained from the same build.
// TODO: remove this requirement.
func (v Value) Subsumes(w Value) bool {
+ ctx := v.ctx().opCtx
p := subsume.Profile{Defaults: true}
- return p.Check(v.ctx().opCtx, v.v, w.v)
+ return p.Check(ctx, v.v, w.v)
}
// Unify reports the greatest lower bound of v and w.
diff --git a/cue/types_test.go b/cue/types_test.go
index 87df504..5bda591 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1159,6 +1159,88 @@
}
}
+func TestSubsume(t *testing.T) {
+ a := ParsePath("a")
+ b := ParsePath("b")
+ testCases := []struct {
+ value string
+ pathA Path
+ pathB Path
+ options []Option
+ want bool
+ }{{
+ value: `4`,
+ want: true,
+ }, {
+ value: `a: string, b: "foo"`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: string, b: "foo"`,
+ pathA: b,
+ pathB: a,
+ want: false,
+ }, {
+ value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: [string, 4], b: ["foo", 4]`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: [...string], b: ["foo"]`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: [...int], b: ["foo"]`,
+ pathA: a,
+ pathB: b,
+ want: false,
+ }, {
+ // Issue #566
+ // Closed struct subsuming open struct.
+ value: `
+ #Run: { action: "run", command: [...string] }
+ b: { action: "run", command: ["echo", "hello"] }
+ `,
+ pathA: ParsePath("#Run"),
+ pathB: b,
+
+ // NOTE: this is for v0.2 compatibility. Logically a closed struct
+ // does not subsume an open struct. One could argue that the default
+ // of an open struct is the closed struct with the minimal number
+ // of fields that is an instance of it, though.
+ want: true, // open struct is not subsumed by closed if not final.
+ }, {
+ // Issue #566
+ // Closed struct subsuming open struct.
+ value: `
+ #Run: { action: "run", command: [...string] }
+ b: { action: "run", command: ["echo", "hello"] }
+ `,
+ pathA: ParsePath("#Run"),
+ pathB: b,
+ options: []Option{Final()},
+ want: true,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ v := getInstance(t, tc.value)
+ a := v.Value().LookupPath(tc.pathA)
+ b := v.Value().LookupPath(tc.pathB)
+ got := a.Subsume(b, tc.options...) == nil
+ if got != tc.want {
+ t.Errorf("got %v (%v); want %v (%v)", got, a, tc.want, b)
+ }
+ })
+ }
+}
+
func TestSubsumes(t *testing.T) {
a := []string{"a"}
b := []string{"b"}
@@ -1190,6 +1272,24 @@
pathA: a,
pathB: b,
want: true,
+ }, {
+ value: `a: [...string], b: ["foo"]`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: [...int], b: ["foo"]`,
+ pathA: a,
+ pathB: b,
+ want: false,
+ }, {
+ value: `
+ a: { action: "run", command: [...string] }
+ b: { action: "run", command: ["echo", "hello"] }
+ `,
+ pathA: a,
+ pathB: b,
+ want: true,
}}
for _, tc := range testCases {
t.Run(tc.value, func(t *testing.T) {
diff --git a/internal/core/subsume/value.go b/internal/core/subsume/value.go
index c6e691f..1adb068 100644
--- a/internal/core/subsume/value.go
+++ b/internal/core/subsume/value.go
@@ -34,7 +34,6 @@
}
if s.Defaults {
- a = adt.Default(a)
b = adt.Default(b)
}
diff --git a/internal/core/subsume/value_test.go b/internal/core/subsume/value_test.go
index e5ed21d..06bea8a 100644
--- a/internal/core/subsume/value_test.go
+++ b/internal/core/subsume/value_test.go
@@ -32,6 +32,7 @@
subFinal
subNoOptional
subSchema
+ subDefaults
)
func TestValues(t *testing.T) {
@@ -367,6 +368,41 @@
807: {subsumes: true, in: `a: {}, b: close({})`, mode: subSchema},
808: {subsumes: false, in: `a: {[string]: 1}, b: {foo: 2}`, mode: subSchema},
809: {subsumes: true, in: `a: {}, b: close({foo?: 1})`, mode: subSchema},
+
+ // Lists
+ 950: {subsumes: true, in: `a: [], b: []`},
+ 951: {subsumes: true, in: `a: [...], b: []`},
+ 952: {subsumes: true, in: `a: [...], b: [...]`},
+ 953: {subsumes: false, in: `a: [], b: [...]`},
+
+ 954: {subsumes: true, in: `a: [2], b: [2]`},
+ 955: {subsumes: true, in: `a: [int], b: [2]`},
+ 956: {subsumes: false, in: `a: [2], b: [int]`},
+ 957: {subsumes: true, in: `a: [int], b: [int]`},
+
+ 958: {subsumes: true, in: `a: [...2], b: [2]`},
+ 959: {subsumes: true, in: `a: [...int], b: [2]`},
+ 960: {subsumes: false, in: `a: [...2], b: [int]`},
+ 961: {subsumes: true, in: `a: [...int], b: [int]`},
+
+ 962: {subsumes: false, in: `a: [2], b: [...2]`},
+ 963: {subsumes: false, in: `a: [int], b: [...2]`},
+ 964: {subsumes: false, in: `a: [2], b: [...int]`},
+ 965: {subsumes: false, in: `a: [int], b: [...int]`},
+
+ 966: {subsumes: false, in: `a: [...int], b: ["foo"]`},
+ 967: {subsumes: false, in: `a: ["foo"], b: [...int]`},
+
+ // Defaults:
+ // TODO: for the purpose of v0.2 compatibility these
+ // evaluate to true. Reconsider before making this package
+ // public.
+ 970: {subsumes: true, in: `a: [], b: [...int]`, mode: subDefaults},
+ 971: {subsumes: true, in: `a: [2], b: [2, ...int]`, mode: subDefaults},
+
+ // Final
+ 980: {subsumes: true, in: `a: [], b: [...int]`, mode: subFinal},
+ 981: {subsumes: true, in: `a: [2], b: [2, ...int]`, mode: subFinal},
}
re := regexp.MustCompile(`a: (.*).*b: ([^\n]*)`)
@@ -414,6 +450,9 @@
// TODO: see comments above.
// case subNoOptional:
// err = IgnoreOptional.Value(ctx, a, b)
+ case subDefaults:
+ p := Profile{Defaults: true}
+ err = p.Value(ctx, a, b)
case subFinal:
err = Final.Value(ctx, a, b)
}
diff --git a/internal/core/subsume/vertex.go b/internal/core/subsume/vertex.go
index 6ece49e..189b1d9 100644
--- a/internal/core/subsume/vertex.go
+++ b/internal/core/subsume/vertex.go
@@ -31,7 +31,6 @@
}
if s.Defaults {
- x = x.Default()
y = y.Default()
}
@@ -78,7 +77,10 @@
}
xClosed := x.IsClosed(ctx) && !s.IgnoreClosedness
- yClosed := y.IsClosed(ctx) && !s.IgnoreClosedness
+ // TODO: this should not close for taking defaults. Do a more principled
+ // makeover of this package before making it public, though.
+ yClosed := s.Final || s.Defaults ||
+ (y.IsClosed(ctx) && !s.IgnoreClosedness)
if xClosed && !yClosed && !final {
return false
@@ -228,7 +230,7 @@
case x.IsClosed(ctx):
return false
default:
- a := &adt.Vertex{Label: adt.InvalidLabel}
+ a := &adt.Vertex{Label: 0}
x.MatchAndInsert(ctx, a)
a.Finalize(ctx)