cue: simplify cycle handlng for expressions
Errors need not mention that a constraint was delayed,
as this is an implementation detail.
Add tests for handling expression cycles in disjunctions.
This is now disallowed.
Change-Id: Ib8492b5a81b44448528f779249b48e779de26bb2
Reviewed-on: https://cue-review.googlesource.com/c/1528
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/context.go b/cue/context.go
index 961b3b4..7bfaae5 100644
--- a/cue/context.go
+++ b/cue/context.go
@@ -28,10 +28,10 @@
oldSize []int
// constraints are to be evaluated at the end values to be evaluated later.
- constraints []value
- noDelay bool
+ constraints []*binaryExpr
exprDepth int // nesting of expression that are not unification
evalDepth int
+ inSum int
cycleErr bool
// for debug strings
@@ -60,10 +60,8 @@
// delayConstraint schedules constraint to be evaluated and returns ret. If
// delaying constraints is currently not allowed, it returns an error instead.
-func (c *context) delayConstraint(ret evaluated, constraint value) evaluated {
- if c.noDelay {
- return c.mkErr(ret, "delayed constraint %v violated", debugStr(c, constraint))
- }
+func (c *context) delayConstraint(ret evaluated, constraint *binaryExpr) evaluated {
+ c.cycleErr = true
c.constraints = append(c.constraints, constraint)
return ret
}
@@ -72,11 +70,9 @@
cons := c.constraints
c.constraints = c.constraints[:0]
for _, dc := range cons {
- c.noDelay = true
- v := c.manifest(dc)
- c.noDelay = false
+ v := binOp(c, dc, dc.op, dc.left.evalPartial(c), dc.right.evalPartial(c))
if isBottom(v) {
- return c.mkErr(dc, "constraint violated: %v", debugStr(c, v))
+ return v
}
}
return nil
diff --git a/cue/eval.go b/cue/eval.go
index 8832ffc..d7ee66a 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -288,6 +288,7 @@
defer func() { ctx.debugPrint("result:", result) }()
}
+ ctx.inSum++
dn := &disjunction{x.baseValue, make([]dValue, 0, len(x.values))}
changed := false
for _, v := range x.values {
@@ -307,6 +308,7 @@
if !changed {
dn = x
}
+ ctx.inSum--
return dn.normalize(ctx, x).val
}
@@ -322,7 +324,7 @@
case d.marked:
if marked != nil {
// TODO: allow disjunctions to be returned as is.
- return ctx.mkErr(x, "more than one default remaining (%v and %v)", debugStr(ctx, marked), debugStr(ctx, d.val))
+ return ctx.mkErr(x, codeIncomplete, "more than one default remaining (%v and %v)", debugStr(ctx, marked), debugStr(ctx, d.val))
}
marked = d.val.(evaluated)
case unmarked1 == nil:
@@ -336,7 +338,7 @@
return marked
case unmarked2 != nil:
- return ctx.mkErr(x, "more than one element remaining (%v and %v)",
+ return ctx.mkErr(x, codeIncomplete, "more than one element remaining (%v and %v)",
debugStr(ctx, unmarked1), debugStr(ctx, unmarked2))
case unmarked1 != nil:
@@ -358,8 +360,10 @@
var left, right evaluated
if x.op != opUnify {
+ ctx.exprDepth++
left = ctx.manifest(x.left)
right = ctx.manifest(x.right)
+ ctx.exprDepth--
// TODO: allow comparing to a literal bottom only. Find something more
// principled perhaps. One should especially take care that two values
@@ -379,13 +383,11 @@
left = x.left.evalPartial(ctx)
right = x.right.evalPartial(ctx)
- if err := cycleError(left); err != nil && right.kind().isAtom() {
- return ctx.delayConstraint(right,
- mkBin(ctx, x.Pos(), opUnify, x.left, right))
+ if err := cycleError(left); err != nil && ctx.inSum == 0 && right.kind().isAtom() {
+ return ctx.delayConstraint(right, x)
}
- if err := cycleError(right); err != nil && left.kind().isAtom() {
- return ctx.delayConstraint(left,
- mkBin(ctx, x.Pos(), opUnify, left, x.right))
+ if err := cycleError(right); err != nil && ctx.inSum == 0 && left.kind().isAtom() {
+ return ctx.delayConstraint(left, x)
}
// check if it is a cycle that can be unwrapped.
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 3cce0cc..7b5a09d 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -468,7 +468,10 @@
x: 100
x: x + 1
`,
- out: `<0>{a: _|_(((<1>.a + 110) & 200):constraint violated: _|_((210 & 200):cannot unify numbers 210 and 200)), b: 200, x: _|_((100 & (<1>.x + 1)):constraint violated: _|_((100 & 101):cannot unify numbers 100 and 101))}`,
+ out: `<0>{` +
+ `a: _|_((210 & 200):cannot unify numbers 210 and 200), ` +
+ `b: _|_((210 & 200):cannot unify numbers 210 and 200), ` +
+ `x: _|_((100 & 101):cannot unify numbers 100 and 101)}`,
// TODO: find a way to mark error in data.
}}
rewriteHelper(t, testCases, evalPartial)
@@ -1059,7 +1062,7 @@
}
a x: "hey"
`,
- out: `<0>{a: <1>{x: _|_(((<2>.y + "?") & "hey"):constraint violated: _|_(("hey!?" & "hey"):failed to unify: hey!? != hey)), y: "hey!"}}`,
+ out: `<0>{a: <1>{x: _|_(("hey!?" & "hey"):failed to unify: hey!? != hey), y: "hey!"}}`,
}, {
desc: "resolved self-reference cycles with disjunctions",
in: `
@@ -1068,6 +1071,187 @@
c: a&{y:3} | {z:3} // {x:1,y:3,z:2} | {z:3}
`,
out: `<0>{a: (<1>{x: 1, y: 3, z: 2} | <2>{y: 1}), b: (<3>{x: 2} | <4>{x: 1, y: 3, z: 2}), c: (<5>{x: 1, y: 3, z: 2} | <6>{z: 3})}`,
+ }, {
+ // We take a very conservative stance on delaying arithmetic
+ // expressions within disjunctions. It should remain resolvable, though,
+ // 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.
+ 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.
+ xb1: (xb2 & 8) | (xb4 & 9)
+ xb2: xb3 + 2
+ xb3: (6 & (xb1-2)) | (xb4 & 9)
+ xb4: xb2 + 2
+
+ // Another variant with more disjunctions. xc1 remains with two
+ // possibilities. Technically, only the first value is valid.
+ // 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
+ xc2: xc3 + 2
+ xc3: 6 & xc1-2
+ xc4: xc2 + 1
+ xc5: xc2 + 2
+
+ // The above is resolved by setting xd1 explicitly.
+ xd1: xd2 & 8 | xd4 & 9 | xd5 & 9
+ xd2: xd3 + 2
+ 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.
+ xe1: xe2 & 8 | xe4 & 9 | xe5 & 9
+ xe2: xe3 + 2
+ xe3: 6 & xe1-2
+ xe4: xe2 + 1
+ 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
+
+ z1: z2 + 1 | z3 + 5
+ z2: z3 + 2
+ z3: z1 - 3
+ z3: 8
+ `,
+ out: `<0>{` +
+ `xa1: (8 | 9), ` +
+ `xa2: (<1>.xa3 + 2), ` +
+ `xa4: (<1>.xa2 + 2), ` +
+ `xa3: (6 & (<1>.xa1 - 2)), ` +
+
+ `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), ` +
+
+ `xc1: (8 | 9), ` +
+ `xc2: (<1>.xc3 + 2), ` +
+ `xc4: (<1>.xc2 + 1), ` +
+ `xc5: (<1>.xc2 + 2), ` +
+ `xc3: (6 & (<1>.xc1 - 2)), ` +
+
+ `xd1: 8, ` +
+ `xd2: 8, ` +
+ `xd4: 9, ` +
+ `xd5: 10, ` +
+ `xd3: 6, ` +
+
+ `xe1: 9, ` +
+ `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)}`,
+ }, {
+ // Defaults should not alter the result of the above disjunctions.
+ // The results may differ, but errors and resolution should be roughly
+ // the same.
+ desc: "resolved self-reference cycles with disjunction with defaults",
+ in: `
+ // The disjunction in xa could be resolved, but as disjunctions
+ // are not resolved for expression, it remains unresolved.
+ 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.
+ xb1: *(xb2 & 8) | (xb4 & 9)
+ xb2: xb3 + 2
+ xb3: *(6 & (xb1-2)) | (xb4 & 9)
+ xb4: xb2 + 2
+
+ // Another variant with more disjunctions. xc1 remains with two
+ // possibilities. Technically, only the first value is valid.
+ // 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
+ xc2: xc3 + 2
+ xc3: 6 & xc1-2
+ xc4: xc2 + 1
+ xc5: xc2 + 2
+
+ // The above is resolved by setting xd1 explicitly.
+ xd1: *(xd2 & 8) | xd4 & 9 | xd5 & 9
+ xd2: xd3 + 2
+ 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.
+ xe1: *(xe2 & 8) | xe4 & 9 | xe5 & 9
+ xe2: xe3 + 2
+ xe3: 6 & xe1-2
+ xe4: xe2 + 1
+ xe5: xe2 + 2
+ xe1: 9
+
+ z1: *(z2 + 1) | z3 + 5
+ z2: z3 + 2
+ z3: z1 - 3
+ z3: 8
+ `,
+ out: `<0>{` +
+ `xa1: (*8 | 9), ` + // TODO: should resolve. Just testing logic, so okay for now.
+ `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), ` +
+
+ `xc1: (*8 | 9), ` + // TODO: resolve
+ `xc2: 8, ` +
+ `xc4: 9, ` +
+ `xc5: 10, ` +
+ `xc3: 6, ` +
+
+ `xd1: 8, ` +
+ `xd2: 8, ` +
+ `xd4: 9, ` +
+ `xd5: 10, ` +
+ `xd3: 6, ` +
+
+ `xe1: 9, ` +
+ `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)}`,
}}
rewriteHelper(t, testCases, evalPartial)
}
diff --git a/cue/subsume_test.go b/cue/subsume_test.go
index 021fd54..34066d2 100644
--- a/cue/subsume_test.go
+++ b/cue/subsume_test.go
@@ -250,7 +250,7 @@
145: {subsumes: false, in: ` a: "s \(d) e", b: "s a e", d: _`},
146: {subsumes: false, in: ` a: "s \(d)m\(d) e", b: "s a e", d: _`},
- 147: {subsumes: true, in: ` a: 7080, b: 7080 | int`, mode: subChoose},
+ 147: {subsumes: true, in: ` a: 7080, b: *7080 | int`, mode: subChoose},
// Defaults
150: {subsumes: false, in: `a: number | *1, b: number | *2`},