tools/trim: move trim logic to package

This is by no means a complete API, but is a first pass to hoist
the logic from the cmd directory.

Change-Id: I39059fa05d51bc5f5f5ab628325728f27231ee61
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4201
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/trim.txt b/cmd/cue/cmd/testdata/script/trim.txt
index 155a14a..974c4f4 100644
--- a/cmd/cue/cmd/testdata/script/trim.txt
+++ b/cmd/cue/cmd/testdata/script/trim.txt
@@ -30,6 +30,7 @@
 	_value: "here"
 	b:      "foo"
 	c:      45
+	f:      ">> here <<" // TODO: remove
 
 	sList: [{b: "foo"}, {}]
 }
@@ -40,7 +41,7 @@
 	t: [string]: {
 		// Combined with the other constraints, we know the value must be 5 and
 		// thus the entry below can be eliminated.
-		x: >=5 & <=8
+		x: >=5 & <=8 & int
 	}
 
 	t: u: {
@@ -85,10 +86,23 @@
 
 foo: bar: {
 	_value: "here"
-	b:      "foo"
-	c:      45
 
-	sList: [{b: "foo"}, {}]
+	a: 4
+	b: "foo"
+	c: 45
+	e: string
+	f: ">> here <<"  // TODO: remove
+
+	// 5 is an integer, so this can be removed.
+	n: int
+
+	struct: {a: 3.0}
+
+	list: ["foo", float]
+
+	sList: [{a: 8, b: "foo"}, {b: "foo"}]
+	rList: [{a: string}]
+	rcList: [{a: "a", c: "foo"}]
 }
 
 foo: baz: {}
@@ -97,10 +111,11 @@
 	t: [string]: {
 		// Combined with the other constraints, we know the value must be 5 and
 		// thus the entry below can be eliminated.
-		x: >=5 & <=8
+		x: >=5 & <=8 & int
 	}
 
 	t: u: {
+		x: 5
 	}
 }
 
@@ -111,7 +126,10 @@
 	}
 
 	comp: bar: {
+		a:  4
 		aa: 8 // new value
 	}
+
+	comp: baz: {} // removed: fully implied by comprehension above
 }
 -- cue.mod --
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index 52d0ec1..d690e29 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -15,18 +15,13 @@
 package cmd
 
 import (
-	"bytes"
 	"fmt"
 	"io/ioutil"
 	"os"
-	"strconv"
-	"strings"
 
-	"cuelang.org/go/cue"
-	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/format"
-	"cuelang.org/go/cue/token"
 	"cuelang.org/go/internal"
+	"cuelang.org/go/tools/trim"
 	"github.com/spf13/cobra"
 )
 
@@ -122,14 +117,14 @@
 	}
 
 	for i, inst := range binst {
-		gen := newTrimSet(cmd)
-		for _, f := range inst.Files {
-			gen.markNodes(f)
+		root := instances[i]
+		err := trim.Files(inst.Files, root, &trim.Config{
+			Trace: flagTrace.Bool(cmd),
+		})
+		if err != nil {
+			return err
 		}
 
-		root := instances[i].Lookup()
-		rm := gen.trim("root", root, cue.Value{}, root)
-
 		if flagDryrun.Bool(cmd) {
 			continue
 		}
@@ -137,8 +132,6 @@
 		for _, f := range inst.Files {
 			filename := f.Filename
 
-			f.Decls = gen.trimDecls(f.Decls, rm, root, true)
-
 			opts := []format.Option{}
 			if flagSimplify.Bool(cmd) {
 				opts = append(opts, format.Simplify())
@@ -167,382 +160,3 @@
 	}
 	return nil
 }
-
-type trimSet struct {
-	cmd       *Command
-	stack     []string
-	exclude   map[ast.Node]bool // don't remove fields marked here
-	alwaysGen map[ast.Node]bool // node is always from a generated source
-	fromComp  map[ast.Node]bool // node originated from a comprehension
-}
-
-func newTrimSet(cmd *Command) *trimSet {
-	return &trimSet{
-		cmd:       cmd,
-		exclude:   map[ast.Node]bool{},
-		alwaysGen: map[ast.Node]bool{},
-		fromComp:  map[ast.Node]bool{},
-	}
-}
-
-func (t *trimSet) path() string {
-	return strings.Join(t.stack[1:], " ")
-}
-
-func (t *trimSet) traceMsg(msg string) {
-	if flagTrace.Bool(t.cmd) {
-		fmt.Print(t.path())
-		msg = strings.TrimRight(msg, "\n")
-		msg = strings.Replace(msg, "\n", "\n    ", -1)
-		fmt.Printf(": %s\n", msg)
-	}
-}
-
-func (t *trimSet) markNodes(n ast.Node) {
-	ast.Walk(n, nil, func(n ast.Node) {
-		switch x := n.(type) {
-		case *ast.Ident:
-			if x.Node != nil {
-				t.exclude[x.Node] = true
-			}
-			if x.Scope != nil {
-				t.exclude[x.Scope] = true
-			}
-
-		case *ast.ListLit:
-			_, e := internal.ListEllipsis(x)
-			if e != nil && e.Type != nil {
-				t.markAlwaysGen(e.Type, false)
-			}
-
-		case *ast.Field:
-			switch x.Label.(type) {
-			case *ast.TemplateLabel, *ast.ListLit:
-				t.markAlwaysGen(x.Value, false)
-			}
-
-		case *ast.ListComprehension, *ast.Comprehension:
-			t.markAlwaysGen(x, true)
-		}
-	})
-}
-
-func (t *trimSet) markAlwaysGen(first ast.Node, isComp bool) {
-	ast.Walk(first, func(n ast.Node) bool {
-		if t.alwaysGen[n] {
-			return false
-		}
-		t.alwaysGen[n] = true
-		if isComp {
-			t.fromComp[n] = true
-		}
-		if x, ok := n.(*ast.Ident); ok && n != first {
-			// Also mark any value used within a constraint from an optional field.
-			if x.Node != nil {
-				// fmt.Println("MARKED", internal.DebugStr(x.Node),
-				// "by", internal.DebugStr(first))
-				// t.markAlwaysGen(x.Node)
-			}
-		}
-		return true
-	}, nil)
-}
-
-func (t *trimSet) canRemove(n ast.Node) bool {
-	return !t.exclude[n] && !t.alwaysGen[n]
-}
-
-func isDisjunctionOfStruct(n ast.Node) bool {
-	switch x := n.(type) {
-	case *ast.BinaryExpr:
-		if x.Op == token.OR {
-			return hasStruct(x.X) || hasStruct(x.Y)
-		}
-	}
-	return false
-}
-
-func hasStruct(n ast.Node) bool {
-	hasStruct := false
-	ast.Walk(n, func(n ast.Node) bool {
-		if _, ok := n.(*ast.StructLit); ok {
-			hasStruct = true
-		}
-		return !hasStruct
-	}, nil)
-	return hasStruct
-}
-
-// trim strips fields from structs that would otherwise be generated by implied
-// content, such as optional fields turned required, comprehensions, and list types.
-//
-// The algorithm walks the tree with two values in parallel: one for the full
-// configuration, and one for implied content. For each node in the tree it
-// determines the value of the implied content and that of the full value
-// and strips any of the non-implied fields if it subsumes the implied ones.
-//
-// There are a few gotchas:
-// - Fields in the implied content may refer to fields in the complete config.
-//   To support this, incomplete fields are detected and evaluated within the
-//   configuration.
-// - Values of optional fields are instantiated as a result of the declaration
-//   of concrete sibling fields. Such fields should not be removed even if the
-//   instantiated constraint completely subsumes such fields as the reason to
-//   apply the optional constraint will disappear with it.
-// - As the parallel structure is different, it may resolve to different
-//   default values. There is no support yet for selecting defaults of a value
-//   based on another value without doing a full unification. So for now we
-//   skip any disjunction containing structs.
-//
-//		v      the current value
-//		m      the "mixed-in" values
-//		scope  in which to evaluate expressions
-//		rmSet  nodes in v that may be removed by the caller
-func (t *trimSet) trim(label string, v, m, scope cue.Value) (rmSet []ast.Node) {
-	saved := t.stack
-	t.stack = append(t.stack, label)
-	defer func() { t.stack = saved }()
-
-	vSplit := v.Split()
-
-	// At the moment disjunctions of structs are note supported. Detect them and
-	// punt.
-	// TODO: support disjunctions.
-	mSplit := m.Split()
-	for _, v := range mSplit {
-		if isDisjunctionOfStruct(v.Source()) {
-			return
-		}
-	}
-
-	// Collect generated nodes.
-	// Only keep the good parts of the optional constraint.
-	// Incoming structs may be incomplete resulting in errors. It is safe
-	// to ignore these. If there is an actual error, it will manifest in
-	// the evaluation of v.
-	in := cue.Value{}
-	gen := []ast.Node{}
-	for _, v := range mSplit {
-		// TODO: consider resolving incomplete values within the current
-		// scope, as we do for fields.
-		if v.Exists() {
-			in = in.Unify(v)
-		}
-		gen = append(gen, v.Source())
-	}
-
-	switch v.Kind() {
-	case cue.StructKind:
-		// TODO: merge optional field preprocessing with that of fields.
-
-		// Identify generated components and unify them with the mixin value.
-		exists := false
-		for _, v := range v.Split() {
-			if src := v.Source(); t.alwaysGen[src] {
-				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)
-			}
-		}
-
-		// Build map of mixin fields.
-		valueMap := map[key]cue.Value{}
-		for mIter, _ := in.Fields(cue.All(), cue.Optional(false)); mIter.Next(); {
-			valueMap[iterKey(mIter)] = mIter.Value()
-		}
-
-		fn := v.Template()
-
-		// Process fields.
-		rm := []ast.Node{}
-		for iter, _ := v.Fields(cue.All()); iter.Next(); {
-			mSub := valueMap[iterKey(iter)]
-			if fn != nil {
-				mSub = mSub.Unify(fn(iter.Label()))
-			}
-
-			removed := t.trim(iter.Label(), iter.Value(), mSub, v)
-			rm = append(rm, removed...)
-		}
-
-		canRemove := fn == nil
-		for _, v := range vSplit {
-			src := v.Source()
-			if t.fromComp[src] {
-				canRemove = true
-			}
-		}
-
-		// Remove fields from source.
-		for _, v := range vSplit {
-			if src := v.Source(); !t.alwaysGen[src] {
-				switch x := src.(type) {
-				case *ast.File:
-					// TODO: use in instead?
-					x.Decls = t.trimDecls(x.Decls, rm, m, canRemove)
-
-				case *ast.StructLit:
-					x.Elts = t.trimDecls(x.Elts, rm, m, canRemove)
-					exists = exists || m.Exists()
-					if len(x.Elts) == 0 && exists && t.canRemove(src) && !inNodes(gen, src) {
-						rmSet = append(rmSet, src)
-					}
-
-				default:
-					if len(t.stack) == 1 {
-						// TODO: fix this hack to pass down the fields to remove
-						return rm
-					}
-				}
-			}
-		}
-
-		if flagTrace.Bool(t.cmd) {
-			w := &bytes.Buffer{}
-			fmt.Fprintln(w)
-			fmt.Fprintln(w, "value:    ", v)
-			if in.Exists() {
-				fmt.Fprintln(w, "mixed in: ", in)
-			}
-			for _, v := range vSplit {
-				status := "[]"
-				src := v.Source()
-				if inNodes(rmSet, src) {
-					status = "[re]"
-				} else if t.alwaysGen[src] {
-					status = "[i]"
-				}
-				fmt.Fprintf(w, "    %4s %v: %v %T\n", status, v.Pos(), internal.DebugStr(src), src)
-			}
-
-			t.traceMsg(w.String())
-		}
-
-	case cue.ListKind:
-		mIter, _ := m.List()
-		i := 0
-		rmElem := []ast.Node{}
-		for iter, _ := v.List(); iter.Next(); i++ {
-			mIter.Next()
-			rm := t.trim(strconv.Itoa(i), iter.Value(), mIter.Value(), 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
-						break
-					}
-				}
-				if rmList && m.Exists() && t.canRemove(src) && !inNodes(gen, src) {
-					rmSet = append(rmSet, src)
-				}
-			}
-		}
-		fallthrough
-
-	default:
-		for _, v := range vSplit {
-			src := v.Source()
-			if t.alwaysGen[src] || inNodes(gen, src) {
-				if v.IsIncomplete() {
-					// 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 := v.Syntax()
-					// TODO: this only resolves references contained in scope.
-					v = internal.EvalExpr(scope, expr).(cue.Value)
-				}
-				in = in.Unify(v)
-				gen = append(gen, src)
-			}
-		}
-
-		// Mark any subsumed part that is covered by generated config.
-		if in.Err() == nil && v.Subsumes(in) {
-			for _, v := range vSplit {
-				src := v.Source()
-				if t.canRemove(src) && !inNodes(gen, src) {
-					rmSet = append(rmSet, src)
-				}
-			}
-		}
-
-		if flagTrace.Bool(t.cmd) {
-			w := &bytes.Buffer{}
-			if len(rmSet) > 0 {
-				fmt.Fprint(w, "field: SUBSUMED\n")
-			} else {
-				fmt.Fprint(w, "field: \n")
-			}
-			fmt.Fprintln(w, "value:    ", v)
-			if in.Exists() {
-				fmt.Fprintln(w, "mixed in: ", in)
-			}
-			for _, v := range vSplit {
-				status := "["
-				if inNodes(gen, v.Source()) {
-					status += "i"
-				}
-				if inNodes(rmSet, v.Source()) {
-					status += "r"
-				}
-				status += "]"
-				src := v.Source()
-				fmt.Fprintf(w, "   %4s %v: %v\n", status, v.Pos(), internal.DebugStr(src))
-			}
-
-			t.traceMsg(w.String())
-		}
-	}
-	return rmSet
-}
-
-func (t *trimSet) trimDecls(decls []ast.Decl, rm []ast.Node, m cue.Value, allow bool) []ast.Decl {
-	a := make([]ast.Decl, 0, len(decls))
-
-	for _, d := range decls {
-		if f, ok := d.(*ast.Field); ok {
-			label, _, err := ast.LabelName(f.Label)
-			v := m.Lookup(label)
-			if err == nil && inNodes(rm, f.Value) && (allow || v.Exists()) {
-				continue
-			}
-		}
-		a = append(a, d)
-	}
-	return a
-}
-
-func inNodes(a []ast.Node, n ast.Node) bool {
-	for _, e := range a {
-		if e == n {
-			return true
-		}
-	}
-	return false
-}
-
-type key struct {
-	label  string
-	hidden bool
-}
-
-func iterKey(v cue.Iterator) key {
-	return key{v.Label(), v.IsHidden()}
-}
diff --git a/tools/trim/trim.go b/tools/trim/trim.go
new file mode 100644
index 0000000..d3a5a53
--- /dev/null
+++ b/tools/trim/trim.go
@@ -0,0 +1,484 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package trim removes defintiions that may be inferred from
+// templates.
+//
+// trim removes fields from structs that can be inferred from constraints
+
+// A field, struct, or list is removed if it is implied by a constraint, such
+// as from an optional field maching a required field, a list type value,
+// a comprehension or any other implied content. It will modify the files in place.
+
+// Limitations
+//
+// Removal is on a best effort basis. Some caveats:
+// - Fields in implied content may refer to fields within the struct in which
+//   they are included, but are only resolved on a best-effort basis.
+// - Disjunctions that contain structs in implied content cannot be used to
+//   remove fields.
+// - There is currently no verification step: manual verification is required.
+
+// Examples:
+//
+// 	light: [string]: {
+// 		room:          string
+// 		brightnessOff: *0.0 | >=0 & <=100.0
+// 		brightnessOn:  *100.0 | >=0 & <=100.0
+// 	}
+
+// 	light: ceiling50: {
+// 		room:          "MasterBedroom"
+// 		brightnessOff: 0.0    // this line
+// 		brightnessOn:  100.0  // and this line will be removed
+// 	}
+//
+// Results in:
+//
+// 	light: [string]: {
+// 		room:          string
+// 		brightnessOff: *0.0 | >=0 & <=100.0
+// 		brightnessOn:  *100.0 | >=0 & <=100.0
+// 	}
+//
+// 	light: ceiling50: {
+// 		room: "MasterBedroom"
+// 	}
+//
+package trim
+
+import (
+	"bytes"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal"
+)
+
+// TODO:
+// - remove the limitations mentioned in the documentation
+// - implement verification post-processing as extra safety
+
+// Config configures trim options.
+type Config struct {
+	Trace bool
+}
+
+// Files trims fields in the given files that can be implied from other fields,
+// as can be derived from the evaluated values in inst.
+// Trimming is done on a best-effort basis and only when the removed field
+// is clearly implied by another field, rather than equal sibling fields.
+func Files(files []*ast.File, inst *cue.Instance, cfg *Config) error {
+	internal.DropOptional = true
+	defer func() { internal.DropOptional = false }()
+
+	gen := newTrimSet(cfg)
+	for _, f := range files {
+		gen.markNodes(f)
+	}
+
+	root := inst.Lookup()
+	rm := gen.trim("root", root, cue.Value{}, root)
+
+	for _, f := range files {
+		f.Decls = gen.trimDecls(f.Decls, rm, root, false)
+	}
+	return nil
+}
+
+type trimSet struct {
+	cfg       Config
+	stack     []string
+	exclude   map[ast.Node]bool // don't remove fields marked here
+	alwaysGen map[ast.Node]bool // node is always from a generated source
+	fromComp  map[ast.Node]bool // node originated from a comprehension
+}
+
+func newTrimSet(cfg *Config) *trimSet {
+	if cfg == nil {
+		cfg = &Config{}
+	}
+	return &trimSet{
+		cfg:       *cfg,
+		exclude:   map[ast.Node]bool{},
+		alwaysGen: map[ast.Node]bool{},
+		fromComp:  map[ast.Node]bool{},
+	}
+}
+
+func (t *trimSet) path() string {
+	return strings.Join(t.stack[1:], " ")
+}
+
+func (t *trimSet) traceMsg(msg string) {
+	if t.cfg.Trace {
+		fmt.Print(t.path())
+		msg = strings.TrimRight(msg, "\n")
+		msg = strings.Replace(msg, "\n", "\n    ", -1)
+		fmt.Printf(": %s\n", msg)
+	}
+}
+
+func (t *trimSet) markNodes(n ast.Node) {
+	ast.Walk(n, nil, func(n ast.Node) {
+		switch x := n.(type) {
+		case *ast.Ident:
+			if x.Node != nil {
+				t.exclude[x.Node] = true
+			}
+			if x.Scope != nil {
+				t.exclude[x.Scope] = true
+			}
+
+		case *ast.ListLit:
+			_, e := internal.ListEllipsis(x)
+			if e != nil && e.Type != nil {
+				t.markAlwaysGen(e.Type, false)
+			}
+
+		case *ast.Field:
+			switch x.Label.(type) {
+			case *ast.TemplateLabel, *ast.ListLit:
+				t.markAlwaysGen(x.Value, false)
+			}
+
+		case *ast.ListComprehension, *ast.Comprehension:
+			t.markAlwaysGen(x, true)
+		}
+	})
+}
+
+func (t *trimSet) markAlwaysGen(first ast.Node, isComp bool) {
+	ast.Walk(first, func(n ast.Node) bool {
+		if t.alwaysGen[n] {
+			return false
+		}
+		t.alwaysGen[n] = true
+		if isComp {
+			t.fromComp[n] = true
+		}
+		if x, ok := n.(*ast.Ident); ok && n != first {
+			// Also mark any value used within a constraint from an optional field.
+			if x.Node != nil {
+				// fmt.Println("MARKED", internal.DebugStr(x.Node),
+				// "by", internal.DebugStr(first))
+				// t.markAlwaysGen(x.Node)
+			}
+		}
+		return true
+	}, nil)
+}
+
+func (t *trimSet) canRemove(n ast.Node) bool {
+	return !t.exclude[n] && !t.alwaysGen[n]
+}
+
+func isDisjunctionOfStruct(n ast.Node) bool {
+	switch x := n.(type) {
+	case *ast.BinaryExpr:
+		if x.Op == token.OR {
+			return hasStruct(x.X) || hasStruct(x.Y)
+		}
+	}
+	return false
+}
+
+func hasStruct(n ast.Node) bool {
+	hasStruct := false
+	ast.Walk(n, func(n ast.Node) bool {
+		if _, ok := n.(*ast.StructLit); ok {
+			hasStruct = true
+		}
+		return !hasStruct
+	}, nil)
+	return hasStruct
+}
+
+// trim strips fields from structs that would otherwise be generated by implied
+// content, such as optional fields turned required, comprehensions, and list types.
+//
+// The algorithm walks the tree with two values in parallel: one for the full
+// configuration, and one for implied content. For each node in the tree it
+// determines the value of the implied content and that of the full value
+// and strips any of the non-implied fields if it subsumes the implied ones.
+//
+// There are a few gotchas:
+// - Fields in the implied content may refer to fields in the complete config.
+//   To support this, incomplete fields are detected and evaluated within the
+//   configuration.
+// - Values of optional fields are instantiated as a result of the declaration
+//   of concrete sibling fields. Such fields should not be removed even if the
+//   instantiated constraint completely subsumes such fields as the reason to
+//   apply the optional constraint will disappear with it.
+// - As the parallel structure is different, it may resolve to different
+//   default values. There is no support yet for selecting defaults of a value
+//   based on another value without doing a full unification. So for now we
+//   skip any disjunction containing structs.
+//
+//		v      the current value
+//		m      the "mixed-in" values
+//		scope  in which to evaluate expressions
+//		rmSet  nodes in v that may be removed by the caller
+func (t *trimSet) trim(label string, v, m, scope cue.Value) (rmSet []ast.Node) {
+	saved := t.stack
+	t.stack = append(t.stack, label)
+	defer func() { t.stack = saved }()
+
+	vSplit := v.Split()
+
+	// At the moment disjunctions of structs are not supported. Detect them and
+	// punt.
+	// TODO: support disjunctions.
+	mSplit := m.Split()
+	for _, v := range mSplit {
+		if isDisjunctionOfStruct(v.Source()) {
+			return
+		}
+	}
+
+	// Collect generated nodes.
+	// Only keep the good parts of the optional constraint.
+	// Incoming structs may be incomplete resulting in errors. It is safe
+	// to ignore these. If there is an actual error, it will manifest in
+	// the evaluation of v.
+	in := cue.Value{}
+	gen := []ast.Node{}
+	for _, v := range mSplit {
+		// TODO: consider resolving incomplete values within the current
+		// scope, as we do for fields.
+		if v.Exists() {
+			in = in.Unify(v)
+		}
+		gen = append(gen, v.Source())
+	}
+
+	switch v.Kind() {
+	case cue.StructKind:
+		// TODO: merge optional field preprocessing with that of fields.
+
+		// Identify generated components and unify them with the mixin value.
+		exists := false
+		for _, v := range v.Split() {
+			if src := v.Source(); t.alwaysGen[src] {
+				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)
+			}
+		}
+
+		// Build map of mixin fields.
+		valueMap := map[key]cue.Value{}
+		for mIter, _ := in.Fields(cue.All(), cue.Optional(false)); mIter.Next(); {
+			valueMap[iterKey(mIter)] = mIter.Value()
+		}
+
+		fn := v.Template()
+
+		// Process fields.
+		rm := []ast.Node{}
+		for iter, _ := v.Fields(cue.All()); iter.Next(); {
+			mSub := valueMap[iterKey(iter)]
+			if fn != nil {
+				mSub = mSub.Unify(fn(iter.Label()))
+			}
+
+			removed := t.trim(iter.Label(), iter.Value(), mSub, v)
+			rm = append(rm, removed...)
+		}
+
+		canRemove := fn == nil
+		for _, v := range vSplit {
+			src := v.Source()
+			if t.fromComp[src] {
+				canRemove = true
+			}
+		}
+
+		// Remove fields from source.
+		for _, v := range vSplit {
+			if src := v.Source(); !t.alwaysGen[src] {
+				switch x := src.(type) {
+				case *ast.File:
+					// TODO: use in instead?
+					x.Decls = t.trimDecls(x.Decls, rm, m, canRemove)
+
+				case *ast.StructLit:
+					x.Elts = t.trimDecls(x.Elts, rm, m, canRemove)
+					exists = exists || m.Exists()
+					if len(x.Elts) == 0 && exists && t.canRemove(src) && !inNodes(gen, src) {
+						rmSet = append(rmSet, src)
+					}
+
+				default:
+					if len(t.stack) == 1 {
+						// TODO: fix this hack to pass down the fields to remove
+						return rm
+					}
+				}
+			}
+		}
+
+		if t.cfg.Trace {
+			w := &bytes.Buffer{}
+			fmt.Fprintln(w)
+			fmt.Fprintln(w, "value:    ", v)
+			if in.Exists() {
+				fmt.Fprintln(w, "mixed in: ", in)
+			}
+			for _, v := range vSplit {
+				status := "[]"
+				src := v.Source()
+				if inNodes(rmSet, src) {
+					status = "[re]"
+				} else if t.alwaysGen[src] {
+					status = "[i]"
+				}
+				fmt.Fprintf(w, "    %4s %v: %v %T\n", status, v.Pos(), internal.DebugStr(src), src)
+			}
+
+			t.traceMsg(w.String())
+		}
+
+	case cue.ListKind:
+		mIter, _ := m.List()
+		i := 0
+		rmElem := []ast.Node{}
+		for iter, _ := v.List(); iter.Next(); i++ {
+			mIter.Next()
+			rm := t.trim(strconv.Itoa(i), iter.Value(), mIter.Value(), 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
+						break
+					}
+				}
+				if rmList && m.Exists() && t.canRemove(src) && !inNodes(gen, src) {
+					rmSet = append(rmSet, src)
+				}
+			}
+		}
+		fallthrough
+
+	default:
+		for _, v := range vSplit {
+			src := v.Source()
+			if t.alwaysGen[src] || inNodes(gen, src) {
+				if v.IsIncomplete() {
+					// 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 := v.Syntax()
+					// TODO: this only resolves references contained in scope.
+					v = internal.EvalExpr(scope, expr).(cue.Value)
+				}
+				in = in.Unify(v)
+				gen = append(gen, src)
+			}
+		}
+
+		// Mark any subsumed part that is covered by generated config.
+		if in.Err() == nil && v.Subsumes(in) {
+			for _, v := range vSplit {
+				src := v.Source()
+				if t.canRemove(src) && !inNodes(gen, src) {
+					rmSet = append(rmSet, src)
+				}
+			}
+		}
+
+		if t.cfg.Trace {
+			w := &bytes.Buffer{}
+			if len(rmSet) > 0 {
+				fmt.Fprint(w, "field: SUBSUMED\n")
+			} else {
+				fmt.Fprint(w, "field: \n")
+			}
+			fmt.Fprintln(w, "value:    ", v)
+			if in.Exists() {
+				fmt.Fprintln(w, "mixed in: ", in)
+			}
+			for _, v := range vSplit {
+				status := "["
+				if inNodes(gen, v.Source()) {
+					status += "i"
+				}
+				if inNodes(rmSet, v.Source()) {
+					status += "r"
+				}
+				status += "]"
+				src := v.Source()
+				fmt.Fprintf(w, "   %4s %v: %v\n", status, v.Pos(), internal.DebugStr(src))
+			}
+
+			t.traceMsg(w.String())
+		}
+	}
+	return rmSet
+}
+
+func (t *trimSet) trimDecls(decls []ast.Decl, rm []ast.Node, m cue.Value, allow bool) []ast.Decl {
+	a := make([]ast.Decl, 0, len(decls))
+
+	for _, d := range decls {
+		if f, ok := d.(*ast.Field); ok {
+			label, _, err := ast.LabelName(f.Label)
+			v := m.Lookup(label)
+			if err == nil && inNodes(rm, f.Value) && (allow || v.Exists()) {
+				continue
+			}
+		}
+		a = append(a, d)
+	}
+	return a
+}
+
+func inNodes(a []ast.Node, n ast.Node) bool {
+	for _, e := range a {
+		if e == n {
+			return true
+		}
+	}
+	return false
+}
+
+type key struct {
+	label  string
+	hidden bool
+}
+
+func iterKey(v cue.Iterator) key {
+	return key{v.Label(), v.IsHidden()}
+}