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) {