Skip to content

Commit eac8f9a

Browse files
committed
cue: treat cycle errors as normal incomplete errors
Reference cycles are no different from any other non-concrete values that can still be resolved. Reflect this accordingly. Not doing so can cause unfication to fail in the validation step done after it. Add an option to explicitly disallow cycles in Validate. This may be useful if applications want to ensure there are no cycles but otherwise don't care about the precense of non-concrete values. Change-Id: Ib718081067e230d8eabd37b9169e292cefec8536 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2708 Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent 9934915 commit eac8f9a

File tree

7 files changed

+48
-22
lines changed

7 files changed

+48
-22
lines changed

cmd/cue/cmd/eval.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,31 +116,31 @@ func runEval(cmd *cobra.Command, args []string) error {
116116

117117
if exprs == nil {
118118
v := inst.Value()
119-
if flagConcrete.Bool(cmd) {
119+
if flagConcrete.Bool(cmd) && !flagIgnore.Bool(cmd) {
120120
if err := v.Validate(cue.Concrete(true)); err != nil {
121121
exitIfErr(cmd, inst, err, false)
122122
continue
123123
}
124124
}
125125
b, _ := format.Node(getSyntax(v, syn), opts...)
126-
w.Write(b)
126+
_, _ = w.Write(b)
127127
}
128128
for _, e := range exprs {
129129
if len(exprs) > 1 {
130130
fmt.Fprint(w, "// ")
131131
b, _ := format.Node(e)
132-
w.Write(b)
132+
_, _ = w.Write(b)
133133
fmt.Fprintln(w)
134134
}
135135
v := inst.Eval(e)
136-
if flagConcrete.Bool(cmd) {
136+
if flagConcrete.Bool(cmd) && !flagIgnore.Bool(cmd) {
137137
if err := v.Validate(cue.Concrete(true)); err != nil {
138138
exitIfErr(cmd, inst, err, false)
139139
continue
140140
}
141141
}
142142
b, _ := format.Node(getSyntax(v, syn), opts...)
143-
w.Write(b)
143+
_, _ = w.Write(b)
144144
fmt.Fprintln(w)
145145
}
146146
}

cue/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const (
102102

103103
func isIncomplete(v value) bool {
104104
if err, ok := v.(*bottom); ok {
105-
return err.code == codeIncomplete
105+
return err.code == codeIncomplete || err.code == codeCycle
106106
}
107107
return false
108108
}

cue/resolve_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -466,9 +466,9 @@ func TestBasicRewrite(t *testing.T) {
466466
467467
c: [c[1], c[0]]
468468
`,
469-
out: `<0>{a: _|_((<1>.b - 100):cycle detected), ` +
470-
`b: _|_((<1>.a + 100):cycle detected), ` +
471-
`c: [_|_(<1>.c[1]:cycle detected),_|_(<1>.c[0]:cycle detected)]}`,
469+
out: `<0>{a: (<1>.b - 100), ` +
470+
`b: (<1>.a + 100), ` +
471+
`c: [<1>.c[1],<1>.c[0]]}`,
472472
}, {
473473
desc: "resolved self-reference cycles",
474474
in: `
@@ -1054,8 +1054,8 @@ func TestResolve(t *testing.T) {
10541054
b: a & { l: [_, "bar"] }
10551055
`,
10561056
out: `<0>{` +
1057-
`a: <1>{l: ["foo",_|_(<2>.v:cycle detected)], ` +
1058-
`v: _|_(<2>.l[1]:cycle detected)}, ` +
1057+
`a: <1>{l: ["foo",<2>.v], ` +
1058+
`v: <2>.l[1]}, ` +
10591059
`b: <3>{l: ["foo","bar"], v: "bar"}}`,
10601060
}, {
10611061
desc: "correct error messages",
@@ -1929,9 +1929,9 @@ func TestFullEval(t *testing.T) {
19291929
c2: (*{a:2} | {b:2}) & c1
19301930
`,
19311931
out: `<0>{` +
1932-
`a1: _|_(((*0 | 1) & (<1>.a3 - <1>.a2)):cycle detected), ` +
1932+
`a1: ((*0 | 1) & (<1>.a3 - <1>.a2)), ` +
19331933
`a3: 1, ` +
1934-
`a2: _|_(((*0 | 1) & (<1>.a3 - <1>.a1)):cycle detected), ` +
1934+
`a2: ((*0 | 1) & (<1>.a3 - <1>.a1)), ` +
19351935
`b1: (0 | 1), ` +
19361936
`b2: (0 | 1), ` +
19371937
`c1: (<2>{a: 1, b: 2} | <3>{a: 2, b: 1}), ` +

cue/types.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,12 +1346,13 @@ func (p *pathFinder) find(ctx *context, v value) (value, bool) {
13461346
}
13471347

13481348
type options struct {
1349-
concrete bool // enforce that values are concrete
1350-
raw bool // show original values
1351-
hasHidden bool
1352-
omitHidden bool
1353-
omitOptional bool
1354-
omitAttrs bool
1349+
concrete bool // enforce that values are concrete
1350+
raw bool // show original values
1351+
hasHidden bool
1352+
omitHidden bool
1353+
omitOptional bool
1354+
omitAttrs bool
1355+
disallowCycles bool // implied by concrete
13551356
}
13561357

13571358
// An Option defines modes of evaluation.
@@ -1380,6 +1381,12 @@ func Concrete(concrete bool) Option {
13801381
}
13811382
}
13821383

1384+
// DisallowCycles forces validation in the precense of cycles, even if
1385+
// non-concrete values are allowed. This is implied by Concrete(true).
1386+
func DisallowCycles(disallow bool) Option {
1387+
return func(p *options) { p.disallowCycles = disallow }
1388+
}
1389+
13831390
// All indicates that all fields and values should be included in processing
13841391
// even if they can be elided or omitted.
13851392
func All() Option {
@@ -1430,7 +1437,10 @@ func (v Value) Validate(opts ...Option) error {
14301437
var errs errors.Error
14311438
v.Walk(func(v Value) bool {
14321439
if err := v.checkKind(v.ctx(), bottomKind); err != nil {
1433-
if !o.concrete && isIncomplete(v.eval(v.ctx())) {
1440+
if !o.concrete && isIncomplete(err) {
1441+
if o.disallowCycles && err.code == codeCycle {
1442+
errs = errors.Append(errs, v.toErr(err))
1443+
}
14341444
return false
14351445
}
14361446
errs = errors.Append(errs, v.toErr(err))

cue/types_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,22 @@ func TestValidate(t *testing.T) {
10921092
in: `a: [{b: string}, 3]`,
10931093
opts: []Option{Concrete(true)},
10941094
err: true,
1095+
}, {
1096+
desc: "allow cycles",
1097+
in: `
1098+
a: b - 100
1099+
b: a + 100
1100+
c: [c[1], c[0]]
1101+
`,
1102+
}, {
1103+
desc: "disallow cycles",
1104+
in: `
1105+
a: b - 100
1106+
b: a + 100
1107+
c: [c[1], c[0]]
1108+
`,
1109+
opts: []Option{DisallowCycles(true)},
1110+
err: true,
10951111
}}
10961112
for _, tc := range testCases {
10971113
t.Run(tc.desc, func(t *testing.T) {

doc/ref/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2315,7 +2315,7 @@ and validate after the field in which the expression occurs has been evaluated
23152315
that `a == e`.
23162316

23172317
```
2318-
// Config Evaluates to
2318+
// Config Evaluates to (requiring concrete values)
23192319
x: { x: {
23202320
a: b + 100 a: _|_ // cycle detected
23212321
b: a - 100 b: _|_ // cycle detected

doc/tutorial/basics/cycle.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ b: a - 100
2929
```
3030

3131
<!-- result -->
32-
`$ cue eval -i cycle.cue`
32+
`$ cue eval -i -c cycle.cue`
3333
```
3434
x: 200
3535
y: 100

0 commit comments

Comments
 (0)