tools/trim: fix list element removal bug

This breaks comprehension removal, but that beats
having bugs that break semantics.

Trim has seen its best time since all the changes to
the evaluator and should probably be rewritten come
the new evaluator.

Change-Id: I13b0aa58d3f2c1e4eb0fc617811be7d6078e6265
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6641
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/types.go b/cue/types.go
index faf87b7..a5deb07 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1101,6 +1101,9 @@
 
 // Elem returns the value of undefined element types of lists and structs.
 func (v Value) Elem() (Value, bool) {
+	if v.v == nil {
+		return Value{}, false
+	}
 	ctx := v.ctx()
 	switch x := v.v.Value.(type) {
 	case *structLit:
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/bartender/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/bartender/kube.cue
index 8570398..6239ea7 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/bartender/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/bartender/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: bartender: spec: ports: [{
+	port:       7080
+	targetPort: 7080
+}]
 deployment: bartender: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/bartender:v0.1.34"
 	args: [
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/breaddispatcher/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/breaddispatcher/kube.cue
index 6e06a6a..8f29213 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/breaddispatcher/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/breaddispatcher/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: breaddispatcher: spec: ports: [{
+	port:       7080
+	targetPort: 7080
+}]
 deployment: breaddispatcher: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/breaddispatcher:v0.3.24"
 	args: [
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/host/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/host/kube.cue
index e58c822..8f22355 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/host/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/host/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: host: spec: ports: [{
+	port:       7080
+	targetPort: 7080
+}]
 deployment: host: spec: {
 	replicas: 2
 	template: spec: containers: [{
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/maitred/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/maitred/kube.cue
index 4829e95..255bb07 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/maitred/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/maitred/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: maitred: spec: ports: [{
+	port:       7080
+	targetPort: 7080
+}]
 deployment: maitred: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/maitred:v0.0.4"
 	args: [
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/valeter/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/valeter/kube.cue
index 3f0963e..23a2e41 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/valeter/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/valeter/kube.cue
@@ -1,7 +1,9 @@
 package kube
 
 service: valeter: spec: ports: [{
-	name: "http"
+	port:       8080
+	targetPort: 8080
+	name:       "http"
 }]
 deployment: valeter: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/valeter:v0.0.4"
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/waiter/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/waiter/kube.cue
index 7a2c1de..def0cd4 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/waiter/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/waiter/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: waiter: spec: ports: [{
+	port:       7080
+	targetPort: 7080
+}]
 deployment: waiter: spec: {
 	replicas: 5
 	template: spec: containers: [{
diff --git a/doc/tutorial/kubernetes/quick/services/frontend/waterdispatcher/kube.cue b/doc/tutorial/kubernetes/quick/services/frontend/waterdispatcher/kube.cue
index 058f185..2f16adb 100644
--- a/doc/tutorial/kubernetes/quick/services/frontend/waterdispatcher/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/frontend/waterdispatcher/kube.cue
@@ -1,7 +1,9 @@
 package kube
 
 service: waterdispatcher: spec: ports: [{
-	name: "http"
+	port:       7080
+	targetPort: 7080
+	name:       "http"
 }]
 deployment: waterdispatcher: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/waterdispatcher:v0.0.48"
diff --git a/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue b/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue
index a7860ae..61b0534 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/download/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: download: spec: ports: [{
+	port:       7080
+	targetPort: 7080
+}]
 deployment: download: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/download:v0.0.2"
 	ports: [{
diff --git a/doc/tutorial/kubernetes/quick/services/infra/etcd/kube.cue b/doc/tutorial/kubernetes/quick/services/infra/etcd/kube.cue
index ae2f756..a86745d 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/etcd/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/etcd/kube.cue
@@ -3,8 +3,12 @@
 service: etcd: spec: {
 	clusterIP: "None"
 	ports: [{
+		port:       2379
+		targetPort: 2379
 	}, {
-		name: "peer"
+		port:       2380
+		targetPort: 2380
+		name:       "peer"
 	}]
 }
 statefulSet: etcd: spec: {
diff --git a/doc/tutorial/kubernetes/quick/services/infra/events/kube.cue b/doc/tutorial/kubernetes/quick/services/infra/events/kube.cue
index d55ceb7..ff97d3f 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/events/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/events/kube.cue
@@ -1,7 +1,9 @@
 package kube
 
 service: events: spec: ports: [{
-	name: "grpc"
+	port:       7788
+	targetPort: 7788
+	name:       "grpc"
 }]
 deployment: events: spec: {
 	replicas: 2
diff --git a/doc/tutorial/kubernetes/quick/services/infra/tasks/service.cue b/doc/tutorial/kubernetes/quick/services/infra/tasks/service.cue
index ce4b3b3..f7e2373 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/tasks/service.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/tasks/service.cue
@@ -4,7 +4,8 @@
 	type:           "LoadBalancer"
 	loadBalancerIP: "1.2.3.4" // static ip
 	ports: [{
-		port: 443
-		name: "http"
+		port:       443
+		targetPort: 7443
+		name:       "http"
 	}]
 }
diff --git a/doc/tutorial/kubernetes/quick/services/infra/updater/kube.cue b/doc/tutorial/kubernetes/quick/services/infra/updater/kube.cue
index ca7b848..aff5ca5 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/updater/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/updater/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: updater: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: updater: spec: template: spec: {
 	volumes: [{
 		name: "secret-updater"
diff --git a/doc/tutorial/kubernetes/quick/services/infra/watcher/service.cue b/doc/tutorial/kubernetes/quick/services/infra/watcher/service.cue
index 3d0e60a..e2c25bc 100644
--- a/doc/tutorial/kubernetes/quick/services/infra/watcher/service.cue
+++ b/doc/tutorial/kubernetes/quick/services/infra/watcher/service.cue
@@ -4,6 +4,8 @@
 	type:           "LoadBalancer"
 	loadBalancerIP: "1.2.3.4." // static ip
 	ports: [{
-		name: "http"
+		port:       7788
+		targetPort: 7788
+		name:       "http"
 	}]
 }
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/caller/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/caller/kube.cue
index ddd728b..8e59d60 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/caller/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/caller/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: caller: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: caller: spec: {
 	replicas: 3
 	template: spec: {
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/dishwasher/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/dishwasher/kube.cue
index 49430a4..2baffd3 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/dishwasher/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/dishwasher/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: dishwasher: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: dishwasher: spec: {
 	replicas: 5
 	template: spec: {
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/expiditer/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/expiditer/kube.cue
index c152000..83cc027 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/expiditer/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/expiditer/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: expiditer: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: expiditer: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/expiditer:v0.5.34"
 	args: [
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/headchef/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/headchef/kube.cue
index 983d689..41aae84 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/headchef/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/headchef/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: headchef: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: headchef: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/headchef:v0.2.16"
 	volumeMounts: [{
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/linecook/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/linecook/kube.cue
index 179b993..0a7cc70 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/linecook/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/linecook/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: linecook: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: linecook: spec: template: spec: {
 	volumes: [{
 	}, {
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/pastrychef/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/pastrychef/kube.cue
index 3810eda..7c38c2b 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/pastrychef/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/pastrychef/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: pastrychef: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: pastrychef: spec: template: spec: {
 	volumes: [{
 	}, {
diff --git a/doc/tutorial/kubernetes/quick/services/kitchen/souschef/kube.cue b/doc/tutorial/kubernetes/quick/services/kitchen/souschef/kube.cue
index 3d035f4..8e02450 100644
--- a/doc/tutorial/kubernetes/quick/services/kitchen/souschef/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/kitchen/souschef/kube.cue
@@ -1,5 +1,9 @@
 package kube
 
+service: souschef: spec: ports: [{
+	port:       8080
+	targetPort: 8080
+}]
 deployment: souschef: spec: template: spec: containers: [{
 	image: "gcr.io/myproj/souschef:v0.5.3"
 }]
diff --git a/doc/tutorial/kubernetes/quick/services/mon/alertmanager/kube.cue b/doc/tutorial/kubernetes/quick/services/mon/alertmanager/kube.cue
index 3af8c40..97f6284 100644
--- a/doc/tutorial/kubernetes/quick/services/mon/alertmanager/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/mon/alertmanager/kube.cue
@@ -11,7 +11,9 @@
 	spec: {
 		// type: ClusterIP
 		ports: [{
-			name: "main"
+			name:       "main"
+			port:       9093
+			targetPort: 9093
 		}]
 	}
 }
diff --git a/doc/tutorial/kubernetes/quick/services/mon/nodeexporter/kube.cue b/doc/tutorial/kubernetes/quick/services/mon/nodeexporter/kube.cue
index 4ac3b8c..5acac89 100644
--- a/doc/tutorial/kubernetes/quick/services/mon/nodeexporter/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/mon/nodeexporter/kube.cue
@@ -7,6 +7,7 @@
 		clusterIP: "None"
 		ports: [{
 			name: "metrics"
+			port: 9100
 		}]
 	}
 }
diff --git a/doc/tutorial/kubernetes/quick/services/mon/prometheus/kube.cue b/doc/tutorial/kubernetes/quick/services/mon/prometheus/kube.cue
index 096a89d..14d1b50 100644
--- a/doc/tutorial/kubernetes/quick/services/mon/prometheus/kube.cue
+++ b/doc/tutorial/kubernetes/quick/services/mon/prometheus/kube.cue
@@ -9,6 +9,7 @@
 		type: "NodePort"
 		ports: [{
 			name:     "main"
+			port:     9090
 			nodePort: 30900
 		}]
 	}
diff --git a/doc/tutorial/kubernetes/quick/services/proxy/authproxy/service.cue b/doc/tutorial/kubernetes/quick/services/proxy/authproxy/service.cue
index f84e1e4..c9f9c5f 100644
--- a/doc/tutorial/kubernetes/quick/services/proxy/authproxy/service.cue
+++ b/doc/tutorial/kubernetes/quick/services/proxy/authproxy/service.cue
@@ -1 +1,6 @@
 package kube
+
+service: authproxy: spec: ports: [{
+	port:       4180
+	targetPort: 4180
+}]
diff --git a/doc/tutorial/kubernetes/quick/services/proxy/goget/service.cue b/doc/tutorial/kubernetes/quick/services/proxy/goget/service.cue
index 70529cf..95afda0 100644
--- a/doc/tutorial/kubernetes/quick/services/proxy/goget/service.cue
+++ b/doc/tutorial/kubernetes/quick/services/proxy/goget/service.cue
@@ -4,7 +4,8 @@
 	type:           "LoadBalancer"
 	loadBalancerIP: "1.3.5.7" // static ip
 	ports: [{
-		port: 443
-		name: "https"
+		port:       443
+		targetPort: 7443
+		name:       "https"
 	}]
 }
diff --git a/doc/tutorial/kubernetes/quick/services/proxy/nginx/service.cue b/doc/tutorial/kubernetes/quick/services/proxy/nginx/service.cue
index b310cb9..c2436a4 100644
--- a/doc/tutorial/kubernetes/quick/services/proxy/nginx/service.cue
+++ b/doc/tutorial/kubernetes/quick/services/proxy/nginx/service.cue
@@ -4,8 +4,13 @@
 	type:           "LoadBalancer"
 	loadBalancerIP: "1.3.4.5"
 	ports: [{
-		name: "http"
+		port: 80 // the port that this service should serve on
+		// the container on each pod to connect to, can be a name
+		// (e.g. 'www') or a number (e.g. 80)
+		targetPort: 80
+		name:       "http"
 	}, {
+		port: 443
 		name: "https"
 	}]
 }
diff --git a/tools/trim/trim.go b/tools/trim/trim.go
index 49c24a2..b355f2f 100644
--- a/tools/trim/trim.go
+++ b/tools/trim/trim.go
@@ -267,26 +267,30 @@
 	// Identify generated components and unify them with the mixin value.
 	exists := false
 	for _, v := range vSplit {
-		if src := v.Source(); t.alwaysGen[src] || inNodes(gen, src) {
-			if !v.IsConcrete() {
-				// The template has an expression that cannot be fully
-				// resolved. Attempt to complete the expression by
-				// evaluting it within the struct to which the template
-				// is applied.
-				expr := internal.ToExpr(v.Syntax())
-
-				// TODO: this only resolves references contained in scope.
-				v = internal.EvalExpr(scope, expr).(cue.Value)
-			}
-
-			if w := in.Unify(v); w.Err() == nil {
-				in = w
-			}
-			// One of the sources of this struct is generated. That means
-			// we can safely delete a non-generated version.
-			exists = true
-			gen = append(gen, src)
+		src := v.Source()
+		alwaysGen := t.alwaysGen[src]
+		inNodes := inNodes(gen, src)
+		if !(alwaysGen || inNodes) {
+			continue
 		}
+		if !v.IsConcrete() {
+			// The template has an expression that cannot be fully
+			// resolved. Attempt to complete the expression by
+			// evaluting it within the struct to which the template
+			// is applied.
+			expr := internal.ToExpr(v.Syntax())
+
+			// TODO: this only resolves references contained in scope.
+			v = internal.EvalExpr(scope, expr).(cue.Value)
+		}
+
+		if w := in.Unify(v); w.Err() == nil {
+			in = w
+		}
+		// One of the sources of this struct is generated. That means
+		// we can safely delete a non-generated version.
+		exists = true
+		gen = append(gen, src)
 	}
 
 	switch v.Kind() {
@@ -371,32 +375,45 @@
 
 	case cue.ListKind:
 		mIter, _ := m.List()
+		elem, hasElem := m.Elem()
 		i := 0
 		rmElem := []ast.Node{}
+		allowRemove := true
 		for iter, _ := v.List(); iter.Next(); i++ {
-			mIter.Next()
-			rm := t.trim(strconv.Itoa(i), iter.Value(), mIter.Value(), scope)
+			var m cue.Value
+			if mIter.Next() {
+				m = mIter.Value()
+			} else if hasElem {
+				m = elem
+				allowRemove = false
+			} else {
+				allowRemove = false
+				break
+			}
+			rm := t.trim(strconv.Itoa(i), iter.Value(), m, scope)
 			rmElem = append(rmElem, rm...)
 		}
 
 		// Signal the removal of lists of which all elements have been marked
 		// for removal.
-		for _, v := range vSplit {
-			if src := v.Source(); !t.alwaysGen[src] {
-				l, ok := src.(*ast.ListLit)
-				if !ok {
-					break
-				}
-				rmList := true
-				iter, _ := v.List()
-				for i := 0; i < len(l.Elts) && iter.Next(); i++ {
-					if !inNodes(rmElem, l.Elts[i]) {
-						rmList = false
+		if allowRemove {
+			for _, v := range vSplit {
+				if src := v.Source(); !t.alwaysGen[src] {
+					l, ok := src.(*ast.ListLit)
+					if !ok {
 						break
 					}
-				}
-				if rmList && m.Exists() && t.canRemove(src) && !inNodes(gen, src) {
-					rmSet = append(rmSet, src)
+					rmList := true
+					iter, _ := v.List()
+					for i := 0; i < len(l.Elts) && iter.Next(); i++ {
+						if !inNodes(rmElem, l.Elts[i]) {
+							rmList = false
+							break
+						}
+					}
+					if rmList && m.Exists() && t.canRemove(src) && !inNodes(gen, src) {
+						rmSet = append(rmSet, src)
+					}
 				}
 			}
 		}
@@ -404,6 +421,7 @@
 
 	default:
 		// Mark any subsumed part that is covered by generated config.
+		in, _ := in.Default()
 		if in.Err() == nil && v.Subsume(in) == nil {
 			for _, v := range vSplit {
 				src := v.Source()
diff --git a/tools/trim/trim_test.go b/tools/trim/trim_test.go
index 7b4587e..55b9d50 100644
--- a/tools/trim/trim_test.go
+++ b/tools/trim/trim_test.go
@@ -24,7 +24,6 @@
 )
 
 func TestFiles(t *testing.T) {
-	const trace = false
 	testCases := []struct {
 		name string
 		in   string
@@ -113,6 +112,23 @@
 foo: M
 M :: c?: bool
 `,
+	}, {
+		name: "list removal",
+		in: `
+		service: [string]: {
+			ports: [{a: 1}, {a: 1}, ...{ extra: 3 }]
+		}
+		service: a: {
+			ports: [{a: 1}, {a: 1, extra: 3}, {}, { extra: 3 }]
+		}
+`,
+		out: `service: [string]: {
+	ports: [{a: 1}, {a: 1}, ...{extra: 3}]
+}
+service: a: {
+	ports: [{}, {extra: 3}, {}, {}]
+}
+`,
 		// TODO: This used to work.
 		// 	name: "remove implied interpolations",
 		// 	in: `
@@ -138,7 +154,7 @@
 			if err != nil {
 				t.Fatal(err)
 			}
-			err = Files([]*ast.File{f}, inst, &Config{Trace: trace})
+			err = Files([]*ast.File{f}, inst, &Config{Trace: false})
 			if err != nil {
 				t.Fatal(err)
 			}