cue: move up processing of comprehension

This needs to happen before a comprehension is closed.

Fixes #153

Change-Id: I75bfc43a3c8f4844423bbb7135d4158e21879021
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3783
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/binop.go b/cue/binop.go
index 278e251..d2ebfb6 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -605,7 +605,7 @@
 	}
 	lambda := ctx.deref(l).(*lambdaExpr)
 	if finalize {
-		lambda.value = wrapFinalize(lambda.value)
+		lambda.value = wrapFinalize(ctx, lambda.value)
 	}
 	return lambda, nil
 }
@@ -705,10 +705,10 @@
 				}
 				w := b.v
 				if x.closeStatus.shouldFinalize() {
-					w = wrapFinalize(w)
+					w = wrapFinalize(ctx, w)
 				}
 				if y.closeStatus.shouldFinalize() {
-					v = wrapFinalize(v)
+					v = wrapFinalize(ctx, v)
 				}
 				v = mkBin(ctx, src.Pos(), op, w, v)
 				obj.arcs[i].v = v
diff --git a/cue/builtin.go b/cue/builtin.go
index 7c667b7..3613b96 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -204,7 +204,7 @@
 		if len(d) == 0 {
 			// TODO(manifest): This should not be unconditionally incomplete,
 			// but it requires results from comprehensions and all to have
-			// some special status. Mayb this can be solved by having results
+			// some special status. Maybe this can be solved by having results
 			// of list comprehensions be open if they result from iterating over
 			// an open list or struct. This would actually be exactly what
 			// that means. The error here could then only add an incomplete
diff --git a/cue/eval.go b/cue/eval.go
index ba20f4a..01051f1 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -321,7 +321,7 @@
 
 func (x *closeIfStruct) evalPartial(ctx *context) evaluated {
 	v := x.value.evalPartial(ctx)
-	updateCloseStatus(v)
+	updateCloseStatus(ctx, v)
 	return v
 }
 
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index b48d5e1..7f1d4c9 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -1476,13 +1476,20 @@
 			}
 			...
 		}
+
+		E :: A & {
+			for k, v in { f3: int } {
+				"\(k)": v
+			}
+		}
 		`,
 		out: `<0>{` +
+			`E :: _|_(int:field "f3" not allowed in closed struct), ` +
 			`A :: <1>C{f1: int, f2: int}, ` +
-			`a: <2>C{f1: int, f2: int, f3: int}, ` +
-			`B :: <3>C{f1: int}, ` +
-			`C :: <4>C{f1: int}, ` +
-			`D :: <5>{f1: int, ...}` +
+			`a: _|_(int:field "f3" not allowed in closed struct), ` +
+			`B :: <2>C{f1: int}, ` +
+			`C :: <3>C{f1: int}, ` +
+			`D :: <4>{f1: int, ...}` +
 			`}`,
 	}, {
 		desc: "incomplete comprehensions",
@@ -2573,6 +2580,30 @@
 		}
 		`,
 		out: `<0>{Workflow :: <1>C{jobs: <2>{<>: <3>(jobID: string)-><4>C{}, }, JobID :: or ([ <5>for k, _ in <6>.jobs yield <5>.k ])}, foo: <7>C{jobs: <8>{<>: <9>(jobID: string)-><10>C{}, foo: <11>C{}}, JobID :: "foo"}}`,
+	}, {
+		desc: "Issue #153",
+		in: `
+		Foo: {
+			listOfCloseds: [...Closed]
+		}
+		
+		Closed :: {
+			a: int | *0
+		}
+		
+		Junk: {
+			b: 2
+		}
+		
+		Foo & {
+			listOfCloseds: [{
+				for k, v in Junk {
+					"\(k)": v
+				}
+			 }]
+		}
+		`,
+		out: `<0>{<1>{listOfCloseds: [_|_(2:field "b" not allowed in closed struct)]}, Foo: <2>{listOfCloseds: []}, Closed :: <3>C{a: 0}, Junk: <4>{b: 2}}`,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
diff --git a/cue/rewrite.go b/cue/rewrite.go
index 3f81daf..f329f29 100644
--- a/cue/rewrite.go
+++ b/cue/rewrite.go
@@ -39,7 +39,7 @@
 	if v == x.value {
 		return x
 	}
-	return wrapFinalize(v)
+	return wrapFinalize(ctx, v)
 }
 
 func (x *structLit) rewrite(ctx *context, fn rewriteFunc) value {
diff --git a/cue/value.go b/cue/value.go
index 495f4d5..17a8ec2 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -796,7 +796,7 @@
 		// it is safe to cache the result.
 		ctx.cycleErr = false
 
-		updateCloseStatus(v)
+		updateCloseStatus(ctx, v)
 		x.arcs[i].cache = v
 		if doc != nil {
 			x.arcs[i].docs = &docNode{n: doc, left: x.arcs[i].docs}
@@ -862,7 +862,7 @@
 		orig := *x
 		orig.comprehensions = incomplete
 		orig.emit = nil
-		n = binOp(ctx, src, opUnifyUnchecked, &orig, n)
+		n = binOp(ctx, src, opUnify, &orig, n)
 
 	default:
 		if len(comprehensions) == len(incomplete) {
@@ -984,13 +984,13 @@
 	value
 }
 
-func wrapFinalize(v value) value {
+func wrapFinalize(ctx *context, v value) value {
 	if v.kind().isAnyOf(structKind | listKind) {
 		switch x := v.(type) {
 		case *top:
 			return v
 		case *structLit, *list, *disjunction:
-			updateCloseStatus(v)
+			updateCloseStatus(ctx, v)
 		case *closeIfStruct:
 			return x
 		}
@@ -999,23 +999,27 @@
 	return v
 }
 
-func updateCloseStatus(v value) {
+func updateCloseStatus(ctx *context, v value) {
 	switch x := v.(type) {
 	case *structLit:
-		if x.closeStatus.shouldClose() {
-			x.closeStatus = isClosed
+		y, err := x.expandFields(ctx)
+		if err == nil {
+			if x.closeStatus.shouldClose() {
+				x.closeStatus = isClosed
+			}
+			x.closeStatus |= shouldFinalize
+			y.closeStatus = x.closeStatus
 		}
-		x.closeStatus |= shouldFinalize
 
 	case *disjunction:
 		for _, d := range x.values {
-			d.val = wrapFinalize(d.val)
+			d.val = wrapFinalize(ctx, d.val)
 		}
 
 	case *list:
-		wrapFinalize(x.elem)
+		wrapFinalize(ctx, x.elem)
 		if x.typ != nil {
-			wrapFinalize(x.typ)
+			wrapFinalize(ctx, x.typ)
 		}
 	}
 }