cue/testdata/cycle: fix another cycle bug
The special case here is that the cycle passes over a
reference into a builtin.
Fixes #655
Change-Id: Ibc650932ccdf63aa3163a81bf6af101c7d942271
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8283
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/cue/testdata/cycle/builtins.txtar b/cue/testdata/cycle/builtins.txtar
new file mode 100644
index 0000000..0e48766
--- /dev/null
+++ b/cue/testdata/cycle/builtins.txtar
@@ -0,0 +1,179 @@
+-- in.cue --
+import "regexp"
+
+// Issue #655
+// When evaluating a value into a struct, and then back into a value, the
+// evaluation mode flips from Partial to AllArcs to Back. This is typically
+// not an issue, but if a referred field is within a struct generated by a
+// builtin, effectively the entire struct needs to be evaluated and special care
+// should be taking to not evaluate too early.
+builtinCyclePerm0: {
+ X: "example.com"
+
+ Y: {
+ #components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
+ host: #components.host
+ }
+
+ X: Y.host
+}
+
+builtinCyclePerm1: {
+ X: Y.host
+
+ Y: {
+ #components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
+ host: #components.host
+ }
+
+ X: "example.com"
+}
+
+builtinCyclePerm2: {
+ Y: {
+ #components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
+ host: #components.host
+ }
+
+ X: Y.host
+ X: "example.com"
+}
+
+builtinCyclePerm3: {
+ Y: {
+ #components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
+ host: #components.host
+ }
+
+ X: "example.com"
+ X: Y.host
+}
+
+builtinCyclePerm4: {
+ X: "example.com"
+ X: Y.host
+
+ Y: {
+ #components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
+ host: #components.host
+ }
+}
+
+builtinCyclePerm5: {
+ X: Y.host
+ X: "example.com"
+
+ Y: {
+ #components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
+ host: #components.host
+ }
+}
+-- out/eval --
+(struct){
+ builtinCyclePerm0: (struct){
+ X: (string){ "example.com" }
+ Y: (struct){
+ #components: (#struct){
+ host: (string){ "example.com" }
+ }
+ host: (string){ "example.com" }
+ }
+ }
+ builtinCyclePerm1: (struct){
+ X: (string){ "example.com" }
+ Y: (struct){
+ #components: (#struct){
+ host: (string){ "example.com" }
+ }
+ host: (string){ "example.com" }
+ }
+ }
+ builtinCyclePerm2: (struct){
+ Y: (struct){
+ #components: (#struct){
+ host: (string){ "example.com" }
+ }
+ host: (string){ "example.com" }
+ }
+ X: (string){ "example.com" }
+ }
+ builtinCyclePerm3: (struct){
+ Y: (struct){
+ #components: (#struct){
+ host: (string){ "example.com" }
+ }
+ host: (string){ "example.com" }
+ }
+ X: (string){ "example.com" }
+ }
+ builtinCyclePerm4: (struct){
+ X: (string){ "example.com" }
+ Y: (struct){
+ #components: (#struct){
+ host: (string){ "example.com" }
+ }
+ host: (string){ "example.com" }
+ }
+ }
+ builtinCyclePerm5: (struct){
+ X: (string){ "example.com" }
+ Y: (struct){
+ #components: (#struct){
+ host: (string){ "example.com" }
+ }
+ host: (string){ "example.com" }
+ }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ builtinCyclePerm0: {
+ X: "example.com"
+ Y: {
+ #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
+ host: 〈0;#components〉.host
+ }
+ X: 〈0;Y〉.host
+ }
+ builtinCyclePerm1: {
+ X: 〈0;Y〉.host
+ Y: {
+ #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
+ host: 〈0;#components〉.host
+ }
+ X: "example.com"
+ }
+ builtinCyclePerm2: {
+ Y: {
+ #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
+ host: 〈0;#components〉.host
+ }
+ X: 〈0;Y〉.host
+ X: "example.com"
+ }
+ builtinCyclePerm3: {
+ Y: {
+ #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
+ host: 〈0;#components〉.host
+ }
+ X: "example.com"
+ X: 〈0;Y〉.host
+ }
+ builtinCyclePerm4: {
+ X: "example.com"
+ X: 〈0;Y〉.host
+ Y: {
+ #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
+ host: 〈0;#components〉.host
+ }
+ }
+ builtinCyclePerm5: {
+ X: 〈0;Y〉.host
+ X: "example.com"
+ Y: {
+ #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
+ host: 〈0;#components〉.host
+ }
+ }
+}
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index a194e9e..9f7d233 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -264,7 +264,8 @@
n.doNotify()
if !n.done() {
- if len(n.disjunctions) > 0 && v.BaseValue == cycle {
+ switch {
+ case len(n.disjunctions) > 0 && v.BaseValue == cycle:
// We disallow entering computations of disjunctions with
// incomplete data.
if state == Finalized {
@@ -276,12 +277,16 @@
n.node.UpdateStatus(Partial)
}
return
- }
- }
- if !n.done() && state <= Partial {
- n.node.UpdateStatus(Partial)
- return
+ case state <= Partial:
+ n.node.UpdateStatus(Partial)
+ return
+
+ case state <= AllArcs:
+ c.AddBottom(n.incompleteErrors())
+ n.node.UpdateStatus(Partial)
+ return
+ }
}
if s := v.Status(); state <= s {
@@ -416,25 +421,7 @@
default:
if n.node.BaseValue == cycle {
if !n.done() {
- // collect incomplete errors.
- var err *Bottom // n.incomplete
- for _, d := range n.dynamicFields {
- err = CombineErrors(nil, err, d.err)
- }
- for _, c := range n.forClauses {
- err = CombineErrors(nil, err, c.err)
- }
- for _, c := range n.ifClauses {
- err = CombineErrors(nil, err, c.err)
- }
- for _, x := range n.exprs {
- err = CombineErrors(nil, err, x.err)
- }
- if err == nil {
- // safeguard.
- err = incompleteSentinel
- }
- n.node.BaseValue = err
+ n.node.BaseValue = n.incompleteErrors()
} else {
n.node.BaseValue = nil
}
@@ -538,6 +525,28 @@
n.completeArcs(state)
}
+func (n *nodeContext) incompleteErrors() *Bottom {
+ // collect incomplete errors.
+ var err *Bottom // n.incomplete
+ for _, d := range n.dynamicFields {
+ err = CombineErrors(nil, err, d.err)
+ }
+ for _, c := range n.forClauses {
+ err = CombineErrors(nil, err, c.err)
+ }
+ for _, c := range n.ifClauses {
+ err = CombineErrors(nil, err, c.err)
+ }
+ for _, x := range n.exprs {
+ err = CombineErrors(nil, err, x.err)
+ }
+ if err == nil {
+ // safeguard.
+ err = incompleteSentinel
+ }
+ return err
+}
+
func (n *nodeContext) completeArcs(state VertexStatus) {
if state <= AllArcs {