cue: bring default values up to spec
This is bolted on to the old implementation. This results
in a more compact representation that could be considered
a premature optimization, but so be it.
Fixes #28.
Change-Id: I57939e69d39d486cf0d8f21d7cdf525d376df670
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1981
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/ast.go b/cue/ast.go
index d2bed29..1db9ad8 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -536,6 +536,7 @@
mark = true
n = x.X
}
+ d.hasDefaults = true
}
d.values = append(d.values, dValue{v.walk(n), mark})
}
diff --git a/cue/binop.go b/cue/binop.go
index 0b1831b..e9bbd43 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -37,15 +37,15 @@
}
func binOp(ctx *context, src source, op op, left, right evaluated) (result evaluated) {
- if isBottom(left) {
- if op == opUnify && ctx.exprDepth == 0 && cycleError(left) != nil {
+ if b, ok := left.(*bottom); ok {
+ if op == opUnify && b.exprDepth == 0 && cycleError(b) != nil {
ctx.cycleErr = true
return right
}
return left
}
- if isBottom(right) {
- if op == opUnify && ctx.exprDepth == 0 && cycleError(right) != nil {
+ if b, ok := right.(*bottom); ok {
+ if op == opUnify && b.exprDepth == 0 && cycleError(b) != nil {
ctx.cycleErr = true
return left
}
@@ -70,9 +70,9 @@
if !leftKind.isGround() || !rightKind.isGround() {
return ctx.mkErr(src, codeIncomplete, "incomplete error")
}
- ctx.exprDepth++
+ ctx.incEvalDepth()
v := left.binOp(ctx, src, op, right) // may return incomplete
- ctx.exprDepth--
+ ctx.decEvalDepth()
return v
}
@@ -119,29 +119,41 @@
}
// distribute distributes a value over the element of a disjunction in a
-// unification operation. If allowCycle is true, references that resolve
-// to a cycle are dropped.
-func distribute(ctx *context, src source, x *disjunction, y evaluated) evaluated {
- return dist(ctx, src, x, mVal{y, false}).val
+// unification operation.
+// TODO: this is an exponential algorithm. There is no reason to have to
+// resolve this early. Revise this to only do early pruning but not a full
+// evaluation.
+func distribute(ctx *context, src source, x, y evaluated) evaluated {
+ dn := &disjunction{baseValue: src.base()}
+ dist(ctx, dn, false, mVal{x, true}, mVal{y, true})
+ return dn.normalize(ctx, src).val
}
-func dist(ctx *context, src source, dx *disjunction, y mVal) mVal {
- dn := &disjunction{src.base(), make([]dValue, 0, len(dx.values))}
- for _, dv := range dx.values {
- x := mVal{dv.val.evalPartial(ctx), dv.marked}
- src := binSrc(src.Pos(), opUnify, x.val, y.val)
-
- var v mVal
- if dy, ok := y.val.(*disjunction); ok {
- v = dist(ctx, src, dy, x)
- } else if ddv, ok := dv.val.(*disjunction); ok {
- v = dist(ctx, src, ddv, y)
- } else {
- v = mVal{binOp(ctx, src, opUnify, x.val, y.val), x.mark || y.mark}
+func dist(ctx *context, d *disjunction, mark bool, x, y mVal) {
+ if dx, ok := x.val.(*disjunction); ok {
+ if dx.hasDefaults {
+ mark = true
+ d.hasDefaults = true
}
- dn.add(ctx, v.val, v.mark)
+ for _, dxv := range dx.values {
+ m := dxv.marked || !dx.hasDefaults
+ dist(ctx, d, mark, mVal{dxv.val.evalPartial(ctx), m}, y)
+ }
+ return
}
- return dn.normalize(ctx, src)
+ if dy, ok := y.val.(*disjunction); ok {
+ if dy.hasDefaults {
+ mark = true
+ d.hasDefaults = true
+ }
+ for _, dxy := range dy.values {
+ m := dxy.marked || !dy.hasDefaults
+ dist(ctx, d, mark, x, mVal{dxy.val.evalPartial(ctx), m})
+ }
+ return
+ }
+ src := binSrc(0, opUnify, x.val, y.val)
+ d.add(ctx, binOp(ctx, src, opUnify, x.val, y.val), mark && x.mark && y.mark)
}
func (x *disjunction) binOp(ctx *context, src source, op op, other evaluated) evaluated {
diff --git a/cue/builtin.go b/cue/builtin.go
index 63d0547..a2dce51 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -162,7 +162,7 @@
for iter.Next() {
d = append(d, dValue{iter.Value().path.v, false})
}
- c.ret = &disjunction{baseValue{c.src}, d}
+ c.ret = &disjunction{baseValue{c.src}, d, false}
if len(d) == 0 {
c.ret = errors.New("empty or")
}
diff --git a/cue/context.go b/cue/context.go
index c115c7f..ff81001 100644
--- a/cue/context.go
+++ b/cue/context.go
@@ -29,10 +29,10 @@
// constraints are to be evaluated at the end values to be evaluated later.
constraints []*binaryExpr
- exprDepth int // nesting of expression that are not unification
- evalDepth int
- inSum int
- cycleErr bool
+ evalStack []bottom
+
+ inSum int
+ cycleErr bool
noManifest bool
@@ -44,6 +44,18 @@
level int
}
+func (c *context) incEvalDepth() {
+ if len(c.evalStack) > 0 {
+ c.evalStack[len(c.evalStack)-1].exprDepth++
+ }
+}
+
+func (c *context) decEvalDepth() {
+ if len(c.evalStack) > 0 {
+ c.evalStack[len(c.evalStack)-1].exprDepth--
+ }
+}
+
var baseContext apd.Context
func init() {
diff --git a/cue/errors.go b/cue/errors.go
index f15927a..fe8ed52 100644
--- a/cue/errors.go
+++ b/cue/errors.go
@@ -66,6 +66,7 @@
index *index
code errCode
+ exprDepth int
value value
offendingValue value
replacement evaluated // for cycle resolution
@@ -143,20 +144,6 @@
return nil
}
-// sentinel values used for signalling a type of error.
-var (
- // errNonGround = // values are not compatible
- cycleSentinel = &bottom{
- code: codeCycle,
- msg: "cycle detected",
- }
-
- // unifyNotSupported may be returned when a node implementation cannot
- // handle the other type. In this case it may still be the case that the
- // converse can be implemented.
- // unifyNotSupported node = &bottom{}
-)
-
func (idx *index) mkErrUnify(src source, a, b evaluated) evaluated {
if err := firstBottom(a, b); err != nil {
return err
diff --git a/cue/eval.go b/cue/eval.go
index 681b21d..68056ab 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -279,8 +279,11 @@
defer func() { ctx.debugPrint("result:", result) }()
}
- ctx.inSum++
- dn := &disjunction{x.baseValue, make([]dValue, 0, len(x.values))}
+ // decSum := false
+ if len(ctx.evalStack) > 1 {
+ ctx.inSum++
+ }
+ dn := &disjunction{x.baseValue, make([]dValue, 0, len(x.values)), x.hasDefaults}
changed := false
for _, v := range x.values {
n := v.val.evalPartial(ctx)
@@ -299,7 +302,9 @@
if !changed {
dn = x
}
- ctx.inSum--
+ if len(ctx.evalStack) > 1 {
+ ctx.inSum--
+ }
return dn.normalize(ctx, x).val
}
@@ -351,10 +356,10 @@
var left, right evaluated
if x.op != opUnify {
- ctx.exprDepth++
+ ctx.incEvalDepth()
left = ctx.manifest(x.left)
right = ctx.manifest(x.right)
- ctx.exprDepth--
+ ctx.decEvalDepth()
// TODO: allow comparing to a literal bottom only. Find something more
// principled perhaps. One should especially take care that two values
diff --git a/cue/go.go b/cue/go.go
index c452228..b2610b6 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -191,10 +191,7 @@
case nil:
// Interpret a nil pointer as an undefined value that is only
// null by default, but may still be set: *null | _.
- return &disjunction{values: []dValue{
- {val: &nullLit{src.base()}, marked: true},
- {val: &top{src.base()}}},
- }
+ return makeNullable(&top{src.base()}).(evaluated)
case *big.Int:
n := newNum(src, intKind)
@@ -291,10 +288,7 @@
if value.IsNil() {
// Interpret a nil pointer as an undefined value that is only
// null by default, but may still be set: *null | _.
- return &disjunction{values: []dValue{
- {val: &nullLit{src.base()}, marked: true},
- {val: &top{src.base()}}},
- }
+ return makeNullable(&top{src.base()}).(evaluated)
}
return convert(ctx, src, value.Elem().Interface())
@@ -387,7 +381,7 @@
// TODO: this can be much more efficient.
mutex.Lock()
defer mutex.Unlock()
- return goTypeToValue(ctx, t)
+ return goTypeToValue(ctx, true, t)
}
var (
@@ -400,7 +394,7 @@
//
// TODO: if this value will always be unified with a concrete type in Go, then
// many of the fields may be omitted.
-func goTypeToValue(ctx *context, t reflect.Type) (e value) {
+func goTypeToValue(ctx *context, allowNullDefault bool, t reflect.Type) (e value) {
if e, ok := typeCache.Load(t); ok {
return e.(value)
}
@@ -419,7 +413,10 @@
for elem.Kind() == reflect.Ptr {
elem = elem.Elem()
}
- e = wrapOrNull(goTypeToValue(ctx, elem))
+ e = goTypeToValue(ctx, false, elem)
+ if allowNullDefault {
+ e = wrapOrNull(e)
+ }
case reflect.Interface:
switch t.Name() {
@@ -477,7 +474,8 @@
if f.PkgPath != "" {
continue
}
- elem := goTypeToValue(ctx, f.Type)
+ _, ok := f.Tag.Lookup("cue")
+ elem := goTypeToValue(ctx, !ok, f.Type)
// leave errors like we do during normal evaluation or do we
// want to return the error?
@@ -507,7 +505,6 @@
// with the constraints from the tags. The type constraints
// will be implied when unified with a concrete value.
obj.arcs[i].v = mkBin(ctx, 0, opUnify, a.v, v)
- // obj.arcs[i].v = v
}
}
}
@@ -518,7 +515,7 @@
if t.Elem().Kind() == reflect.Uint8 {
e = &basicType{k: bytesKind}
} else {
- elem := goTypeToValue(ctx, t.Elem())
+ elem := goTypeToValue(ctx, allowNullDefault, t.Elem())
var ln value = &top{}
if t.Kind() == reflect.Array {
@@ -540,7 +537,7 @@
obj := newStruct(baseValue{})
sig := ¶ms{}
sig.add(ctx.label("_", true), &basicType{k: stringKind})
- v := goTypeToValue(ctx, t.Elem())
+ v := goTypeToValue(ctx, allowNullDefault, t.Elem())
obj.template = &lambdaExpr{params: sig, value: v}
e = wrapOrNull(obj)
@@ -558,9 +555,17 @@
if e.kind().isAnyOf(nullKind) {
return e
}
- e = &disjunction{values: []dValue{
- {val: &nullLit{}, marked: true},
- {val: e}},
+ return makeNullable(e)
+}
+
+const nullIsDefault = true
+
+func makeNullable(e value) value {
+ return &disjunction{
+ baseValue: baseValue{e},
+ values: []dValue{
+ {val: &nullLit{}, marked: nullIsDefault},
+ {val: e}},
+ hasDefaults: nullIsDefault,
}
- return e
}
diff --git a/cue/go_test.go b/cue/go_test.go
index 8bb6907..50f9248 100644
--- a/cue/go_test.go
+++ b/cue/go_test.go
@@ -198,7 +198,7 @@
for _, tc := range testCases {
ctx := inst.newContext()
t.Run("", func(t *testing.T) {
- v := goTypeToValue(ctx, reflect.TypeOf(tc.goTyp))
+ v := goTypeToValue(ctx, true, reflect.TypeOf(tc.goTyp))
got := debugStr(ctx, v)
if got != tc.want {
t.Errorf("\n got %q;\nwant %q", got, tc.want)
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 6cb4050..262af6b 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -423,7 +423,7 @@
// All errors are treated the same as per the unification model.
i1: [1, 2][3] | "c"
`,
- out: `<0>{o1: (1 | 2 | 3), o2: 1, o3: 2, o4: (1 | *2 | *3), o5: (1 | *2 | 3), o6: (1 | 2 | 3), o7: (2 | 3), o8: (2 | 3), o9: (2 | 3), o10: (3 | *2), m1: (*2 | 3), m2: (*2 | 3), m3: (*2 | 3), m4: (*2 | 3), m5: (*2 | 3), i1: "c"}`,
+ out: `<0>{o1: (1 | 2 | 3), o2: 1, o3: 2, o4: (1 | 2 | 3), o5: (1 | *2 | 3), o6: (1 | 2 | 3), o7: (2 | 3), o8: (2 | 3), o9: (2 | 3), o10: (3 | *2), m1: (*2 | 3), m2: (*2 | 3), m3: (*2 | 3), m4: (*2 | 3), m5: (*2 | 3), i1: "c"}`,
}, {
desc: "types",
in: `
@@ -474,7 +474,9 @@
c: [c[1], c[0]]
`,
- out: `<0>{a: _|_(cycle detected), b: _|_(cycle detected), c: [_|_(cycle detected),_|_(cycle detected)]}`,
+ out: `<0>{a: _|_((<1>.b - 100):cycle detected), ` +
+ `b: _|_((<1>.a + 100):cycle detected), ` +
+ `c: [_|_(<1>.c[1]:cycle detected),_|_(<1>.c[0]:cycle detected)]}`,
}, {
desc: "resolved self-reference cycles",
in: `
@@ -543,7 +545,7 @@
b: *"b" | "a"
c: a & b
`,
- out: `<0>{a: "a", b: "b", c: _|_((*"a" | *"b"):more than one default remaining ("a" and "b"))}`,
+ out: `<0>{a: "a", b: "b", c: _|_(("a" | "b"):more than one element remaining ("a" and "b"))}`,
}, {
desc: "associativity of defaults",
in: `
@@ -1039,9 +1041,9 @@
b: a & { l: [_, "bar"] }
`,
out: `<0>{` +
- `a: <1>{l: ["foo",_|_(cycle detected)], ` +
- `v: _|_(cycle detected)}, ` +
- `b: <2>{l: ["foo","bar"], v: "bar"}}`,
+ `a: <1>{l: ["foo",_|_(<2>.v:cycle detected)], ` +
+ `v: _|_(<2>.l[1]:cycle detected)}, ` +
+ `b: <3>{l: ["foo","bar"], v: "bar"}}`,
}, {
desc: "correct error messages",
// Tests that it is okay to partially evaluate structs.
@@ -1361,16 +1363,22 @@
// once the user specifies one.
desc: "resolved self-reference cycles with disjunction",
in: `
- // The disjunction in xa could be resolved, but as disjunctions
- // are not resolved for expression, it remains unresolved.
+ // The second disjunct in xa1 is not resolvable and can be
+ // eliminated:
+ // xa4 & 9
+ // (xa2 + 2) & 9
+ // ((xa3 + 2) + 2) & 9
+ // (((6 & xa1-2) + 2) + 2) & 9
+ // ((6 + 2) + 2) & 9 // 6 == xa1-2
+ // 10 & 9 => _|_
+ // The remaining values resolve.
xa1: (xa2 & 8) | (xa4 & 9)
xa2: xa3 + 2
xa3: 6 & xa1-2
xa4: xa2 + 2
- // As xb3 is a disjunction, xb2 cannot be resolved and evaluating
- // the cycle completely is broken. However, it is not an error
- // as the user might still resolve the disjunction.
+ // The second disjunct in xb4 can be eliminated as both disjuncts
+ // of xb3 result in an incompatible sum when substituted.
xb1: (xb2 & 8) | (xb4 & 9)
xb2: xb3 + 2
xb3: (6 & (xb1-2)) | (xb4 & 9)
@@ -1404,14 +1412,11 @@
xe5: xe2 + 2
xe1: 9
- // TODO: this is resolvable, but requires some detection logic
- // in disjunctions.
- // xf1: xf2 & 8 | xf4 & 9
- // xf2: xf3 + 2
- // xf3: 6 & xf1-2 | xf4 & 9
- // xf4: xf2 + 2
- // xf1: 8
- // xf3: 6
+ // Only one solution.
+ xf1: xf2 & 8 | xf4 & 9
+ xf2: xf3 + 2
+ xf3: 6 & xf1-2 | xf4 & 9
+ xf4: xf2 + 2
z1: z2 + 1 | z3 + 5
z2: z3 + 2
@@ -1419,17 +1424,17 @@
z3: 8
`,
out: `<0>{` +
- `xa1: (8 | 9), ` +
- `xa2: (<1>.xa3 + 2), ` +
- `xa4: (<1>.xa2 + 2), ` +
- `xa3: (6 & (<1>.xa1 - 2)), ` +
+ `xa1: 8, ` +
+ `xa2: 8, ` +
+ `xa4: 10, ` +
+ `xa3: 6, ` +
- `xb1: _|_(((<1>.xb2 & 8) | (<1>.xb4 & 9)):empty disjunction: empty disjunction: cycle detected), ` +
- `xb2: _|_(((6 & (<1>.xb1 - 2)) | (<1>.xb4 & 9)):empty disjunction: cycle detected), ` +
- `xb4: _|_(((6 & (<1>.xb1 - 2)) | (<1>.xb4 & 9)):empty disjunction: cycle detected), ` +
- `xb3: _|_(((6 & (<1>.xb1 - 2)) | (<1>.xb4 & 9)):empty disjunction: cycle detected), ` +
+ `xb1: 8, ` +
+ `xb2: 8, ` +
+ `xb4: 10, ` +
+ `xb3: 6, ` +
- `xc1: (8 | 9), ` +
+ `xc1: ((<1>.xc2 & 8) | (<1>.xc4 & 9) | (<1>.xc5 & 9)), ` +
`xc2: (<1>.xc3 + 2), ` +
`xc4: (<1>.xc2 + 1), ` +
`xc5: (<1>.xc2 + 2), ` +
@@ -1441,15 +1446,20 @@
`xd5: 10, ` +
`xd3: 6, ` +
- `xe1: 9, ` +
+ `xe1: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe2: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe4: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe5: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe3: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
- `z1: _|_(((<1>.z2 + 1) | (<1>.z3 + 5)):empty disjunction: cycle detected), ` +
- `z2: _|_(((<1>.z2 + 1) | (<1>.z3 + 5)):empty disjunction: cycle detected), ` +
- `z3: _|_(((<1>.z2 + 1) | (<1>.z3 + 5)):empty disjunction: cycle detected)}`,
+ `xf1: 8, ` +
+ `xf2: 8, ` +
+ `xf4: 10, ` +
+ `xf3: 6, ` +
+
+ `z1: ((<1>.z2 + 1) | (<1>.z3 + 5)), ` +
+ `z2: (<1>.z3 + 2), ` +
+ `z3: ((<1>.z1 - 3) & 8)}`,
}, {
// Defaults should not alter the result of the above disjunctions.
// The results may differ, but errors and resolution should be roughly
@@ -1458,7 +1468,7 @@
in: `
// The disjunction in xa could be resolved, but as disjunctions
// are not resolved for expression, it remains unresolved.
- xa1: *(xa2 & 8) | (xa4 & 9)
+ xa1: (xa2 & 8) | *(xa4 & 9)
xa2: xa3 + 2
xa3: 6 & xa1-2
xa4: xa2 + 2
@@ -1476,7 +1486,7 @@
// However, to fully determine that, all options of the remaining
// disjunction will have to be evaluated algebraically, which is
// not done.
- xc1: *(xc2 & 8) | xc4 & 9 | xc5 & 9
+ xc1: *(xc2 & 8) | (xc4 & 9) | (xc5 & 9)
xc2: xc3 + 2
xc3: 6 & xc1-2
xc4: xc2 + 1
@@ -1488,7 +1498,6 @@
xd3: 6 & xd1-2
xd4: xd2 + 1
xd5: xd2 + 2
- xd1: 8
// The above is resolved by setting xd1 explicitly to the wrong
// value, resulting in an error.
@@ -1505,37 +1514,37 @@
z3: 8
`,
out: `<0>{` +
- `xa1: (*8 | 9), ` + // TODO: should resolve. Just testing logic, so okay for now.
+ `xa1: 8, ` +
`xa2: 8, ` +
`xa4: 10, ` +
`xa3: 6, ` +
- `xb1: _|_((*(<1>.xb2 & 8) | (<1>.xb4 & 9)):empty disjunction: empty disjunction: cycle detected), ` +
- `xb2: _|_((*(6 & (<1>.xb1 - 2)) | (<1>.xb4 & 9)):empty disjunction: cycle detected), ` +
- `xb4: _|_((*(6 & (<1>.xb1 - 2)) | (<1>.xb4 & 9)):empty disjunction: cycle detected), ` +
- `xb3: _|_((*(6 & (<1>.xb1 - 2)) | (<1>.xb4 & 9)):empty disjunction: cycle detected), ` +
+ `xb1: 8, ` +
+ `xb2: 8, ` +
+ `xb4: 10, ` +
+ `xb3: 6, ` +
- `xc1: (*8 | 9), ` + // TODO: resolve
+ `xc1: (*8 | 9), ` + // not resolved because we use evalPartial
`xc2: 8, ` +
`xc4: 9, ` +
`xc5: 10, ` +
`xc3: 6, ` +
- `xd1: 8, ` +
+ `xd1: (*8 | 9), ` + // TODO: eliminate 9?
`xd2: 8, ` +
`xd4: 9, ` +
`xd5: 10, ` +
`xd3: 6, ` +
- `xe1: 9, ` +
+ `xe1: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe2: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe4: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe5: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
`xe3: _|_((6 & 7):cannot unify numbers 6 and 7), ` +
- `z1: _|_((*(<1>.z2 + 1) | (<1>.z3 + 5)):empty disjunction: cycle detected), ` +
- `z2: _|_((*(<1>.z2 + 1) | (<1>.z3 + 5)):empty disjunction: cycle detected), ` +
- `z3: _|_((*(<1>.z2 + 1) | (<1>.z3 + 5)):empty disjunction: cycle detected)}`,
+ `z1: (*11 | 13), ` + // 13 is eliminated with evalFull
+ `z2: 10, ` +
+ `z3: 8}`,
}}
rewriteHelper(t, testCases, evalPartial)
}
@@ -1716,7 +1725,7 @@
`,
out: `<0>{a: string, b: int, c: float}`,
}, {
- desc: "default disambiguation",
+ desc: "default disambiguation and elimination",
in: `
a: *1 | int
b: *3 | int
@@ -1725,7 +1734,7 @@
e: *1 | *1
`,
- out: `<0>{a: 1, b: 3, c: _|_((*1 | *3 | int):more than one default remaining (1 and 3)), d: _|_((*3 | *1 | int):more than one default remaining (3 and 1)), e: 1}`,
+ out: `<0>{a: 1, b: 3, c: int, d: int, e: 1}`,
}, {
desc: "list comprehension",
in: `
@@ -1884,6 +1893,29 @@
y: x & {a:3}
`,
out: `<3>{x: _|_((<0>{a: 1} | <1>{a: 2}):more than one element remaining (<0>{a: 1} and <1>{a: 2})), y: _|_((<4>.x & <5>{a: 3}):empty disjunction: <2>{a: (1 & 3)})}`,
+ }, {
+ desc: "cannot resolve references that would be ambiguous",
+ in: `
+ a1: *0 | 1
+ a1: a3 - a2
+ a2: *0 | 1
+ a2: a3 - a1
+ a3: 1
+
+ b1: (*0 | 1) & b2
+ b2: (0 | *1) & b1
+
+ c1: (*{a:1} | {b:1}) & c2
+ c2: (*{a:2} | {b:2}) & c1
+ `,
+ out: `<4>{` +
+ `a1: _|_(((*0 | 1) & (<5>.a3 - <5>.a2)):cycle detected), ` +
+ `a3: 1, ` +
+ `a2: _|_(((*0 | 1) & (<5>.a3 - <5>.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: _|_((<0>{a: 1, b: 2} | <1>{a: 2, b: 1}):more than one element remaining (<0>{a: 1, b: 2} and <1>{a: 2, b: 1})), ` +
+ `c2: _|_((<2>{a: 2, b: 1} | <3>{a: 1, b: 2}):more than one element remaining (<2>{a: 2, b: 1} and <3>{a: 1, b: 2}))}`,
}}
rewriteHelper(t, testCases, evalFull)
}
diff --git a/cue/rewrite.go b/cue/rewrite.go
index eda68c4..44dc982 100644
--- a/cue/rewrite.go
+++ b/cue/rewrite.go
@@ -195,7 +195,7 @@
if !changed {
return x
}
- return &disjunction{x.baseValue, values}
+ return &disjunction{x.baseValue, values, x.hasDefaults}
}
func (x *listComprehension) rewrite(ctx *context, fn rewriteFunc) value {
diff --git a/cue/validate.go b/cue/validate.go
index 0379d71..15c2e23 100644
--- a/cue/validate.go
+++ b/cue/validate.go
@@ -17,7 +17,7 @@
// validate returns whether there is any error, recursively.
func validate(ctx *context, v value) *bottom {
eval := v.evalPartial(ctx)
- if err, ok := eval.(*bottom); ok && err.code != codeIncomplete {
+ if err, ok := eval.(*bottom); ok && err.code != codeIncomplete && err.code != codeCycle {
return eval.(*bottom)
}
switch x := eval.(type) {
diff --git a/cue/value.go b/cue/value.go
index 36268f4..67944a2 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -677,18 +677,26 @@
// Lookup is done by selector or index references. Either this is done on
// literal nodes or nodes obtained from references. In the later case,
// noderef will have ensured that the ancestors were evaluated.
- if x.arcs[i].cache == nil {
+ if v := x.arcs[i].cache; v == nil {
// cycle detection
- x.arcs[i].cache = cycleSentinel
- ctx.evalDepth++
+ popped := ctx.evalStack
+ ctx.evalStack = append(ctx.evalStack, bottom{
+ baseValue: x.base(),
+ index: ctx.index,
+ code: codeCycle,
+ value: x.arcs[i].v,
+ msg: "cycle detected",
+ })
+ x.arcs[i].cache = &(ctx.evalStack[len(ctx.evalStack)-1])
+
v := x.arcs[i].v.evalPartial(ctx)
- ctx.evalDepth--
+ ctx.evalStack = popped
v = x.applyTemplate(ctx, i, v)
- if (ctx.evalDepth > 0 && ctx.cycleErr) || cycleError(v) != nil {
+ if (len(ctx.evalStack) > 0 && ctx.cycleErr) || cycleError(v) != nil {
// Don't cache while we're in a evaluation cycle as it will cache
// partial results. Each field involved in the cycle will have to
// reevaluated the values from scratch. As the result will be
@@ -702,11 +710,14 @@
ctx.cycleErr = false
x.arcs[i].cache = v
- if ctx.evalDepth == 0 {
+ if len(ctx.evalStack) == 0 {
if err := ctx.processDelayedConstraints(); err != nil {
x.arcs[i].cache = err
}
}
+ } else if b := cycleError(v); b != nil {
+ copy := *b
+ return ©
}
return x.arcs[i].cache
}
@@ -1070,6 +1081,8 @@
values []dValue
+ hasDefaults bool // also true if it had elminated defaults.
+
// bind is the node that a successful disjunction will bind to. This
// allows other arcs to point to this node before the disjunction is
// completed. For early failure, this node can be set to the glb of all
diff --git a/cuego/cuego.go b/cuego/cuego.go
index aaa8cc3..7e799c9 100644
--- a/cuego/cuego.go
+++ b/cuego/cuego.go
@@ -168,7 +168,7 @@
context := build.NewContext()
fset = context.FileSet()
inst := context.NewInstance("<cuego>", nil)
- if err := inst.AddFile("<ceugo>", "{}"); err != nil {
+ if err := inst.AddFile("<cuego>", "{}"); err != nil {
panic(err)
}
instance = cue.Build([]*build.Instance{inst})[0]