cue: fix crash if yielded value is a disjunction

The value is not always a struct, it can also be
a disjunction of structs.

Now relies on regular unfication to produce an error.

Fixes #310

Change-Id: I60cefca9b55da826f057f6e46ab87e5f52b3aa45
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5323
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/eval.go b/cue/eval.go
index 0d2eb24..41bd0ce 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -433,22 +433,21 @@
 }
 
 func (x *structComprehension) evalPartial(ctx *context) evaluated {
-	st := &structLit{baseValue: x.baseValue}
+	var st evaluated = &structLit{baseValue: x.baseValue}
 	err := x.clauses.yield(ctx, func(v evaluated) *bottom {
-		embed := v.evalPartial(ctx).(*structLit)
-		embed, err := embed.expandFields(ctx)
-		if err != nil {
-			return err
+		embed := v.evalPartial(ctx)
+		if st, ok := embed.(*structLit); ok {
+			x, err := st.expandFields(ctx)
+			if err != nil {
+				return err
+			}
+			embed = x
 		}
 		res := binOp(ctx, x, opUnify, st, embed)
-		switch u := res.(type) {
-		case *bottom:
-			return u
-		case *structLit:
-			st = u
-		default:
-			panic("unreachable")
+		if b, ok := res.(*bottom); ok {
+			return b
 		}
+		st = res
 		return nil
 	})
 	if err != nil {
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 9f1e555..ccbd647 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -2842,7 +2842,7 @@
 		`,
 		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)}`,
+			`c2: _|_(conflicting values {bar: 1} and 1 (mismatched types struct and int))}`,
 	}, {
 		desc: "don't bind to string labels",
 		in: `
@@ -2944,6 +2944,24 @@
 			}
 			`,
 		out: `<0>{Spec :: <1>C{_vars: <2>C{something: string}, data: <3>C{foo :: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{foo :: <11>C{use: "var-string"}, baz: "\"var-string\"", foobar: "{\"use\":\"var-string\"}"}}}`,
+	}, {
+		desc: "issue312",
+		in: `
+		for x in [1] {
+			*close({}) | { [_]: null }
+		}
+		`,
+		out: `<0>{ <1>for _, x in [1] yield <2>{}, (*close (<3>{}) | <4>{[]: <5>(_: string)->null, })}`,
+	}, {
+		// TODO(eval): note that this behavior is incompatible with allowing
+		// non-struct as emit values. If we ever want to do this, we need to
+		// do it soon.
+		desc: "issue312",
+		in: `
+		y: *1 | {a: 2}
+		for x in [1] { y }
+		`,
+		out: `<0>{y: 1, a: 2}`,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
diff --git a/cue/value.go b/cue/value.go
index 0ebca64..3ba7d65 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -1163,28 +1163,22 @@
 
 	switch n.(type) {
 	case *bottom, *top:
-	case *structLit:
+	default:
 		orig := x.comprehensions
 		x.comprehensions = incomplete
 		src := binSrc(x.Pos(), opUnify, x, n)
 		n = binOp(ctx, src, opUnifyUnchecked, x, n)
 		x.comprehensions = orig
-
-	default:
-		return nil, ctx.mkErr(x, n, "cannot embed value %s of type %s in struct", ctx.str(n), n.kind())
 	}
 
 	switch checked.(type) {
 	case *bottom, *top:
-	case *structLit:
+	default:
 		orig := x.comprehensions
 		x.comprehensions = incomplete
 		src := binSrc(x.Pos(), opUnify, n, checked)
 		n = binOp(ctx, src, opUnify, x, checked)
 		x.comprehensions = orig
-
-	default:
-		return nil, ctx.mkErr(x, n, "cannot embed value %s of type %s in struct", ctx.str(n), n.kind())
 	}
 
 	switch v := n.(type) {