cue: treat disjunctions as normal non-concrete values
This brings the implementation in line with the spec
relating to the treatment of default values.
Before getting a default resulted in a "multiple
values remaining" error if the value was a disjunction.
However, there is nothing special about such
disjuctions compared to any other non-concrete
value. Generating an error early resulted in some
strange and sometimes spurious error messages.
Issue #52
Change-Id: I221ff8affc6469b3caf633be0674dfd265ea8b0f
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2680
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/partial/eval_conc.out b/cmd/cue/cmd/testdata/partial/eval_conc.out
index 006c7ca..eae33c1 100644
--- a/cmd/cue/cmd/testdata/partial/eval_conc.out
+++ b/cmd/cue/cmd/testdata/partial/eval_conc.out
@@ -1,4 +1,4 @@
-sum: more than one element remaining (1 and 2):
+sum: incomplete value ((1 | 2)):
./testdata/partial/partial.cue:4:6
b.idx: invalid non-ground value string (must be concrete int|string):
./testdata/partial/partial.cue:7:9
diff --git a/cue/ast_test.go b/cue/ast_test.go
index 96ea535..806cce1 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -291,7 +291,7 @@
a: 8000 | 7080
a: 7080 | int
}`,
- out: `<0>{a: _|_((8000 | 7080):more than one element remaining (8000 and 7080))}`,
+ out: `<0>{a: (8000 | 7080)}`,
rw: evalFull,
}}
for _, tc := range testCases {
diff --git a/cue/eval.go b/cue/eval.go
index 44db675..74256db 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -318,43 +318,40 @@
}
func (x *disjunction) manifest(ctx *context) (result evaluated) {
- var err, marked, unmarked1, unmarked2 evaluated
- for _, d := range x.values {
- // Because of the lazy evaluation strategy, we may still have
- // latent unification.
- if err := validate(ctx, d.val); err != nil {
- continue
- }
+ values := make([]dValue, 0, len(x.values))
+ validValue := false
+ for _, dv := range x.values {
switch {
- case d.marked:
- if marked != nil {
- // TODO: allow disjunctions to be returned as is.
- return ctx.mkErr(x, codeIncomplete, "more than one default remaining (%v and %v)", ctx.str(marked), ctx.str(d.val))
- }
- marked = d.val.(evaluated)
- case unmarked1 == nil:
- unmarked1 = d.val.(evaluated)
+ case isBottom(dv.val):
+ case dv.marked:
+ values = append(values, dv)
default:
- unmarked2 = d.val.(evaluated)
+ validValue = true
}
}
+
switch {
- case marked != nil:
- return marked
-
- case unmarked2 != nil:
- return ctx.mkErr(x, codeIncomplete, "more than one element remaining (%v and %v)",
- ctx.str(unmarked1), ctx.str(unmarked2))
-
- case unmarked1 != nil:
- return unmarked1
-
- case err != nil:
- return err
-
+ case len(values) > 0:
+ // values contains all the valid defaults
+ case !validValue:
+ return x
default:
- return ctx.mkErr(x, "empty disjunction")
+ for _, dv := range x.values {
+ dv.marked = false
+ values = append(values, dv)
+ }
}
+
+ switch len(values) {
+ case 0:
+ return x
+
+ case 1:
+ return values[0].val.evalPartial(ctx)
+ }
+
+ x = &disjunction{x.baseValue, values, true}
+ return x.normalize(ctx, x).val
}
func (x *binaryExpr) evalPartial(ctx *context) (result evaluated) {
diff --git a/cue/evaluator.go b/cue/evaluator.go
index f8c7bb4..48ff71c 100644
--- a/cue/evaluator.go
+++ b/cue/evaluator.go
@@ -20,17 +20,15 @@
return evaluated
}
outer:
- for {
- switch x := evaluated.(type) {
- case *disjunction:
- evaluated = x.manifest(c)
+ switch x := evaluated.(type) {
+ case *disjunction:
+ evaluated = x.manifest(c)
- case *list:
- return x.manifest(c)
+ case *list:
+ return x.manifest(c)
- default:
- break outer
- }
+ default:
+ break outer
}
return evaluated
}
diff --git a/cue/export.go b/cue/export.go
index 54f5923..e856a30 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -524,11 +524,7 @@
list := &ast.ListLit{}
var expr ast.Expr = list
for _, e := range x.elem.arcs {
- if doEval(p.mode) {
- list.Elts = append(list.Elts, p.expr(e.v.evalPartial(p.ctx)))
- } else {
- list.Elts = append(list.Elts, p.expr(e.v))
- }
+ list.Elts = append(list.Elts, p.expr(e.v))
}
max := maxNum(x.len)
num, ok := max.(*numLit)
diff --git a/cue/export_test.go b/cue/export_test.go
index 392fb60..c3ca25b 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -160,8 +160,8 @@
}`,
out: unindent(`
{
- a: 1 | 2 | *_|_
- b: [1 | 2 | *_|_]
+ a: 1 | 2
+ b: [1 | 2]
}`),
}, {
raw: true,
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index c44856d..920465d 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -518,7 +518,7 @@
rewriteHelper(t, testCases, evalPartial)
}
-func TestChooseFirst(t *testing.T) {
+func TestChooseDefault(t *testing.T) {
testCases := []testCase{{
desc: "pick first",
in: `
@@ -531,13 +531,15 @@
`,
out: "<0>{a: 5, b: <1>{c: <2>{a: 2}}}",
}, {
+ // In this test, default results to bottom, meaning that the non-default
+ // value remains.
desc: "simple disambiguation conflict",
in: `
a: *"a" | "b"
b: *"b" | "a"
c: a & b
`,
- out: `<0>{a: "a", b: "b", c: _|_(("a" | "b" | *_|_):more than one element remaining ("a" and "b"))}`,
+ out: `<0>{a: "a", b: "b", c: ("a" | "b")}`,
}, {
desc: "associativity of defaults",
in: `
@@ -547,7 +549,7 @@
x: a & b
y: b & c
`,
- out: `<0>{a: "a", b: "a", c: _|_((*"a" | *"b" | "c"):more than one default remaining ("a" and "b")), x: "a", y: _|_((*"a" | *"b" | "c"):more than one default remaining ("a" and "b"))}`,
+ out: `<0>{a: "a", b: "a", c: (*"a" | *"b"), x: "a", y: (*"a" | *"b")}`,
}}
rewriteHelper(t, testCases, evalFull)
}
@@ -1910,7 +1912,7 @@
x: {a:1}|{a:2}
y: x & {a:3}
`,
- out: `<0>{x: _|_((<1>{a: 1} | <2>{a: 2}):more than one element remaining ({a: 1} and {a: 2})), y: _|_((<3>.x & <4>{a: 3}):empty disjunction: {a: (1 & 3)})}`,
+ out: `<0>{x: (<1>{a: 1} | <2>{a: 2}), y: _|_((<3>.x & <4>{a: 3}):empty disjunction: {a: (1 & 3)})}`,
}, {
desc: "cannot resolve references that would be ambiguous",
in: `
@@ -1930,10 +1932,10 @@
`a1: _|_(((*0 | 1) & (<1>.a3 - <1>.a2)):cycle detected), ` +
`a3: 1, ` +
`a2: _|_(((*0 | 1) & (<1>.a3 - <1>.a1)):cycle detected), ` +
- `b1: _|_((0 | 1 | *_|_):more than one element remaining (0 and 1)), ` +
- `b2: _|_((0 | 1 | *_|_):more than one element remaining (0 and 1)), ` +
- `c1: _|_((<2>{a: 1, b: 2} | <3>{a: 2, b: 1} | *_|_):more than one element remaining ({a: 1, b: 2} and {a: 2, b: 1})), ` +
- `c2: _|_((<4>{a: 2, b: 1} | <5>{a: 1, b: 2} | *_|_):more than one element remaining ({a: 2, b: 1} and {a: 1, b: 2}))}`,
+ `b1: (0 | 1), ` +
+ `b2: (0 | 1), ` +
+ `c1: (<2>{a: 1, b: 2} | <3>{a: 2, b: 1}), ` +
+ `c2: (<4>{a: 2, b: 1} | <5>{a: 1, b: 2})}`,
}}
rewriteHelper(t, testCases, evalFull)
}
diff --git a/doc/tutorial/basics/defaults.md b/doc/tutorial/basics/defaults.md
index c348c25..99d9e48 100644
--- a/doc/tutorial/basics/defaults.md
+++ b/doc/tutorial/basics/defaults.md
@@ -29,5 +29,5 @@
`$ cue eval defaults.cue`
```
replicas: 1
-protocol: "tcp" | "udp" | *_|_
+protocol: "tcp" | "udp"
```
\ No newline at end of file
diff --git a/doc/tutorial/kubernetes/testdata/manual.out b/doc/tutorial/kubernetes/testdata/manual.out
index ba2e1a8..5112aa9 100644
--- a/doc/tutorial/kubernetes/testdata/manual.out
+++ b/doc/tutorial/kubernetes/testdata/manual.out
@@ -1990,7 +1990,7 @@
containerPort: 7788
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-volume"
mountPath: "/etc/ssl"
@@ -2230,7 +2230,7 @@
containerPort: 7443
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-volume"
mountPath: "/etc/ssl"
@@ -2436,7 +2436,7 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-updater"
mountPath: "/etc/certs"
@@ -2627,7 +2627,7 @@
containerPort: 7788
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-volume"
mountPath: "/etc/ssl"
@@ -2897,18 +2897,18 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "ssd-caller"
mountPath: "/logs"
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-ssh-key"
mountPath: "/sslcerts"
readOnly: true
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-caller"
mountPath: "/etc/certs"
@@ -3177,18 +3177,18 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-ssh-key"
mountPath: "/sslcerts"
readOnly: true
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "dishwasher-disk"
mountPath: "/logs"
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-dishwasher"
mountPath: "/etc/certs"
@@ -3454,12 +3454,12 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "expiditer-disk"
mountPath: "/logs"
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-expiditer"
mountPath: "/etc/certs"
@@ -3710,13 +3710,13 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-headchef"
mountPath: "/sslcerts"
readOnly: true
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "headchef-disk"
mountPath: "/logs"
@@ -3965,13 +3965,13 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-kitchen"
mountPath: "/etc/certs"
readOnly: true
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "linecook-disk"
mountPath: "/logs"
@@ -4224,13 +4224,13 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-ssh-key"
mountPath: "/etc/certs"
readOnly: true
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "pastrychef-disk"
mountPath: "/logs"
@@ -4763,12 +4763,12 @@
containerPort: 9093
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "alertmanager"
mountPath: "/alertmanager"
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "config-volume"
mountPath: "/etc/alertmanager"
@@ -5041,7 +5041,7 @@
containerPort: 8080
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "grafana-volume"
mountPath: "/var/lib/grafana"
@@ -5278,13 +5278,13 @@
hostPort: 9100
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "proc"
mountPath: "/host/proc"
readOnly: true
}, {
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "sys"
mountPath: "/host/sys"
@@ -5538,7 +5538,7 @@
containerPort: 9090
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "config-volume"
mountPath: "/etc/prometheus"
@@ -6294,7 +6294,7 @@
containerPort: 4180
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "config-volume"
mountPath: "/etc/authproxy"
@@ -6606,7 +6606,7 @@
containerPort: 7443
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-volume"
mountPath: "/etc/ssl"
@@ -6810,7 +6810,7 @@
containerPort: 443
}]
volumeMounts: [{
- _|_ /* more than one element remaining (false and true) */
+ _|_ /* non-concrete value bool */
name: "secret-volume"
mountPath: "/etc/ssl"
diff --git a/internal/cuetest/sim.go b/internal/cuetest/sim.go
index ed105c7..c14a0c1 100644
--- a/internal/cuetest/sim.go
+++ b/internal/cuetest/sim.go
@@ -93,7 +93,7 @@
want := strings.TrimSpace(cfg.Golden)
if got != want {
- t.Errorf("files differ:\n%s", diff.Diff(want, got))
+ t.Errorf("files differ:\n%s", diff.Diff(got, want))
}
}