cue: fix bug related to non-structural direct self-references
Fixes #208
Change-Id: I1ca0f1fa2383ae4df780ab72f8aef71c30860b07
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4301
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/eval.go b/cue/eval.go
index f90aa64..f28a25a 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -25,11 +25,59 @@
var _ resolver = &selectorExpr{}
var _ resolver = &indexExpr{}
+// decycleRef rewrites a reference that resolves to an evaluation cycle to
+// an embedding that can be unified as is.
+func decycleRef(ctx *context, v value) (value, scope) {
+ switch x := v.(type) {
+ case *selectorExpr:
+ v, sc := decycleRef(ctx, x.x)
+ if v == nil {
+ e := x.evalPartial(ctx)
+ v = e
+ if cycleError(e) != nil {
+ sc = &structLit{baseValue: x.base()}
+ return &nodeRef{x.base(), sc, x.feature}, sc
+ }
+ return nil, nil
+ }
+ return &selectorExpr{x.baseValue, v, x.feature}, sc
+ case *indexExpr:
+ v, sc := decycleRef(ctx, x.x)
+ if v == x {
+ return nil, nil
+ }
+ return &indexExpr{x.baseValue, v, x.index}, sc
+ case *nodeRef:
+ return nil, nil
+ }
+ return v, nil
+}
+
func resolveReference(ctx *context, v value) evaluated {
if r, ok := v.(resolver); ok {
- if st, ok := r.reference(ctx).(*structLit); ok {
+ e := r.reference(ctx)
+ if st, ok := e.(*structLit); ok {
return st
}
+ if b, ok := e.(*bottom); ok {
+ if b := cycleError(b); b != nil {
+ // This is only called if we are unifying. The value referenced
+ // is either a struct or not. In case the other value is not a
+ // struct, we ensure an error by returning a struct. In case the
+ // value is a struct, we postpone the evaluation of this
+ // reference by creating an embedding for it (which are
+ // evaluated after evaluating the struct itself.)
+ if y, sc := decycleRef(ctx, v); y != v {
+ st := &structLit{baseValue: v.base()}
+ ctx.pushForwards(sc, st)
+ cp := ctx.copy(y)
+ ctx.popForwards()
+ st.comprehensions = []compValue{{comp: cp}}
+ return st
+ }
+ return b
+ }
+ }
}
return v.evalPartial(ctx)
}
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index a89f442..b2b1a1f 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -2822,6 +2822,15 @@
}
`,
out: `<0>{x: <1>{v: <2>{1: 2}, _p: 3}}`,
+ }, {
+ desc: "non-structural direct cycles",
+ in: `
+ c1: {bar: baz: 2} & c1.bar
+ c2: {bar: 1} & c2.bar
+ `,
+ out: `<0>{` +
+ `c1: <1>{bar: <2>{baz: 2}, baz: 2}, ` +
+ `c2: _|_(<3>{bar: 1<3>.bar}:cannot embed value 1 of type int in struct)}`,
}}
rewriteHelper(t, testCases, evalFull)
}
diff --git a/cue/value.go b/cue/value.go
index 2ed0746..3d1fbf8 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -1149,7 +1149,7 @@
x.comprehensions = orig
default:
- return nil, ctx.mkErr(x, n, "invalid embedding")
+ return nil, ctx.mkErr(x, n, "cannot embed value %s of type %s in struct", ctx.str(n), n.kind())
}
switch checked.(type) {
@@ -1162,7 +1162,7 @@
x.comprehensions = orig
default:
- return nil, ctx.mkErr(x, n, "invalid embedding")
+ return nil, ctx.mkErr(x, n, "cannot embed value %s of type %s in struct", ctx.str(n), n.kind())
}
switch v := n.(type) {