evaluator: allow source value for `for` comprehension to be partially evaluated
What version of CUE are you using (cue version)?
$ cue version
cue version v0.0.0-20220824153543-547c4390430c
-compiler gc
CGO_ENABLED 1
GOARCH arm64
GOOS linux
vcs git
vcs.revision 547c4390430c9ab4274a8f5aa3548dacfc1f4981
vcs.time 2022-08-24T15:35:43Z
vcs.modified false
Does this issue reproduce with the latest release?
Yes
What did you do?
# z.cue - unrolled comprehension
exec cue eval x.cue z.cue
cmp stdout stdout.golden
# y.cue - comprehension
exec cue eval x.cue y.cue
cmp stdout stdout.golden
-- x.cue --
package x
#AllOutputs: {
reject: string
resource: string
retry: {
output: #Output
}
}
o: #Output & {
retry: {
output: {
reject: "ok"
}
}
}
-- y.cue --
package x
#Output: or([ for name, config in #AllOutputs {
(name): config
}])
-- z.cue --
package x
#Output: {
reject: string
} | {
resource: string
} | {
retry: {
output: null | #Output
}
}
-- stdout.golden --
#AllOutputs: {
reject: string
resource: string
retry: {
output: {
reject: string
} | {
resource: string
} | {
retry: {
output: null
}
}
}
}
#Output: {
reject: string
} | {
resource: string
} | {
retry: {
output: null
}
}
o: {
retry: {
output: {
reject: "ok"
}
}
}
What did you expect to see?
Passing test.
What did you see instead?
# z.cue - unrolled comprehension (0.017s)
# y.cue - comprehension (0.014s)
> exec cue eval x.cue y.cue
[stderr]
2.retry: structural cycle:
./y.cue:3:13
[exit status 1]
FAIL: /tmp/testscript1560619719/repro.txtar/script.txt:6: unexpected command failure
Note that z.cue is the unrolled version of y.cue, so I think these two should be identical in behaviour.
Note this is note fixed by https://review.gerrithub.io/c/cue-lang/cue/+/542674.
Also, this appears to have been an issue for some time. At least as far back as v0.3.2.
See
#Output: or([ for name, config in #AllOutputs { (name): config }])
#AllOutputs: {
reject: string
resource: string
retry: output: #Output
}
The source for a for comprehension must be fully evaluated before it is used. This is due to how "void" arcs are handled. I guess theoretically this could be done "lazily", but this more complicated and currently it is defined as such.
As a result, evaluating #Output triggers the evaluation of #AllOutput, which in turn triggers the evaluation of #Output (a cycle), before the comprehension can be evaluated and thus before a call to or can be made.
See https://review.gerrithub.io/c/cue-lang/cue/+/533224 for feasibility.
Just noting per earlier discussion, that in the repro above, z.cue is the "unrolled" version of the comprehension in y.cue. exec cue eval x.cue z.cue shows there is not a cycle, so arguably exec cue eval x.cue y.cue should give the same result. Therefore, this issue is tracking ensuring that the comprehension version behaves like the "unrolled" version.