internal/core/eval: fixes to closedness algorithm
- Drop closeIDs that are no longer used.
Change-Id: I7f27b327c4470d016acb97efc055982fc144b454
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6704
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/fulleval/035_optionals_with_label_filters.txtar b/cue/testdata/fulleval/035_optionals_with_label_filters.txtar
index 9204d37..e9c1cf8 100644
--- a/cue/testdata/fulleval/035_optionals_with_label_filters.txtar
+++ b/cue/testdata/fulleval/035_optionals_with_label_filters.txtar
@@ -113,10 +113,6 @@
./in.cue:9:2
./in.cue:22:8
./in.cue:23:8
-jobs3.fooTest1: field `cmd` not allowed:
- ./in.cue:2:7
- ./in.cue:6:8
- ./in.cue:23:18
jobs2.fooTest.name: invalid value "badName" (out of bound =~"^test"):
./in.cue:9:22
@@ -162,11 +158,7 @@
// ./in.cue:9:2
// ./in.cue:22:8
// ./in.cue:23:8
- fooTest1: (_|_){
- // [eval] jobs3.fooTest1: field `cmd` not allowed:
- // ./in.cue:2:7
- // ./in.cue:6:8
- // ./in.cue:23:18
+ fooTest1: (#struct){
name: (string){ "badName" }
cmd: (string){ string }
}
diff --git a/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar b/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar
index e719e07..ebd92c4 100644
--- a/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar
+++ b/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar
@@ -65,7 +65,6 @@
-- out/eval --
Errors:
V.b: field `extra` not allowed:
- ./in.cue:1:5
./in.cue:6:10
./in.cue:11:5
V.c: field `e` not allowed:
@@ -102,7 +101,6 @@
}
b: (_|_){
// [eval] V.b: field `extra` not allowed:
- // ./in.cue:1:5
// ./in.cue:6:10
// ./in.cue:11:5
open: (int){ int }
diff --git a/internal/ci/workflows.cue b/internal/ci/workflows.cue
index 6d2d564..1a5c1a5 100644
--- a/internal/ci/workflows.cue
+++ b/internal/ci/workflows.cue
@@ -7,7 +7,7 @@
workflowsDir: *"./" | string @tag(workflowsDir)
-workflows: [...{file: string, schema: json.#Workflow}]
+workflows: [...{file: string, schema: (json.#Workflow&{})}]
workflows: [
{
file: "test.yml"
@@ -29,7 +29,7 @@
// TODO: drop when cuelang.org/issue/390 is fixed.
// Declare definitions for sub-schemas
-#job: (json.#Workflow.jobs & {x: _}).x
+#job: ((json.#Workflow & {}).jobs & {x: _}).x
#step: ((#job & {steps: _}).steps & [_])[0]
#latestGo: "1.14.3"
diff --git a/internal/core/eval/closed.go b/internal/core/eval/closed.go
index 5c9ae62..56a0d7f 100644
--- a/internal/core/eval/closed.go
+++ b/internal/core/eval/closed.go
@@ -162,11 +162,19 @@
// of this flat list.
//
func updateClosed(c *CloseDef, replace map[uint32]*CloseDef) *CloseDef { // used in eval.go
+ // Insert an entry for CloseID 0 if we are about to replace it. By default
+ // 0, which is the majority case, is omitted.
+ if c != nil && replace[0] != nil && !containsClosed(c, 0) {
+ c = &CloseDef{IsAnd: true, List: []*CloseDef{c, {}}}
+ }
+
switch {
case c == nil:
and := []*CloseDef{}
for _, c := range replace {
- and = append(and, c)
+ if c != nil {
+ and = append(and, c)
+ }
}
switch len(and) {
case 0:
@@ -190,11 +198,17 @@
// If c is a leaf or AND node, replace it outright. If both are an OR node,
// merge the lists.
if len(c.List) == 0 || !c.IsAnd {
- if sub := replace[c.ID]; sub != nil {
+ switch sub, ok := replace[c.ID]; {
+ case sub != nil:
if isOr(sub) && isOr(c) {
sub.List = append(sub.List, c.List...)
}
return sub
+
+ case !ok:
+ if len(c.List) == 0 {
+ return nil // drop from list
+ }
}
}
@@ -213,11 +227,14 @@
return c
}
- if k == 1 {
+ switch k {
+ case 0:
+ return nil
+ case 1:
return buf[0]
+ default:
+ return &CloseDef{Src: c.Src, ID: c.ID, IsAnd: c.IsAnd, List: buf[:k]}
}
-
- return &CloseDef{Src: c.Src, ID: c.ID, IsAnd: c.IsAnd, List: buf[:k]}
}
// UpdateReplace is called after evaluating a conjunct at the top of the arc
@@ -387,3 +404,17 @@
collectPositions(ctx, x)
}
}
+
+// containsClosed reports whether c contains any CloseDef with ID x,
+// recursively.
+func containsClosed(c *CloseDef, x uint32) bool {
+ if c.ID == x && !c.IsAnd {
+ return true
+ }
+ for _, e := range c.List {
+ if containsClosed(e, x) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/internal/core/eval/closed_test.go b/internal/core/eval/closed_test.go
index 144ecce..c0ed278 100644
--- a/internal/core/eval/closed_test.go
+++ b/internal/core/eval/closed_test.go
@@ -27,6 +27,28 @@
replace map[uint32]*CloseDef
want *CloseDef
}{{
+ desc: "introduce new",
+ close: nil,
+ replace: map[uint32]*CloseDef{
+ 0: {ID: 2, IsAnd: false, List: nil},
+ },
+ want: &CloseDef{
+ ID: 0x02,
+ },
+ }, {
+ desc: "auto insert missing 0",
+ close: &CloseDef{
+ ID: 1,
+ },
+ replace: map[uint32]*CloseDef{
+ 0: {ID: 2, IsAnd: false, List: nil},
+ 1: nil, // keep 1
+ },
+ want: &CloseDef{
+ IsAnd: true,
+ List: []*CloseDef{{ID: 1}, {ID: 2}},
+ },
+ }, {
desc: "a: #A & #B",
close: &CloseDef{
ID: 1,
@@ -40,18 +62,38 @@
List: []*CloseDef{{ID: 2}, {ID: 3}},
},
}, {
+ desc: "eliminateUnusedToEmpty",
+ close: &CloseDef{
+ ID: 1,
+ },
+ replace: map[uint32]*CloseDef{
+ 0: nil,
+ },
+ want: nil,
+ }, {
// Eliminate an embedding for which there are no more entries.
- // desc: "eliminateOneEmbedding",
- // close: &CloseDef{
- // ID: 0,
- // List: []*CloseDef{
- // {ID: 2},
- // {ID: 3},
- // },
- // },
- // replace: map[uint32]*CloseDef{2: nil},
- // want: &CloseDef{ID: 2},
- // }, {
+ desc: "eliminateOneEmbedding",
+ close: &CloseDef{
+ ID: 0,
+ List: []*CloseDef{
+ {ID: 2},
+ {ID: 3},
+ },
+ },
+ replace: map[uint32]*CloseDef{2: nil},
+ want: &CloseDef{ID: 2},
+ }, {
+ desc: "eliminateAllEmbeddings",
+ close: &CloseDef{
+ ID: 2,
+ List: []*CloseDef{
+ {ID: 2},
+ {ID: 3},
+ },
+ },
+ replace: map[uint32]*CloseDef{0: {ID: 4}, 4: nil},
+ want: &CloseDef{ID: 4},
+ }, {
// Do not eliminate an embedding that has a replacement.
desc: "eliminateOneEmbeddingByMultiple",
close: &CloseDef{
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 896c983..8b58c7e 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -570,7 +570,28 @@
n.needClose = n.needClose || a.isClosed
}
- updated := updateClosed(c, n.replace)
+ replace := n.replace
+ if replace == nil {
+ replace = map[uint32]*CloseDef{}
+ }
+
+ // Mark any used CloseID to keep, if not already replaced.
+ for _, x := range n.optionals {
+ if _, ok := replace[x.env.CloseID]; !ok {
+ replace[x.env.CloseID] = nil
+ }
+ }
+ for _, a := range n.node.Arcs {
+ for _, c := range a.Conjuncts {
+ if c.Env != nil {
+ if _, ok := replace[c.Env.CloseID]; !ok {
+ replace[c.Env.CloseID] = nil
+ }
+ }
+ }
+ }
+
+ updated := updateClosed(c, replace)
if updated == nil && n.needClose {
updated = &CloseDef{Src: n.node}
}
@@ -1044,7 +1065,7 @@
// values on which they depend fail.
ctx.Unify(ctx, arc, adt.Finalized)
- if arc.Label.IsDef() {
+ if arc.Label.IsDef() { // should test whether closed, not isDef?
id := n.eval.nextID()
n.insertClosed(arc, id, cyclic, arc)
} else {