cue/format: output new comprehension format

Change-Id: Id10ae14fa8a3238dec76d766982f09bfc59e2465
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3185
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/trim/trim.cue b/cmd/cue/cmd/testdata/trim/trim.cue
index 61353fe..e5d5da2 100644
--- a/cmd/cue/cmd/testdata/trim/trim.cue
+++ b/cmd/cue/cmd/testdata/trim/trim.cue
@@ -46,7 +46,9 @@
 
 // TODO: top-level fields are currently not removed.
 group: {
-	comp "\(k)": v for k, v in foo
+	for k, v in foo {
+		comp "\(k)": v
+	}
 
 	comp bar: {
 		aa: 8 // new value
diff --git a/cmd/cue/cmd/testdata/trim/trim.out b/cmd/cue/cmd/testdata/trim/trim.out
index 61353fe..e5d5da2 100644
--- a/cmd/cue/cmd/testdata/trim/trim.out
+++ b/cmd/cue/cmd/testdata/trim/trim.out
@@ -46,7 +46,9 @@
 
 // TODO: top-level fields are currently not removed.
 group: {
-	comp "\(k)": v for k, v in foo
+	for k, v in foo {
+		comp "\(k)": v
+	}
 
 	comp bar: {
 		aa: 8 // new value
diff --git a/cue/export_test.go b/cue/export_test.go
index f968c7a..5fed65a 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -234,7 +234,9 @@
 			{
 				a: [1, 2]
 				b: {
-					"\(k)": v for k, v in a if v > 1
+					for k, v in a if v > 1 {
+						"\(k)": v
+					}
 				}
 			}`),
 	}, {
@@ -399,7 +401,9 @@
 		in: `{
 			b: [{
 				<X>: int
-				f: 4 if a > 4
+				if a > 4 {
+					f: 4
+				}
 			}][a]
 			a: int
 			c: *1 | 2
@@ -409,11 +413,32 @@
 		{
 			b: [{
 				<X>: int
-				f:   4 if a > 4
+				if a > 4 {
+					f: 4
+				}
 			}][a]
 			a: int
 			c: 1
 		}`),
+	}, {
+		raw: true,
+		in: `{
+			if false {
+				{ a: 1 } | { b: 1 }
+			}
+		}`,
+		// reference to a must be redirected to outer a through alias
+		out: unindent(`
+		{
+			if false {
+				{
+					a: 1
+				} | {
+					b: 1
+				}
+		
+			}
+		}`),
 	}}
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {
diff --git a/cue/format/node.go b/cue/format/node.go
index 0058cd1..ffd9eb4 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -96,10 +96,11 @@
 	f.after(nil)
 }
 
-func (f *formatter) walkClauseList(list []ast.Clause) {
+func (f *formatter) walkClauseList(list []ast.Clause, ws whiteSpace) {
 	f.before(nil)
 	for _, x := range list {
 		f.before(x)
+		f.print(ws)
 		f.clause(x)
 		f.after(x)
 	}
@@ -224,20 +225,12 @@
 		}
 
 	case *ast.Comprehension:
-		st, ok := n.Value.(*ast.StructLit)
-		if !ok || len(st.Elts) != 1 {
-			f.print(n.Value, "*bad decl*", declcomma)
+		if !n.Pos().HasRelPos() || n.Pos().RelPos() >= token.Newline {
+			f.print(formfeed)
 		}
-		field := st.Elts[0]
-		f.decl(field)
-		f.print(blank)
-		if n.Clauses[0].Pos().RelPos() >= token.Newline {
-			f.print(token.ARROW, blank)
-		}
-		f.print(indent)
-		f.walkClauseList(n.Clauses)
-		f.print(unindent)
-		f.print("") // force whitespace to be written
+		f.walkClauseList(n.Clauses, blank)
+		f.print(blank, nooverride)
+		f.expr(n.Value)
 
 	case *ast.BadDecl:
 		f.print(n.From, "*bad decl*", declcomma)
@@ -268,6 +261,9 @@
 		f.print(newsection, nooverride)
 
 	case *ast.EmbedDecl:
+		if !n.Pos().HasRelPos() || n.Pos().RelPos() >= token.Newline {
+			f.print(formfeed)
+		}
 		f.expr(n.Expr)
 		f.print(newline, newsection, nooverride) // force newline
 
@@ -275,6 +271,9 @@
 		f.ellipsis(n)
 
 	case *ast.Alias:
+		if !decl.Pos().HasRelPos() || decl.Pos().RelPos() >= token.Newline {
+			f.print(formfeed)
+		}
 		f.expr(n.Ident)
 		f.print(blank, n.Equal, token.BIND, blank)
 		f.expr(n.Expr)
@@ -484,10 +483,21 @@
 		}
 
 	case *ast.StructLit:
-		f.print(x.Lbrace, token.LBRACE, noblank, f.formfeed(), indent)
+		var l line
+		ws := noblank
+		if !x.Lbrace.HasRelPos() || (len(x.Elts) > 0 && !x.Elts[0].Pos().HasRelPos()) {
+			ws |= newline | nooverride
+		}
+		f.print(x.Lbrace, token.LBRACE, &l, ws, f.formfeed(), indent)
+
 		f.walkDeclList(x.Elts)
 		f.matchUnindent()
-		f.print(noblank, x.Rbrace, token.RBRACE)
+
+		ws = noblank
+		if f.lineout != l {
+			ws = newline | nooverride
+		}
+		f.print(ws, x.Rbrace, token.RBRACE)
 
 	case *ast.ListLit:
 		f.print(x.Lbrack, token.LBRACK, indent)
@@ -504,7 +514,7 @@
 		f.print(x.Lbrack, token.LBRACK, blank, indent)
 		f.expr(x.Expr)
 		f.print(blank)
-		f.walkClauseList(x.Clauses)
+		f.walkClauseList(x.Clauses, blank)
 		f.print(unindent, f.wsOverride(blank), x.Rbrack, token.RBRACK)
 
 	default:
@@ -515,7 +525,7 @@
 func (f *formatter) clause(clause ast.Clause) {
 	switch n := clause.(type) {
 	case *ast.ForClause:
-		f.print(blank, n.For, "for", blank)
+		f.print(n.For, "for", blank)
 		if n.Key != nil {
 			f.label(n.Key, false)
 			f.print(n.Colon, token.COMMA, blank)
@@ -528,7 +538,7 @@
 		f.expr(n.Source)
 
 	case *ast.IfClause:
-		f.print(blank, n.If, "if", blank)
+		f.print(n.If, "if", blank)
 		f.expr(n.Condition)
 
 	default:
diff --git a/cue/format/node_test.go b/cue/format/node_test.go
index 12a4892..ae358b6 100644
--- a/cue/format/node_test.go
+++ b/cue/format/node_test.go
@@ -43,7 +43,8 @@
 		}},
 		// Force a new struct.
 		out: `foo: {
-	bar :: {}
+	bar :: {
+	}
 }`,
 	}}
 	for _, tc := range testCases {
diff --git a/cue/format/printer.go b/cue/format/printer.go
index ab0faa9..24a973c 100644
--- a/cue/format/printer.go
+++ b/cue/format/printer.go
@@ -33,7 +33,8 @@
 	requested   whiteSpace
 	indentStack []whiteSpace
 
-	pos token.Position // current pos in AST
+	pos     token.Position // current pos in AST
+	lineout line
 
 	lastTok token.Token // last token printed (syntax.ILLEGAL if it's whitespace)
 
@@ -42,6 +43,8 @@
 	spaceBefore bool
 }
 
+type line int
+
 func (p *printer) init(cfg *config) {
 	p.cfg = cfg
 	p.pos = token.Position{Line: 1, Column: 1}
@@ -69,6 +72,9 @@
 		nextWS       whiteSpace
 	)
 	switch x := v.(type) {
+	case *line:
+		*x = p.lineout
+
 	case token.Token:
 		s := x.String()
 		before, after := mayCombine(p.lastTok, x)
@@ -209,14 +215,17 @@
 	case ws&newsection != 0:
 		p.maybeIndentLine(ws)
 		p.writeByte('\f', 2)
+		p.lineout += 2
 		p.spaceBefore = true
 	case ws&formfeed != 0:
 		p.maybeIndentLine(ws)
 		p.writeByte('\f', 1)
+		p.lineout++
 		p.spaceBefore = true
 	case ws&newline != 0:
 		p.maybeIndentLine(ws)
 		p.writeByte('\n', 1)
+		p.lineout++
 		p.spaceBefore = true
 	case ws&declcomma != 0:
 		p.writeByte(',', 1)
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index 275ccb2..5e56198 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -47,7 +47,9 @@
 		bbbb: 100 @go(Bbbb) /* a */ @xml(,attr) // comment
 	}
 
-	foo: {bar: string @go(-)}
+	foo: {
+		bar: string @go(-)
+	}
 
 	e:  1 + 2*3
 	e:  1 * 2 * 3 // error
@@ -136,23 +138,37 @@
 		for x in someObject
 		if x > 9
 	]
-	"\(k)": v for k, v in someObject
-
-	"\(k)": v <-
-		for k, v in someObject
-
-	e: {"\(k)": v <-
-			for k, v in someObject
-			if k > "a"
+	for k, v in someObject {
+		"\(k)": v
+	}
+	for k, v in someObject {
+		"\(k)": v
 	}
 
-	e: {"\(k)": v for k, v in someObject if k > "a"}
-	e: {"\(k)": v <-
-			for k, v in someObject if k > "a"}
+	e: {
+		for k, v in someObject
+		if k > "a" {
+			"\(k)": v
+		}
+	}
 
-	e: {"\(k)": v <-
-			for k, v in someObject
-			if k > "a"}
+	e: {
+		for k, v in someObject if k > "a" {
+			"\(k)": v
+		}
+	}
+	e: {
+		for k, v in someObject if k > "a" {
+			"\(k)": v
+		}
+	}
+
+	e: {
+		for k, v in someObject
+		if k > "a" {
+			"\(k)": v
+		}
+	}
 
 	e: [{
 		a: 1, b: 2
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index e03341d..6234910 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -841,6 +841,19 @@
 			p.errf(p.pos, "comprehension not allowed for this field")
 		}
 		clauses, _ := p.parseComprehensionClauses(false)
+		if len(clauses) == 0 {
+			p.errf(p.pos, "empty comprehension")
+			return &ast.BadDecl{From: p.pos, To: p.pos}
+		}
+		// Erase first position to allow fmt to move to recommended style for
+		// new syntax.
+		switch c := clauses[0].(type) {
+		case *ast.ForClause:
+			c.For = token.NoPos
+		case *ast.IfClause:
+			c.If = token.NoPos
+		}
+
 		return &ast.Comprehension{
 			Clauses: clauses,
 			Value: &ast.StructLit{
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index ef26627..0d35ef5 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -237,6 +237,15 @@
 			 }`,
 		`{y: {a: 1, b: 2}, a: {for k: v in y if v>2 {"\(k)": v}}}`,
 	}, {
+		"legacy comprehensions", // TODO: remove
+		`
+		a: b for a, v in s
+
+		z: 3 // Remove to have some fun.
+		for a, v in t {d:c}
+		`,
+		`for a: v in s {a: b}, z: 3, for a: v in t {d: c}`,
+	}, {
 		"duplicates allowed",
 		`{
 			a b: 3
diff --git a/doc/tutorial/basics/conditional.md b/doc/tutorial/basics/conditional.md
index 86e5932..2cbf2d0 100644
--- a/doc/tutorial/basics/conditional.md
+++ b/doc/tutorial/basics/conditional.md
@@ -17,7 +17,9 @@
 price: number
 
 // Require a justification if price is too high
-justification: string if price > 100
+if price > 100 {
+    justification: string
+}
 
 price: 200
 ```
diff --git a/doc/tutorial/basics/fieldcomp.md b/doc/tutorial/basics/fieldcomp.md
index 92375b7..b7bbc96 100644
--- a/doc/tutorial/basics/fieldcomp.md
+++ b/doc/tutorial/basics/fieldcomp.md
@@ -17,11 +17,13 @@
 a: [ "Barcelona", "Shanghai", "Munich" ]
 
 {
-    "\( strings.ToLower(v) )": {
-        pos:     k + 1
-        name:    v
-        nameLen: len(v)
-    } for k, v in a
+    for k, v in a {
+        "\( strings.ToLower(v) )": {
+            pos:     k + 1
+            name:    v
+            nameLen: len(v)
+        }
+    }
 }
 ```
 
diff --git a/doc/tutorial/kubernetes/README.md b/doc/tutorial/kubernetes/README.md
index 8ad7ff0..ef7eeb1 100644
--- a/doc/tutorial/kubernetes/README.md
+++ b/doc/tutorial/kubernetes/README.md
@@ -489,18 +489,20 @@
     }]
 }]
 
-service "\(k)": {
-    spec selector: v.spec.template.metadata.labels
+for x in [deployment, daemonSet, statefulSet] for k, v in x {
+    service "\(k)": {
+        spec selector: v.spec.template.metadata.labels
 
-    spec ports: [ {
-        Port = p.containerPort // Port is an alias
-        port:       *Port | int
-        targetPort: *Port | int
-    } for c in v.spec.template.spec.containers
-        for p in c.ports
-        if p._export ]
+        spec ports: [ {
+            Port = p.containerPort // Port is an alias
+            port:       *Port | int
+            targetPort: *Port | int
+        } for c in v.spec.template.spec.containers
+            for p in c.ports
+            if p._export ]
 
-} for x in [deployment, daemonSet, statefulSet] for k, v in x
+    }
+}
 EOF
 $ cue fmt
 ```
@@ -704,25 +706,28 @@
 deployment <ID> spec template spec: {
     hasDisks :: *true | bool
 
-    volumes: [{
-        name: *"\(ID)-disk" | string
-        gcePersistentDisk pdName: *"\(ID)-disk" | string
-        gcePersistentDisk fsType: "ext4"
-    }, {
-        name: *"secret-\(ID)" | string
-        secret secretName: *"\(ID)-secrets" | string
-    }, ...] if hasDisks
-
-    containers: [{
-        volumeMounts: [{
-            name:      *"\(ID)-disk" | string
-            mountPath: *"/logs" | string
+    // field comprehension using just "if"
+    if hasDisks {
+        volumes: [{
+            name: *"\(ID)-disk" | string
+            gcePersistentDisk pdName: *"\(ID)-disk" | string
+            gcePersistentDisk fsType: "ext4"
         }, {
-            mountPath: *"/etc/certs" | string
-            name:      *"secret-\(ID)" | string
-            readOnly:  true
+            name: *"secret-\(ID)" | string
+            secret secretName: *"\(ID)-secrets" | string
         }, ...]
-    }] if hasDisks // field comprehension using just "if"
+
+        containers: [{
+            volumeMounts: [{
+                name:      *"\(ID)-disk" | string
+                mountPath: *"/logs" | string
+            }, {
+                mountPath: *"/etc/certs" | string
+                name:      *"secret-\(ID)" | string
+                readOnly:  true
+            }, ...]
+        }]
+    }
 }
 EOF
 
@@ -1247,16 +1252,18 @@
 
 ```
 kubernetes services: {
-	"\(k)": x.kubernetes & {
-		apiVersion: "v1"
-		kind:       "Service"
+    for k, x in service {
+        "\(k)": x.kubernetes & {
+            apiVersion: "v1"
+            kind:       "Service"
 
-		metadata name:   x.name
-		metadata labels: x.label
-		spec selector:   x.label
+            metadata name:   x.name
+            metadata labels: x.label
+            spec selector:   x.label
 
-		spec ports: [ p for p in x.port ]
-	} for k, x in service
+            spec ports: [ p for p in x.port ]
+        }
+    }
 }
 ```
 
diff --git a/doc/tutorial/kubernetes/manual/services/cloud.cue b/doc/tutorial/kubernetes/manual/services/cloud.cue
index 747fab5..55ffe63 100644
--- a/doc/tutorial/kubernetes/manual/services/cloud.cue
+++ b/doc/tutorial/kubernetes/manual/services/cloud.cue
@@ -32,7 +32,11 @@
 	env <Key>: string
 
 	envSpec <Key>: {}
-	envSpec: {"\(k)" value: v for k, v in env}
+	envSpec: {
+		for k, v in env {
+			"\(k)" value: v
+		}
+	}
 
 	volume <Name>: {
 		name:      string | *Name
@@ -60,17 +64,22 @@
 }
 
 // define services implied by deployments
-service "\(k)": {
+for k, spec in deployment if len(spec.expose.port) > 0 {
+	service "\(k)": {
 
-	// Copy over all ports exposed from containers.
-	port "\(Name)": {
-		// Set default external port to Port. targetPort must be
-		// the respective containerPort (Port) if it differs from port.
-		port:       int | *Port
-		targetPort: Port if port != Port
-	} for Name, Port in spec.expose.port
+		// Copy over all ports exposed from containers.
+		for Name, Port in spec.expose.port {
+			port "\(Name)": {
+				// Set default external port to Port. targetPort must be
+				// the respective containerPort (Port) if it differs from port.
+				port: int | *Port
+				if port != Port {
+					targetPort: Port
+				}
+			}
+		}
 
-	// Copy over the labels
-	label: spec.label
-
-} for k, spec in deployment if len(spec.expose.port) > 0
+		// Copy over the labels
+		label: spec.label
+	}
+}
diff --git a/doc/tutorial/kubernetes/manual/services/k8s.cue b/doc/tutorial/kubernetes/manual/services/k8s.cue
index adf88f7..74e7228 100644
--- a/doc/tutorial/kubernetes/manual/services/k8s.cue
+++ b/doc/tutorial/kubernetes/manual/services/k8s.cue
@@ -1,17 +1,18 @@
 package kube
 
 kubernetes services: {
-	"\(k)": x.kubernetes & {
-		apiVersion: "v1"
-		kind:       "Service"
+	for k, x in service {
+		"\(k)": x.kubernetes & {
+			apiVersion: "v1"
+			kind:       "Service"
 
-		metadata name:   x.name
-		metadata labels: x.label
-		spec selector:   x.label
+			metadata name:   x.name
+			metadata labels: x.label
+			spec selector:   x.label
 
-		spec ports: [ p for p in x.port ]
-// jba: how does [p for p in x.port ] differ from x.port?
-	} for k, x in service
+			spec ports: [ p for p in x.port ] // convert struct to list
+		}
+	}
 	// Note that we cannot write
 	//   kubernetes services "\(k)": {} for k, x in service
 	// "service" is also a field comprehension and the spec prohibits one field
@@ -26,37 +27,45 @@
 // This would look nicer and would allow for superior type checking.
 
 kubernetes deployments: {
-	"\(k)": (_k8sSpec & {X: x}).X.kubernetes & {
-		apiVersion: "extensions/v1beta1"
-		kind:       "Deployment"
-		spec replicas: x.replicas
-	} for k, x in deployment if x.kind == "deployment"
+	for k, x in deployment if x.kind == "deployment" {
+		"\(k)": (_k8sSpec & {X: x}).X.kubernetes & {
+			apiVersion: "extensions/v1beta1"
+			kind:       "Deployment"
+			spec replicas: x.replicas
+		}
+	}
 }
 
 kubernetes statefulSets: {
-	"\(k)": (_k8sSpec & {X: x}).X.kubernetes & {
-		apiVersion: "apps/v1beta1"
-		kind:       "StatefulSet"
-		spec replicas: x.replicas
-	} for k, x in deployment if x.kind == "stateful"
+	for k, x in deployment if x.kind == "stateful" {
+		"\(k)": (_k8sSpec & {X: x}).X.kubernetes & {
+			apiVersion: "apps/v1beta1"
+			kind:       "StatefulSet"
+			spec replicas: x.replicas
+		}
+	}
 }
 
 kubernetes daemonSets: {
-	"\(k)": (_k8sSpec & {X: x}).X.kubernetes & {
-		apiVersion: "extensions/v1beta1"
-		kind:       "DaemonSet"
-	} for k, x in deployment if x.kind == "daemon"
+	for k, x in deployment if x.kind == "daemon" {
+		"\(k)": (_k8sSpec & {X: x}).X.kubernetes & {
+			apiVersion: "extensions/v1beta1"
+			kind:       "DaemonSet"
+		}
+	}
 }
 
 kubernetes configMaps: {
-	"\(k)": {
-		apiVersion: "v1"
-		kind:       "ConfigMap"
+	for k, v in configMap {
+		"\(k)": {
+			apiVersion: "v1"
+			kind:       "ConfigMap"
 
-		metadata name: k
-		metadata labels component: _base.label.component
-		data: v
-	} for k, v in configMap
+			metadata name: k
+			metadata labels component: _base.label.component
+			data: v
+		}
+	}
 }
 
 // _k8sSpec injects Kubernetes definitions into a deployment
@@ -74,7 +83,9 @@
 			name:  X.name
 			image: X.image
 			args:  X.args
-			env:   [ {name: k} & v for k, v in X.envSpec ] if len(X.envSpec) > 0
+			if len(X.envSpec) > 0 {
+				env: [ {name: k} & v for k, v in X.envSpec ]
+			}
 
 			ports: [ {
 				name:          k
@@ -85,20 +96,28 @@
 
 	// Volumes
 	spec template spec: {
-		volumes: [
-				v.kubernetes & {name: v.name} for v in X.volume
-		] if len(X.volume) > 0
+		if len(X.volume) > 0 {
+			volumes: [
+					v.kubernetes & {name: v.name} for v in X.volume
+			]
+		}
 
 		containers: [{
 			// TODO: using conversions this would look like:
 			// volumeMounts: [ k8s.VolumeMount(v) for v in d.volume ]
-			volumeMounts: [ {
-				name:      v.name
-				mountPath: v.mountPath
-				subPath:   v.subPath if v.subPath != null | true
-				readOnly:  v.readOnly if v.readOnly
-			} for v in X.volume
-			] if len(X.volume) > 0
+			if len(X.volume) > 0 {
+				volumeMounts: [ {
+					name:      v.name
+					mountPath: v.mountPath
+					if v.subPath != null | true {
+						subPath: v.subPath
+					}
+					if v.readOnly {
+						readOnly: v.readOnly
+					}
+				} for v in X.volume
+				]
+			}
 		}]
 	}
 }
diff --git a/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue b/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue
index 2e86b4e..7a56c6f 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue
@@ -2,5 +2,7 @@
 
 deployment download spec template spec containers: [{
 	image: "gcr.io/myproj/download:v0.0.2"
-	ports: [{containerPort: 7080}]
+	ports: [{
+		containerPort: 7080
+	}]
 }]
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue
index e607ec4..71450a1 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/kube.cue
@@ -22,23 +22,26 @@
 deployment <ID> spec template spec: {
 	hasDisks :: *true | bool
 
-	volumes: [{
-		name: *"\(ID)-disk" | string
-		gcePersistentDisk pdName: *"\(ID)-disk" | string
-		gcePersistentDisk fsType: "ext4"
-	}, {
-		name: *"secret-\(ID)" | string
-		secret secretName: *"\(ID)-secrets" | string
-	}, ...] if hasDisks
-
-	containers: [{
-		volumeMounts: [{
-			name:      *"\(ID)-disk" | string
-			mountPath: *"/logs" | string
+	// field comprehension using just "if"
+	if hasDisks {
+		volumes: [{
+			name: *"\(ID)-disk" | string
+			gcePersistentDisk pdName: *"\(ID)-disk" | string
+			gcePersistentDisk fsType: "ext4"
 		}, {
-			mountPath: *"/etc/certs" | string
-			name:      *"secret-\(ID)" | string
-			readOnly:  true
+			name: *"secret-\(ID)" | string
+			secret secretName: *"\(ID)-secrets" | string
 		}, ...]
-	}] if hasDisks // field comprehension using just "if"
+
+		containers: [{
+			volumeMounts: [{
+				name:      *"\(ID)-disk" | string
+				mountPath: *"/logs" | string
+			}, {
+				mountPath: *"/etc/certs" | string
+				name:      *"secret-\(ID)" | string
+				readOnly:  true
+			}, ...]
+		}]
+	}
 }
diff --git a/doc/tutorial/kubernetes/quick/services/kube.cue b/doc/tutorial/kubernetes/quick/services/kube.cue
index a66ef9f..90c0e93 100644
--- a/doc/tutorial/kubernetes/quick/services/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kube.cue
@@ -90,15 +90,16 @@
 	}]
 }]
 
-service "\(k)": {
-	spec selector: v.spec.template.metadata.labels
+for x in [deployment, daemonSet, statefulSet] for k, v in x {
+	service "\(k)": {
+		spec selector: v.spec.template.metadata.labels
 
-	spec ports: [ {
-		Port = p.containerPort // Port is an alias
-		port:       *Port | int
-		targetPort: *Port | int
-	} for c in v.spec.template.spec.containers
-		for p in c.ports
-		if p._export ]
-
-} for x in [deployment, daemonSet, statefulSet] for k, v in x
+		spec ports: [ {
+			Port = p.containerPort // Port is an alias
+			port:       *Port | int
+			targetPort: *Port | int
+		} for c in v.spec.template.spec.containers
+			for p in c.ports
+			if p._export ]
+	}
+}
diff --git a/encoding/protobuf/testdata/client_config.proto.out.cue b/encoding/protobuf/testdata/client_config.proto.out.cue
index 50d61d5..3f9cb25 100644
--- a/encoding/protobuf/testdata/client_config.proto.out.cue
+++ b/encoding/protobuf/testdata/client_config.proto.out.cue
@@ -27,7 +27,6 @@
 
 // Specifies the behavior when the client is unable to connect to Mixer.
 NetworkFailPolicy: {
-
 	// Specifies the behavior when the client is unable to connect to Mixer.
 	policy?: NetworkFailPolicy_FailPolicy @protobuf(1,type=FailPolicy)
 
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue
index bf28bfb..1397a5a 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/config/client/client_config_proto_gen.cue
@@ -26,7 +26,6 @@
 
 // Specifies the behavior when the client is unable to connect to Mixer.
 NetworkFailPolicy: {
-
 	// Specifies the behavior when the client is unable to connect to Mixer.
 	policy?: NetworkFailPolicy_FailPolicy @protobuf(1,type=FailPolicy)
 
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue
index 1e34cff..4d07c39 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/mixer_proto_gen.cue
@@ -23,7 +23,6 @@
 
 // Used to get a thumbs-up/thumbs-down before performing an action.
 CheckRequest: {
-
 	// The attributes to use for this request.
 	//
 	// Mixer's configuration determines how these attributes are used to
@@ -56,7 +55,6 @@
 
 // The response generated by the Check method.
 CheckResponse: {
-
 	// The precondition check results.
 	precondition?: CheckResponse_PreconditionResult @protobuf(2,type=PreconditionResult,"(gogoproto.nullable)=false")
 
@@ -68,7 +66,6 @@
 
 // Expresses the result of a precondition check.
 CheckResponse_PreconditionResult: {
-
 	// A status code of OK indicates all preconditions were satisfied. Any other code indicates not
 	// all preconditions were satisfied and details describe why.
 	status?: __status.Status @protobuf(1,type=google.rpc.Status,"(gogoproto.nullable)=false")
@@ -106,7 +103,6 @@
 // Describes the attributes that were used to determine the response.
 // This can be used to construct a response cache.
 ReferencedAttributes: {
-
 	// The message-level dictionary. Refer to [CompressedAttributes][istio.mixer.v1.CompressedAttributes] for information
 	// on using dictionaries.
 	words?: [...string] @protobuf(1)
@@ -160,7 +156,6 @@
 // Pseudo-headers ":path", ":authority", and ":method" are supported to modify
 // the request headers.
 HeaderOperation: {
-
 	// Header name.
 	name?: string @protobuf(1)
 
diff --git a/internal/third_party/yaml/decode_test.go b/internal/third_party/yaml/decode_test.go
index 87b72f1..813166b 100644
--- a/internal/third_party/yaml/decode_test.go
+++ b/internal/third_party/yaml/decode_test.go
@@ -411,7 +411,8 @@
 		"a: &a {c: 1}\nb: *a",
 		`a: {c: 1}
 b: {
-	c: 1}`, // TODO fix this spacing. Expansions low priority though.
+	c: 1
+}`,
 	}, {
 		"a: &a [1, 2]\nb: *a",
 		"a: [1, 2]\nb: [1, 2]", // TODO: a: [1, 2], b: a